/******************************************************************************* * Copyright (c) 2004, 2013 Tasktop Technologies and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Eike Stepper- initial API and implementation * Tasktop Technologies - improvements *******************************************************************************/ package org.eclipse.mylyn.internal.team.ui.templates; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtension; import org.eclipse.core.runtime.IExtensionPoint; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.mylyn.commons.core.StatusHandler; import org.eclipse.mylyn.internal.team.ui.FocusedTeamUiPlugin; import org.eclipse.mylyn.tasks.core.ITask; import org.eclipse.mylyn.team.ui.AbstractCommitTemplateVariable; /** * @author Eike Stepper * @author Mik Kersten */ public class CommitTemplateManager { private static final Pattern ARGUMENT_PATTERN = Pattern.compile("(.+)" + "\\(\\s*\"" //$NON-NLS-1$ //$NON-NLS-2$ + "(.+(?:\"\\s*,\\s*\".+)*)" + "\"\\s*\\)}" + "(.*)", Pattern.DOTALL); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ private static final String ATTR_CLASS = "class"; //$NON-NLS-1$ private static final String ATTR_DESCRIPTION = "description"; //$NON-NLS-1$ private static final String ATTR_RECOGNIZED_KEYWORD = "recognizedKeyword"; //$NON-NLS-1$ private static final String ELEM_TEMPLATE_HANDLER = "templateVariable"; //$NON-NLS-1$ private static final String EXT_POINT_TEMPLATE_HANDLERS = "commitTemplates"; //$NON-NLS-1$ private static final String[] EMPTY_STRING_ARRAY = new String[0]; public String generateComment(ITask task, String template) { return processKeywords(task, template); } public String getTaskIdFromCommentOrLabel(String comment) { try { String template = FocusedTeamUiPlugin.getDefault() .getPreferenceStore() .getString(FocusedTeamUiPlugin.COMMIT_TEMPLATE); int templateNewline = template.indexOf('\n'); String templateFirstLineIndex = template; if (templateNewline != -1) { templateFirstLineIndex = template.substring(0, templateNewline - 1); } String regex = getTaskIdRegEx(templateFirstLineIndex); int commentNewlineIndex = comment.indexOf('\n'); String commentFirstLine = comment; if (commentNewlineIndex != -1) { commentFirstLine = comment.substring(0, commentNewlineIndex); } Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(commentFirstLine); if (matcher.find()) { return matcher.group(1); } } catch (Exception e) { StatusHandler.log(new Status(IStatus.ERROR, FocusedTeamUiPlugin.ID_PLUGIN, "Problem while parsing task id from comment", e)); //$NON-NLS-1$ } return null; } public String getTaskIdRegEx(String template) { final String META_CHARS = " $()*+.< [\\]^{|}"; //$NON-NLS-1$ final String TASK_ID_PLACEHOLDER = "\uffff"; //$NON-NLS-1$ final String KEYWORD_PLACEHOLDER = "\ufffe"; //$NON-NLS-1$ template = template.replaceFirst("\\$\\{task\\.id\\}", TASK_ID_PLACEHOLDER); //$NON-NLS-1$ template = template.replaceFirst("\\$\\{task\\.key\\}", TASK_ID_PLACEHOLDER); //$NON-NLS-1$ template = replaceKeywords(template, KEYWORD_PLACEHOLDER); template = quoteChars(template, META_CHARS); template = template.replaceFirst(TASK_ID_PLACEHOLDER, "(\\\\d+)"); //$NON-NLS-1$ template = template.replaceAll(KEYWORD_PLACEHOLDER, ".*"); //$NON-NLS-1$ return template; } private String replaceKeywords(String str, String placeholder) { String[] recognizedKeywords = getRecognizedKeywords(); for (String keyword : recognizedKeywords) { str = str.replaceAll("\\$\\{" + keyword + "\\}", placeholder); //$NON-NLS-1$ //$NON-NLS-2$ } return str; } private String quoteChars(String str, String charsToQuote) { StringBuilder builder = new StringBuilder(str.length() * 2); for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (charsToQuote.indexOf(c) != -1) { builder.append('\\'); } builder.append(c); } return builder.toString(); } public String[] getRecognizedKeywords() { final ArrayList<String> result = new ArrayList<String>(); new ExtensionProcessor() { @Override protected Object processContribution(IConfigurationElement element, String keyword, String description, String className) throws Exception { result.add(keyword); return null; } }.run(); return result.toArray(new String[result.size()]); } public String getHandlerDescription(final String keyword) { return (String) new ExtensionProcessor() { @Override protected Object processContribution(IConfigurationElement element, String foundKeyword, String description, String className) throws Exception { return keyword.equals(foundKeyword) ? description : null; } }.run(); } public AbstractCommitTemplateVariable createHandler(final String keyword) { return (AbstractCommitTemplateVariable) new ExtensionProcessor() { @Override protected Object processContribution(IConfigurationElement element, String foundKeyword, String description, String className) throws Exception { if (keyword.equals(foundKeyword)) { AbstractCommitTemplateVariable handler = (AbstractCommitTemplateVariable) element.createExecutableExtension(ATTR_CLASS); if (handler != null) { handler.setDescription(description); handler.setRecognizedKeyword(foundKeyword); } // else { // String recognizedKeyword = handler.getRecognizedKeyword(); // if (recognizedKeyword == null || !recognizedKeyword.equals(foundKeyword)) { // throw new IllegalArgumentException("Keyword markup does not match handler implementation"); // } // } return handler; } return null; } }.run(); } private String processKeywords(ITask task, String template) { String[] segments = template.split("\\$\\{"); //$NON-NLS-1$ Stack<String> evaluated = new Stack<String>(); evaluated.add(segments[0]); for (int i = 1; i < segments.length; i++) { String segment = segments[i]; String value = null; String trailingCharacters; Matcher argumentMatcher = ARGUMENT_PATTERN.matcher(segment); if (argumentMatcher.matches()) { String keyword = argumentMatcher.group(1); String[] args = argumentMatcher.group(2).split("\"\\s*,\\s*\""); //$NON-NLS-1$ value = processKeyword(task, keyword, args); trailingCharacters = argumentMatcher.group(3); } else { int brace = segment.indexOf('}'); if (brace > 0) { String keyword = segment.substring(0, brace); value = processKeyword(task, keyword, EMPTY_STRING_ARRAY); } trailingCharacters = segment.substring(brace + 1); } if (value != null) { evaluated.add(value); evaluated.add(trailingCharacters); } else if (!evaluated.isEmpty()) { evaluated.add(trailingCharacters); } // else { // buffer.append("${"); // buffer.append(segment); // } } StringBuffer buffer = new StringBuffer(); for (String string : evaluated) { buffer.append(string); } // remove duplicate whitespace String commitTemplate = buffer.toString(); return commitTemplate.replaceAll("[ ]+", " "); //$NON-NLS-1$ //$NON-NLS-2$ } private String processKeyword(ITask task, String keyword, String[] args) { try { AbstractCommitTemplateVariable handler = createHandler(keyword); if (handler != null) { handler.setArguments(args); return handler.getValue(task); } } catch (Exception e) { StatusHandler.log(new Status(IStatus.ERROR, FocusedTeamUiPlugin.ID_PLUGIN, "Problem while dispatching to template handler for: " + keyword, e)); //$NON-NLS-1$ } return null; } /** * @author Eike Stepper */ private static class ExtensionProcessor { public Object run() { IExtensionPoint extPoint = Platform.getExtensionRegistry().getExtensionPoint(FocusedTeamUiPlugin.ID_PLUGIN, EXT_POINT_TEMPLATE_HANDLERS); IExtension[] extensions = extPoint.getExtensions(); for (IExtension extension : extensions) { IConfigurationElement[] elements = extension.getConfigurationElements(); for (IConfigurationElement element : elements) { if (ELEM_TEMPLATE_HANDLER.equals(element.getName())) { try { Object result = processContribution(element); if (result != null) { return result; } } catch (Exception e) { String msg = MessageFormat.format( Messages.CommitTemplateManager_Error_while_processing_template_handler_contribution_X_from_plugin_X, element.getAttribute(ATTR_CLASS), element.getContributor().getName()); StatusHandler.log(new Status(IStatus.ERROR, FocusedTeamUiPlugin.ID_PLUGIN, msg, e)); } } } } return null; } protected Object processContribution(IConfigurationElement element) throws Exception { String keyword = element.getAttribute(ATTR_RECOGNIZED_KEYWORD); String description = element.getAttribute(ATTR_DESCRIPTION); String className = element.getAttribute(ATTR_CLASS); return processContribution(element, keyword, description, className); } protected Object processContribution(IConfigurationElement element, String keyword, String description, String className) throws Exception { return null; } } }