Java tutorial
/******************************************************************************* * Copyright (c) 2009 Codehaus.org, SpringSource, 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: * Andy Clement - Initial API and implementation * Andrew Eisenberg - Additional work *******************************************************************************/ package org.codehaus.jdt.groovy.internal.compiler.ast; import groovy.lang.GroovyClassLoader; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.StringTokenizer; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.GenericsType; import org.codehaus.groovy.ast.InnerClassNode; import org.codehaus.groovy.control.CompilationFailedException; import org.codehaus.groovy.control.CompilationUnit; import org.codehaus.groovy.control.ResolveVisitor; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.jdt.groovy.internal.compiler.ast.GroovyParser.GrapeAwareGroovyClassLoader; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.Wildcard; import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.Binding; import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; import org.eclipse.jdt.internal.compiler.lookup.ParameterizedMethodBinding; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding; import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding; /** * An extension to the standard groovy ResolveVisitor that can ask JDT for types when groovy cannot find them. A groovy project in * Eclipse is typically configured with very limited knowledge of its dependencies so most lookups are through JDT. * * Resolver lifecycle:<br> * The JDTResolver is created at the same time as the (Groovy) CompilationUnit. The CompilationUnit knows about all the code that is * to be compiled together. The resolver maintains a cache from Binding to JDTClassNode and the cache contents have the same * lifetime as the JDTResolver. The resolver does type lookups through the currently active scope - the active scope is set when the * method 'commencingResolution()' is called. This is called by the superclass (ResolveVisitor) when it is about to start resolving * every reference in a type. * * @author Andy Clement */ @SuppressWarnings("restriction") public class JDTResolver extends ResolveVisitor { /** * length of the boolean type. any type name that is equal to or shorter than this is likely to be a primitive type. */ private static final int BOOLEAN_LENGTH = "boolean".length(); // For resolver debugging private static final boolean debug = false; // Arbitrary selection of common types private static Map<String, ClassNode> commonTypes = new HashMap<String, ClassNode>(); // So that testcases can quiz a resolver instance public static boolean recordInstances = false; public static List<JDTResolver> instances = null; static { commonTypes.put("java.lang.Object", ClassHelper.OBJECT_TYPE); commonTypes.put("java.lang.String", ClassHelper.STRING_TYPE); commonTypes.put("java.lang.Class", ClassHelper.CLASS_Type); commonTypes.put("java.lang.Boolean", ClassHelper.Boolean_TYPE); commonTypes.put("java.lang.Byte", ClassHelper.Byte_TYPE); commonTypes.put("java.lang.Character", ClassHelper.Character_TYPE); commonTypes.put("java.lang.Double", ClassHelper.Double_TYPE); commonTypes.put("java.lang.Float", ClassHelper.Float_TYPE); commonTypes.put("java.lang.Integer", ClassHelper.Integer_TYPE); commonTypes.put("java.lang.Long", ClassHelper.Long_TYPE); commonTypes.put("java.lang.Short", ClassHelper.Short_TYPE); commonTypes.put("boolean", ClassHelper.boolean_TYPE); commonTypes.put("byte", ClassHelper.byte_TYPE); commonTypes.put("char", ClassHelper.char_TYPE); commonTypes.put("double", ClassHelper.double_TYPE); commonTypes.put("float", ClassHelper.float_TYPE); commonTypes.put("int", ClassHelper.int_TYPE); commonTypes.put("long", ClassHelper.long_TYPE); commonTypes.put("short", ClassHelper.short_TYPE); } private Stack<GenericsType[]> memberGenericsCurrentlyActive = new Stack<GenericsType[]>(); private Stack<GenericsType[]> typeGenericsCurrentlyActive = new Stack<GenericsType[]>(); void pushMemberGenerics(GenericsType[] generics) { memberGenericsCurrentlyActive.push(generics); } void popMemberGenerics() { memberGenericsCurrentlyActive.pop(); } public void pushTypeGenerics(GenericsType[] genericsTypes) { typeGenericsCurrentlyActive.push(genericsTypes); } public void popTypeGenerics() { typeGenericsCurrentlyActive.pop(); } // By recording what is currently in progress in terms of creation, we avoid recursive problems (like Enum<E extends Enum<E>>) private Map<TypeBinding, JDTClassNode> inProgress = new HashMap<TypeBinding, JDTClassNode>(); private Stack<JDTClassNode> inProgressStack = new Stack<JDTClassNode>(); // Type references are resolved through the 'activeScope'. This ensures visibility rules are obeyed - just because a // type exists does not mean it is visible to some other type and scope lookups verify this. private GroovyCompilationUnitScope activeScope = null; // map of scopes in which resolution can happen private Map<ClassNode, GroovyTypeDeclaration> scopes = new HashMap<ClassNode, GroovyTypeDeclaration>(); private List<ClassNode> haveBeenResolved = new ArrayList<ClassNode>(); // Cache from bindings to JDTClassNodes to avoid unnecessary JDTClassNode creation private Map<Binding, JDTClassNode> nodeCache = Collections .synchronizedMap(new HashMap<Binding, JDTClassNode>()); public JDTResolver(CompilationUnit groovyCompilationUnit) { super(groovyCompilationUnit); if (recordInstances) { if (instances == null) { instances = new ArrayList<JDTResolver>(); } instances.add(this); } } public JDTClassNode getCachedNode(String name) { for (Map.Entry<Binding, JDTClassNode> nodeFromCache : nodeCache.entrySet()) { String nodename = new String(nodeFromCache.getKey().readableName()); if (nodename.equals(name)) { return nodeFromCache.getValue(); } } return null; } /** * resolveFromModule() - look at other types in the same source file (no need to talk to JDT) */ @Override protected boolean resolveFromModule(ClassNode type, boolean testModuleImports) { boolean foundit = super.resolveFromModule(type, testModuleImports); recordDependency(type.getName()); if (debug) { log("resolveFromModule", type, foundit); } return foundit; } /** * resolveFromCompileUnit() - look at other source types in this CompilationUnit (ie. this 'project' in JDT terms). */ @Override protected boolean resolveFromCompileUnit(ClassNode type) { boolean foundit = super.resolveFromCompileUnit(type); recordDependency(type.getName()); if (debug) { log("resolveFromCompileUnit", type, foundit); } if (foundit) { return true; } // Ask JDT for a source file, visible from this scope String typename = type.getName(); ClassNode node = getScope().lookupClassNodeForSource(typename, this); if (debug) { log("resolveFromCompileUnit (jdt) ", type, node != null); } if (node != null) { type.setRedirect(node); return true; } else { return false; } // CHECK_IT(redirect); } @Override protected boolean resolveFromDefaultImports(ClassNode type, boolean testDefaultImports) { boolean foundit = super.resolveFromDefaultImports(type, testDefaultImports); if (activeScope != null) { // TODO need to refactor (duplicated in GroovyCompilationUnitScope) boolean b = testDefaultImports & !type.hasPackageName(); // we do not resolve a vanilla name starting with a lower case letter // try to resolve against adefault import, because we know that the // default packages do not contain classes like these b &= !(type instanceof LowerCaseClass); if (b) { String extraImports = activeScope.compilerOptions().groovyExtraImports; if (extraImports != null) { try { String filename = new String(activeScope.referenceContext.getFileName()); // may be something to do StringTokenizer st = new StringTokenizer(extraImports, ";"); // Form would be 'com.foo.*,com.bar.MyType;.gradle=com.this.*,com.foo.Type" // If there is no qualifying suffix it applies to all types while (st.hasMoreTokens()) { String onesuffix = st.nextToken(); int equals = onesuffix.indexOf('='); boolean shouldApply = false; String imports = null; if (equals == -1) { // definetly applies shouldApply = true; imports = onesuffix; } else { // need to check the suffix String suffix = onesuffix.substring(0, equals); shouldApply = filename.endsWith(suffix); imports = onesuffix.substring(equals + 1); } StringTokenizer st2 = new StringTokenizer(imports, ","); while (st2.hasMoreTokens()) { String nextElement = st2.nextToken(); // One of two forms: a.b.c.* or a.b.c.Type if (nextElement.endsWith(".*")) { String withoutStar = nextElement.substring(0, nextElement.length() - 1); ConstructedClassWithPackage tmp = new ConstructedClassWithPackage(withoutStar, type.getName()); if (resolve(tmp, false, false, false)) { type.setRedirect(tmp.redirect()); return true; } } else { String importedTypeName = nextElement; if (importedTypeName.endsWith(type.getName())) { int lastdot = importedTypeName.lastIndexOf('.'); String importTypeNameChopped = importedTypeName.substring(0, lastdot + 1); ConstructedClassWithPackage tmp = new ConstructedClassWithPackage( importTypeNameChopped, type.getName()); if (resolve(tmp, false, false, false)) { type.setRedirect(tmp.redirect()); return true; } } } } } } catch (Exception e) { new RuntimeException("Problem processing extraImports: " + extraImports, e) .printStackTrace(); } } } } recordDependency(type.getName()); if (debug) { log("resolveFromDefaultImports", type, foundit); } return foundit; } @Override protected boolean resolveFromStaticInnerClasses(ClassNode type, boolean testStaticInnerClasses) { boolean foundit = super.resolveFromStaticInnerClasses(type, testStaticInnerClasses); recordDependency(type.getName()); if (debug) { log("resolveFromStaticInnerClasses", type, foundit); } return foundit; // FIXASC (M3) anything special for inner types? } // @Override // protected boolean resolveStaticInner(ClassNode type) { // boolean foundit = super.resolveStaticInner(type); // recordDependency(type.getName()); // if (debug) { // log("resolveStaticInner", type, foundit); // } // return foundit; // } /** * resolveFromClassCache() - no point in asking, the cache does not get populated. */ @Override protected boolean resolveFromClassCache(ClassNode type) { return false; } protected boolean resolveToOuter(ClassNode type) { return resolveToClass(type); } /** * resolveToOuter() - this would normally ask the groovy class loader, but we don't want to do that - let JDT find everything. */ protected boolean resolveToClass(ClassNode type) { String typename = type.getName(); ClassNode node = getScope().lookupClassNodeForBinary(typename, this); if (debug) { log("resolveToClass (jdt)", type, node != null); } if (node != null) { type.setRedirect(node); return true; } // Rudimentary grab support - if the compilation unit has our special classloader and a // grab has occurred, try and find the class through it GroovyClassLoader loader = compilationUnit.getClassLoader(); if (loader instanceof GrapeAwareGroovyClassLoader) { GrapeAwareGroovyClassLoader gagc = (GrapeAwareGroovyClassLoader) loader; if (gagc.grabbed) { // System.out.println("Checking grabbed loader for " + type.getName()); Class<?> cls; try { cls = loader.loadClass(type.getName(), false, true); } catch (ClassNotFoundException cnfe) { return false; } catch (CompilationFailedException cfe) { return false; } if (cls == null) { return false; } node = ClassHelper.make(cls); type.setRedirect(node); return true; } } return false; // boolean foundit = super.resolveToClass(type); // if (debug) { // log("resolveToClass", type, foundit); // } // return foundit; } /** * resolveToScript() - ask the groovy class loader. We don't want to do this - let JDT find everything. */ @Override protected boolean resolveToScript(ClassNode type) { return false; } // Records a list of type names that aren't resolvable for the current resolution (unresolvables is cleared in // finishedResolution()). This means we won't constantly attempt to lookup something that is not found through the same routes // over and over (GRECLIPSE-870) private Set<String> unresolvables = new HashSet<String>(); @Override protected boolean resolve(ClassNode type, boolean testModuleImports, boolean testDefaultImports, boolean testStaticInnerClasses) { String name = type.getName(); // save time by being selective about whether to consult the commonRedirectMap if (name.charAt(0) == 'j' || name.length() <= BOOLEAN_LENGTH) { ClassNode commonRedirect = commonTypes.get(type.getName()); if (commonRedirect != null) { type.setRedirect(commonRedirect); return true; } } if (unresolvables.contains(name)) { // System.out.println("Skipping... " + name); return false; } else { boolean b = super.resolve(type, testModuleImports, testDefaultImports, testStaticInnerClasses); // System.out.println("resolving... " + type.getName() + " = " + b); if (!b) { unresolvables.add(name); } return b; } } public ClassNode resolve(String qualifiedName) { ClassNode type = ClassHelper.makeWithoutCaching(qualifiedName); if (super.resolve(type)) { return type.redirect(); } else { return ClassHelper.DYNAMIC_TYPE; } } @Override protected boolean resolveToInnerEnum(ClassNode type) { if (existsAsInnerClass(type)) { return super.resolveToInnerEnum(type); } return false; } @Override protected boolean resolveToInner(ClassNode type) { if (existsAsInnerClass(type)) { return super.resolveToInner(type); } return false; } private boolean existsAsInnerClass(ClassNode type) { Iterator<InnerClassNode> innerClasses = currentClass.getInnerClasses(); if (innerClasses != null) { while (innerClasses.hasNext()) { InnerClassNode innerClass = innerClasses.next(); if (innerClass.getName().equals(type.getName())) { return true; } } } return false; } // FIXASC callers could check if it is a 'funky' type before always recording a depedency // by 'funky' I mean that the type was constructed just to try something (org.foo.bar.java$lang$Wibble doesn't want recording!) private void recordDependency(String typename) { GroovyCompilationUnitScope gcuScope = getScope(); // if (gcuScope == null) { // return; // } // System.err.println("Recording reference from " + toShortString(gcuScope) + " to " + typename); if (typename.indexOf(".") != -1) { gcuScope.recordQualifiedReference(CharOperation.splitOn('.', typename.toCharArray())); } else { gcuScope.recordSimpleReference(typename.toCharArray()); } } /** * Convert from a JDT Binding to a Groovy ClassNode */ ClassNode convertToClassNode(TypeBinding jdtBinding) { if (inProgress.containsKey(jdtBinding)) { return inProgress.get(jdtBinding); } JDTClassNode existingNode = nodeCache.get(jdtBinding); if (existingNode != null) { if (debug) { log("Using cached JDTClassNode for binding " + new String(jdtBinding.readableName())); } return existingNode; } if (debug) { log("createJDTClassNode: Building new JDTClassNode for binding " + new String(jdtBinding.readableName())); } ClassNode jdtNode = createJDTClassNode(jdtBinding); return jdtNode; } /** * Create a Groovy ClassNode that represents the JDT TypeBinding. Build the basic structure, mark it as 'in progress' and then * continue with initialization. This allows self referential generic declarations. * * @param jdtBinding the JDT binding for which to create a ClassNode * @return the new ClassNode, of type JDTClassNode */ private ClassNode createJDTClassNode(TypeBinding jdtBinding) { ClassNode classNode = createClassNode(jdtBinding); if (classNode instanceof JDTClassNode) { JDTClassNode jdtNode = (JDTClassNode) classNode; inProgress.put(jdtBinding, jdtNode); inProgressStack.push(jdtNode); jdtNode.setupGenerics(); inProgressStack.pop(); inProgress.remove(jdtBinding); nodeCache.put(jdtBinding, jdtNode); } return classNode; } /** * Create a ClassNode based on the type of the JDT binding, this takes account of all the possible kinds of JDT binding. */ private ClassNode createClassNode(TypeBinding jdtTypeBinding) { if (jdtTypeBinding instanceof WildcardBinding) { return createClassNodeForWildcardBinding((WildcardBinding) jdtTypeBinding); } else if (jdtTypeBinding instanceof BaseTypeBinding) { return createClassNodeForPrimitiveBinding((BaseTypeBinding) jdtTypeBinding); } else if (jdtTypeBinding instanceof ArrayBinding) { return createClassNodeForArrayBinding((ArrayBinding) jdtTypeBinding); } else if (jdtTypeBinding instanceof TypeVariableBinding) { String typeVariableName = new String(jdtTypeBinding.sourceName()); TypeVariableBinding typeVariableBinding = (TypeVariableBinding) jdtTypeBinding; if (typeVariableBinding.declaringElement instanceof SourceTypeBinding) { GenericsType[] genericTypes = typeGenericsCurrentlyActive.peek(); GenericsType matchingGenericType = findMatchingGenericType(genericTypes, typeVariableName); if (matchingGenericType != null) { ClassNode newNode = ClassHelper.makeWithoutCaching(typeVariableName); newNode.setRedirect(matchingGenericType.getType()); newNode.setGenericsTypes(new GenericsType[] { matchingGenericType }); newNode.setGenericsPlaceHolder(true); return newNode; } // What does it means if we are here? // it means we've encountered a type variable but this class doesn't declare it. // So far this has been seen in the case where a synthetic binding is created for // a bridge method from a supertype. It appears what we can do here is collapse // that type variable to its bound (as this is meant to be a bridge method) // But what if it was bound by something a little higher up? // What other cases are there to worry about? if (typeVariableBinding.firstBound == null) { return ClassHelper.OBJECT_TYPE; } else { // c'est vrai? return convertToClassNode(typeVariableBinding.firstBound); } // throw new GroovyEclipseBug("Cannot find type variable on source type declaring element " // + typeVariableBinding.declaringElement); } else if (typeVariableBinding.declaringElement instanceof BinaryTypeBinding) { GenericsType[] genericTypes = convertToClassNode( ((BinaryTypeBinding) typeVariableBinding.declaringElement)).getGenericsTypes(); GenericsType matchingGenericType = findMatchingGenericType(genericTypes, typeVariableName); if (matchingGenericType != null) { ClassNode newNode = ClassHelper.makeWithoutCaching(typeVariableName); ClassNode[] upper = matchingGenericType.getUpperBounds(); if (upper != null && upper.length > 0) { newNode.setRedirect(upper[0]); } else { newNode.setRedirect(matchingGenericType.getType()); } newNode.setGenericsTypes(new GenericsType[] { matchingGenericType }); newNode.setGenericsPlaceHolder(true); return newNode; } throw new GroovyEclipseBug("Cannot find type variable on type declaring element " + typeVariableBinding.declaringElement); } else if (typeVariableBinding.declaringElement instanceof ParameterizedMethodBinding || typeVariableBinding.declaringElement instanceof MethodBinding) { GenericsType[] genericTypes = memberGenericsCurrentlyActive.peek(); GenericsType matchingGenericType = findMatchingGenericType(genericTypes, typeVariableName); if (matchingGenericType != null) { ClassNode newNode = ClassHelper.makeWithoutCaching(typeVariableName); newNode.setRedirect(matchingGenericType.getType()); newNode.setGenericsTypes(new GenericsType[] { matchingGenericType }); newNode.setGenericsPlaceHolder(true); return newNode; } throw new GroovyEclipseBug("Cannot find type variable on method declaring element " + typeVariableBinding.declaringElement); } throw new GroovyEclipseBug("Unexpected type variable reference. Declaring element is " + typeVariableBinding.declaringElement); // Next case handles: RawTypeBinding, ParameterizedTypeBinding, SourceTypeBinding } else if (jdtTypeBinding instanceof ReferenceBinding) { // It could be possible to unwrap SourceTypeBindings that have been built for Groovy types // (and thus get back to the original ClassNode, rather than building another one here). However, // they are not always reusable due to a link with the Groovy CompilationUnit that led to // their creation. // Unwrap code is something like: // if (sourceTypeBinding.scope != null) { // TypeDeclaration typeDeclaration = sourceTypeBinding.scope.referenceContext; // if (typeDeclaration instanceof GroovyTypeDeclaration) { // GroovyTypeDeclaration groovyTypeDeclaration = (GroovyTypeDeclaration) typeDeclaration; // ClassNode wrappedNode = groovyTypeDeclaration.getClassNode(); // return wrappedNode; // } // } if (jdtTypeBinding.id == TypeIds.T_JavaLangObject) { return ClassHelper.OBJECT_TYPE; } return new JDTClassNode((ReferenceBinding) jdtTypeBinding, this); } else { throw new GroovyEclipseBug("Unable to convert this binding: " + jdtTypeBinding.getClass()); } } private GenericsType findMatchingGenericType(GenericsType[] genericTypes, String typeVariableName) { if (genericTypes != null) { for (GenericsType genericType : genericTypes) { if (genericType.getName().equals(typeVariableName)) { return genericType; } } } return null; } ClassNode createClassNodeForArrayBinding(ArrayBinding arrayBinding) { int dims = arrayBinding.dimensions; ClassNode classNode = convertToClassNode(arrayBinding.leafComponentType); while (dims > 0) { classNode = new ClassNode(classNode); dims--; } return classNode; } ClassNode createClassNodeForPrimitiveBinding(BaseTypeBinding jdtBinding) { switch (jdtBinding.id) { case TypeIds.T_boolean: return ClassHelper.boolean_TYPE; case TypeIds.T_char: return ClassHelper.char_TYPE; case TypeIds.T_byte: return ClassHelper.byte_TYPE; case TypeIds.T_short: return ClassHelper.short_TYPE; case TypeIds.T_int: return ClassHelper.int_TYPE; case TypeIds.T_long: return ClassHelper.long_TYPE; case TypeIds.T_double: return ClassHelper.double_TYPE; case TypeIds.T_float: return ClassHelper.float_TYPE; case TypeIds.T_void: return ClassHelper.VOID_TYPE; default: throw new GroovyEclipseBug("Don't know what this is: " + jdtBinding); } } private ClassNode[] convertToClassNodes(TypeBinding[] typeBindings) { if (typeBindings.length == 0) { return null; } ClassNode[] nodes = new ClassNode[typeBindings.length]; for (int i = 0; i < typeBindings.length; i++) { nodes[i] = convertToClassNode(typeBindings[i]); } return nodes; } private ClassNode createClassNodeForWildcardBinding(WildcardBinding wildcardBinding) { // FIXASC could use LazyGenericsType object here ClassNode base = ClassHelper.makeWithoutCaching("?"); ClassNode lowerBound = null; ClassNode[] allUppers = null; if (wildcardBinding.boundKind == Wildcard.EXTENDS) { ClassNode firstUpper = convertToClassNode(wildcardBinding.bound); ClassNode[] otherUppers = (wildcardBinding.otherBounds == null ? null : convertToClassNodes(wildcardBinding.otherBounds)); if (otherUppers == null) { allUppers = new ClassNode[] { firstUpper }; } else { allUppers = new ClassNode[otherUppers.length + 1]; System.arraycopy(otherUppers, 0, allUppers, 1, otherUppers.length); allUppers[0] = firstUpper; } } else if (wildcardBinding.boundKind == Wildcard.SUPER) { lowerBound = convertToClassNode(wildcardBinding.bound); } else { assert (wildcardBinding.boundKind == Wildcard.UNBOUND); return JDTClassNode.unboundWildcard; } GenericsType t = new GenericsType(base, allUppers, lowerBound); t.setWildcard(true); ClassNode ref = ClassHelper.makeWithoutCaching(Object.class, false); ref.setGenericsTypes(new GenericsType[] { t }); return ref; } /** * Called when a resolvevisitor is commencing resolution for a type - allows us to setup the JDTResolver to point at the right * scope for resolutionification. If not able to find a scope, that is a serious problem! */ @Override protected boolean commencingResolution() { GroovyTypeDeclaration gtDeclaration = scopes.get(this.currentClass); if (gtDeclaration == null) { if (haveBeenResolved.contains(currentClass)) { // already resolved! return false; } GroovyEclipseBug geb = new GroovyEclipseBug( "commencingResolution failed: no declaration found for class " + currentClass); geb.printStackTrace(); throw geb; } activeScope = null; if (gtDeclaration.scope == null) { // The scope may be null if there were errors in the code - let's not freak out the user here if (gtDeclaration.hasErrors()) { return false; // throw new GroovyEclipseBug("commencingResolution failed: aborting resolution, type " + currentClass.getName() // + " had earlier problems"); } GroovyEclipseBug geb = new GroovyEclipseBug( "commencingResolution failed: declaration found, but unexpectedly found no scope for " + currentClass.getName()); geb.printStackTrace(); throw geb; } activeScope = (GroovyCompilationUnitScope) gtDeclaration.scope.compilationUnitScope(); if (debug) { System.err.println("Resolver: commencing resolution for " + this.currentClass.getName()); } return true; } @Override protected void finishedResolution() { scopes.remove(this.currentClass); haveBeenResolved.add(currentClass); unresolvables.clear(); } private GroovyCompilationUnitScope getScope() { return activeScope; } private void log(String string) { System.err.println("Resolver: " + string); } // FIXASC can the relationship here from classNode to scope be better preserved to remove the need for this map? /** * When recorded, the jdt resolver will be able to (later on) navigate from the classnode back to the JDT scope that should be * used. */ public void record(GroovyTypeDeclaration gtDeclaration) { scopes.put(gtDeclaration.getClassNode(), gtDeclaration); if (gtDeclaration.memberTypes != null) { TypeDeclaration[] members = gtDeclaration.memberTypes; for (int m = 0; m < members.length; m++) { record((GroovyTypeDeclaration) members[m]); } } } private void log(String string, ClassNode type, boolean foundit) { System.err.println("Resolver: " + string + " " + type.getName() + " ?" + foundit); } public void startResolving(ClassNode node, SourceUnit source) { try { super.startResolving(node, source); unresolvables.clear(); } catch (AbortResolutionException are) { // Can occur if there are other problems with the node (syntax errors) - so don't try resolving it } } }