/* * 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.jdt.groovy.internal.compiler.ast; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.GenericsType; 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.LookupEnvironment; import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.RawTypeBinding; 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.UnresolvedReferenceBinding; import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding; /** * This class is configured with a resolver and then builds ClassNodes for Eclipse TypeBindings. It follows the pattern in the * groovy <code>Java5</code> class. See the entry point <code>setAdditionalClassInformation()</code> in that class. By following the * code structure from Java5 as closely as we can, we will build ClassNodes that contain the unusual form of generics configuration * that the rest of groovy wants to see. (Note: Java5.setAdditionalClassInformation() is used for building ClassNode objects for JVM * reflective class objects). */ class JDTClassNodeBuilder { private JDTResolver resolver; JDTClassNodeBuilder(JDTResolver resolver) { this.resolver = resolver; } public static ClassNode build(JDTResolver resolver, TypeBinding typeBinding) { JDTClassNodeBuilder builder = new JDTClassNodeBuilder(resolver); return builder.configureType(typeBinding); } /** * Based on Java5.configureType() */ public ClassNode configureType(TypeBinding type) { // GRECLIPSE-1639: Not all TypeBinding instances have been resolved when we get to this point. // See comment on org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.getTypeFromCompoundName(char[][], boolean, boolean) if (type instanceof UnresolvedReferenceBinding) { type = resolver.getScope().environment.askForType(((UnresolvedReferenceBinding) type).compoundName); } if (type instanceof TypeVariableBinding) { return configureTypeVariableReference((TypeVariableBinding) type); } else if (type instanceof ParameterizedTypeBinding) { return configureParameterizedType((ParameterizedTypeBinding) type); } else if (type instanceof BinaryTypeBinding) { return configureClass((BinaryTypeBinding) type); } else if (type instanceof WildcardBinding) { return configureWildcardType((WildcardBinding) type); } else if (type instanceof ArrayBinding) { return configureGenericArray((ArrayBinding) type); } else if (type instanceof BaseTypeBinding) { return configureBaseTypeBinding((BaseTypeBinding) type); } else if (type instanceof SourceTypeBinding) { return configureSourceType((SourceTypeBinding) type); } throw new IllegalStateException("'type' was null or an unhandled type: " + (type == null ? "null" : type.getClass().getName())); } /** * Based on Java5.configureTypeVariable() */ GenericsType[] configureTypeVariables(TypeVariableBinding[] bindings) { int n; if (bindings == null || (n = bindings.length) == 0) { return null; } GenericsType[] gts = new GenericsType[n]; for (int i = 0; i < n; i += 1) { gts[i] = configureTypeVariableDefinition(bindings[i]); } return gts; } /** * Based on Java5.configureTypeArguments() */ GenericsType[] configureTypeArguments(TypeBinding[] bindings) { int n; if (bindings == null || (n = bindings.length) == 0) { return null; } GenericsType[] gts = new GenericsType[n]; for (int i = 0; i < n; i += 1) { ClassNode t = configureType(bindings[i]); if (bindings[i] instanceof WildcardBinding) { GenericsType[] gen = t.getGenericsTypes(); gts[i] = gen[0]; } else { gts[i] = new GenericsType(t); } } return gts; } // TODO still not 100% confident that the callers of this are doing the right thing or have the right expectations TypeBinding toRawType(TypeBinding tb) { if (tb instanceof RawTypeBinding) { return tb; } else if (tb instanceof ParameterizedTypeBinding) { ParameterizedTypeBinding ptb = (ParameterizedTypeBinding) tb; // resolver.getScope() can return null (if the resolver hasn't yet been used to resolve something) - using // the environment on the ptb seems safe. Other occurrences of getScope in this file could feasibly // be changed in the same way if NPEs become problems there too return ptb.environment().convertToRawType(ptb.genericType(), false); } else if (tb instanceof TypeVariableBinding) { TypeBinding fb = ((TypeVariableBinding) tb).firstBound; if (fb == null) { return tb.erasure(); // Should be JLObject } return fb; } else if (tb instanceof BinaryTypeBinding) { if (tb.isGenericType()) { try { Field f = BinaryTypeBinding.class.getDeclaredField("environment"); f.setAccessible(true); LookupEnvironment le = (LookupEnvironment) f.get(tb); return le.convertToRawType(tb, false); // return resolver.getScope().environment.convertToRawType(tb, false); } catch (Exception e) { throw new RuntimeException("Problem building rawtype ", e); } } else { return tb; } } else if (tb instanceof ArrayBinding) { return tb; } else if (tb instanceof BaseTypeBinding) { return tb; } else if (tb instanceof SourceTypeBinding) { return tb; } throw new IllegalStateException("nyi " + tb.getClass()); } /** * Based on Java5.configureTypes() */ private ClassNode[] configureTypes(TypeBinding[] bindings) { int n; if (bindings == null || (n = bindings.length) == 0) { return null; } ClassNode[] nodes = new ClassNode[n]; for (int i = 0; i < n; i += 1) { nodes[i] = configureType(bindings[i]); } return nodes; } /** * Return the groovy ClassNode for an Eclipse BaseTypeBinding. */ private ClassNode configureBaseTypeBinding(BaseTypeBinding type) { switch (type.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("Unexpected BaseTypeBinding: " + type + "(type.id=" + type.id + ")"); } } /** * Loosely based on Java5.configureGenericArray() */ private ClassNode configureGenericArray(ArrayBinding genericArrayType) { TypeBinding component = genericArrayType.leafComponentType; ClassNode node = configureType(component); int dims = genericArrayType.dimensions; ClassNode result = node; for (int d = 0; d < dims; d += 1) { result = result.makeArray(); } return result; } private Map<TypeVariableBinding, ClassNode> typeVariableConfigurationInProgress = new HashMap<TypeVariableBinding, ClassNode>(); /** * Based on Java5.configureTypeVariableReference() */ private ClassNode configureTypeVariableReference(TypeVariableBinding tv) { ClassNode nodeInProgress = typeVariableConfigurationInProgress.get(tv); if (nodeInProgress != null) { return nodeInProgress; } ClassNode cn = ClassHelper.makeWithoutCaching(tv.debugName()); cn.setGenericsPlaceHolder(true); ClassNode cn2 = ClassHelper.makeWithoutCaching(tv.debugName()); cn2.setGenericsPlaceHolder(true); GenericsType[] gts = new GenericsType[] { new GenericsType(cn2) }; cn.setGenericsTypes(gts); cn.setRedirect(ClassHelper.OBJECT_TYPE); typeVariableConfigurationInProgress.put(tv, cn); // doing a bit of what is in Java5.makeClassNode() where it sorts out its front/back GR1563 TypeBinding tb = tv.firstBound; if (tb != null && !tb.debugName().equals("java.lang.Object")) { ClassNode back = configureType(tb); cn.setRedirect(back); } typeVariableConfigurationInProgress.remove(tv); return cn; } /** * Based on Java5.configureTypeVariableDefinition() */ private GenericsType configureTypeVariableDefinition(TypeVariableBinding tv) { ClassNode base = configureTypeVariableReference(tv); ClassNode redirect = base.redirect(); base.setRedirect(null); TypeBinding[] tBounds = getBounds(tv); GenericsType gt; if (tBounds.length == 0) { gt = new GenericsType(base); } else { ClassNode[] cBounds = configureTypes(tBounds); gt = new GenericsType(base, cBounds, null); gt.setName(base.getName()); gt.setPlaceholder(true); } base.setRedirect(redirect); return gt; } private TypeBinding[] getBounds(TypeVariableBinding tv) { List<TypeBinding> bounds = new ArrayList<TypeBinding>(); if (tv.firstBound == null) { TypeBinding erasure = tv.erasure(); if (erasure == null) { erasure = resolver.getScope().getJavaLangObject(); } return new TypeBinding[] { erasure }; // Should be JLObject } bounds.add(tv.firstBound); TypeBinding[] obs = tv.otherUpperBounds(); for (int i = 0; i < obs.length; i++) { bounds.add(obs[i]); } return bounds.toArray(new TypeBinding[bounds.size()]); } /** * Based on Java5.configureWildcardType() */ private ClassNode configureWildcardType(WildcardBinding wildcardType) { ClassNode base = ClassHelper.makeWithoutCaching("?"); base.setRedirect(ClassHelper.OBJECT_TYPE); ClassNode[] uppers = configureTypes(getUpperbounds(wildcardType)); ClassNode[] lowers = configureTypes(getLowerbounds(wildcardType)); ClassNode lower = null; if (lowers != null && lowers.length > 0) { lower = lowers[0]; } GenericsType t = new GenericsType(base, uppers, lower); t.setWildcard(true); ClassNode ref = ClassHelper.makeWithoutCaching(Object.class, false); ref.setGenericsTypes(new GenericsType[] { t }); return ref; } private TypeBinding[] getLowerbounds(WildcardBinding wildcardType) { if (wildcardType.boundKind == Wildcard.SUPER) { return new TypeBinding[] { wildcardType.bound }; } return Binding.NO_TYPES; } private TypeBinding[] getUpperbounds(WildcardBinding wildcardType) { if (wildcardType.boundKind == Wildcard.EXTENDS) { int nBounds = (wildcardType.otherBounds == null) ? 1 : 1 + wildcardType.otherBounds.length; TypeBinding[] bounds = new TypeBinding[nBounds]; bounds[0] = wildcardType.bound; if (--nBounds > 0) { System.arraycopy(wildcardType.otherBounds, 0, bounds, 1, nBounds); } return bounds; } return Binding.NO_TYPES; } private ClassNode configureParameterizedType(ParameterizedTypeBinding parameterizedType) { if (parameterizedType instanceof RawTypeBinding) { TypeBinding rt = toRawType(parameterizedType); if (!(rt instanceof RawTypeBinding)) { System.out.println("yikes"); } return new JDTClassNode((RawTypeBinding) rt, resolver); // doesn't need generics initializing } TypeBinding rt = toRawType(parameterizedType); if ((rt instanceof ParameterizedTypeBinding) && !(rt instanceof RawTypeBinding)) { // the type was the inner type of a parameterized type return new JDTClassNode((ParameterizedTypeBinding) rt, resolver); // doesn't need generics initializing } ClassNode base = configureType(rt); if (base instanceof JDTClassNode) { ((JDTClassNode) base).setJdtBinding(parameterizedType); // the messing about in here is for a few reasons. Contrast it with the ClassHelper.makeWithoutCaching // that code when called for Iterable will set the redirect to point to the generics. That is what // we are trying to achieve here. if (!(parameterizedType instanceof RawTypeBinding)) { ClassNode cn = configureType(parameterizedType.genericType()); ((JDTClassNode) base).setRedirect(cn); } } GenericsType[] gts = configureTypeArguments(parameterizedType.arguments); base.setGenericsTypes(gts); return base; } private ClassNode configureClass(BinaryTypeBinding type) { if (type.id == TypeIds.T_JavaLangObject) { return ClassHelper.OBJECT_TYPE; } else if (type.id == TypeIds.T_JavaLangString) { return ClassHelper.STRING_TYPE; }/* else if (type.id == TypeIds.T_JavaLangClass) { return ClassHelper.CLASS_Type; }*/ return new JDTClassNode(type, resolver); } private ClassNode configureSourceType(SourceTypeBinding type) { // TODO: Not being cached -- should it be? return new JDTClassNode(type, resolver); } }