All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.jdt.groovy.internal.compiler.ast.GroovyClassScope Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * 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 java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.runtime.MetaClassHelper;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.ImportBinding;
import org.eclipse.jdt.internal.compiler.lookup.LazilyResolvedMethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.MissingTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding;

@SuppressWarnings("restriction")
public class GroovyClassScope extends ClassScope {

	// SET FOR TESTING ONLY, enables tests to listen for interesting events
	public static EventListener debugListener = null;

	private TraitHelper traitHelper = new TraitHelper();

	public GroovyClassScope(Scope parent, TypeDeclaration typeDecl) {
		super(parent, typeDecl);
	}

	@Override
	protected boolean connectSuperInterfaces() {
		boolean noProblems = super.connectSuperInterfaces();
		return noProblems;
	}

	// FIXASC pull out into common util area (see GCUScope too)
	char[] GROOVY = "groovy".toCharArray(); //$NON-NLS-1$
	char[][] GROOVY_LANG_METACLASS = { GROOVY, TypeConstants.LANG, "MetaClass".toCharArray() }; //$NON-NLS-1$
	char[][] GROOVY_LANG_GROOVYOBJECT = { GROOVY, TypeConstants.LANG, "GroovyObject".toCharArray() }; // $NON-NLS-1$

	public final ReferenceBinding getGroovyLangMetaClassBinding() {
		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordQualifiedReference(GROOVY_LANG_METACLASS);
		return unitScope.environment.getResolvedType(GROOVY_LANG_METACLASS, this);
	}

	/**
	 * Add any groovy specific method bindings to the set determined by the compiler. These
	 */
	@Override
	protected MethodBinding[] augmentMethodBindings(MethodBinding[] methodBindings) {
		// Don't add these methods to annotations
		SourceTypeBinding binding = this.referenceContext.binding;
		if (binding != null && (binding.isAnnotationType() || binding.isInterface())) {
			return methodBindings;
		}
		boolean implementsGroovyLangObject = false;

		ReferenceBinding[] superInterfaces = binding.superInterfaces;
		if (superInterfaces != null) {
			for (int i = 0, max = superInterfaces.length; i < max; i++) {
				char[][] interfaceName = superInterfaces[i].compoundName;
				if (CharOperation.equals(interfaceName, GROOVY_LANG_GROOVYOBJECT)) {
					implementsGroovyLangObject = true;
					break;
				}
			}
		}

		List groovyMethods = new ArrayList();

		// If we don't then a supertype did and these methods do not have to be added here
		if (implementsGroovyLangObject) {
			if (debugListener != null) {
				debugListener.record("augment: type " + new String(this.referenceContext.name)
						+ " having GroovyObject methods added");
			}
			TypeBinding bindingJLO = getJavaLangObject();
			TypeBinding bindingJLS = getJavaLangString();
			TypeBinding bindingGLM = getGroovyLangMetaClassBinding();

			// Now add the groovy.lang.GroovyObject methods:
			//
			// Object invokeMethod(String name, Object args);
			// Object getProperty(String propertyName);
			// void setProperty(String propertyName, Object newValue);
			// MetaClass getMetaClass();
			// void setMetaClass(MetaClass metaClass);

			// Note on synthetic
			// javac/ecj don't see synthetic methods when considering if a type implements an interface. So don't make these
			// synthetic

			// Visibility is public and possibly static/abstract depending on the containing type
			createMethod("invokeMethod", false, "", new TypeBinding[] { bindingJLS, bindingJLO }, bindingJLO, groovyMethods,
					methodBindings, null);
			createMethod("getProperty", false, "", new TypeBinding[] { bindingJLS }, bindingJLO, groovyMethods, methodBindings,
					null);
			createMethod("setProperty", false, "", new TypeBinding[] { bindingJLS, bindingJLO }, TypeBinding.VOID, groovyMethods,
					methodBindings, null);
			createMethod("getMetaClass", false, "", null, bindingGLM, groovyMethods, methodBindings, null);
			createMethod("setMetaClass", false, "", new TypeBinding[] { bindingGLM }, TypeBinding.VOID, groovyMethods,
					methodBindings, null);
		}
		// FIXASC decide what difference this makes - should we not be adding anything at all?
		// will not be an instance of GroovyTypeDeclaration if created through SourceTypeConverter
		if (this.referenceContext instanceof GroovyTypeDeclaration) {
			GroovyTypeDeclaration typeDeclaration = (GroovyTypeDeclaration) this.referenceContext;

			boolean useOldWay = false;
			if (useOldWay) {
				// FIXASC the methods created here need to be a subtype of
				// MethodBinding because they need their source position to be the
				// property
				List properties = typeDeclaration.properties;
				for (PropertyNode property : properties) {
					String name = property.getName();
					FieldBinding fBinding = typeDeclaration.binding.getField(name.toCharArray(), false);
					// null binding indicates there was a problem resolving its type
					if (fBinding != null && !(fBinding.type instanceof MissingTypeBinding)) {
						String getterName = "get" + MetaClassHelper.capitalize(name);
						createMethod(getterName, property.isStatic(), "", /* TypeBinding.NO_TYPES */null, fBinding.type,
								groovyMethods, methodBindings, typeDeclaration);
						if (!fBinding.isFinal()) {
							String setterName = "set" + MetaClassHelper.capitalize(name);
							createMethod(setterName, property.isStatic(), "", new TypeBinding[] { fBinding.type },
									TypeBinding.VOID, groovyMethods, methodBindings, typeDeclaration);
						}
						if (fBinding.type == TypeBinding.BOOLEAN) {
							createMethod("is" + MetaClassHelper.capitalize(name), property.isStatic(), "", /* TypeBinding.NO_TYPES, */
									null, fBinding.type, groovyMethods, methodBindings, typeDeclaration);
						}
					}
				}
			} else {
				// Create getters/setters without resolving the types.
				List properties = typeDeclaration.properties;
				for (PropertyNode property : properties) {
					String name = property.getName();
					String capitalizedName = MetaClassHelper.capitalize(name);
					// Create getter
					createGetterMethod(name, "get" + capitalizedName, property.isStatic(), groovyMethods, methodBindings,
							typeDeclaration);
					// Create setter if non-final property
					if (!Modifier.isFinal(property.getModifiers())) {
						createSetterMethod(name, "set" + capitalizedName, property.isStatic(), groovyMethods, methodBindings,
								typeDeclaration, property.getType().getName());
					}
					// Create isA if type is boolean
					String propertyType = property.getType().getName();
					if (propertyType.equals("boolean")) {
						createGetterMethod(name, "is" + capitalizedName, property.isStatic(), groovyMethods, methodBindings,
								typeDeclaration);
					}
				}
			}
		}

		Map methodsMap = new HashMap();
		for (ReferenceBinding i : superInterfaces) {
			if (traitHelper.isTrait(i)) {
				ReferenceBinding helperBinding = getHelperBinding(i);
				for (MethodBinding method : i.availableMethods()) {
					if (method.isPrivate() || method.isStatic()) {
						continue;
					}
					if (isNotActuallyAbstract(method, helperBinding)) {
						methodsMap.put(getMethodAsString(method), method);
					}
				}
			}
		}
		if (!methodsMap.isEmpty()) {
			Set canBeOverridden = new HashSet();
			ReferenceBinding superclass = binding.superclass();
			while (superclass != null) {
				for (MethodBinding method : superclass.availableMethods()) {
					if (method.isPrivate() || method.isPublic() || method.isStatic()) {
						continue;
					}
					canBeOverridden.add(getMethodAsString(method));
				}
				superclass = superclass.superclass();
			}
			for (MethodBinding method : methodBindings) {
				canBeOverridden.remove(getMethodAsString(method));
			}
			for (String key : canBeOverridden) {
				MethodBinding method = methodsMap.get(key);
				if (method != null) {
					method = new MethodBinding(method, binding);
					method.modifiers &= ~Modifier.ABSTRACT;
					groovyMethods.add(method);
				}
			}
		}

		MethodBinding[] newMethodBindings = groovyMethods.toArray(new MethodBinding[methodBindings.length + groovyMethods.size()]);
		System.arraycopy(methodBindings, 0, newMethodBindings, groovyMethods.size(), methodBindings.length);
		return newMethodBindings;
	}

	private String getMethodAsString(MethodBinding method) {
		StringBuilder key = new StringBuilder(new String(method.selector));
		key.append(" ");
		for (TypeBinding param : method.parameters) {
			char[] type = param.readableName();
			if (type != null) {
				key.append(type);
				key.append(" ");
			} else {
				key.append("null ");
			}
		}
		return key.toString();
	}

	private ReferenceBinding getHelperBinding(ReferenceBinding interfaceBinding) {
		if (interfaceBinding instanceof BinaryTypeBinding) {
			StringBuilder nameBuilder = new StringBuilder();
			nameBuilder.append(interfaceBinding.sourceName);
			nameBuilder.append("$Trait$Helper");
			ReferenceBinding helperBinding = compilationUnitScope().findType(nameBuilder.toString().toCharArray(),
					interfaceBinding.fPackage, interfaceBinding.fPackage);
			if (helperBinding != null) {
				if (helperBinding instanceof ProblemReferenceBinding) {
					helperBinding = ((ProblemReferenceBinding) helperBinding).closestReferenceMatch();
				}
			}
			return helperBinding;
		}
		return null;
	}

	private boolean isNotActuallyAbstract(MethodBinding methodBinding, ReferenceBinding helperBinding) {
		if (methodBinding.declaringClass instanceof SourceTypeBinding) {
			AbstractMethodDeclaration methodDeclaration = ((SourceTypeBinding) methodBinding.declaringClass).scope.referenceContext
					.declarationOf(methodBinding);
			if (methodDeclaration != null) {
				return (methodDeclaration.modifiers & ClassFileConstants.AccAbstract) == 0;
			}
		}
		if (methodBinding.declaringClass instanceof BinaryTypeBinding) {
			if (helperBinding != null) {
				for (MethodBinding m : helperBinding.methods()) {
					if (!Arrays.equals(methodBinding.selector, m.selector)) {
						continue;
					}
					TypeBinding[] actualParameters = m.parameters;
					TypeBinding[] expectedParameters = methodBinding.parameters;
					if (actualParameters.length != expectedParameters.length + 1) {
						continue;
					}
					if (!actualParameters[0].equals(methodBinding.declaringClass)) {
						continue;
					}
					boolean same = true;
					for (int i = 0; i < expectedParameters.length; i++) {
						if (!actualParameters[i + 1].equals(expectedParameters[i])) {
							same = false;
							break;
						}
					}
					return same && !m.isAbstract();
				}
			}
		}
		return true;
	}

	private void createMethod(String name, boolean isStatic, String signature, TypeBinding[] parameterTypes,
			TypeBinding returnType, List groovyMethods, MethodBinding[] existingMethods,
			GroovyTypeDeclaration typeDeclaration) {
		boolean found = false;
		for (MethodBinding existingMethod : existingMethods) {
			if (new String(existingMethod.selector).equals(name)) {
				// FIXASC safe to do this resolution so early?
				((SourceTypeBinding) existingMethod.declaringClass).resolveTypesFor(existingMethod);
				boolean equalParameters = true;
				if (parameterTypes == null) {
					// not looking for parameters, if this has none, that is OK
					if (existingMethod.parameters.length != 0) {
						equalParameters = false;
					}
				} else if (existingMethod.parameters.length == parameterTypes.length) {
					TypeBinding[] existingParams = existingMethod.parameters;
					for (int p = 0, max = parameterTypes.length; p < max; p++) {
						if (!CharOperation.equals(parameterTypes[p].signature(), existingParams[p].signature())) {
							equalParameters = false;
							break;
						}
					}
				}
				// FIXASC consider return type?
				if (equalParameters) {
					found = true;
					break;
				}
				// FIXASC what about inherited methods - what if the supertype
				// provides an implementation, does the subtype get a new method?
			}
		}
		if (!found) {
			int modifiers = ClassFileConstants.AccPublic;
			if (isStatic) {
				modifiers |= ClassFileConstants.AccStatic;
			}
			if (this.referenceContext.binding.isInterface()) {
				modifiers |= ClassFileConstants.AccAbstract;
			}
			char[] methodName = name.toCharArray();
			/*
			 * if (typeDeclaration != null) { // check we are not attempting to override a final method MethodBinding[]
			 * existingBindings = typeDeclaration.binding.getMethods(name.toCharArray()); int stop = 1; }
			 */
			MethodBinding mb = new MethodBinding(modifiers, methodName, returnType, parameterTypes, null,
					this.referenceContext.binding);
			// FIXASC parameter names - what value would it have to set them correctly?
			groovyMethods.add(mb);
		}
	}

	private void createGetterMethod(String propertyName, String name, boolean isStatic, List groovyMethods,
			MethodBinding[] existingMethods, GroovyTypeDeclaration typeDeclaration) {
		boolean found = false;

		char[] nameAsCharArray = name.toCharArray();
		for (MethodBinding existingMethod : existingMethods) {
			if (CharOperation.equals(nameAsCharArray, existingMethod.selector)) {
				// check if this possible candidate has parameters (if it does, it can't be our getter)
				if ((existingMethod.modifiers & ExtraCompilerModifiers.AccUnresolved) != 0) {
					// need some intelligence here
					AbstractMethodDeclaration methodDecl = existingMethod.sourceMethod();
					if (methodDecl == null) {
						// FIXASC decide what we can do here
					} else {
						Argument[] arguments = methodDecl.arguments;
						if (arguments == null || arguments.length == 0) {
							found = true;
						}
					}
				} else {
					TypeBinding[] existingParams = existingMethod.parameters;
					if (existingParams == null || existingParams.length == 0) {
						found = true;
					}
				}
			}
		}

		// FIXASC what about inherited methods - what if the supertype
		// provides an implementation, does the subtype get a new method?
		if (!found) {
			int modifiers = ClassFileConstants.AccPublic;
			if (isStatic) {
				modifiers |= ClassFileConstants.AccStatic;
			}
			if (this.referenceContext.binding.isInterface()) {
				modifiers |= ClassFileConstants.AccAbstract;
			}
			/*
			 * if (typeDeclaration != null) { // check we are not attempting to override a final method MethodBinding[]
			 * existingBindings = typeDeclaration.binding.getMethods(name.toCharArray()); int stop = 1; }
			 */
			MethodBinding mb = new LazilyResolvedMethodBinding(true, propertyName, modifiers, nameAsCharArray, null,
					this.referenceContext.binding);
			// FIXASC parameter names - what value would it have to set them correctly?
			groovyMethods.add(mb);
		}
	}

	private void createSetterMethod(String propertyName, String name, boolean isStatic, List groovyMethods,
			MethodBinding[] existingMethods, GroovyTypeDeclaration typeDeclaration, String propertyType) {
		boolean found = false;

		char[] nameAsCharArray = name.toCharArray();
		for (MethodBinding existingMethod : existingMethods) {
			if (CharOperation.equals(nameAsCharArray, existingMethod.selector)) {
				// check if this possible candidate has parameters (if it does, it can't be our getter)
				if ((existingMethod.modifiers & ExtraCompilerModifiers.AccUnresolved) != 0) {
					// lets look at the declaration
					AbstractMethodDeclaration methodDecl = existingMethod.sourceMethod();
					if (methodDecl == null) {
						// FIXASC decide what we can do here
					} else {
						Argument[] arguments = methodDecl.arguments;
						if (arguments != null && arguments.length == 1) {
							// might be a candidate, it takes one parameter
							// TypeReference tr = arguments[0].type;
							// String typename = new String(CharOperation.concatWith(tr.getTypeName(), '.'));
							// // not really an exact comparison here...
							// if (typename.endsWith(propertyName)) {
							found = true;
							// }
						}
					}
				} else {
					TypeBinding[] existingParams = existingMethod.parameters;
					if (existingParams != null && existingParams.length == 1) {
						// if (CharOperation.equals(existingParams[0].signature(),)) {
						// might be a candidate, it takes one parameter
						found = true;
						// }
					}
				}
			}
		}

		// FIXASC what about inherited methods - what if the supertype
		// provides an implementation, does the subtype get a new method?
		if (!found) {
			int modifiers = ClassFileConstants.AccPublic;
			if (isStatic) {
				modifiers |= ClassFileConstants.AccStatic;
			}
			if (this.referenceContext.binding.isInterface()) {
				modifiers |= ClassFileConstants.AccAbstract;
			}
			char[] methodName = name.toCharArray();
			/*
			 * if (typeDeclaration != null) { // check we are not attempting to override a final method MethodBinding[]
			 * existingBindings = typeDeclaration.binding.getMethods(name.toCharArray()); int stop = 1; }
			 */
			MethodBinding mb = new LazilyResolvedMethodBinding(false, propertyName, modifiers, methodName, null,
					this.referenceContext.binding);
			// FIXASC parameter names - what value would it have to set them correctly?
			groovyMethods.add(mb);
		}
	}

	@Override
	public boolean shouldReport(int problem) {
		if (problem == IProblem.SuperclassMustBeAClass) {
			return false;
		}
		if (problem == IProblem.IncompatibleReturnType) {
			return false;
		}
		if (problem == IProblem.AbstractMethodMustBeImplemented) {
			return false;
		}
		if (problem == IProblem.MethodNameClash) {
			return false;
		}
		if (problem == IProblem.VarargsConflict) {
			return false;
		}
		return true;
	}

	// FIXASC (M3) currently inactive - this enables getSingleton()
	// FIXASC (M3) make this switchable as it is too damn powerful
	// @Override
	@Override
	public MethodBinding[] getAnyExtraMethods(char[] selector) {
		if (true) {
			return null;
		}
		List mns = ((GroovyTypeDeclaration) referenceContext).getClassNode().getMethods(new String(selector));
		MethodBinding[] newMethods = new MethodBinding[mns.size()];
		int idx = 0;
		for (MethodNode methodNode : mns) {
			TypeBinding[] parameterTypes = null;
			TypeBinding returnType = compilationUnitScope().environment.getResolvedType(
					CharOperation.splitAndTrimOn('.', methodNode.getReturnType().getName().toCharArray()), this);
			newMethods[idx++] = new MethodBinding(methodNode.getModifiers(), selector, returnType, parameterTypes, null,
					this.referenceContext.binding);
		}
		// unitScope.environment.getResolvedType(JAVA_LANG_STRING, this);
		return newMethods;
	}

	@Override
	protected ClassScope buildClassScope(Scope parent, TypeDeclaration typeDecl) {
		return new GroovyClassScope(parent, typeDecl);
	}

	@Override
	public void buildFieldsAndMethods() {
		super.buildFieldsAndMethods();
		GroovyTypeDeclaration context = (GroovyTypeDeclaration) referenceContext;
		GroovyTypeDeclaration[] anonymousTypes = context.getAnonymousTypes();
		if (anonymousTypes != null) {
			for (GroovyTypeDeclaration anonType : anonymousTypes) {
				GroovyClassScope anonScope = new GroovyClassScope(this, anonType);
				anonType.scope = anonScope;
				anonType.resolve(anonType.enclosingMethod.scope);
			}
		}
		// STS-3930 start
		for (MethodBinding method : this.referenceContext.binding.methods()) {
			fixupTypeParameters(method);
		}
		// STS-3930 end
	}

	/**
	 * This is fix for generic methods with default parameter values. For those methods type variables and parameter arguments
	 * should be the same as it is for all other methods.
	 *
	 * @param method method to be fixed
	 */
	private void fixupTypeParameters(MethodBinding method) {
		if (method.typeVariables == null || method.typeVariables.length == 0) {
			return;
		}
		if (method.parameters == null || method.parameters.length == 0) {
			return;
		}
		Map bindings = new HashMap();
		for (TypeVariableBinding v : method.typeVariables) {
			bindings.put(new String(v.sourceName), v);
		}
		for (TypeBinding parameter : method.parameters) {
			if (!(parameter instanceof ParameterizedTypeBinding)) {
				continue;
			}
			TypeBinding[] arguments = ((ParameterizedTypeBinding) parameter).arguments;
			if (arguments == null) {
				continue;
			}
			for (int i = 0; i < arguments.length; i++) {
				if (arguments[i] instanceof TypeVariableBinding) {
					String name = new String(arguments[i].sourceName());
					TypeBinding argument = bindings.get(name);
					if (arguments[i].id != argument.id) {
						arguments[i] = argument;
					}
				}
			}
		}
	}

	/**
	 * The class helps to check if some class node is trait.
	 */
	private class TraitHelper {

		private boolean toBeInitialized = true;
		private boolean lookForTraitAlias = false;

		private void initialize() {
			ImportBinding[] imports = referenceContext.scope.compilationUnitScope().imports;
			if (imports != null) {
				for (ImportBinding i : imports) {
					String importedType = new String(i.readableName());
					if ("groovy.transform.Trait".equals(importedType)) {
						lookForTraitAlias = true;
						break;
					}
					if (importedType.endsWith(".Trait")) {
						lookForTraitAlias = false;
						break;
					}
					if ("groovy.transform.*".equals(importedType)) {
						lookForTraitAlias = true;
					}
				}
				toBeInitialized = true;
			}
		}

		private boolean isTrait(ReferenceBinding referenceBinding) {
			if (referenceBinding == null) {
				return false;
			}
			if (toBeInitialized) {
				initialize();
			}
			AnnotationBinding[] annotations = referenceBinding.getAnnotations();
			if (annotations != null) {
				for (AnnotationBinding annotation : annotations) {
					String annotationName = CharOperation.toString(annotation.getAnnotationType().compoundName);
					if ("groovy.transform.Trait".equals(annotationName)) {
						return true;
					}
					if (lookForTraitAlias && "Trait".equals(annotationName)) {
						return true;
					}
				}
			}
			return false;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy