/******************************************************************************* * 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.elements; import groovyjarjarasm.asm.Opcodes; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.codehaus.groovy.ast.AnnotatedNode; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.CodeVisitorSupport; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.Variable; import org.codehaus.groovy.ast.expr.ArgumentListExpression; import org.codehaus.groovy.ast.expr.BinaryExpression; import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.FieldExpression; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.expr.PropertyExpression; import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.ExpressionStatement; import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.syntax.Types; import org.codehaus.jdt.groovy.model.GroovyCompilationUnit; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IResource; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.groovy.search.AbstractSimplifiedTypeLookup.TypeAndDeclaration; import org.eclipse.jdt.groovy.search.VariableScope; import org.grails.ide.eclipse.core.GrailsCoreActivator; import org.grails.ide.eclipse.core.internal.plugins.GrailsCore; import org.grails.ide.eclipse.core.internal.plugins.GrailsElementKind; import org.grails.ide.eclipse.editor.groovy.types.PerProjectMemberCache; /** * @author Andrew Eisenberg * @author Christian Dupuis * @author Nieraj Singh * @created Dec 4, 2009 */ public class TagLibClass extends AbstractGrailsElement implements INavigableGrailsElement { public static final String DEFAULT_NAMESPACE = "g"; //$NON-NLS-1$ private class AttributeCollector extends CodeVisitorSupport { Set<String> attrs; Set<String> findAttrs(ClosureExpression c) { attrs = new HashSet<String>(); c.visit(this); return attrs; } /** * Finds attributes that look like: * attrs.myAttr */ @Override public void visitPropertyExpression(PropertyExpression expression) { boolean isAttr = false; Expression objectExpression = expression.getObjectExpression(); if (objectExpression instanceof Variable) { isAttr = ((Variable) objectExpression).getName().equals("attrs"); //$NON-NLS-1$ } else if (objectExpression instanceof ConstantExpression) { isAttr = ((ConstantExpression) objectExpression).getText().equals("attrs"); //$NON-NLS-1$ } if (isAttr) { Expression propertyExpression = expression.getProperty(); if (propertyExpression instanceof ConstantExpression) { attrs.add(((ConstantExpression) propertyExpression).getText()); } else if (propertyExpression instanceof MethodCallExpression) { } } super.visitPropertyExpression(expression); } /** * Finds attributes that look like: * attrs.get('myAttr') or attrs.remove('myAttr') */ @Override public void visitMethodCallExpression(MethodCallExpression call) { super.visitMethodCallExpression(call); String methodName = call.getMethodAsString(); // check to see if we have a get or remove if ("get".equals(methodName) || "remove".equals(methodName)) { //$NON-NLS-1$ //$NON-NLS-2$ Expression expr = call.getArguments(); // attempt to find the first argument Expression firstArg = null; if (expr != null) { if (expr instanceof ArgumentListExpression) { ArgumentListExpression args = (ArgumentListExpression) expr; if (args.getLength() > 0) { firstArg = args.getExpression(0); } } else { firstArg = expr; } } if (firstArg instanceof ConstantExpression) { ConstantExpression constArg = (ConstantExpression) firstArg; attrs.add(constArg.getText()); } } } /** * Finds attributes that look like: * attrs['myAttr'] */ @Override public void visitBinaryExpression(BinaryExpression expression) { boolean isAttr = false; Expression objectExpression = expression.getLeftExpression(); if (objectExpression instanceof Variable) { isAttr = ((Variable) objectExpression).getName().equals("attrs"); //$NON-NLS-1$ } else if (objectExpression instanceof ConstantExpression) { isAttr = ((ConstantExpression) objectExpression).getText().equals("attrs"); //$NON-NLS-1$ } if (isAttr) { Expression propertyExpression = expression.getRightExpression(); if (propertyExpression instanceof ConstantExpression) { attrs.add(((ConstantExpression) propertyExpression).getText()); } } super.visitBinaryExpression(expression); } } private String namespace; protected IField[] cachedFields; public TagLibClass(GroovyCompilationUnit unit) { super(unit); } public DomainClass getDomainClass() { String origName = unit.getElementName(); int tagLibIndex = origName.lastIndexOf("TagLib"); //$NON-NLS-1$ String domainName = origName.substring(0, tagLibIndex) + ".groovy"; //$NON-NLS-1$ String packageName = unit.getParent().getElementName(); IJavaProject javaProject = unit.getJavaProject(); GrailsProject gp = new GrailsProject(javaProject); return gp.getDomainClass(packageName, domainName); } public ControllerClass getControllerClass() { String origName = unit.getElementName(); return ControllerClass.getControllerClassForElement(unit, origName.substring(0,origName.lastIndexOf("TagLib"))); } public ServiceClass getServiceClass() { String origName = unit.getElementName(); return ServiceClass.getServiceClassForElement(unit, origName.substring(0,origName.lastIndexOf("TagLib"))); } public TestClass getTestClass() { return TestClass.getTestClassForElement(this, unit, getPrimaryTypeName()); } public TagLibClass getTagLibClass() { return this; } public GrailsElementKind getKind() { return GrailsElementKind.TAGLIB_CLASS; } public void initializeTypeLookup(VariableScope scope) { populateInjectedServices(scope); } public TypeAndDeclaration lookupTypeAndDeclaration(ClassNode declaringType, String name, VariableScope scope) { // static members PerProjectMemberCache memberCache = GrailsCore.get().connect(unit.getJavaProject().getProject(), PerProjectMemberCache.class); Map<String, ClassNode> members = memberCache.getTagLibMembers(); ClassNode node = members.get(name); if (node != null) { AnnotatedNode cached = getCachedMember(name); if (cached == null) { cached = new FieldNode(name, Opcodes.ACC_PUBLIC, node, declaringType, null); cached.setDeclaringClass(declaringType); super.cacheGeneratedMember(cached); } return new TypeAndDeclaration(node, cached); } return null; } public Map<String, ClassNode> getTagLibMembers() { PerProjectMemberCache memberCache = GrailsCore.get().connect(unit.getJavaProject().getProject(), PerProjectMemberCache.class); return memberCache.getTagLibMembers(); } /** * Find all attributes used by this tag. * May not be able to find all tags. Will * only be able to find tags that are explicitly * referenced by the attr parameter. * <br><br> * Assumes that this field is a member of this tag lib * and is itself a tag definition * @param tag * @return a collection of attribute names used by this tag */ public Collection<String> getAttributesForTag(FieldNode tag) { ClosureExpression c = (ClosureExpression) tag.getInitialExpression(); return new AttributeCollector().findAttrs(c); } @SuppressWarnings({ "nls", "cast" }) public List<FieldNode> getAllTagFields() { ClassNode groovyClass = getGroovyClass(); if (groovyClass != null) { List<FieldNode> fields = (List<FieldNode>) groovyClass.getFields(); List<FieldNode> tagFields = new ArrayList<FieldNode>(fields.size()); for (FieldNode field : fields) { if (!field.isStatic() && field.hasInitialExpression()) { Expression expr = field.getInitialExpression(); if (expr instanceof ClosureExpression) { ClosureExpression c = (ClosureExpression) expr; Parameter[] params = getParameters(c); // tags can have 0, 1, or 2 arguments. // the first argument if it exists must be attrs // the second argument, if it exists must be body // order does not matter if (params.length == 0) { tagFields.add(field); } else if (params.length == 1 && params[0].getName().equals("attrs")) { tagFields.add(field); } else if (params.length == 2 && ( (params[0].getName().equals("attrs") && params[1].getName().equals("body")) || (params[0].getName().equals("body") && params[1].getName().equals("attrs")) )) { tagFields.add(field); } } } } return tagFields; } else { return Collections.emptyList(); } } /** * @param c * @return */ private Parameter[] getParameters(ClosureExpression c) { return c.getParameters() == null ? new Parameter[0] : c.getParameters(); } @SuppressWarnings({ "nls", "cast" }) public String getNamespace() { if (namespace == null) { ClassNode tagClass = getGroovyClass(); if (tagClass == null) { // something bad happened return DEFAULT_NAMESPACE; } FieldNode field = tagClass.getField("namespace"); if (field != null && field.isStatic()) { // often the initializer for static fields is moved to clinit if (field.hasInitialExpression()) { Expression expr = field.getInitialExpression(); if (expr instanceof ConstantExpression) { namespace = ((ConstantExpression) expr).getText(); } } else { MethodNode clinit = tagClass.getMethod("<clinit>", new Parameter[0]); if (clinit != null) { Statement block = clinit.getCode(); if (block instanceof BlockStatement) { for (Statement state : (Iterable<Statement>) ((BlockStatement) block).getStatements()) { if (state instanceof ExpressionStatement) { Expression exprStat = ((ExpressionStatement) state).getExpression(); if (exprStat instanceof BinaryExpression) { BinaryExpression binaryExpr = (BinaryExpression) exprStat; if (binaryExpr.getOperation().getType() == Types.EQUALS && isNamespaceReference(binaryExpr.getLeftExpression()) && binaryExpr.getRightExpression() instanceof ConstantExpression) { namespace = ((ConstantExpression) binaryExpr.getRightExpression()).getText(); break; } } } } } } } } if (namespace == null) { namespace = DEFAULT_NAMESPACE; } } return namespace; } private boolean isNamespaceReference(Expression expr) { if (expr instanceof VariableExpression) { VariableExpression var = (VariableExpression) expr; return var.getName().equals("namespace"); //$NON-NLS-1$ } else if (expr instanceof FieldExpression) { FieldExpression fieldExpre = (FieldExpression) expr; return fieldExpre.getField().getName().equals("namespace"); //$NON-NLS-1$ } else { return false; } } public String getBaseLocation() { IResource resource = getCompilationUnit().getResource(); if (resource == null) { resource = getCompilationUnit().getJavaProject().getResource(); } return resource.getLocation().toOSString(); } public String getUri() { IResource resource = getCompilationUnit().getResource(); if (resource == null) { resource = getCompilationUnit().getJavaProject().getResource(); } return resource.getLocationURI().toString(); } public IField getTagField(String fieldName) { if (cachedFields == null) { initializeCachedFields(); } if (cachedFields != null) { for (IField tagField : cachedFields) { if (tagField.getElementName().equals(fieldName)) { return tagField; } } } return null; } /** * */ protected void initializeCachedFields() { try { cachedFields = unit.getType(this.getPrimaryTypeName()).getFields(); } catch (JavaModelException e) { GrailsCoreActivator.log(e); } } public IFolder getGSPFolder() { DomainClass d = getDomainClass(); return d != null ? d.getGSPFolder() : null; } public String getAssociatedDomainClassName() { String className = getGroovyClass().getName(); int cIndex = className.lastIndexOf("TagLib"); className = className.substring(0, cIndex); return className; } /** * Finds a corresponding TagLib class for the given type name * @param unit {@link ICompilationUnit} of the original class * @param typeName simple name of the original class * @return a corresponding TagLib class */ public static TagLibClass getTagLibClassForElement(ICompilationUnit unit, String typeName) { String controllerName = typeName + "TagLib.groovy"; //$NON-NLS-1$ String packageName = unit.getParent().getElementName(); IJavaProject javaProject = unit.getJavaProject(); GrailsProject gp = GrailsWorkspaceCore.get().create(javaProject); return gp.getTagLibClass(packageName, controllerName); } }