| /******************************************************************************* |
| * Copyright (c) 2000, 2015 IBM Corporation 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: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jface.text.templates; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * The template translator translates a string into a template buffer. Regions marked as variables |
| * are translated into <code>TemplateVariable</code>s. |
| * <p> |
| * The EBNF grammar of a valid string is as follows: |
| * </p> |
| * <pre> template := (text | escape)*. |
| * text := character - dollar. |
| * escape := dollar ('{' variable '}' | dollar). |
| * dollar := '$'. |
| * variable := identifier | identifier ':' type. |
| * type := qualifiedname | qualifiedname '(' arguments ')'. |
| * arguments := (argument ',')* argument. |
| * argument := qualifiedname | argumenttext. |
| * qualifiedname := (identifier '.')* identifier. |
| * argumenttext := "'" (character - "'" | "'" "'")* "'". |
| * identifier := javaidentifierpart - "$".</pre> |
| * <p> |
| * Clients may only replace the <code>createVariable</code> method of this class. |
| * </p> |
| * |
| * @since 3.0 |
| */ |
| public class TemplateTranslator { |
| /** |
| * Regex pattern for identifier. |
| * Note: For historic reasons, this pattern <em>allows</em> numbers at the beginning of an identifier. |
| * @since 3.7 |
| */ |
| private static final String IDENTIFIER= "(?:[\\p{javaJavaIdentifierPart}&&[^\\$]]++)"; //$NON-NLS-1$ |
| |
| /** |
| * Regex pattern for qualifiedname |
| * @since 3.4 |
| */ |
| private static final String QUALIFIED_NAME= "(?:" + IDENTIFIER + "\\.)*+" + IDENTIFIER; //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| /** |
| * Regex pattern for argumenttext |
| * @since 3.4 |
| */ |
| private static final String ARGUMENT_TEXT= "'(?:(?:'')|(?:[^']))*+'"; //$NON-NLS-1$ |
| |
| /** |
| * Regex pattern for argument |
| * @since 3.4 |
| */ |
| private static final String ARGUMENT= "(?:" + QUALIFIED_NAME + ")|(?:" + ARGUMENT_TEXT + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| |
| /** |
| * Regex pattern for whitespace |
| * @since 3.5 |
| */ |
| private static final String SPACES= "\\s*+"; //$NON-NLS-1$ |
| |
| /** |
| * Precompiled regex pattern for qualified names. |
| * @since 3.3 |
| */ |
| private static final Pattern PARAM_PATTERN= Pattern.compile(ARGUMENT); |
| /** |
| * Precompiled regex pattern for valid dollar escapes (dollar literals and variables) and |
| * (invalid) single dollars. |
| * @since 3.3 |
| */ |
| private static final Pattern ESCAPE_PATTERN= Pattern.compile( |
| "\\$\\$|\\$\\{" + // $$|${ //$NON-NLS-1$ |
| SPACES + |
| "(" + IDENTIFIER + "?+)" + // variable id group (1) //$NON-NLS-1$ //$NON-NLS-2$ |
| SPACES + |
| "(?:" + //$NON-NLS-1$ |
| ":" + //$NON-NLS-1$ |
| SPACES + |
| "(" + QUALIFIED_NAME + ")" + // variable type group (2) //$NON-NLS-1$ //$NON-NLS-2$ |
| SPACES + |
| "(?:" + //$NON-NLS-1$ |
| "\\(" + // ( //$NON-NLS-1$ |
| SPACES + |
| "((?:(?:" + ARGUMENT + ")" + SPACES + "," + SPACES + ")*+(?:" + ARGUMENT + "))" + // arguments group (3) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ |
| SPACES + |
| "\\)" + // ) //$NON-NLS-1$ |
| ")?" + //$NON-NLS-1$ |
| SPACES + |
| ")?" + //$NON-NLS-1$ |
| "\\}|\\$"); // }|$ //$NON-NLS-1$ |
| |
| /** |
| * @since 3.3 |
| */ |
| private final class VariableDescription { |
| final List<Integer> fOffsets= new ArrayList<>(5); |
| final String fName; |
| TemplateVariableType fType; |
| |
| VariableDescription(String name, TemplateVariableType type) { |
| fName= name; |
| fType= type; |
| } |
| |
| void mergeType(TemplateVariableType type) throws TemplateException { |
| if (type == null) |
| return; |
| if (fType == null) |
| fType= type; |
| if (!type.equals(fType)) |
| fail(TextTemplateMessages.getFormattedString("TemplateTranslator.error.incompatible.type", fName)); //$NON-NLS-1$ |
| } |
| } |
| |
| /** Last translation error. */ |
| private String fErrorMessage; |
| /** |
| * Used to ensure compatibility with subclasses overriding |
| * {@link #createVariable(String, String, int[])}. |
| * @since 3.3 |
| */ |
| private TemplateVariableType fCurrentType; |
| |
| /** |
| * Returns an error message if an error occurred for the last translation, <code>null</code> |
| * otherwise. |
| * |
| * @return the error message if an error occurred during the most recent translation, |
| * <code>null</code> otherwise |
| */ |
| public String getErrorMessage() { |
| return fErrorMessage; |
| } |
| |
| /** |
| * Translates a template to a <code>TemplateBuffer</code>. <code>null</code> is returned if |
| * there was an error. <code>getErrorMessage()</code> retrieves the associated error message. |
| * |
| * @param template the template to translate. |
| * @return returns the template buffer corresponding to the string |
| * @see #getErrorMessage() |
| * @throws TemplateException if translation failed |
| */ |
| public TemplateBuffer translate(Template template) throws TemplateException { |
| return parse(template.getPattern()); |
| } |
| |
| /** |
| * Translates a template string to <code>TemplateBuffer</code>. <code>null</code> is |
| * returned if there was an error. <code>getErrorMessage()</code> retrieves the associated |
| * error message. |
| * |
| * @param string the string to translate. |
| * @return returns the template buffer corresponding to the string |
| * @see #getErrorMessage() |
| * @throws TemplateException if translation failed |
| */ |
| public TemplateBuffer translate(String string) throws TemplateException { |
| return parse(string); |
| } |
| |
| /** |
| * Internal parser. |
| * |
| * @param string the string to parse |
| * @return the parsed <code>TemplateBuffer</code> |
| * @throws TemplateException if the string does not conform to the template format |
| */ |
| private TemplateBuffer parse(String string) throws TemplateException { |
| |
| fErrorMessage= null; |
| final StringBuffer buffer= new StringBuffer(string.length()); |
| final Matcher matcher= ESCAPE_PATTERN.matcher(string); |
| final Map<String, VariableDescription> variables= new LinkedHashMap<>(); |
| |
| int complete= 0; |
| while (matcher.find()) { |
| // append any verbatim text |
| buffer.append(string.substring(complete, matcher.start())); |
| |
| // check the escaped sequence |
| if ("$".equals(matcher.group())) { //$NON-NLS-1$ |
| fail(TextTemplateMessages.getString("TemplateTranslator.error.incomplete.variable")); //$NON-NLS-1$ |
| } else if ("$$".equals(matcher.group())) { //$NON-NLS-1$ |
| // escaped $ |
| buffer.append('$'); |
| } else { |
| // parse variable |
| String name= matcher.group(1); |
| String typeName= matcher.group(2); |
| String params= matcher.group(3); |
| TemplateVariableType type= createType(typeName, params); |
| |
| updateOrCreateVariable(variables, name, type, buffer.length()); |
| |
| buffer.append(name); |
| } |
| complete= matcher.end(); |
| } |
| // append remaining verbatim text |
| buffer.append(string.substring(complete)); |
| |
| TemplateVariable[] vars= createVariables(variables); |
| return new TemplateBuffer(buffer.toString(), vars); |
| } |
| |
| private TemplateVariableType createType(String typeName, String paramString) { |
| if (typeName == null) |
| return null; |
| |
| if (paramString == null) |
| return new TemplateVariableType(typeName); |
| |
| final Matcher matcher= PARAM_PATTERN.matcher(paramString); |
| List<String> params= new ArrayList<>(5); |
| while (matcher.find()) { |
| String argument= matcher.group(); |
| if (argument.charAt(0) == '\'') { |
| // argumentText |
| argument= argument.substring(1, argument.length() - 1).replaceAll("''", "'"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| params.add(argument); |
| } |
| |
| return new TemplateVariableType(typeName, params.toArray(new String[params.size()])); |
| } |
| |
| private void fail(String message) throws TemplateException { |
| fErrorMessage= message; |
| throw new TemplateException(message); |
| } |
| |
| /** |
| * If there is no variable named <code>name</code>, a new variable with the given type, name |
| * and offset is created. If one exists, the offset is added to the variable and the type is |
| * merged with the existing type. |
| * |
| * @param variables the variables by variable name |
| * @param name the name of the variable |
| * @param type the variable type, <code>null</code> for not defined |
| * @param offset the buffer offset of the variable |
| * @throws TemplateException if merging the type fails |
| * @since 3.3 |
| */ |
| private void updateOrCreateVariable(Map<String, VariableDescription> variables, String name, TemplateVariableType type, int offset) throws TemplateException { |
| VariableDescription varDesc= variables.get(name); |
| if (varDesc == null) { |
| varDesc= new VariableDescription(name, type); |
| variables.put(name, varDesc); |
| } else { |
| varDesc.mergeType(type); |
| } |
| varDesc.fOffsets.add(new Integer(offset)); |
| } |
| |
| /** |
| * Creates proper {@link TemplateVariable}s from the variable descriptions. |
| * |
| * @param variables the variable descriptions by variable name |
| * @return the corresponding variables |
| * @since 3.3 |
| */ |
| private TemplateVariable[] createVariables(Map<String, VariableDescription> variables) { |
| TemplateVariable[] result= new TemplateVariable[variables.size()]; |
| int idx= 0; |
| for (Iterator<VariableDescription> it= variables.values().iterator(); it.hasNext(); idx++) { |
| VariableDescription desc= it.next(); |
| TemplateVariableType type= desc.fType == null ? new TemplateVariableType(desc.fName) : desc.fType; |
| int[] offsets= new int[desc.fOffsets.size()]; |
| int i= 0; |
| for (Iterator<Integer> intIt= desc.fOffsets.iterator(); intIt.hasNext(); i++) { |
| Integer offset= intIt.next(); |
| offsets[i]= offset.intValue(); |
| } |
| fCurrentType= type; |
| /* |
| * Call the deprecated version of createVariable. When not overridden, it will delegate |
| * to the new version using fCurrentType. |
| */ |
| TemplateVariable var= createVariable(type.getName(), desc.fName, offsets); |
| result[idx]= var; |
| } |
| fCurrentType= null; // avoid dangling reference |
| return result; |
| } |
| |
| /** |
| * Hook method to create new variables. Subclasses may override to supply their custom variable |
| * type. |
| * <p> |
| * Clients may replace this method. |
| * </p> |
| * |
| * @param type the type of the new variable. |
| * @param name the name of the new variable. |
| * @param offsets the offsets where the variable occurs in the template |
| * @return a new instance of <code>TemplateVariable</code> |
| * @deprecated as of 3.3 use {@link #createVariable(TemplateVariableType, String, int[])} instead |
| */ |
| @Deprecated |
| protected TemplateVariable createVariable(String type, String name, int[] offsets) { |
| return createVariable(fCurrentType, name, offsets); |
| } |
| |
| /** |
| * Hook method to create new variables. Subclasses may override to supply their custom variable |
| * type. |
| * <p> |
| * Clients may replace this method. |
| * </p> |
| * |
| * @param type the type of the new variable. |
| * @param name the name of the new variable. |
| * @param offsets the offsets where the variable occurs in the template |
| * @return a new instance of <code>TemplateVariable</code> |
| * @since 3.3 |
| */ |
| protected TemplateVariable createVariable(TemplateVariableType type, String name, int[] offsets) { |
| return new TemplateVariable(type, name, name, offsets); |
| } |
| } |