/*******************************************************************************
* Copyright (c) 2000, 2011 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.che.ide.ext.java.jdt.templates;
import org.eclipse.che.ide.ext.java.jdt.core.NamingConventions;
import org.eclipse.che.ide.ext.java.jdt.core.compiler.CharOperation;
import org.eclipse.che.ide.ext.java.jdt.core.dom.CompilationUnit;
import org.eclipse.che.ide.ext.java.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.che.ide.ext.java.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
import org.eclipse.che.ide.ext.java.jdt.core.util.CharUtil;
import org.eclipse.che.ide.ext.java.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
import org.eclipse.che.ide.ext.java.jdt.internal.corext.codemanipulation.StubUtility;
import org.eclipse.che.ide.ext.java.jdt.templates.CompilationUnitCompletion.Variable;
import org.eclipse.che.ide.ext.java.jdt.templates.api.Template;
import org.eclipse.che.ide.ext.java.jdt.templates.api.TemplateBuffer;
import org.eclipse.che.ide.ext.java.jdt.templates.api.TemplateContextType;
import org.eclipse.che.ide.ext.java.jdt.templates.api.TemplateException;
import org.eclipse.che.ide.ext.java.jdt.templates.api.TemplateTranslator;
import org.eclipse.che.ide.ext.java.jdt.templates.api.TemplateVariable;
import org.eclipse.che.ide.ext.java.jdt.templates.api.TemplateVariableType;
import org.eclipse.che.ide.ext.java.worker.WorkerDocument;
import org.eclipse.che.ide.ext.java.jdt.text.Document;
import org.eclipse.che.ide.runtime.CoreException;
import org.eclipse.che.ide.runtime.IStatus;
import org.eclipse.che.ide.runtime.Status;
import org.eclipse.che.ide.api.text.BadLocationException;
import org.eclipse.che.ide.api.text.Position;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/** A context for Java source. */
public class JavaContext extends CompilationUnitContext {
/** A code completion requester for guessing local variable names. */
private CompilationUnitCompletion fCompletion;
/**
* The list of used local names.
*
* @since 3.3
*/
private Set<String> fUsedNames = new HashSet<String>();
private Map<String, MultiVariable> fVariables = new HashMap<String, MultiVariable>();
private ImportRewrite fImportRewrite;
private Set<String> fCompatibleContextTypeIds;
/**
* Creates a java template context.
*
* @param type
* the context type.
* @param document
* the document.
* @param completionOffset
* the completion offset within the document.
* @param completionLength
* the completion length.
* @param compilationUnit
* the compilation unit (may be <code>null</code>).
*/
public JavaContext(TemplateContextType type, Document document, int completionOffset, int completionLength,
CompilationUnit compilationUnit) {
super(type, document, completionOffset, completionLength, compilationUnit);
}
/**
* Creates a java template context.
*
* @param type
* the context type.
* @param document
* the document.
* @param completionPosition
* the position defining the completion offset and length
* @param compilationUnit
* the compilation unit (may be <code>null</code>).
*/
public JavaContext(TemplateContextType type, Document document, Position completionPosition,
CompilationUnit compilationUnit) {
super(type, document, completionPosition, compilationUnit);
}
/**
* Adds a context type that is also compatible. That means the context can also process templates of that context type.
*
* @param contextTypeId
* the context type to accept
*/
public void addCompatibleContextType(String contextTypeId) {
if (fCompatibleContextTypeIds == null)
fCompatibleContextTypeIds = new HashSet<String>();
fCompatibleContextTypeIds.add(contextTypeId);
}
// /**
// * Returns the indentation level at the position of code completion.
// *
// * @return the indentation level at the position of the code completion
// */
// private int getIndentation()
// {
// // TODO
// // int start= getStart();
// // IDocument document= getDocument();
// // try {
// // IRegion region= document.getLineInformationOfOffset(start);
// // String lineContent= document.get(region.getOffset(), region.getLength());
// // IJavaProject project= getJavaProject();
// // return Strings.computeIndentUnits(lineContent, project);
// // } catch (BadLocationException e) {
// return 0;
// // }
// }
/*
* @see TemplateContext#evaluate(Template template)
*/
@Override
public TemplateBuffer evaluate(Template template) throws BadLocationException, TemplateException {
clear();
if (!canEvaluate(template))
throw new TemplateException("Cannot evaluate the template.");
TemplateTranslator translator = new TemplateTranslator() {
@Override
protected TemplateVariable createVariable(TemplateVariableType type, String name, int[] offsets) {
// TemplateVariableResolver resolver= getContextType().getResolver(type.getName());
// return resolver.createVariable();
MultiVariable variable = new JavaVariable(type, name, offsets);
fVariables.put(name, variable);
return variable;
}
};
TemplateBuffer buffer = translator.translate(template);
getContextType().resolve(buffer, this);
rewriteImports();
// TODO
// IPreferenceStore prefs= JavaPlugin.getDefault().getPreferenceStore();
// boolean useCodeFormatter= prefs.getBoolean(PreferenceConstants.TEMPLATES_USE_CODEFORMATTER);
// IJavaProject project= getJavaProject();
// JavaFormatter formatter= new JavaFormatter(TextUtilities.getDefaultLineDelimiter(getDocument()), getIndentation(),
// useCodeFormatter, project);
// formatter.format(buffer, this);
clear();
return buffer;
}
private void clear() {
fUsedNames.clear();
fImportRewrite = null;
}
/*
* @see TemplateContext#canEvaluate(Template templates)
*/
@Override
public boolean canEvaluate(Template template) {
if (!hasCompatibleContextType(template))
return false;
if (fForceEvaluation)
return true;
String key = getKey();
return key.length() != 0 && template.getName().toLowerCase().startsWith(key.toLowerCase());
}
private boolean hasCompatibleContextType(Template template) {
String key = getKey();
if (template.matches(key, getContextType().getId()))
return true;
if (fCompatibleContextTypeIds == null)
return false;
Iterator<String> iter = fCompatibleContextTypeIds.iterator();
while (iter.hasNext()) {
if (template.matches(key, iter.next()))
return true;
}
return false;
}
/*
* @see DocumentTemplateContext#getCompletionPosition();
*/
@Override
public int getStart() {
if (fIsManaged && getCompletionLength() > 0)
return super.getStart();
try {
Document document = getDocument();
int start = getCompletionOffset();
int end = getCompletionOffset() + getCompletionLength();
while (start != 0 && CharUtil.isJavaIdentifierPart(document.getChar(start - 1)))
start--;
while (start != end && CharOperation.isWhitespace(document.getChar(start)))
start++;
if (start == end)
start = getCompletionOffset();
return start;
} catch (BadLocationException e) {
return super.getStart();
}
}
/*
* @see org.eclipse.jdt.internal.corext.template.DocumentTemplateContext#getEnd()
*/
@Override
public int getEnd() {
if (fIsManaged || getCompletionLength() == 0)
return super.getEnd();
try {
Document document = getDocument();
int start = getCompletionOffset();
int end = getCompletionOffset() + getCompletionLength();
while (start != end && CharOperation.isWhitespace(document.getChar(end - 1)))
end--;
return end;
} catch (BadLocationException e) {
return super.getEnd();
}
}
/*
* @see org.eclipse.jdt.internal.corext.template.DocumentTemplateContext#getKey()
*/
@Override
public String getKey() {
if (getCompletionLength() == 0)
return super.getKey();
try {
Document document = getDocument();
int start = getStart();
int end = getCompletionOffset();
return start <= end ? document.get(start, end - start) : ""; //$NON-NLS-1$
} catch (BadLocationException e) {
return super.getKey();
}
}
/**
* Returns the character before the start position of the completion.
*
* @return the character before the start position of the completion
*/
public char getCharacterBeforeStart() {
int start = getStart();
try {
return start == 0 ? ' ' : getDocument().getChar(start - 1);
} catch (BadLocationException e) {
return ' ';
}
}
private CompilationUnitCompletion getCompletion() {
CompilationUnit compilationUnit = getCompilationUnit();
if (fCompletion == null) {
fCompletion = new CompilationUnitCompletion(compilationUnit);
if (compilationUnit != null) {
// TODO
// compilationUnit.codeComplete(getStart(), fCompletion);
}
}
return fCompletion;
}
/**
* Returns the names of local arrays.
*
* @return the names of local arrays
*/
public Variable[] getArrays() {
Variable[] localArrays = getCompletion().findLocalArrays();
arrange(localArrays);
return localArrays;
}
/**
* Sorts already used locals behind any that are not yet used.
*
* @param variables
* the variables to sort
* @since 3.3
*/
private void arrange(Variable[] variables) {
Arrays.sort(variables, new Comparator<Variable>() {
public int compare(Variable o1, Variable o2) {
return rank(o1) - rank(o2);
}
private int rank(Variable l) {
return fUsedNames.contains(l.getName()) ? 1 : 0;
}
});
}
/**
* Returns the names of local variables matching <code>type</code>.
*
* @param type
* the type of the variables
* @return the names of local variables matching <code>type</code>
* @since 3.3
*/
public Variable[] getLocalVariables(String type) {
Variable[] localVariables = getCompletion().findLocalVariables(type);
arrange(localVariables);
return localVariables;
}
/**
* Returns the names of fields matching <code>type</code>.
*
* @param type
* the type of the fields
* @return the names of fields matching <code>type</code>
* @since 3.3
*/
public Variable[] getFields(String type) {
Variable[] fields = getCompletion().findFieldVariables(type);
arrange(fields);
return fields;
}
/**
* Returns the names of local iterables or arrays.
*
* @return the names of local iterables or arrays
*/
public Variable[] getIterables() {
Variable[] iterables = getCompletion().findLocalIterables();
arrange(iterables);
return iterables;
}
public void markAsUsed(String name) {
fUsedNames.add(name);
}
public String[] suggestVariableNames(String type) throws IllegalArgumentException {
String[] excludes = computeExcludes();
// TODO erasure, arrays, etc.
String[] result = suggestVariableName(type, excludes);
return result;
}
String[] computeExcludes() {
String[] excludes = getCompletion().getLocalVariableNames();
if (!fUsedNames.isEmpty()) {
String[] allExcludes = new String[fUsedNames.size() + excludes.length];
System.arraycopy(excludes, 0, allExcludes, 0, excludes.length);
System.arraycopy(fUsedNames.toArray(), 0, allExcludes, 0, fUsedNames.size());
excludes = allExcludes;
}
return excludes;
}
private String[] suggestVariableName(String type, String[] excludes) throws IllegalArgumentException {
int dim = 0;
while (type.endsWith("[]")) {//$NON-NLS-1$
dim++;
type = type.substring(0, type.length() - 2);
}
// IJavaProject project= getJavaProject();
// if (project != null)
return StubUtility.getVariableNameSuggestions(NamingConventions.VK_LOCAL, type, dim, Arrays.asList(excludes),
true);
// fallback if we lack proper context: roll-our own lowercasing
// return new String[] {Signature.getSimpleName(type).toLowerCase()};
}
/**
* Adds an import for type with type name <code>type</cod> if possible.
* Returns a string which can be used to reference the type.
*
* @param type
* the fully qualified name of the type to import
* @return returns a type to which the type binding can be assigned to. The returned type contains is unqualified when an
* import could be added or was already known. It is fully qualified, if an import conflict prevented the import.
* @since 3.4
*/
public String addImport(String type) {
if (isReadOnly())
return type;
CompilationUnit cu = getCompilationUnit();
if (cu == null)
return type;
// try {
boolean qualified = type.indexOf('.') != -1;
if (!qualified) {
// IJavaSearchScope searchScope= SearchEngine.createJavaSearchScope(new IJavaElement[] { cu.getJavaProject() });
// SimpleName nameNode= null;
// TypeNameMatch[] matches= findAllTypes(type, searchScope, nameNode, null, cu);
// if (matches.length != 1) // only add import if we have a single match
// return type;
// type= matches[0].getFullyQualifiedName();
}
if (fImportRewrite == null) {
// TODO
// if (root == null) {
// fImportRewrite= StubUtility.createImportRewrite(cu, true);
// } else {
fImportRewrite = StubUtility.createImportRewrite(fDocument, cu, true);
// }
}
ImportRewriteContext context;
// if (root == null)
// context= null;
// else
context = new ContextSensitiveImportRewriteContext(cu, getCompletionOffset(), fImportRewrite);
return fImportRewrite.addImport(type, context);
// } catch (JavaModelException e) {
// handleException(null, e);
// return type;
// }
}
/**
* Adds a static import for the member with name <code>qualifiedMemberName</code>. The member is either a static field or a
* static method or a '*' to import all static members of a type.
*
* @param qualifiedMemberName
* the fully qualified name of the member to import or a qualified type name plus a '.*' suffix.
* @return returns either the simple member name if the import was successful or else the qualified name.
* @since 3.4
*/
public String addStaticImport(String qualifiedMemberName) {
// TODO
if (isReadOnly())
return qualifiedMemberName;
//
// CompilationUnit cu= getCompilationUnit();
// if (cu == null)
// return qualifiedMemberName;
//
// int memberOffset= qualifiedMemberName.lastIndexOf('.');
// if (memberOffset == -1)
// return qualifiedMemberName;
//
// String typeName= qualifiedMemberName.substring(0, memberOffset);
// String memberName= qualifiedMemberName.substring(memberOffset + 1, qualifiedMemberName.length());
// try {
// boolean isField;
// if ("*".equals(memberName)) { //$NON-NLS-1$
// isField= true;
// } else {
// IJavaProject javaProject= cu.getJavaProject();
//
// IType type= javaProject.findType(typeName);
// if (type == null)
// return qualifiedMemberName;
//
// IField field= type.getField(memberName);
// if (field.exists()) {
// isField= true;
// } else if (hasMethod(type, memberName)) {
// isField= false;
// } else {
// return qualifiedMemberName;
// }
// }
//
// CompilationUnit root= getASTRoot(cu);
// if (fImportRewrite == null) {
// if (root == null) {
// fImportRewrite= StubUtility.createImportRewrite(cu, true);
// } else {
// fImportRewrite= StubUtility.createImportRewrite(root, true);
// }
// }
//
// ImportRewriteContext context;
// if (root == null)
// context= null;
// else
// context= new ContextSensitiveImportRewriteContext(root, getCompletionOffset(), fImportRewrite);
//
// return fImportRewrite.addStaticImport(typeName, memberName, isField, context);
// } catch (JavaModelException e) {
// handleException(null, e);
// return typeName;
// }
return qualifiedMemberName;
}
// /**
// * Does <code>type</code> contain a method with <code>name</code>?
// *
// * @param type the type to inspect
// * @param name the name of the method to search for
// * @return true if has such a method
// * @throws JavaModelException if methods could not be retrieved
// * @since 3.4
// */
// private boolean hasMethod(IType type, String name) throws JavaModelException {
// IMethod[] methods= type.getMethods();
// for (int i= 0; i < methods.length; i++) {
// if (name.equals(methods[i].getElementName()))
// return true;
// }
//
// return false;
// }
private void rewriteImports() {
// TODO
// if (fImportRewrite == null)
// return;
//
// if (isReadOnly())
// return;
//
// ICompilationUnit cu= getCompilationUnit();
// if (cu == null)
// return;
//
// try {
// Position position= new Position(getCompletionOffset(), 0);
// IDocument document= getDocument();
// final String category= "__template_position_importer" + System.currentTimeMillis(); //$NON-NLS-1$
// IPositionUpdater updater= new DefaultPositionUpdater(category);
// document.addPositionCategory(category);
// document.addPositionUpdater(updater);
// document.addPosition(position);
//
// try {
// JavaModelUtil.applyEdit(cu, fImportRewrite.rewriteImports(null), false, null);
//
// setCompletionOffset(position.getOffset());
// } catch (CoreException e) {
// handleException(null, e);
// } finally {
// document.removePosition(position);
// document.removePositionUpdater(updater);
// document.removePositionCategory(category);
// }
// } catch (BadLocationException e) {
// handleException(null, e);
// } catch (BadPositionCategoryException e) {
// handleException(null, e);
// }
}
// /*
// * Finds a type by the simple name. From AddImportsOperation
// */
// private TypeNameMatch[] findAllTypes(String simpleTypeName, IJavaSearchScope searchScope, SimpleName nameNode,
// IProgressMonitor monitor, ICompilationUnit cu) throws JavaModelException {
// boolean is50OrHigher= JavaModelUtil.is50OrHigher(cu.getJavaProject());
//
// int typeKinds= SimilarElementsRequestor.ALL_TYPES;
// if (nameNode != null) {
// typeKinds= ASTResolving.getPossibleTypeKinds(nameNode, is50OrHigher);
// }
//
// ArrayList<TypeNameMatch> typeInfos= new ArrayList<TypeNameMatch>();
// TypeNameMatchCollector requestor= new TypeNameMatchCollector(typeInfos);
// new SearchEngine().searchAllTypeNames(null, 0, simpleTypeName.toCharArray(), SearchPattern.R_EXACT_MATCH |
// SearchPattern.R_CASE_SENSITIVE, getSearchForConstant(typeKinds), searchScope, requestor,
// IJavaSearchConstants.FORCE_IMMEDIATE_SEARCH, monitor);
//
// ArrayList<TypeNameMatch> typeRefsFound= new ArrayList<TypeNameMatch>(typeInfos.size());
// for (int i= 0, len= typeInfos.size(); i < len; i++) {
// TypeNameMatch curr= typeInfos.get(i);
// if (curr.getPackageName().length() > 0) { // do not suggest imports from the default package
// if (isOfKind(curr, typeKinds, is50OrHigher) && isVisible(curr, cu)) {
// typeRefsFound.add(curr);
// }
// }
// }
// return typeRefsFound.toArray(new TypeNameMatch[typeRefsFound.size()]);
// }
// private int getSearchForConstant(int typeKinds) {
// final int CLASSES= SimilarElementsRequestor.CLASSES;
// final int INTERFACES= SimilarElementsRequestor.INTERFACES;
// final int ENUMS= SimilarElementsRequestor.ENUMS;
// final int ANNOTATIONS= SimilarElementsRequestor.ANNOTATIONS;
//
// switch (typeKinds & (CLASSES | INTERFACES | ENUMS | ANNOTATIONS)) {
// case CLASSES: return IJavaSearchConstants.CLASS;
// case INTERFACES: return IJavaSearchConstants.INTERFACE;
// case ENUMS: return IJavaSearchConstants.ENUM;
// case ANNOTATIONS: return IJavaSearchConstants.ANNOTATION_TYPE;
// case CLASSES | INTERFACES: return IJavaSearchConstants.CLASS_AND_INTERFACE;
// case CLASSES | ENUMS: return IJavaSearchConstants.CLASS_AND_ENUM;
// default: return IJavaSearchConstants.TYPE;
// }
// }
//
// private boolean isOfKind(TypeNameMatch curr, int typeKinds, boolean is50OrHigher) {
// int flags= curr.getModifiers();
// if (Flags.isAnnotation(flags)) {
// return is50OrHigher && ((typeKinds & SimilarElementsRequestor.ANNOTATIONS) != 0);
// }
// if (Flags.isEnum(flags)) {
// return is50OrHigher && ((typeKinds & SimilarElementsRequestor.ENUMS) != 0);
// }
// if (Flags.isInterface(flags)) {
// return (typeKinds & SimilarElementsRequestor.INTERFACES) != 0;
// }
// return (typeKinds & SimilarElementsRequestor.CLASSES) != 0;
// }
// private boolean isVisible(TypeNameMatch curr, ICompilationUnit cu) {
// int flags= curr.getModifiers();
// if (Flags.isPrivate(flags)) {
// return false;
// }
// if (Flags.isPublic(flags) || Flags.isProtected(flags)) {
// return true;
// }
// return curr.getPackageName().equals(cu.getParent().getElementName());
// }
/**
* Evaluates a 'java' template in the context of a compilation unit
*
* @param template
* the template to be evaluated
* @param compilationUnit
* the compilation unit in which to evaluate the template
* @param position
* the position inside the compilation unit for which to evaluate the template
* @return the evaluated template
* @throws CoreException
* in case the template is of an unknown context type
* @throws BadLocationException
* in case the position is invalid in the compilation unit
* @throws TemplateException
* in case the evaluation fails
*/
public static String evaluateTemplate(Template template, CompilationUnit compilationUnit, int position)
throws CoreException, BadLocationException, TemplateException {
// TODO
TemplateContextType contextType = new ContextTypeRegistry().getContextType(template.getContextTypeId());
if (!(contextType instanceof CompilationUnitContextType))
throw new CoreException(new Status(IStatus.ERROR, "", IStatus.ERROR,
"Template file incomplete or has errors.", null));
// TODO
Document document = new WorkerDocument();
// if (compilationUnit != null && compilationUnit.exists())
// document.set(compilationUnit.getSource());
CompilationUnitContext context =
((CompilationUnitContextType)contextType).createContext(document, position, 0, compilationUnit);
context.setForceEvaluation(true);
TemplateBuffer buffer = context.evaluate(template);
if (buffer == null)
return null;
return buffer.getString();
}
TemplateVariable getTemplateVariable(String name) {
TemplateVariable variable = fVariables.get(name);
if (variable != null && !variable.isResolved())
getContextType().resolve(variable, this);
return variable;
}
/**
* Adds a multi-variable guess dependency.
*
* @param master
* the master variable - <code>slave</code> needs to be updated when <code>master</code> changes
* @param slave
* the dependent variable
* @since 3.3
*/
public void addDependency(MultiVariable master, MultiVariable slave) {
MultiVariableGuess guess = getMultiVariableGuess();
if (guess == null) {
guess = new MultiVariableGuess();
setMultiVariableGuess(guess);
}
guess.addDependency(master, slave);
}
}