/*
* Copyright 2009-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.groovy.eclipse.codeassist.processors;
import java.util.LinkedList;
import java.util.List;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.eclipse.codeassist.CharArraySourceBuffer;
import org.codehaus.groovy.eclipse.codeassist.GroovyContentAssist;
import org.codehaus.groovy.eclipse.codeassist.ProposalUtils;
import org.codehaus.groovy.eclipse.codeassist.relevance.Relevance;
import org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistContext;
import org.codehaus.groovy.eclipse.core.util.ExpressionFinder;
import org.codehaus.groovy.eclipse.core.util.ExpressionFinder.NameAndLocation;
import org.codehaus.jdt.groovy.model.GroovyCompilationUnit;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.CompletionProposal;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.NamingConventions;
import org.eclipse.jdt.internal.core.InternalNamingConventions;
import org.eclipse.jdt.internal.core.SearchableEnvironment;
import org.eclipse.jdt.internal.ui.text.java.JavaCompletionProposal;
import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext;
import org.eclipse.jface.preference.JFacePreferences;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.swt.graphics.Image;
/**
* Adds a list of new field proposals. All field proposals are dynamically typed static or
* non-static fields with an initializer of a closure.
*/
public class NewFieldCompletionProcessor extends AbstractGroovyCompletionProcessor {
public static class NewGroovyFieldCompletionProposal extends JavaCompletionProposal {
NewGroovyFieldCompletionProposal(String fieldName,
int replacementOffset, int replacementLength, int relevance,
boolean isStatic, boolean useKeywordBeforeReplacement, String typeName) {
super(createReplacementString(fieldName, typeName, isStatic),
replacementOffset, replacementLength,
createImage(isStatic), createDisplayString(fieldName, typeName,
isStatic, useKeywordBeforeReplacement), relevance);
}
// can we do better with the initializer?
static String createReplacementString(String fieldName, String typeName, boolean isStatic) {
if (isStatic) {
if (typeName != null) {
return "static " + typeName + fieldName + " = null";
} else {
return "static " + fieldName + " = null";
}
} else if (typeName != null) {
return typeName + fieldName;
} else {
return "def " + fieldName;
}
}
static Image createImage(boolean isStatic) {
CompletionProposal dummy = CompletionProposal.create(CompletionProposal.FIELD_REF, -1);
if (isStatic) {
dummy.setFlags(Flags.AccStatic);
}
return ProposalUtils.getImage(dummy);
}
static StyledString createDisplayString(String fieldName, String typeName,
boolean isStatic, boolean useKeywordBeforeReplacement) {
StyledString ss = new StyledString();
// use a different styled string depending on the completion context
// if the context completion node is a field, then must include the field modifier
// if not, then don't include it.
// this is because the display string must match the replacement otherwise no
// replacement occurs.
if (useKeywordBeforeReplacement) {
if (isStatic) {
ss.append("static ", StyledString
.createColorRegistryStyler(
JFacePreferences.HYPERLINK_COLOR, null));
if (typeName != null) {
ss.append(typeName);
}
} else {
if (typeName == null) {
ss.append("def ", StyledString.createColorRegistryStyler(JFacePreferences.HYPERLINK_COLOR, null));
} else {
ss.append(typeName);
}
}
}
if (isStatic) {
ss.append(fieldName);
ss.append(" - New static property",
StyledString.QUALIFIER_STYLER);
} else {
ss.append(fieldName);
ss.append(" - New property", StyledString.QUALIFIER_STYLER);
}
return ss;
}
}
public NewFieldCompletionProcessor(ContentAssistContext context, JavaContentAssistInvocationContext javaContext, SearchableEnvironment nameEnvironment) {
super(context, javaContext, nameEnvironment);
}
public List<ICompletionProposal> generateProposals(IProgressMonitor monitor) {
ContentAssistContext context = getContext();
List<String> unimplementedFieldNames = getAllSuggestedFieldNames(context);
List<ICompletionProposal> proposals = new LinkedList<ICompletionProposal>();
IType enclosingType = context.getEnclosingType();
if (enclosingType != null) {
for (String fieldName : unimplementedFieldNames) {
proposals.add(createProposal(fieldName, context, enclosingType));
}
// next check to see if we are at a partially completed field
// declaration,
// ie- is the type specified, but the name is not (or is only
// partially specified)?
NameAndLocation nameAndLocation = findCompletionTypeName(context.unit, context.completionLocation);
if (nameAndLocation != null) {
String typeName = nameAndLocation.toTypeName();
if (typeName.equals("def")) {
typeName = "value";
}
String[] suggestedNames = NamingConventions.suggestVariableNames(NamingConventions.VK_INSTANCE_FIELD,
InternalNamingConventions.BK_SIMPLE_TYPE_NAME, typeName, context.unit.getJavaProject(),
nameAndLocation.dims(), null, true);
if (suggestedNames != null) {
for (String suggestedName : suggestedNames) {
if (suggestedName.startsWith(context.completionExpression)) {
proposals.add(createProposal(suggestedName, nameAndLocation.name, context, enclosingType, false, true,
nameAndLocation.location, context.completionLocation - nameAndLocation.location));
}
}
}
}
}
return proposals;
}
/**
* works backward from the current location to see if there is something
* that looks like a type name as the previous token
*/
private NameAndLocation findCompletionTypeName(GroovyCompilationUnit unit, int completionLocation) {
return new ExpressionFinder().findPreviousTypeNameToken(new CharArraySourceBuffer(unit.getContents()), completionLocation);
}
private List<String> getAllSuggestedFieldNames(ContentAssistContext context) {
List<String> allNewFieldNames = new LinkedList<String>();
try {
List<IProposalProvider> providers = ProposalProviderRegistry.getRegistry().getProvidersFor(context.unit);
for (IProposalProvider provider : providers) {
List<String> newFieldNames = provider.getNewFieldProposals(context);
if (newFieldNames != null) {
allNewFieldNames.addAll(newFieldNames);
}
}
} catch (CoreException e) {
GroovyContentAssist.logError("Exception looking for proposal providers in " + context.unit.getElementName(), e);
}
return allNewFieldNames;
}
private ICompletionProposal createProposal(String fieldName,
ContentAssistContext context, IType enclosingType) {
boolean isStatic;
if (fieldName.startsWith(IProposalProvider.NONSTATIC_FIELD)) {
fieldName = fieldName.substring(IProposalProvider.NONSTATIC_FIELD
.length());
isStatic = false;
} else {
isStatic = true;
}
// use keyword replacement if the def/static keyword is completely or partially present
boolean useKeywordBeforeReplacement = context.completionExpression
.length() > 0
&& ((context.completionNode instanceof FieldNode)
|| "def".startsWith(context.completionExpression) || "static"
.startsWith(context.completionExpression));
// replace start is either the start of the field node (if using keyword
// replacement),
// or it is the completion location - the length of the existing part of
// the expression
int replaceStart = context.completionNode instanceof FieldNode ? context.completionNode.getStart()
: context.completionLocation - context.completionExpression.length();
// the completion length is the length of the bit of text that will be
// replaced
// this is either the completion expression length or the difference
// between the
// start of the field node and the completion location
int replaceLength = context.completionNode instanceof FieldNode ? context.completionLocation - replaceStart
: context.completionExpression.length();
return createProposal(fieldName, null, context, enclosingType, isStatic, useKeywordBeforeReplacement, replaceStart,
replaceLength);
}
private ICompletionProposal createProposal(String fieldName, String typeName, ContentAssistContext context,
IType enclosingType, boolean isStatic, boolean useKeywordBeforeReplacement, int replaceStart, int replaceLength) {
int relevance = Relevance.VERY_HIGH.getRelavance();
return new NewGroovyFieldCompletionProposal(fieldName, replaceStart, replaceLength, relevance, isStatic, useKeywordBeforeReplacement, typeName);
}
}