blob: b0e6e1aab2155ee23f94a566cd712d7a1da42d46 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2013 Tasktop Technologies and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* 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);
}
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;
}
}
}