/* * 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.groovy.eclipse.dsl.lookup; import java.util.Map; import java.util.WeakHashMap; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.GenericsType; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.jdt.groovy.internal.compiler.ast.JDTResolver; import org.eclipse.jdt.groovy.search.VariableScope; /** * A wrapper around the JDT resolver that caches resolve requests. * Handles classes with type parameters. */ public class ResolverCache { private final Map<String, ClassNode> nameTypeCache; private final JDTResolver resolver; public ResolverCache(JDTResolver resolver, ModuleNode thisModule) { this.nameTypeCache = new WeakHashMap<String, ClassNode>(); this.resolver = resolver; } /** * Resolves a class name to a ClassNode. Using the fully qualified type name, * or the array type signature for arrays. Can specify type parameters, also * using fully qualified names. */ public ClassNode resolve(String qName) { if (qName == null || qName.length() == 0) { return ClassHelper.DYNAMIC_TYPE; } qName = qName.trim(); if (qName.equals("java.lang.Void") || qName.equals("void")) { return VariableScope.VOID_CLASS_NODE; } ClassNode clazz = nameTypeCache.get(qName); int arrayCnt = 0; if (clazz == null && resolver != null) { int typeParamEnd = qName.lastIndexOf('>'); int arrayStart = qName.indexOf('[', typeParamEnd); String componentName; if (arrayStart > 0) { componentName = qName.substring(0, arrayStart); arrayCnt = calculateArrayCount(qName, arrayStart); } else { componentName = qName; } String erasureName = componentName; int typeParamStart = -1; if (typeParamEnd > 0) { typeParamStart = componentName.indexOf('<'); if (typeParamStart > 0) { erasureName = componentName.substring(0, typeParamStart); } } clazz = resolver.resolve(erasureName); if (clazz == null) { clazz = VariableScope.OBJECT_CLASS_NODE; } nameTypeCache.put(erasureName, clazz); // now recur down through the type parameters if (typeParamStart > 0) { // only need to clone if generics are involved clazz = VariableScope.clone(clazz); String[] typeParameterNames = componentName.substring(typeParamStart + 1, componentName.length() - 1).split(","); ClassNode[] typeParameters = new ClassNode[typeParameterNames.length]; for (int i = 0; i < typeParameterNames.length; i += 1) { typeParameters[i] = resolve(typeParameterNames[i]); } clazz = VariableScope.clone(clazz); GenericsType[] genericsTypes = clazz.getGenericsTypes(); if (genericsTypes != null) { // need to be careful here...there may be too many or too few type parameters for (int i = 0; i < genericsTypes.length && i < typeParameters.length; i += 1) { genericsTypes[i].setResolved(true); genericsTypes[i].setWildcard(false); genericsTypes[i].setPlaceholder(false); genericsTypes[i].setLowerBound(null); genericsTypes[i].setUpperBounds(null); genericsTypes[i].setType(typeParameters[i]); genericsTypes[i].setName(typeParameters[i].getName()); } nameTypeCache.put(componentName, clazz); } } while (arrayCnt > 0) { clazz = new ClassNode(clazz); componentName += "[]"; nameTypeCache.put(componentName, clazz); arrayCnt -= 1; } } return clazz; } private int calculateArrayCount(String qName, int arrayStart) { if (arrayStart < 0) { return 0; } int cnt = 1; while ((arrayStart = qName.indexOf('[', arrayStart + 1)) > 0) { cnt += 1; } return cnt; } }