/* * 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.creators; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import groovyjarjarasm.asm.Opcodes; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.ImportNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.eclipse.GroovyLogManager; import org.codehaus.groovy.eclipse.TraceCategory; import org.codehaus.groovy.eclipse.codeassist.ProposalUtils; import org.codehaus.groovy.eclipse.codeassist.proposals.GroovyFieldProposal; import org.codehaus.groovy.eclipse.codeassist.proposals.GroovyMethodProposal; import org.codehaus.groovy.eclipse.codeassist.proposals.IGroovyProposal; import org.eclipse.jdt.groovy.search.VariableScope; /** * GRECLIPSE-512: most fields have properties created for them, so we want to avoid duplicate proposals. * Constants are an exception, so return them here. I may have missed something, so be prepared to add more kinds of fields here. */ public class FieldProposalCreator extends AbstractProposalCreator { private static final GroovyFieldProposal CLASS_PROPOSAL = createClassProposal(); private Set<ClassNode> alreadySeen = new HashSet<ClassNode>(); private static GroovyFieldProposal createClassProposal() { FieldNode field = new FieldNode("class", Opcodes.ACC_PUBLIC & Opcodes.ACC_STATIC & Opcodes.ACC_FINAL, VariableScope.CLASS_CLASS_NODE, VariableScope.OBJECT_CLASS_NODE, null); field.setDeclaringClass(VariableScope.OBJECT_CLASS_NODE); return new GroovyFieldProposal(field); } public List<IGroovyProposal> findAllProposals(ClassNode type, Set<ClassNode> categories, String prefix, boolean isStatic, boolean isPrimary) { List<IGroovyProposal> proposals = new ArrayList<IGroovyProposal>(); boolean isFirstTime = alreadySeen.isEmpty(); Collection<FieldNode> allFields = getAllFields(type, alreadySeen); for (FieldNode field : allFields) { // in static context, only allow static fields if ((!isStatic || field.isStatic()) && ProposalUtils.looselyMatches(prefix, field.getName())) { float relevanceMultiplier = isInterestingType(field.getType()) ? 1.1f : 1.0f; if (field.isStatic()) relevanceMultiplier *= 0.1f; // de-emphasize 'this' references inside closure if (!isFirstTime) relevanceMultiplier *= 0.1f; GroovyFieldProposal proposal = new GroovyFieldProposal(field); proposal.setRelevanceMultiplier(relevanceMultiplier); proposals.add(proposal); if (field.getInitialExpression() instanceof ClosureExpression) { // also add a method-like proposal proposals.add(new GroovyMethodProposal(convertToMethodProposal(field))); } } } if (isStatic && "class".startsWith(prefix)) { proposals.add(CLASS_PROPOSAL); } if (currentScope != null) { ClassNode enclosingTypeDeclaration = currentScope.getEnclosingTypeDeclaration(); if (enclosingTypeDeclaration != null && isFirstTime && isPrimary && type.getModule() != null) { findStaticImportProposals(proposals, prefix, type.getModule()); findStaticFavoriteProposals(proposals, prefix, type.getModule()); } } return proposals; } @Override protected boolean isInterestingType(ClassNode type) { return type.isEnum() || super.isInterestingType(type); } private MethodNode convertToMethodProposal(FieldNode field) { MethodNode method = new MethodNode(field.getName(), field.getModifiers(), field.getType(), extractParameters(field.getInitialExpression()), ClassNode.EMPTY_ARRAY, new BlockStatement()); method.setDeclaringClass(field.getDeclaringClass()); return method; } private Parameter[] extractParameters(Expression expr) { if (expr instanceof ClosureExpression) { return ((ClosureExpression) expr).getParameters(); } return Parameter.EMPTY_ARRAY; } private void findStaticImportProposals(List<IGroovyProposal> proposals, String prefix, ModuleNode module) { for (Map.Entry<String, ImportNode> entry : module.getStaticImports().entrySet()) { String fieldName = entry.getValue().getFieldName(); if (fieldName != null && ProposalUtils.looselyMatches(prefix, fieldName)) { FieldNode field = entry.getValue().getType().getField(fieldName); if (field != null && field.isStatic()) { proposals.add(new GroovyFieldProposal(field)); } } } for (Map.Entry<String, ImportNode> entry : module.getStaticStarImports().entrySet()) { ClassNode type = entry.getValue().getType(); if (type != null) { for (FieldNode field : (Iterable<FieldNode>) type.getFields()) { if (field.isStatic() && ProposalUtils.looselyMatches(prefix, field.getName())) { proposals.add(new GroovyFieldProposal(field)); } } } } } private void findStaticFavoriteProposals(List<IGroovyProposal> proposals, String prefix, ModuleNode module) { for (String favoriteStaticMember : favoriteStaticMembers) { int pos = favoriteStaticMember.lastIndexOf('.'); String typeName = favoriteStaticMember.substring(0, pos); String fieldName = favoriteStaticMember.substring(pos + 1); ClassNode typeNode = tryResolveClassNode(typeName, module); if (typeNode == null) { if (GroovyLogManager.manager.hasLoggers()) { GroovyLogManager.manager.log(TraceCategory.CONTENT_ASSIST, "FieldProposalCreator: Cannot resolve favorite type " + typeName); } continue; } if ("*".equals(fieldName)) { for (FieldNode field : (Iterable<FieldNode>) typeNode.getFields()) { if (field.isStatic() && ProposalUtils.looselyMatches(prefix, field.getName())) { GroovyFieldProposal proposal = new GroovyFieldProposal(field); proposal.setRequiredStaticImport(typeName + '.' + field.getName()); proposals.add(proposal); } } } else { if (ProposalUtils.looselyMatches(prefix, fieldName)) { FieldNode field = typeNode.getField(fieldName); if (field != null && field.isStatic()) { GroovyFieldProposal proposal = new GroovyFieldProposal(field); proposal.setRequiredStaticImport(favoriteStaticMember); proposals.add(proposal); } } } } } }