/** * Copyright (c) 2010, 2013 Darmstadt University of Technology. * 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: * Marcel Bruch - initial API and implementation. */ package org.eclipse.recommenders.internal.calls.rcp.templates; import static org.apache.commons.lang3.StringUtils.isEmpty; import static org.apache.commons.lang3.SystemUtils.LINE_SEPARATOR; import static org.eclipse.recommenders.utils.names.VmTypeName.OBJECT; import java.util.Arrays; import org.apache.commons.lang3.StringUtils; import org.eclipse.recommenders.utils.Checks; import org.eclipse.recommenders.utils.names.IMethodName; import org.eclipse.recommenders.utils.names.ITypeName; import org.eclipse.recommenders.utils.names.Names; import org.eclipse.recommenders.utils.names.VmTypeName; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.collect.HashMultiset; import com.google.common.collect.Iterables; import com.google.common.collect.Multiset; public class TemplateBuilder { private static final ToTemplateTypeNames TO_TEMPLATE_TYPE_NAMES = new ToTemplateTypeNames(); private StringBuilder builder = new StringBuilder(); private Multiset<String> usedVariables = HashMultiset.create(); /** * Appends the give string to the template. */ public TemplateBuilder append(String code) { builder.append(code); return this; } public Optional<String> appendCall(IMethodName method, String receiver, String... argNames) { Checks.ensureIsFalse(method.isInit(), "Method must not be a constructor"); //$NON-NLS-1$ String returnId = null; if (!method.isVoid()) { ITypeName type = method.getReturnType(); String defId = suggestId(method); newType(defId + "Type", type).ws(); //$NON-NLS-1$ newName(defId, type).ws().eq().ws(); returnId = "${" + defId + "}"; //$NON-NLS-1$ //$NON-NLS-2$ } if (!isEmpty(receiver)) { append(receiver).dot(); } append(method.getName()).append("(").appendParameters(method, argNames).append(")").sc(); //$NON-NLS-1$ //$NON-NLS-2$ return Optional.fromNullable(returnId); } /** * appends ${id:type(someValue,otherValue,nextValue)} */ public TemplateBuilder appendCommand(String id, String commandId, Iterable<String> values) { builder.append("${").append(id).append(":").append(commandId).append("("); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ Joiner.on(',').appendTo(builder, values); builder.append(")}"); //$NON-NLS-1$ return this; } /** * appends ${id:type(someValue,nextValue,othervalue)} */ public TemplateBuilder appendCommand(String id, String commandId, ITypeName... types) { return appendCommand(id, commandId, toLiterals(types)); } /** * appends ${id:type(someValue)} */ public TemplateBuilder appendCommand(String id, String commandId, String value) { builder.append("${").append(id).append(":").append(commandId).append("(").append(value).append(")}"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ return this; } public TemplateBuilder appendCommand(String id, String commandId, String... values) { return appendCommand(id, commandId, Arrays.asList(values)); } /** * Creates * "${buttonType:newType(org.eclipse.swt.widgets.Button)} ${button:newName(org.eclipse.swt.widgets.Button)}= new ${buttonType}(${parent:var(org.eclipse.swt.widgets.Composite)}, ${style:link(SWT.PUSH, SWT.TOGGLE, SWT.RADIO, SWT.CHECK, SWT.FLAT)});" * * @return a variable string in "${generated-id}" format */ public String appendCtor(IMethodName ctor, String... argNames) { Checks.ensureIsTrue(ctor.isInit()); ITypeName type = ctor.getDeclaringType(); String receiverId = suggestId(ctor); String receiverTypeId = receiverId + "Type"; //$NON-NLS-1$ newType(receiverTypeId, type).ws().newName(receiverId, type).ws().eq().ws().new_().ws().ref(receiverTypeId) .append("("); //$NON-NLS-1$ appendParameters(ctor, argNames); append(")").sc(); //$NON-NLS-1$ return "${" + receiverId + "}"; //$NON-NLS-1$ //$NON-NLS-2$ } private void appendParameter(ITypeName type, String id) { if (isVariable(id)) { ref(id); } else if (OBJECT.equals(type)) { ref(suggestId(id)); } else if (type == VmTypeName.BOOLEAN) { link(suggestId(id), "false", "true"); //$NON-NLS-1$ //$NON-NLS-2$ } else if (type == VmTypeName.INT || type == VmTypeName.DOUBLE || type == VmTypeName.FLOAT || type == VmTypeName.LONG || type == VmTypeName.SHORT) { link(suggestId(id), "0"); //$NON-NLS-1$ } else { var(suggestId(id), type); } } public TemplateBuilder appendParameters(IMethodName method, String... argNames) { ITypeName[] argTypes = method.getParameterTypes(); for (int i = 0; i < argTypes.length; i++) { String arg = argNames[i]; appendParameter(argTypes[i], arg); if (i < argTypes.length - 1) { c().ws(); } } return this; } /** * Evaluates to the nth type argument of the referenced template variable. The reference should be the name of * another template variable. Resolves to java.lang.Object if the referenced variable cannot be found or is not a * parameterized type. * <p> * Example: * * <pre> * ${type:argType(vector, 0)} ${first:name(type)} = ${vector:var(java.util.Vector)}.get(0) * </pre> */ public TemplateBuilder argType(String id, String variable, int n) { return appendCommand(id, "argType", variable, String.valueOf(n)); //$NON-NLS-1$ } /** * Evaluates to a proposal for an array visible in the current scope. */ public TemplateBuilder array() { return append("${array}"); //$NON-NLS-1$ } /** * Evaluates to a name for a new local variable for an element of the ${array} variable match. */ public TemplateBuilder arrayElement() { return append("${array_element}"); //$NON-NLS-1$ } /** * Evaluates to the element type of the ${array} variable match. */ public TemplateBuilder arrayType() { return append("${array_type}"); //$NON-NLS-1$ } /** * Appends a comma (',') to the code. */ public TemplateBuilder c() { return append(","); //$NON-NLS-1$ } /** * Evaluates to a proposal for a collection visible in the current scope. */ public TemplateBuilder collection() { return append("${collection}"); //$NON-NLS-1$ } /** * Specifies the cursor position when the template edit mode is left. This is useful when the cursor should jump to * another place than to the end of the template on leaving template edit mode. */ public TemplateBuilder cursor() { return append("${cursor}"); //$NON-NLS-1$ } /** * Evaluates to the current date. */ public TemplateBuilder date() { return append("${date}"); //$NON-NLS-1$ } /** * Evaluates to the dollar symbol $. Alternatively, two dollars can be used: $$. */ public TemplateBuilder dollar() { return append("${dollar}"); //$NON-NLS-1$ } /** * Appends a dot ('.') to the code. */ public TemplateBuilder dot() { return append("."); //$NON-NLS-1$ } /** * Evaluates to the element type of the referenced template variable. The reference should be the name of another * template variable that resolves to an array or an instance of java.lang.Iterable. The elemType variable type is * similar to ${id:argType(reference,0)}, the difference being that it also resolves the element type of an array. * <p> * ${array_type} is a shortcut for ${array_type:elemType(array)}.<br> * ${iterable_type} is a shortcut for ${iterable_type:elemType(iterable)}. */ public TemplateBuilder elemType(String id, String variable) { return appendCommand(id, "elemType", variable); //$NON-NLS-1$ } /** * Evaluates to the name of the enclosing method. */ public TemplateBuilder enclosingMethod() { return append("${enclosing_method}"); //$NON-NLS-1$ } /** * Evaluates to a comma separated list of argument names of the enclosing method. This variable can be useful when * generating log statements for many methods. */ public TemplateBuilder EnclosingMethodArguments() { return append("${enclosing_method_arguments}"); //$NON-NLS-1$ } /** * Evaluates to the name of the enclosing package. */ public TemplateBuilder enclosingPackage() { return append("${enclosing_package}"); //$NON-NLS-1$ } // /** // * Adds a static import statement for each qualified name that is not already imported. The qualifiedName is the // * fully qualified name of a static field or method, or it is the qualified name of a type plus a .* suffix, // * enclosed in single quotes ''. Does nothing if a conflicting import exists. Evaluates to nothing. // * <p> // * Example: // * // * <pre> // * ${is:importStatic(java.util.Collections.EMPTY_SET, 'java.lang.System.*')} // * </pre> // */ // public TemplateBuilder importStatic(String id, IMethodName names) { // throw Throws.throwUnsupportedOperation(); // // ${:importStatic([qualifiedName[,qualifiedName]*])} // } /** * Evaluates to the name of the enclosing project. */ public TemplateBuilder enclosingProject() { return append("${enclosing_project}"); //$NON-NLS-1$ } /** * Evaluates to the name of the enclosing type. */ public TemplateBuilder enclosingType() { return append("${enclosing_type}"); //$NON-NLS-1$ } /** * Appends the equal sign ('=') to the code. */ public TemplateBuilder eq() { return append("="); //$NON-NLS-1$ } /** * Exception variable name in catch blocks. */ public TemplateBuilder exceptionVariableName() { return append("${exception_variable_name}"); //$NON-NLS-1$ } /** * Evaluates to a field in the current scope that is a subtype of any of the given types. If no type is specified, * any non-primitive field matches. * <p> * Example: * * <pre> * ${count:field(int)} * </pre> */ public TemplateBuilder field(String id, ITypeName... types) { return appendCommand(id, "field", types); //$NON-NLS-1$ } /** * Evaluates to the name of the file. */ public TemplateBuilder file() { return append("${file}"); //$NON-NLS-1$ } /** * Adds an import statement for each type. Does nothing if the import already exists. Does nothing if a conflicting * import exists. Evaluates to nothing. * <p> * Example: * * <pre> * ${:import(java.util.List, java.util.Collection)} * </pre> */ public TemplateBuilder imports(ITypeName... types) { return imports("", types); //$NON-NLS-1$ } /** * Adds an import statement with the given types into the template. */ public TemplateBuilder imports(String id, ITypeName... types) { return appendCommand(id, "import", types); //$NON-NLS-1$ } /** * Evaluates to a proposal for an undeclared array index. */ public TemplateBuilder index() { return append("${index}"); //$NON-NLS-1$ } /** * Returns true when the given string starts with ${ and ends with }. */ public boolean isVariable(String arg) { return arg.startsWith("${") && arg.endsWith("}"); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Evaluates to a proposal for an iterable or array visible in the current scope. */ public TemplateBuilder iterable() { return append("${iterable}"); //$NON-NLS-1$ } /** * Evaluates to a name for a new local variable for an element of the ${iterable} variable match. */ public TemplateBuilder iterableElement() { return append("${iterable_element}"); //$NON-NLS-1$ } /** * Evaluates to the element type of the ${iterable} variable match. */ public TemplateBuilder iterableType() { return append("${iterable_type}"); //$NON-NLS-1$ } /** * Evaluates to an unused name for a new local variable of type java.util.Iterator. */ public TemplateBuilder iterator() { return append("${iterator}"); //$NON-NLS-1$ } /** * Evaluates to content of all currently selected lines. */ public TemplateBuilder lineSelection() { return append("${line_selection}"); //$NON-NLS-1$ } /** * Evaluates to id if the list of proposals is empty, evaluates to the first proposal otherwise. The evaluated value * is put into linked mode. A proposal window shows all the given proposals. * <p> * Example: * * <pre> * java.util.Collections.${kind:link(EMPTY_SET, EMPTY_LIST, EMPTY_MAP)} * </pre> * */ public TemplateBuilder link(String id, String... proposals) { return appendCommand(id, "link", proposals); //$NON-NLS-1$ } /** * Evaluates to a local variable or parameter visible in the current scope that is a subtype of any of the given * type. If no type is specified, any non-primitive local variable matches. * <p> * ${array} is a shortcut for ${array:localVar('java.lang.Object[]')}, but also matches arrays of primitive types.<br> * ${collection} is a shortcut for ${collection:localVar(java.util.Collection)}. ${iterable} is a shortcut for * ${iterable:localVar(java.lang.Iterable)}, but also matches arrays. */ public TemplateBuilder localVar(String id, ITypeName... types) { return appendCommand(id, "localVar", types); //$NON-NLS-1$ } /** * Inserts the new keyword into the snippet. */ public TemplateBuilder new_() { return append("new"); //$NON-NLS-1$ } /** * Evaluates to an non-conflicting name for a new local variable of the type specified by the reference. The * reference may either be a Java type name or the name of another template variable. The generated name respects * the code style settings. * <p> * ${index} is a shortcut for ${index:newName(int)}.<br> * ${iterator} is a shortcut for ${iterator:newName(java.util.Iterator)}.<br> * ${array_element} is a shortcut for ${array_element:newName(array)}.<br> * ${iterable_element} is a shortcut for ${iterable_element:newName(iterable)}.<br> */ public TemplateBuilder newName(String id, ITypeName reference) { return appendCommand(id, "newName", reference); //$NON-NLS-1$ } /** * */ public TemplateBuilder newType(String id, ITypeName type) { return appendCommand(id, "newType", toLiteral(type)); //$NON-NLS-1$ } /** * Inserts a new line into the template. */ public TemplateBuilder nl() { return append(LINE_SEPARATOR); } /** * Evaluates to the name primary type of the current compilation unit. */ public TemplateBuilder primaryTypeName() { return append("${primary_type_name}"); //$NON-NLS-1$ } /** * Inserts a simple reference to a variable (i.e., ${<referencedVariable>})into the template. */ public TemplateBuilder ref(String referencedVariable) { builder.append("${").append(referencedVariable).append("}"); //$NON-NLS-1$ //$NON-NLS-2$ return this; } /** * Evaluates to the return type of the enclosing method. */ public TemplateBuilder returnType() { return append("${return_type}"); //$NON-NLS-1$ } /** * Appends a semicolon to the template; */ public TemplateBuilder sc() { return append(";"); //$NON-NLS-1$ } /** * Suggests a free variable id based on the given method. For constructors it suggests the name of the declaring * class, for any other method names it returns the method name excluding a "get" prefix if available. */ public String suggestId(IMethodName method) { String varName = null; if (method.isInit()) { varName = method.getDeclaringType().getClassName(); } else { String name = method.getName(); // we have methods like List.get(23). Handle that: int start = name.startsWith("get") && !name.equals("get") ? 3 : 0; //$NON-NLS-1$ //$NON-NLS-2$ varName = StringUtils.substring(name, start); } varName = varName.replaceAll("\\W", "").toLowerCase(); //$NON-NLS-1$ //$NON-NLS-2$ return suggestId(varName); } /** * Suggests an id for the given variable name. * <p> * Note, this method is not side-effect free. Every call internally increases the counter for the passed id. */ public String suggestId(String varName) { String suffix = usedVariables.add(varName, 1) == 0 ? "" : "" + (usedVariables.count(varName) - 1); //$NON-NLS-1$ //$NON-NLS-2$ return varName + suffix; } /** * Evaluates to the current time. */ public TemplateBuilder time() { return append("${time}"); //$NON-NLS-1$ } /** * Evaluates to a proposal for the currently specified default task tag. */ public TemplateBuilder todo() { return append("${todo}"); //$NON-NLS-1$ } /** * Converts a type into a String that can be used inside JFace templates. It automatically sanitizes arrays. */ public String toLiteral(ITypeName type) { return TO_TEMPLATE_TYPE_NAMES.apply(type); } /** * Converts a list of type names into strings that can be used inside JFace templates. It automatically sanitizes * arrays. */ public Iterable<String> toLiterals(ITypeName... types) { return Iterables.transform(Arrays.asList(types), TO_TEMPLATE_TYPE_NAMES); } /** * Returns the contents of the template. */ @Override public String toString() { return builder.toString(); } /** * Evaluates to the user name. */ public TemplateBuilder user() { return append("${user}"); //$NON-NLS-1$ } /** * Evaluates to a field, local variable or parameter visible in the current scope that is a subtype of any of the * given types. If no type is specified, any non-primitive variable matches. * <p> * Example: * * <pre> * ${container:var(java.util.List, 'java.lang.Object[]')} * </pre> */ public TemplateBuilder var(String id, ITypeName... types) { return appendCommand(id, "var", types); //$NON-NLS-1$ } /** * Evaluates to the content of the current text selection. */ public TemplateBuilder wordSelection() { return append("${word_selection}"); //$NON-NLS-1$ } /** * Appends a whitespace to the template. */ public TemplateBuilder ws() { return append(" "); //$NON-NLS-1$ } /** * Evaluates to the current year. */ public TemplateBuilder year() { return append("${year}"); //$NON-NLS-1$ } private static class ToTemplateTypeNames implements Function<ITypeName, String> { @Override public String apply(ITypeName t) { String res = Names.vm2srcQualifiedType(t); if (t.isArrayType()) { res = "'" + res + "'"; //$NON-NLS-1$ //$NON-NLS-2$ } return res; } } }