/*******************************************************************************
* Copyright (c) 2000, 2009 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.wst.jsdt.internal.corext.template.java;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DefaultPositionUpdater;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IPositionUpdater;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.text.templates.TemplateBuffer;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.jface.text.templates.TemplateException;
import org.eclipse.jface.text.templates.TemplateTranslator;
import org.eclipse.jface.text.templates.TemplateVariable;
import org.eclipse.jface.text.templates.TemplateVariableType;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.wst.jsdt.core.Flags;
import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
import org.eclipse.wst.jsdt.core.IJavaScriptElement;
import org.eclipse.wst.jsdt.core.IJavaScriptProject;
import org.eclipse.wst.jsdt.core.JavaScriptModelException;
import org.eclipse.wst.jsdt.core.Signature;
import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
import org.eclipse.wst.jsdt.core.dom.SimpleName;
import org.eclipse.wst.jsdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.wst.jsdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
import org.eclipse.wst.jsdt.core.search.IJavaScriptSearchConstants;
import org.eclipse.wst.jsdt.core.search.IJavaScriptSearchScope;
import org.eclipse.wst.jsdt.core.search.SearchEngine;
import org.eclipse.wst.jsdt.core.search.SearchPattern;
import org.eclipse.wst.jsdt.core.search.TypeNameMatch;
import org.eclipse.wst.jsdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
import org.eclipse.wst.jsdt.internal.corext.codemanipulation.StubUtility;
import org.eclipse.wst.jsdt.internal.corext.template.java.CompilationUnitCompletion.Variable;
import org.eclipse.wst.jsdt.internal.corext.util.JavaModelUtil;
import org.eclipse.wst.jsdt.internal.corext.util.Strings;
import org.eclipse.wst.jsdt.internal.corext.util.TypeNameMatchCollector;
import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
import org.eclipse.wst.jsdt.internal.ui.javaeditor.ASTProvider;
import org.eclipse.wst.jsdt.internal.ui.text.correction.ASTResolving;
import org.eclipse.wst.jsdt.internal.ui.text.correction.SimilarElementsRequestor;
import org.eclipse.wst.jsdt.internal.ui.text.template.contentassist.MultiVariable;
import org.eclipse.wst.jsdt.internal.ui.text.template.contentassist.MultiVariableGuess;
import org.eclipse.wst.jsdt.internal.ui.util.ExceptionHandler;
import org.eclipse.wst.jsdt.ui.JavaScriptUI;
import org.eclipse.wst.jsdt.ui.PreferenceConstants;
/**
* 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.
*
*/
private Set fUsedNames= new HashSet();
private Map fVariables= new HashMap();
/**
* 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, IDocument document, int completionOffset, int completionLength, IJavaScriptUnit 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, IDocument document, Position completionPosition, IJavaScriptUnit compilationUnit) {
super(type, document, completionPosition, compilationUnit);
}
/**
* Returns the indentation level at the position of code completion.
*
* @return the indentation level at the position of the code completion
*/
private int getIndentation() {
int start= getStart();
IDocument document= getDocument();
try {
IRegion region= document.getLineInformationOfOffset(start);
String lineContent= document.get(region.getOffset(), region.getLength());
IJavaScriptProject project= getJavaProject();
return Strings.computeIndentUnits(lineContent, project);
} catch (BadLocationException e) {
return 0;
}
}
/*
* @see TemplateContext#evaluate(Template template)
*/
public TemplateBuffer evaluate(Template template) throws BadLocationException, TemplateException {
clear();
if (!canEvaluate(template))
throw new TemplateException(JavaTemplateMessages.Context_error_cannot_evaluate);
TemplateTranslator translator= new TemplateTranslator() {
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);
IPreferenceStore prefs= JavaScriptPlugin.getDefault().getPreferenceStore();
boolean useCodeFormatter= prefs.getBoolean(PreferenceConstants.TEMPLATES_USE_CODEFORMATTER);
IJavaScriptProject project= getJavaProject();
JavaFormatter formatter= new JavaFormatter(TextUtilities.getDefaultLineDelimiter(getDocument()), getIndentation(), useCodeFormatter, project);
formatter.format(buffer, this);
clear();
return buffer;
}
private void clear() {
fUsedNames.clear();
}
/*
* @see TemplateContext#canEvaluate(Template templates)
*/
public boolean canEvaluate(Template template) {
if (fForceEvaluation)
return true;
String key= getKey();
return
template.matches(key, getContextType().getId()) &&
key.length() != 0 && template.getName().toLowerCase().startsWith(key.toLowerCase());
}
/*
* @see DocumentTemplateContext#getCompletionPosition();
*/
public int getStart() {
if (fIsManaged && getCompletionLength() > 0)
return super.getStart();
try {
IDocument document= getDocument();
int start= getCompletionOffset();
int end= getCompletionOffset() + getCompletionLength();
while (start != 0 && isTempalteNamePart(document.getChar(start - 1)))
start--;
while (start != end && Character.isWhitespace(document.getChar(start)))
start++;
if (start == end)
start= getCompletionOffset();
return start;
} catch (BadLocationException e) {
return super.getStart();
}
}
/**
* Tells whether the given character can be part of a template name.
*
* @param ch the character to test
* @return <code>true</code> if the given character can be part of a template name
*
*/
private boolean isTempalteNamePart(char ch) {
return !Character.isWhitespace(ch) && ch != '(' && ch != ')' && ch != '{' && ch != '}' && ch != ';' && ch != '>';
}
/*
* @see org.eclipse.wst.jsdt.internal.corext.template.DocumentTemplateContext#getEnd()
*/
public int getEnd() {
if (fIsManaged || getCompletionLength() == 0)
return super.getEnd();
try {
IDocument document= getDocument();
int start= getCompletionOffset();
int end= getCompletionOffset() + getCompletionLength();
while (start != end && Character.isWhitespace(document.getChar(end - 1)))
end--;
return end;
} catch (BadLocationException e) {
return super.getEnd();
}
}
/*
* @see org.eclipse.wst.jsdt.internal.corext.template.DocumentTemplateContext#getKey()
*/
public String getKey() {
if (getCompletionLength() == 0)
return super.getKey();
try {
IDocument 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 static void handleException(Shell shell, Exception e) {
String title= JavaTemplateMessages.JavaContext_error_title;
if (e instanceof CoreException)
ExceptionHandler.handle((CoreException)e, shell, title, null);
else if (e instanceof InvocationTargetException)
ExceptionHandler.handle((InvocationTargetException)e, shell, title, null);
else {
JavaScriptPlugin.log(e);
MessageDialog.openError(shell, title, e.getMessage());
}
}
private CompilationUnitCompletion getCompletion() {
IJavaScriptUnit compilationUnit= getCompilationUnit();
if (fCompletion == null) {
fCompletion= new CompilationUnitCompletion(compilationUnit);
if (compilationUnit != null) {
try {
compilationUnit.codeComplete(getStart(), fCompletion);
} catch (JavaScriptModelException e) {
// ignore
}
}
}
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
*
*/
private void arrange(Variable[] variables) {
Arrays.sort(variables, new Comparator() {
public int compare(Object o1, Object o2) {
return rank((Variable) o1) - rank((Variable) o2);
}
private int rank(Variable l) {
return fUsedNames.contains(l.getName()) ? 1 : 0;
}
});
}
/**
* Returns the names of local variables matching <code>type</code>.
*
* @return the names of local variables matching <code>type</code>
*
*/
public Variable[] getLocalVariables(String type) {
Variable[] localVariables= getCompletion().findLocalVariables(type);
arrange(localVariables);
return localVariables;
}
/**
* Returns the names of all local variables.
*
* @return the names of all local variables
*
*/
public String[] getLocalVariableNames() {
String[] localVariableNames = getCompletion().getLocalVariableNames();
return localVariableNames;
}
/**
* Returns the names of fields matching <code>type</code>.
*
* @return the names of fields matching <code>type</code>
*
*/
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;
}
private 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);
}
IJavaScriptProject project= getJavaProject();
if (project != null)
return StubUtility.getVariableNameSuggestions(StubUtility.LOCAL, project, type, dim, Arrays.asList(excludes), true);
// fallback if we lack proper context: roll-our own lowercasing
return new String[] {Signature.getSimpleName(type).toLowerCase()};
}
public void addImport(String type) {
if (isReadOnly())
return;
IJavaScriptUnit cu= getCompilationUnit();
if (cu == null)
return;
try {
boolean qualified= type.indexOf('.') != -1;
if (!qualified) {
IJavaScriptSearchScope searchScope= SearchEngine.createJavaSearchScope(new IJavaScriptElement[] { cu.getJavaScriptProject() });
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= matches[0].getFullyQualifiedName();
}
Position position= new Position(getCompletionOffset(), getCompletionLength());
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 {
ImportRewrite rewrite= StubUtility.createImportRewrite(cu, true);
JavaScriptUnit root= getASTRoot(cu);
ImportRewriteContext context;
if (root == null)
context= null;
else
context= new ContextSensitiveImportRewriteContext(root, getCompletionOffset(), rewrite);
rewrite.addImport(type, type, context);
JavaModelUtil.applyEdit(cu, rewrite.rewriteImports(null), false, null);
setCompletionOffset(position.getOffset());
setCompletionLength(position.getLength());
} 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);
} catch (JavaScriptModelException e) {
handleException(null, e);
}
}
private JavaScriptUnit getASTRoot(IJavaScriptUnit compilationUnit) {
return JavaScriptPlugin.getDefault().getASTProvider().getAST(compilationUnit, ASTProvider.WAIT_NO, new NullProgressMonitor());
}
/*
* Finds a type by the simple name. From AddImportsOperation
*/
private TypeNameMatch[] findAllTypes(String simpleTypeName, IJavaScriptSearchScope searchScope, SimpleName nameNode, IProgressMonitor monitor, IJavaScriptUnit cu) throws JavaScriptModelException {
boolean is50OrHigher= JavaModelUtil.is50OrHigher(cu.getJavaScriptProject());
int typeKinds= SimilarElementsRequestor.ALL_TYPES;
if (nameNode != null) {
typeKinds= ASTResolving.getPossibleTypeKinds(nameNode, is50OrHigher);
}
ArrayList typeInfos= new ArrayList();
TypeNameMatchCollector requestor= new TypeNameMatchCollector(typeInfos);
new SearchEngine().searchAllTypeNames(null, 0, simpleTypeName.toCharArray(), SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE, getSearchForConstant(typeKinds), searchScope, requestor, IJavaScriptSearchConstants.FORCE_IMMEDIATE_SEARCH, monitor);
ArrayList typeRefsFound= new ArrayList(typeInfos.size());
for (int i= 0, len= typeInfos.size(); i < len; i++) {
TypeNameMatch curr= (TypeNameMatch) 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 (TypeNameMatch[]) typeRefsFound.toArray(new TypeNameMatch[typeRefsFound.size()]);
}
private int getSearchForConstant(int typeKinds) {
final int CLASSES= SimilarElementsRequestor.CLASSES;
switch (typeKinds & (CLASSES)) {
case CLASSES: return IJavaScriptSearchConstants.CLASS;
default: return IJavaScriptSearchConstants.TYPE;
}
}
private boolean isOfKind(TypeNameMatch curr, int typeKinds, boolean is50OrHigher) {
return (typeKinds & SimilarElementsRequestor.CLASSES) != 0;
}
private boolean isVisible(TypeNameMatch curr, IJavaScriptUnit cu) {
int flags= curr.getModifiers();
if (Flags.isPrivate(flags)) {
return false;
}
if (Flags.isPublic(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, IJavaScriptUnit compilationUnit, int position) throws CoreException, BadLocationException, TemplateException {
TemplateContextType contextType= JavaScriptPlugin.getDefault().getTemplateContextRegistry().getContextType(JavaContextType.NAME);
if (contextType == null)
throw new CoreException(new Status(IStatus.ERROR, JavaScriptUI.ID_PLUGIN, IStatus.ERROR, JavaTemplateMessages.JavaContext_error_message, null));
IDocument document= new Document();
if (compilationUnit != null && compilationUnit.exists())
document.set(compilationUnit.getSource());
JavaContext context= new JavaContext(contextType, 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= (TemplateVariable) 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
*
*/
public void addDependency(MultiVariable master, MultiVariable slave) {
MultiVariableGuess guess= getMultiVariableGuess();
if (guess == null) {
guess= new MultiVariableGuess();
setMultiVariableGuess(guess);
}
guess.addDependency(master, slave);
}
}