/******************************************************************************* * Copyright (c) 2012 Pivotal Software, Inc. * 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: * Pivotal Software, Inc. - initial API and implementation *******************************************************************************/ package org.grails.ide.eclipse.editor.groovy.contentassist; import groovyjarjarasm.asm.Opcodes; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.codehaus.groovy.ast.AnnotatedNode; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.PropertyNode; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.eclipse.codeassist.ProposalUtils; import org.codehaus.groovy.eclipse.codeassist.processors.IProposalProvider; import org.codehaus.groovy.eclipse.codeassist.proposals.GroovyFieldProposal; import org.codehaus.groovy.eclipse.codeassist.proposals.GroovyMethodProposal; import org.codehaus.groovy.eclipse.codeassist.proposals.GroovyPropertyProposal; import org.codehaus.groovy.eclipse.codeassist.proposals.IGroovyProposal; import org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistContext; import org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistLocation; import org.eclipse.jdt.groovy.search.VariableScope; import org.grails.ide.eclipse.core.internal.plugins.GrailsCore; import org.grails.ide.eclipse.core.model.ContributedMethod; import org.grails.ide.eclipse.core.model.ContributedProperty; import org.grails.ide.eclipse.editor.groovy.elements.ControllerClass; import org.grails.ide.eclipse.editor.groovy.elements.DomainClass; import org.grails.ide.eclipse.editor.groovy.elements.DomainClass.NamedQueryClassNode; import org.grails.ide.eclipse.editor.groovy.elements.GrailsProject; import org.grails.ide.eclipse.editor.groovy.elements.GrailsWorkspaceCore; import org.grails.ide.eclipse.editor.groovy.elements.IGrailsElement; import org.grails.ide.eclipse.editor.groovy.elements.TagLibClass; import org.grails.ide.eclipse.editor.groovy.types.PerProjectServiceCache; /** * @author Andrew Eisenberg * @author Nieraj Singh */ public class GrailsProposalProvider implements IProposalProvider { /** * */ private static final String GRAILS = "Grails"; //$NON-NLS-1$ private static final int GRAILS_PROPOSALS_RELEVANCE_MULTIPLIER = 10; /** * If we are in a domain class, then provide all the special fields that can * be added. * Also, if in a relevant grails element, contribute the service names */ public List<String> getNewFieldProposals(ContentAssistContext context) { GrailsProject proj = GrailsWorkspaceCore.get().getGrailsProjectFor(context.unit); if (proj == null) { // not in a grails project return Collections.emptyList(); } IGrailsElement elt = proj.getGrailsElement(context.unit); List<String> unimplemented; if (elt instanceof DomainClass) { DomainClass domainClass = (DomainClass) elt; unimplemented = domainClass .getUnimplementedStaticFields(); // if the prefix is "static", then we return all proposals if (!"static".startsWith(context.completionExpression)) { for (Iterator<String> unimplementedIter = unimplemented.iterator(); unimplementedIter .hasNext();) { String field = unimplementedIter.next(); if (!field.startsWith(context.completionExpression)) { unimplementedIter.remove(); } } } } else{ unimplemented = new ArrayList<String>(); } if (supportsServiceInjection(elt)) { PerProjectServiceCache cache = GrailsCore.get().connect(context.unit.getJavaProject().getProject(), PerProjectServiceCache.class); if (cache != null && context.containingDeclaration instanceof ClassNode) { ClassNode containingClass = (ClassNode) context.containingDeclaration; for (String serviceName : cache.getAllServices().keySet()) { if (( "def".startsWith(context.completionExpression) || serviceName.startsWith(context.completionExpression)) && containingClass.getField(serviceName) == null) { unimplemented.add("NONSTATIC " + serviceName); //$NON-NLS-1$ } } } } return unimplemented; } /** * @param elt * @return */ protected boolean supportsServiceInjection(IGrailsElement elt) { switch (elt.getKind()) { case DOMAIN_CLASS: case SERVICE_CLASS: case CONTROLLER_CLASS: case TAGLIB_CLASS: case BUILD_CONFIG: case INTEGRATION_TEST: return true; default: return false; } } /** * None, really */ public List<MethodNode> getNewMethodProposals(ContentAssistContext context) { return null; } /** * Depending on the kind of the type of the completion expression (eg- * controller, domain, etc), add the appropriate possibilities. Delegate to * the appropriate Grails element */ public List<IGroovyProposal> getStatementAndExpressionProposals( ContentAssistContext context, ClassNode completionType, boolean isStatic, Set<ClassNode> categories) { GrailsProject proj = GrailsWorkspaceCore.get() .getGrailsProjectFor(context.unit); if (proj == null) { // not in a grails project return Collections.emptyList(); } List<IGroovyProposal> proposals = new ArrayList<IGroovyProposal>(10); IGrailsElement grailsElement = proj.getGrailsElement(context.unit); // Proposals here depend on the compilation unit of the context, not the // type. switch (grailsElement.getKind()) { case DOMAIN_CLASS: DomainClass domainClass = (DomainClass) grailsElement; // create the special mapping proposals if inside the mapping // field closure if (domainClass.isMappingField(context.containingDeclaration)) { proposals.addAll(createMappingProposals(context, domainClass)); } else if (domainClass.isConstraintsField(context.containingDeclaration)) { proposals.addAll(createConstraintsProposals(context, domainClass)); } break; default: // either not yet handling this kind, or this kind has no // special completions break; } // now create the methods that are not lexically specific, but rely on // the kind of the type being completed if (completionType instanceof NamedQueryClassNode) { // completing off of a namedQuery of a domain class DomainClass domainClass = ((NamedQueryClassNode) completionType).getDomainClass(); ClassNode declaringType = domainClass.getGroovyClass(); if (declaringType != null) { // finder prefixes proposals.addAll(findFinderProposals(context, domainClass, declaringType)); // dynamic finders proposals.addAll(findDynamicFinderProposals(context, domainClass)); // named queries proposals.addAll(findNamedQueryProposals(context, domainClass, declaringType)); } // no other proposals are valid return proposals; } IGrailsElement completionGrailsElement; if (completionType == null || completionType.equals(context.getEnclosingGroovyType())) { completionGrailsElement = grailsElement; } else { completionGrailsElement = proj.getGrailsElement(completionType); } switch (completionGrailsElement.getKind()) { case DOMAIN_CLASS: DomainClass domainClass = (DomainClass) completionGrailsElement; proposals.addAll(createDomainProposals(context, domainClass, isStatic)); break; case CONTROLLER_CLASS: if (!isStatic) { ControllerClass controllerClass = (ControllerClass) completionGrailsElement; proposals.addAll(createControllerProposals(context, controllerClass)); } break; case TAGLIB_CLASS: TagLibClass tagLibClass = (TagLibClass) completionGrailsElement; proposals.addAll(createTagLibProposals(context, tagLibClass)); default: // either not yet handling this kind, it is an invalid kind, or // this kind has no special completions break; } return proposals; } /** * @param context * @param controllerClass * @return */ private List<IGroovyProposal> createControllerProposals( ContentAssistContext context, ControllerClass controllerClass) { if (context.location == ContentAssistLocation.STATEMENT || context.location == ContentAssistLocation.EXPRESSION || context.location == ContentAssistLocation.SCRIPT) { Map<String, ClassNode> references = controllerClass .getExtraControllerReferences(); List<IGroovyProposal> proposals = new ArrayList<IGroovyProposal>( references.size()); ClassNode declaringType = controllerClass.getGroovyClass(); if (declaringType != null) { for (Map.Entry<String, ClassNode> entry : references.entrySet()) { if (entry.getKey().startsWith(context.completionExpression)) { if (controllerClass.isSpecialMethodReference(entry.getKey())) { proposals.add(new GroovyMethodProposal(createNoArgMethodNode( entry.getKey(), context, declaringType, entry .getValue(), Opcodes.ACC_PUBLIC), GRAILS)); } else { proposals.add(new GroovyFieldProposal(createFieldNode( entry.getKey(), context, declaringType, entry .getValue(), Opcodes.ACC_PUBLIC), GRAILS_PROPOSALS_RELEVANCE_MULTIPLIER, GRAILS)); } } } // now add all the contributed methods and properties Map<String, ContributedProperty> contribProps = controllerClass.getAllContributedProperties(); for (Map.Entry<String, ContributedProperty> contribProp : contribProps.entrySet()) { String contribName = contribProp.getKey(); if (contribName.startsWith(context.completionExpression)) { proposals.add(new GroovyPropertyProposal(contribProp.getValue().createMockProperty(declaringType), contribProp.getValue().getContributedBy())); } } Map<String, Set<ContributedMethod>> contribMethods = controllerClass.getAllContributedMethods(); for (Entry<String, Set<ContributedMethod>> contribMethodEntry : contribMethods.entrySet()) { String contribName = contribMethodEntry.getKey(); if (contribName.startsWith(context.completionExpression)) { for (ContributedMethod contribMethod : contribMethodEntry.getValue()) { proposals.add(new GroovyMethodProposal(contribMethod.createMockMethod(declaringType), contribMethod.getContributedBy())); } } } return proposals; } // if (declaringType != null) { } return Collections.EMPTY_LIST; } private List<IGroovyProposal> createTagLibProposals(ContentAssistContext context, TagLibClass tagLibClass) { Map<String, ClassNode> tagLibMembers = tagLibClass .getTagLibMembers(); List<IGroovyProposal> proposals = new LinkedList<IGroovyProposal>(); for (Entry<String, ClassNode> entry : tagLibMembers .entrySet()) { if (entry.getKey().startsWith(context.completionExpression)) { ClassNode declaringType = tagLibClass.getGroovyClass(); ClassNode type = entry.getValue(); if (type == null) { type = declaringType; } proposals.add(new GroovyFieldProposal( createFieldNode(entry.getKey(), context, declaringType, type, Opcodes.ACC_PUBLIC), GRAILS_PROPOSALS_RELEVANCE_MULTIPLIER, GRAILS)); } } return proposals; } /** * Only valid within the <code>mapping</code> field in the domain class * * @param context * @param domainClass * @return */ private List<IGroovyProposal> createMappingProposals( ContentAssistContext context, DomainClass domainClass) { if (context.location == ContentAssistLocation.STATEMENT) { String[] mappingsFields = DomainClass.getMappingsFields(); ClassNode enclosingGroovyType = context.getEnclosingGroovyType(); List<IGroovyProposal> extraProposals = new ArrayList<IGroovyProposal>( mappingsFields.length); for (String mappingFieldName : mappingsFields) { if (mappingFieldName.startsWith(context.completionExpression)) { extraProposals.add(new GroovyFieldProposal(createFieldNode( mappingFieldName, context, enclosingGroovyType, Opcodes.ACC_STATIC), GRAILS_PROPOSALS_RELEVANCE_MULTIPLIER, GRAILS)); } } return extraProposals; } else { return Collections.EMPTY_LIST; } } /** * Only valid within the <code>constraints</code> field in the domain class * * @param context * @param domainClass * @return */ private List<IGroovyProposal> createConstraintsProposals( ContentAssistContext context, DomainClass domainClass) { if (context.location == ContentAssistLocation.STATEMENT) { String[] contstraintsFields = DomainClass.getContstraintsFields(); ClassNode enclosingGroovyType = context.getEnclosingGroovyType(); List<IGroovyProposal> extraProposals = new ArrayList<IGroovyProposal>( contstraintsFields.length); for (String constraintsFieldName : contstraintsFields) { if (constraintsFieldName.startsWith(context.completionExpression)) { extraProposals.add(new GroovyFieldProposal(createFieldNode( constraintsFieldName, context, enclosingGroovyType, Opcodes.ACC_STATIC), GRAILS_PROPOSALS_RELEVANCE_MULTIPLIER, GRAILS)); } } List<PropertyNode> props = domainClass.getDomainProperties(); for (PropertyNode prop : props) { if (prop.getName().startsWith(context.completionExpression)) { extraProposals.add(new GroovyPropertyProposal(prop, GRAILS)); } } return extraProposals; } else { return Collections.EMPTY_LIST; } } /** * @param context * @param domainClass * @return */ private List<IGroovyProposal> createDomainProposals( ContentAssistContext context, DomainClass domainClass, boolean isStatic) { List<IGroovyProposal> proposals = new LinkedList<IGroovyProposal>(); if (context.location == ContentAssistLocation.STATEMENT || context.location == ContentAssistLocation.EXPRESSION || context.location == ContentAssistLocation.SCRIPT) { ClassNode declaringType = domainClass.getGroovyClass(); if (declaringType != null) { // static: name -> (returnType, declaringType) proposals.addAll(findStaticDomainProposals(context, domainClass, declaringType)); proposals.addAll(findFinderProposals(context, domainClass, declaringType)); proposals.addAll(findNamedQueryProposals(context, domainClass, declaringType)); proposals.addAll(findDynamicFinderProposals(context, domainClass)); if (!isStatic) { proposals.addAll(findNonStaticDomainProposals(context, domainClass, declaringType)); // now add all the contributed methods and properties // only used in 1.3.7 and earlier proposals.addAll(findContributedPropertiesProposals(context, domainClass, declaringType)); } } } return proposals; } private List<IGroovyProposal> findStaticDomainProposals(ContentAssistContext context, DomainClass domainClass, ClassNode declaringType) { Map<String, ClassNode[]> staticMembers = domainClass .getStaticMembers(); List<IGroovyProposal> proposals = new ArrayList<IGroovyProposal>(3); for (Entry<String, ClassNode[]> entry : staticMembers.entrySet()) { if (ProposalUtils.looselyMatches(context.completionExpression, entry.getKey())) { ClassNode returnType = entry.getValue()[0]; // 0 is the return 1 is the declaring if (returnType == null) { returnType = declaringType; } ClassNode inferredDeclaring = entry.getValue()[1]; if (inferredDeclaring == null) { inferredDeclaring = declaringType; } proposals.add(new GroovyMethodProposal( createNoArgMethodNode(entry.getKey(), context, inferredDeclaring, returnType, Opcodes.ACC_PUBLIC & Opcodes.ACC_STATIC), GRAILS)); } } return proposals; } private List<IGroovyProposal> findContributedPropertiesProposals(ContentAssistContext context, DomainClass domainClass, ClassNode declaringType) { Map<String, ContributedProperty> contribProps = domainClass.getAllContributedProperties(); List<IGroovyProposal> proposals = new ArrayList<IGroovyProposal>(2); for (Map.Entry<String, ContributedProperty> contribProp : contribProps.entrySet()) { String contribName = contribProp.getKey(); if (ProposalUtils.looselyMatches(context.completionExpression, contribName)) { proposals.add(new GroovyPropertyProposal(contribProp.getValue().createMockProperty(declaringType), contribProp.getValue().getContributedBy())); } } Map<String, Set<ContributedMethod>> contribMethods = domainClass.getAllContributedMethods(); for (Entry<String, Set<ContributedMethod>> contribMethodEntry : contribMethods.entrySet()) { String contribName = contribMethodEntry.getKey(); if (ProposalUtils.looselyMatches(context.completionExpression, contribName)) { for (ContributedMethod contribMethod : contribMethodEntry.getValue()) { proposals.add(new GroovyMethodProposal(contribMethod.createMockMethod(declaringType), contribMethod.getContributedBy())); } } } return proposals; } private List<IGroovyProposal> findNonStaticDomainProposals(ContentAssistContext context, DomainClass domainClass, ClassNode declaringType) { Map<String, ClassNode> nonstaticMembers = domainClass .getNonstaticMembers(); List<IGroovyProposal> proposals = new ArrayList<IGroovyProposal>(3); for (Entry<String, ClassNode> entry : nonstaticMembers .entrySet()) { if (ProposalUtils.looselyMatches(context.completionExpression, entry.getKey())) { ClassNode type = entry.getValue(); if (type == null) { type = declaringType; } if (DomainClass.isFieldReference(entry.getKey())) { proposals.add(new GroovyFieldProposal( createFieldNode(entry.getKey(), context, declaringType, type, Opcodes.ACC_PUBLIC), GRAILS_PROPOSALS_RELEVANCE_MULTIPLIER, GRAILS)); } else { proposals.add(new GroovyMethodProposal( createNoArgMethodNode(entry.getKey(), context, declaringType, type, Opcodes.ACC_PUBLIC), GRAILS)); } } } return proposals; } private List<IGroovyProposal> findDynamicFinderProposals(ContentAssistContext context, DomainClass domainClass) { List<AnnotatedNode> finders = domainClass.getFinderValidator().findProposals(context.completionExpression); List<IGroovyProposal> finderProposals = new ArrayList<IGroovyProposal>(finders.size()); for (AnnotatedNode finder : finders) { if (finder instanceof FieldNode) { finderProposals.add(new GroovyFieldProposal((FieldNode) finder, "GORM")); //$NON-NLS-1$ } else { finderProposals.add(new GroovyMethodProposal((MethodNode) finder, "GORM")); //$NON-NLS-1$ } } return finderProposals; } private List<IGroovyProposal> findNamedQueryProposals( ContentAssistContext context, DomainClass domainClass, ClassNode declaringType) { String[] namedQueries = domainClass.getNamedQueries(); List<IGroovyProposal> proposals = new ArrayList<IGroovyProposal>(2); for (String namedQuery : namedQueries) { if (ProposalUtils.looselyMatches(context.completionExpression, namedQuery)) { proposals.add(new GroovyFieldProposal(domainClass.createNamedCriteria(declaringType, namedQuery))); } } return proposals; } private List<IGroovyProposal> findFinderProposals(ContentAssistContext context, DomainClass domainClass, ClassNode declaringType) { List<IGroovyProposal> proposals = new ArrayList<IGroovyProposal>(); Map<String, ClassNode> dynamicFinderMembers = domainClass .getDynamicFinderMembers(); for (Entry<String, ClassNode> entry : dynamicFinderMembers.entrySet()) { if (ProposalUtils.looselyMatches(context.completionExpression, entry.getKey())) { ClassNode returnType = entry.getValue(); if (returnType == null) { returnType = declaringType; } proposals.add(new GroovyFieldProposal( createFieldNode(entry.getKey(), context, declaringType, returnType, Opcodes.ACC_PUBLIC & Opcodes.ACC_STATIC), GRAILS)); } } return proposals; } private FieldNode createFieldNode(String fieldName, ContentAssistContext context, ClassNode declaringType, int flags) { return createFieldNode(fieldName, context, declaringType, VariableScope.VOID_CLASS_NODE, flags); } private FieldNode createFieldNode(String fieldName, ContentAssistContext context, ClassNode declaringType, ClassNode returnType, int flags) { FieldNode newField = new FieldNode(fieldName, flags, returnType, declaringType, null); newField.setDeclaringClass(declaringType); return newField; } private MethodNode createNoArgMethodNode(String methodName, ContentAssistContext context, ClassNode declaringType, ClassNode returnType, int flags) { MethodNode newMethod = new MethodNode(methodName, flags, returnType, new Parameter[0], new ClassNode[0], new BlockStatement()); newMethod.setDeclaringClass(declaringType); return newMethod; } }