/** @author ven */ public class TypesUtil { private static final LightCacheKey<Map<String, PsiClass>> PARENT_CACHE_KEY = LightCacheKey.create(); @NonNls public static final Map<String, PsiType> ourQNameToUnboxed = new HashMap<String, PsiType>(); public static final PsiPrimitiveType[] PRIMITIVES = new PsiPrimitiveType[] { PsiType.BYTE, PsiType.CHAR, PsiType.DOUBLE, PsiType.FLOAT, PsiType.INT, PsiType.SHORT, PsiType.LONG, PsiType.BOOLEAN, PsiType.VOID }; private TypesUtil() {} @Nullable public static PsiType getNumericResultType(GrBinaryExpression binaryExpression) { PsiType lType = binaryExpression.getLeftOperand().getType(); final GrExpression rop = binaryExpression.getRightOperand(); PsiType rType = rop == null ? null : rop.getType(); if (lType == null || rType == null) return null; return getLeastUpperBoundForNumericType(lType, rType); } @Nullable private static PsiType getLeastUpperBoundForNumericType( @NotNull PsiType lType, @NotNull PsiType rType) { String lCanonical = lType.getCanonicalText(); String rCanonical = rType.getCanonicalText(); if (JAVA_LANG_FLOAT.equals(lCanonical)) lCanonical = JAVA_LANG_DOUBLE; if (JAVA_LANG_FLOAT.equals(rCanonical)) rCanonical = JAVA_LANG_DOUBLE; if (TYPE_TO_RANK.containsKey(lCanonical) && TYPE_TO_RANK.containsKey(rCanonical)) { return TYPE_TO_RANK.get(lCanonical) > TYPE_TO_RANK.get(rCanonical) ? lType : rType; } return null; } @NotNull public static GroovyResolveResult[] getOverloadedOperatorCandidates( @NotNull PsiType thisType, IElementType tokenType, @NotNull GroovyPsiElement place, PsiType[] argumentTypes) { return getOverloadedOperatorCandidates(thisType, tokenType, place, argumentTypes, false); } @NotNull public static GroovyResolveResult[] getOverloadedOperatorCandidates( @NotNull PsiType thisType, IElementType tokenType, @NotNull GroovyPsiElement place, PsiType[] argumentTypes, boolean incompleteCode) { return ResolveUtil.getMethodCandidates( thisType, ourOperationsToOperatorNames.get(tokenType), place, true, incompleteCode, false, argumentTypes); } public static GroovyResolveResult[] getOverloadedUnaryOperatorCandidates( @NotNull PsiType thisType, IElementType tokenType, @NotNull GroovyPsiElement place, PsiType[] argumentTypes) { return ResolveUtil.getMethodCandidates( thisType, ourUnaryOperationsToOperatorNames.get(tokenType), place, argumentTypes); } private static final Map<IElementType, String> ourPrimitiveTypesToClassNames = new HashMap<IElementType, String>(); private static final String NULL = "null"; static { ourPrimitiveTypesToClassNames.put(mSTRING_LITERAL, JAVA_LANG_STRING); ourPrimitiveTypesToClassNames.put(mGSTRING_LITERAL, JAVA_LANG_STRING); ourPrimitiveTypesToClassNames.put(mREGEX_LITERAL, JAVA_LANG_STRING); ourPrimitiveTypesToClassNames.put(mDOLLAR_SLASH_REGEX_LITERAL, JAVA_LANG_STRING); ourPrimitiveTypesToClassNames.put(mNUM_INT, JAVA_LANG_INTEGER); ourPrimitiveTypesToClassNames.put(mNUM_LONG, JAVA_LANG_LONG); ourPrimitiveTypesToClassNames.put(mNUM_FLOAT, JAVA_LANG_FLOAT); ourPrimitiveTypesToClassNames.put(mNUM_DOUBLE, JAVA_LANG_DOUBLE); ourPrimitiveTypesToClassNames.put(mNUM_BIG_INT, JAVA_MATH_BIG_INTEGER); ourPrimitiveTypesToClassNames.put(mNUM_BIG_DECIMAL, JAVA_MATH_BIG_DECIMAL); ourPrimitiveTypesToClassNames.put(kFALSE, JAVA_LANG_BOOLEAN); ourPrimitiveTypesToClassNames.put(kTRUE, JAVA_LANG_BOOLEAN); ourPrimitiveTypesToClassNames.put(kNULL, NULL); ourPrimitiveTypesToClassNames.put(kINT, JAVA_LANG_INTEGER); ourPrimitiveTypesToClassNames.put(kLONG, JAVA_LANG_LONG); ourPrimitiveTypesToClassNames.put(kFLOAT, JAVA_LANG_FLOAT); ourPrimitiveTypesToClassNames.put(kDOUBLE, JAVA_LANG_DOUBLE); ourPrimitiveTypesToClassNames.put(kBOOLEAN, JAVA_LANG_BOOLEAN); ourPrimitiveTypesToClassNames.put(kCHAR, JAVA_LANG_CHARACTER); ourPrimitiveTypesToClassNames.put(kBYTE, JAVA_LANG_BYTE); } private static final Map<IElementType, String> ourOperationsToOperatorNames = new HashMap<IElementType, String>(); private static final Map<IElementType, String> ourUnaryOperationsToOperatorNames = new HashMap<IElementType, String>(); static { ourOperationsToOperatorNames.put(mPLUS, "plus"); ourOperationsToOperatorNames.put(mMINUS, "minus"); ourOperationsToOperatorNames.put(mBAND, "and"); ourOperationsToOperatorNames.put(mBOR, "or"); ourOperationsToOperatorNames.put(mBXOR, "xor"); ourOperationsToOperatorNames.put(mDIV, "div"); ourOperationsToOperatorNames.put(mMOD, "mod"); ourOperationsToOperatorNames.put(mSTAR, "multiply"); ourOperationsToOperatorNames.put(kAS, "asType"); ourOperationsToOperatorNames.put(mCOMPARE_TO, "compareTo"); ourOperationsToOperatorNames.put(mGT, "compareTo"); ourOperationsToOperatorNames.put(mGE, "compareTo"); ourOperationsToOperatorNames.put(mLT, "compareTo"); ourOperationsToOperatorNames.put(mLE, "compareTo"); ourOperationsToOperatorNames.put(mSTAR_STAR, "power"); ourOperationsToOperatorNames.put(COMPOSITE_LSHIFT_SIGN, "leftShift"); ourOperationsToOperatorNames.put(COMPOSITE_RSHIFT_SIGN, "rightShift"); ourOperationsToOperatorNames.put(mEQUAL, "equals"); ourOperationsToOperatorNames.put(mNOT_EQUAL, "equals"); ourUnaryOperationsToOperatorNames.put(mLNOT, "asBoolean"); ourUnaryOperationsToOperatorNames.put(mPLUS, "positive"); ourUnaryOperationsToOperatorNames.put(mMINUS, "negative"); ourUnaryOperationsToOperatorNames.put(mDEC, "previous"); ourUnaryOperationsToOperatorNames.put(mINC, "next"); ourUnaryOperationsToOperatorNames.put(mBNOT, "bitwiseNegate"); } private static final TObjectIntHashMap<String> TYPE_TO_RANK = new TObjectIntHashMap<String>(); static { TYPE_TO_RANK.put(JAVA_LANG_BYTE, 1); TYPE_TO_RANK.put(JAVA_LANG_SHORT, 2); TYPE_TO_RANK.put(JAVA_LANG_CHARACTER, 2); TYPE_TO_RANK.put(JAVA_LANG_INTEGER, 3); TYPE_TO_RANK.put(JAVA_LANG_LONG, 4); TYPE_TO_RANK.put(JAVA_MATH_BIG_INTEGER, 5); TYPE_TO_RANK.put(JAVA_MATH_BIG_DECIMAL, 6); TYPE_TO_RANK.put(JAVA_LANG_FLOAT, 7); TYPE_TO_RANK.put(JAVA_LANG_DOUBLE, 8); TYPE_TO_RANK.put(JAVA_LANG_NUMBER, 9); } static { ourQNameToUnboxed.put(JAVA_LANG_BOOLEAN, PsiType.BOOLEAN); ourQNameToUnboxed.put(JAVA_LANG_BYTE, PsiType.BYTE); ourQNameToUnboxed.put(JAVA_LANG_CHARACTER, PsiType.CHAR); ourQNameToUnboxed.put(JAVA_LANG_SHORT, PsiType.SHORT); ourQNameToUnboxed.put(JAVA_LANG_INTEGER, PsiType.INT); ourQNameToUnboxed.put(JAVA_LANG_LONG, PsiType.LONG); ourQNameToUnboxed.put(JAVA_LANG_FLOAT, PsiType.FLOAT); ourQNameToUnboxed.put(JAVA_LANG_DOUBLE, PsiType.DOUBLE); } private static final TIntObjectHashMap<String> RANK_TO_TYPE = new TIntObjectHashMap<String>(); static { RANK_TO_TYPE.put(1, JAVA_LANG_INTEGER); RANK_TO_TYPE.put(2, JAVA_LANG_INTEGER); RANK_TO_TYPE.put(3, JAVA_LANG_INTEGER); RANK_TO_TYPE.put(4, JAVA_LANG_LONG); RANK_TO_TYPE.put(5, JAVA_MATH_BIG_INTEGER); RANK_TO_TYPE.put(6, JAVA_MATH_BIG_DECIMAL); RANK_TO_TYPE.put(7, JAVA_LANG_DOUBLE); RANK_TO_TYPE.put(8, JAVA_LANG_DOUBLE); RANK_TO_TYPE.put(9, JAVA_LANG_NUMBER); } public static boolean isAssignable( @Nullable PsiType lType, @Nullable PsiType rType, @NotNull PsiElement context) { if (lType == null || rType == null) { return false; } if (rType instanceof PsiIntersectionType) { for (PsiType child : ((PsiIntersectionType) rType).getConjuncts()) { if (isAssignable(lType, child, context)) { return true; } } return false; } if (lType instanceof PsiIntersectionType) { for (PsiType child : ((PsiIntersectionType) lType).getConjuncts()) { if (!isAssignable(child, rType, context)) { return false; } } return true; } if (rType == PsiType.NULL) { return !(lType instanceof PsiPrimitiveType); } if (isNumericType(lType) && isNumericType(rType)) { return true; } if (isClassType(lType, JAVA_LANG_STRING)) { return true; } final PsiManager manager = context.getManager(); final GlobalSearchScope scope = context.getResolveScope(); if (lType instanceof PsiArrayType) { PsiType lComponentType = ((PsiArrayType) lType).getComponentType(); PsiType rComponentType = ClosureParameterEnhancer.findTypeForIteration(rType, context); if (rComponentType != null && isAssignable(lComponentType, rComponentType, context)) { return true; } } if (unboxPrimitiveTypeWrapper(lType) == PsiType.CHAR && (isClassType(rType, JAVA_LANG_STRING) || isClassType(rType, GROOVY_LANG_GSTRING))) { return true; } if (isAssignableByMethodCallConversion(lType, rType, context)) return true; lType = boxPrimitiveType(lType, manager, scope); rType = boxPrimitiveType(rType, manager, scope); if (lType.isAssignableFrom(rType)) { return true; } if (context instanceof GroovyPsiElement) { for (GrTypeConverter converter : GrTypeConverter.EP_NAME.getExtensions()) { if (!converter.isAllowedInMethodCall()) { Boolean result = converter.isConvertible(lType, rType, (GroovyPsiElement) context); if (result != null) { return result; } } } } return false; } public static boolean isAssignableByMethodCallConversion( @Nullable PsiType lType, @Nullable PsiType rType, @NotNull PsiElement context) { if (lType == null || rType == null) { return false; } if (rType instanceof PsiIntersectionType) { for (PsiType child : ((PsiIntersectionType) rType).getConjuncts()) { if (isAssignable(lType, child, context)) { return true; } } return false; } if (lType instanceof PsiIntersectionType) { for (PsiType child : ((PsiIntersectionType) lType).getConjuncts()) { if (!isAssignable(child, rType, context)) { return false; } } return true; } if (rType == PsiType.NULL) { return !(lType instanceof PsiPrimitiveType); } if (isAssignableWithoutConversions(lType, rType, context)) { return true; } if (context instanceof GroovyPsiElement) { for (GrTypeConverter converter : GrTypeConverter.EP_NAME.getExtensions()) { if (converter.isAllowedInMethodCall()) { final Boolean result = converter.isConvertible(lType, rType, (GroovyPsiElement) context); if (result != null) { return result; } } } } return false; } public static boolean isAssignableWithoutConversions( @Nullable PsiType lType, @Nullable PsiType rType, @NotNull PsiElement context) { if (lType == null || rType == null) return false; if (rType == PsiType.NULL) { return !(lType instanceof PsiPrimitiveType); } PsiManager manager = context.getManager(); GlobalSearchScope scope = context.getResolveScope(); if (rType instanceof GrTupleType && ((GrTupleType) rType).getComponentTypes().length == 0) { if (lType instanceof PsiArrayType || InheritanceUtil.isInheritor(lType, JAVA_UTIL_LIST) || InheritanceUtil.isInheritor(lType, JAVA_UTIL_SET)) { return true; } } if (isClassType(rType, GROOVY_LANG_GSTRING) && lType.equalsToText(JAVA_LANG_STRING)) { return true; } if (isNumericType(lType) && isNumericType(rType)) { lType = unboxPrimitiveTypeWrapper(lType); if (isClassType(lType, JAVA_MATH_BIG_DECIMAL)) lType = PsiType.DOUBLE; rType = unboxPrimitiveTypeWrapper(rType); if (isClassType(rType, JAVA_MATH_BIG_DECIMAL)) rType = PsiType.DOUBLE; } else { rType = boxPrimitiveType(rType, manager, scope); lType = boxPrimitiveType(lType, manager, scope); } if (rType instanceof GrMapType || rType instanceof GrTupleType) { Boolean result = isAssignableForNativeTypes(lType, (PsiClassType) rType, context); if (result != null && result.booleanValue()) return true; } if (TypeConversionUtil.isAssignable(lType, rType)) { return true; } return false; } @Nullable private static Boolean isAssignableForNativeTypes( @NotNull PsiType lType, @NotNull PsiClassType rType, @NotNull PsiElement context) { if (!(lType instanceof PsiClassType)) return null; final PsiClassType.ClassResolveResult leftResult = ((PsiClassType) lType).resolveGenerics(); final PsiClassType.ClassResolveResult rightResult = rType.resolveGenerics(); final PsiClass leftClass = leftResult.getElement(); PsiClass rightClass = rightResult.getElement(); if (rightClass == null || leftClass == null) return null; if (!InheritanceUtil.isInheritorOrSelf(rightClass, leftClass, true)) return Boolean.FALSE; PsiSubstitutor rightSubstitutor = rightResult.getSubstitutor(); if (!leftClass.hasTypeParameters()) return Boolean.TRUE; PsiSubstitutor leftSubstitutor = leftResult.getSubstitutor(); if (!leftClass.getManager().areElementsEquivalent(leftClass, rightClass)) { rightSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(leftClass, rightClass, rightSubstitutor); rightClass = leftClass; } else if (!rightClass.hasTypeParameters()) return Boolean.TRUE; Iterator<PsiTypeParameter> li = PsiUtil.typeParametersIterator(leftClass); Iterator<PsiTypeParameter> ri = PsiUtil.typeParametersIterator(rightClass); while (li.hasNext()) { if (!ri.hasNext()) return Boolean.FALSE; PsiTypeParameter lp = li.next(); PsiTypeParameter rp = ri.next(); final PsiType typeLeft = leftSubstitutor.substitute(lp); if (typeLeft == null) continue; final PsiType typeRight = rightSubstitutor.substituteWithBoundsPromotion(rp); if (typeRight == null) { return Boolean.TRUE; } if (!isAssignableWithoutConversions(typeLeft, typeRight, context)) return Boolean.FALSE; } return Boolean.TRUE; } public static boolean isNumericType(@Nullable PsiType type) { if (type instanceof PsiClassType) { return TYPE_TO_RANK.contains(type.getCanonicalText()); } return type instanceof PsiPrimitiveType && TypeConversionUtil.isNumericType(type); } public static PsiType unboxPrimitiveTypeWrapperAndEraseGenerics(PsiType result) { return TypeConversionUtil.erasure(unboxPrimitiveTypeWrapper(result)); } public static PsiType unboxPrimitiveTypeWrapper(@Nullable PsiType type) { if (type instanceof PsiClassType) { final PsiClass psiClass = ((PsiClassType) type).resolve(); if (psiClass != null) { PsiType unboxed = ourQNameToUnboxed.get(psiClass.getQualifiedName()); if (unboxed != null) type = unboxed; } } return type; } public static PsiType boxPrimitiveType( @Nullable PsiType result, @NotNull PsiManager manager, @NotNull GlobalSearchScope resolveScope, boolean boxVoid) { if (result instanceof PsiPrimitiveType && (boxVoid || result != PsiType.VOID)) { PsiPrimitiveType primitive = (PsiPrimitiveType) result; String boxedTypeName = primitive.getBoxedTypeName(); if (boxedTypeName != null) { return GroovyPsiManager.getInstance(manager.getProject()) .createTypeByFQClassName(boxedTypeName, resolveScope); } } return result; } public static PsiType boxPrimitiveType( @Nullable PsiType result, @NotNull PsiManager manager, @NotNull GlobalSearchScope resolveScope) { return boxPrimitiveType(result, manager, resolveScope, false); } @NotNull public static PsiClassType createType(String fqName, @NotNull PsiElement context) { return createTypeByFQClassName(fqName, context); } @NotNull public static PsiClassType getJavaLangObject(@NotNull PsiElement context) { return PsiType.getJavaLangObject(context.getManager(), context.getResolveScope()); } @Nullable public static PsiType getLeastUpperBoundNullable( @Nullable PsiType type1, @Nullable PsiType type2, @NotNull PsiManager manager) { if (type1 == null) return type2; if (type2 == null) return type1; return getLeastUpperBound(type1, type2, manager); } @Nullable public static PsiType getLeastUpperBoundNullable( @NotNull Iterable<PsiType> collection, @NotNull PsiManager manager) { Iterator<PsiType> iterator = collection.iterator(); if (!iterator.hasNext()) return null; PsiType result = iterator.next(); while (iterator.hasNext()) { result = getLeastUpperBoundNullable(result, iterator.next(), manager); } return result; } @Nullable public static PsiType getLeastUpperBound( @NotNull PsiType type1, @NotNull PsiType type2, PsiManager manager) { if (type1 instanceof GrTupleType && type2 instanceof GrTupleType) { GrTupleType tuple1 = (GrTupleType) type1; GrTupleType tuple2 = (GrTupleType) type2; PsiType[] components1 = tuple1.getComponentTypes(); PsiType[] components2 = tuple2.getComponentTypes(); if (components1.length == 0) return genNewListBy(type2, manager); if (components2.length == 0) return genNewListBy(type1, manager); PsiType[] components3 = new PsiType[Math.min(components1.length, components2.length)]; for (int i = 0; i < components3.length; i++) { PsiType c1 = components1[i]; PsiType c2 = components2[i]; if (c1 == null || c2 == null) { components3[i] = null; } else { components3[i] = getLeastUpperBound(c1, c2, manager); } } return new GrTupleType( components3, JavaPsiFacade.getInstance(manager.getProject()), tuple1.getScope().intersectWith(tuple2.getResolveScope())); } else if (checkEmptyListAndList(type1, type2)) { return genNewListBy(type2, manager); } else if (checkEmptyListAndList(type2, type1)) { return genNewListBy(type1, manager); } else if (type1 instanceof GrMapType && type2 instanceof GrMapType) { return GrMapType.merge(((GrMapType) type1), ((GrMapType) type2)); } else if (checkEmptyMapAndMap(type1, type2)) { return genNewMapBy(type2, manager); } else if (checkEmptyMapAndMap(type2, type1)) { return genNewMapBy(type1, manager); } else if (type1 instanceof GrClosureType && type2 instanceof GrClosureType) { GrClosureType clType1 = (GrClosureType) type1; GrClosureType clType2 = (GrClosureType) type2; GrSignature signature1 = clType1.getSignature(); GrSignature signature2 = clType2.getSignature(); if (signature1 instanceof GrClosureSignature && signature2 instanceof GrClosureSignature) { if (((GrClosureSignature) signature1).getParameterCount() == ((GrClosureSignature) signature2).getParameterCount()) { final GrClosureSignature signature = GrClosureSignatureImpl.getLeastUpperBound( ((GrClosureSignature) signature1), ((GrClosureSignature) signature2), manager); if (signature != null) { GlobalSearchScope scope = clType1.getResolveScope().intersectWith(clType2.getResolveScope()); final LanguageLevel languageLevel = ComparatorUtil.max(clType1.getLanguageLevel(), clType2.getLanguageLevel()); return GrClosureType.create( signature, scope, JavaPsiFacade.getInstance(manager.getProject()), languageLevel, true); } } } } else if (GroovyCommonClassNames.GROOVY_LANG_GSTRING.equals(type1.getCanonicalText()) && CommonClassNames.JAVA_LANG_STRING.equals(type2.getInternalCanonicalText())) { return type2; } else if (GroovyCommonClassNames.GROOVY_LANG_GSTRING.equals(type2.getCanonicalText()) && CommonClassNames.JAVA_LANG_STRING.equals(type1.getInternalCanonicalText())) { return type1; } final PsiType result = getLeastUpperBoundForNumericType(type1, type2); if (result != null) return result; return GenericsUtil.getLeastUpperBound(type1, type2, manager); } private static boolean checkEmptyListAndList(PsiType type1, PsiType type2) { if (type1 instanceof GrTupleType) { PsiType[] types = ((GrTupleType) type1).getComponentTypes(); if (types.length == 0 && InheritanceUtil.isInheritor(type2, JAVA_UTIL_LIST)) return true; } return false; } private static PsiType genNewListBy(PsiType genericOwner, PsiManager manager) { PsiClass list = JavaPsiFacade.getInstance(manager.getProject()) .findClass(JAVA_UTIL_LIST, genericOwner.getResolveScope()); PsiElementFactory factory = JavaPsiFacade.getElementFactory(manager.getProject()); if (list == null) return factory.createTypeFromText(JAVA_UTIL_LIST, null); return factory.createType(list, PsiUtil.extractIterableTypeParameter(genericOwner, false)); } private static boolean checkEmptyMapAndMap(PsiType type1, PsiType type2) { if (type1 instanceof GrMapType) { PsiType[] types = ((GrMapType) type1).getAllKeyTypes(); if (types.length == 0 && InheritanceUtil.isInheritor(type2, JAVA_UTIL_MAP)) return true; } return false; } private static PsiType genNewMapBy(PsiType genericOwner, PsiManager manager) { PsiClass map = JavaPsiFacade.getInstance(manager.getProject()) .findClass(JAVA_UTIL_MAP, genericOwner.getResolveScope()); PsiElementFactory factory = JavaPsiFacade.getElementFactory(manager.getProject()); if (map == null) return factory.createTypeFromText(JAVA_UTIL_MAP, null); final PsiType key = PsiUtil.substituteTypeParameter(genericOwner, JAVA_UTIL_MAP, 0, false); final PsiType value = PsiUtil.substituteTypeParameter(genericOwner, JAVA_UTIL_MAP, 1, false); return factory.createType(map, key, value); } @Nullable public static PsiType getPsiType(PsiElement context, IElementType elemType) { if (elemType == kNULL) { return PsiType.NULL; } final String typeName = getBoxedTypeName(elemType); if (typeName != null) { return createTypeByFQClassName(typeName, context); } return null; } @Nullable public static String getBoxedTypeName(IElementType elemType) { return ourPrimitiveTypesToClassNames.get(elemType); } @NotNull public static PsiType getLeastUpperBound(PsiClass[] classes, PsiManager manager) { PsiElementFactory factory = JavaPsiFacade.getElementFactory(manager.getProject()); if (classes.length == 0) return factory.createTypeByFQClassName(JAVA_LANG_OBJECT); PsiType type = factory.createType(classes[0]); for (int i = 1; i < classes.length; i++) { PsiType t = getLeastUpperBound(type, factory.createType(classes[i]), manager); if (t != null) { type = t; } } return type; } public static boolean isClassType(@Nullable PsiType type, @NotNull String qName) { if (type instanceof PsiClassType) { final PsiClass psiClass = ((PsiClassType) type).resolve(); return psiClass != null && qName.equals(psiClass.getQualifiedName()); } return false; } public static PsiSubstitutor composeSubstitutors(PsiSubstitutor s1, PsiSubstitutor s2) { final Map<PsiTypeParameter, PsiType> map = s1.getSubstitutionMap(); Map<PsiTypeParameter, PsiType> result = new THashMap<PsiTypeParameter, PsiType>(map.size()); for (PsiTypeParameter parameter : map.keySet()) { result.put(parameter, s2.substitute(map.get(parameter))); } final Map<PsiTypeParameter, PsiType> map2 = s2.getSubstitutionMap(); for (PsiTypeParameter parameter : map2.keySet()) { if (!result.containsKey(parameter)) { result.put(parameter, map2.get(parameter)); } } return PsiSubstitutorImpl.createSubstitutor(result); } @NotNull public static PsiClassType createTypeByFQClassName( @NotNull String fqName, @NotNull PsiElement context) { return GroovyPsiManager.getInstance(context.getProject()) .createTypeByFQClassName(fqName, context.getResolveScope()); } @Nullable public static PsiType createJavaLangClassType( @Nullable PsiType type, Project project, GlobalSearchScope resolveScope) { final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); PsiType result = null; PsiClass javaLangClass = facade.findClass(JAVA_LANG_CLASS, resolveScope); if (javaLangClass != null) { PsiSubstitutor substitutor = PsiSubstitutor.EMPTY; final PsiTypeParameter[] typeParameters = javaLangClass.getTypeParameters(); if (typeParameters.length == 1) { substitutor = substitutor.put(typeParameters[0], type); } result = facade.getElementFactory().createType(javaLangClass, substitutor); } return result; } @NotNull public static PsiPrimitiveType getPrimitiveTypeByText(String typeText) { for (final PsiPrimitiveType primitive : PRIMITIVES) { if (PsiType.VOID.equals(primitive)) { return primitive; } if (primitive.getCanonicalText().equals(typeText)) { return primitive; } } assert false : "Unknown primitive type"; return null; } @NotNull public static PsiClassType createListType(@NotNull PsiClass elements) { JavaPsiFacade facade = JavaPsiFacade.getInstance(elements.getProject()); GlobalSearchScope resolveScope = elements.getResolveScope(); PsiClass listClass = facade.findClass(JAVA_UTIL_LIST, resolveScope); if (listClass == null) { return facade.getElementFactory().createTypeByFQClassName(JAVA_UTIL_LIST, resolveScope); } return facade .getElementFactory() .createType(listClass, facade.getElementFactory().createType(elements)); } @NotNull public static PsiType createSetType(@NotNull PsiElement context, @NotNull PsiType type) { JavaPsiFacade facade = JavaPsiFacade.getInstance(context.getProject()); GlobalSearchScope resolveScope = context.getResolveScope(); PsiClass setClass = facade.findClass(JAVA_UTIL_SET, resolveScope); if (setClass != null && setClass.getTypeParameters().length == 1) { return facade.getElementFactory().createType(setClass, type); } return facade.getElementFactory().createTypeByFQClassName(JAVA_UTIL_SET, resolveScope); } public static boolean isAnnotatedCheckHierarchyWithCache( @NotNull PsiClass aClass, @NotNull String annotationFQN) { Map<String, PsiClass> classMap = getSuperClassesWithCache(aClass); for (PsiClass psiClass : classMap.values()) { PsiModifierList modifierList = psiClass.getModifierList(); if (modifierList != null) { if (modifierList.findAnnotation(annotationFQN) != null) { return true; } } } return false; } public static Map<String, PsiClass> getSuperClassesWithCache(@NotNull PsiClass aClass) { Map<String, PsiClass> superClassNames = PARENT_CACHE_KEY.getCachedValue(aClass); if (superClassNames == null) { Set<PsiClass> superClasses = new THashSet<PsiClass>(); superClasses.add(aClass); InheritanceUtil.getSuperClasses(aClass, superClasses, true); superClassNames = new LinkedHashMap<String, PsiClass>(); for (PsiClass superClass : superClasses) { superClassNames.put(superClass.getQualifiedName(), superClass); } superClassNames = PARENT_CACHE_KEY.putCachedValue(aClass, superClassNames); } return superClassNames; } @Nullable public static PsiType substituteBoxAndNormalizeType( @Nullable PsiType type, @NotNull PsiSubstitutor substitutor, @Nullable SpreadState state, @NotNull GrExpression expression) { if (type == null) return null; GlobalSearchScope resolveScope = expression.getResolveScope(); PsiManager manager = expression.getManager(); type = substitutor.substitute(type); type = boxPrimitiveType(type, manager, resolveScope); if (type == null) return null; type = PsiImplUtil.normalizeWildcardTypeByPosition(type, expression); type = SpreadState.apply(type, state, expression.getProject()); return type; } @Nullable public static PsiType getItemType(@Nullable PsiType containerType) { if (containerType == null) return null; if (containerType instanceof PsiArrayType) return ((PsiArrayType) containerType).getComponentType(); return PsiUtil.extractIterableTypeParameter(containerType, false); } @Nullable public static PsiClassType createSimilarCollection( @Nullable PsiType collection, Project project, PsiType... itemType) { if (InheritanceUtil.isInheritor(collection, "java.util.SortedSet")) { return createCollection(project, "java.util.SortedSet", itemType); } if (InheritanceUtil.isInheritor(collection, "java.util.LinkedHashSet")) { return createCollection(project, "java.util.LinkedHashSet", itemType); } if (InheritanceUtil.isInheritor(collection, CommonClassNames.JAVA_UTIL_SET)) { return createCollection(project, "java.util.HashSet", itemType); } if (InheritanceUtil.isInheritor(collection, "java.util.LinkedList")) { return createCollection(project, "java.util.LInkedList", itemType); } if (InheritanceUtil.isInheritor(collection, "java.util.Stack")) { return createCollection(project, "java.util.Stack", itemType); } if (InheritanceUtil.isInheritor(collection, "java.util.Vector")) { return createCollection(project, "java.util.Vector", itemType); } if (InheritanceUtil.isInheritor(collection, CommonClassNames.JAVA_UTIL_LIST)) { return createCollection(project, "java.util.ArrayList", itemType); } if (InheritanceUtil.isInheritor(collection, "java.util.Queue")) { return createCollection(project, "java.util.LinkedList", itemType); } return createCollection(project, "java.util.ArrayList", itemType); } @Nullable private static PsiClassType createCollection( Project project, String collectionName, PsiType... item) { PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); PsiClass collection = JavaPsiFacade.getInstance(project) .findClass(collectionName, GlobalSearchScope.allScope(project)); if (collection == null) return null; PsiTypeParameter[] parameters = collection.getTypeParameters(); if (parameters.length != 1) return null; return factory.createType(collection, item); } @Nullable public static PsiType inferAnnotationMemberValueType(GrAnnotationMemberValue value) { if (value instanceof GrExpression) { return ((GrExpression) value).getType(); } else if (value instanceof GrAnnotation) { final PsiElement resolved = ((GrAnnotation) value).getClassReference().resolve(); if (resolved instanceof PsiClass) { return JavaPsiFacade.getElementFactory(value.getProject()) .createType((PsiClass) resolved, PsiSubstitutor.EMPTY); } return null; } else if (value instanceof GrAnnotationArrayInitializer) { return getTupleByAnnotationArrayInitializer((GrAnnotationArrayInitializer) value); } return null; } public static PsiType getTupleByAnnotationArrayInitializer(GrAnnotationArrayInitializer value) { final GrAnnotationMemberValue[] initializers = value.getInitializers(); PsiType[] types = ContainerUtil.map( initializers, new Function<GrAnnotationMemberValue, PsiType>() { @Override public PsiType fun(GrAnnotationMemberValue value) { return inferAnnotationMemberValueType(value); } }, new PsiType[initializers.length]); return new GrTupleType( types, JavaPsiFacade.getInstance(value.getProject()), value.getResolveScope()); } public static boolean resolvesTo(PsiType type, String fqn) { if (type instanceof PsiClassType) { final PsiClass resolved = ((PsiClassType) type).resolve(); return resolved != null && fqn.equals(resolved.getQualifiedName()); } return false; } @Nullable public static PsiType rawSecondGeneric(PsiType type, Project project) { if (!(type instanceof PsiClassType)) return null; final PsiClassType.ClassResolveResult result = ((PsiClassType) type).resolveGenerics(); final PsiClass element = result.getElement(); if (element == null) return null; final PsiType[] parameters = ((PsiClassType) type).getParameters(); boolean changed = false; for (int i = 0; i < parameters.length; i++) { PsiType parameter = parameters[i]; if (parameter == null) continue; final Ref<PsiType> newParam = new Ref<PsiType>(); parameter.accept( new PsiTypeVisitorEx<Object>() { @Nullable @Override public Object visitClassType(PsiClassType classType) { if (classType.getParameterCount() > 0) { newParam.set(classType.rawType()); } return null; } @Nullable @Override public Object visitCapturedWildcardType(PsiCapturedWildcardType capturedWildcardType) { newParam.set(capturedWildcardType.getWildcard().getBound()); return null; } @Nullable @Override public Object visitWildcardType(PsiWildcardType wildcardType) { newParam.set(wildcardType.getBound()); return null; } }); if (!newParam.isNull()) { changed = true; parameters[i] = newParam.get(); } } if (!changed) return null; return JavaPsiFacade.getElementFactory(project).createType(element, parameters); } }
/** @author Sergey Evdokimov */ public class SpockUtils { public static final String SPEC_CLASS_NAME = "spock.lang.Specification"; private static final LightCacheKey<Map<String, SpockVariableDescriptor>> KEY = LightCacheKey.create(); private SpockUtils() {} public static Map<String, SpockVariableDescriptor> getVariableMap(@NotNull GrMethod method) { GrMethod originalMethod; PsiFile containingFile = method.getContainingFile(); if (containingFile != containingFile.getOriginalFile()) { int methodOffset = method.getTextOffset(); PsiElement originalPlace = containingFile.getOriginalFile().findElementAt(methodOffset); originalMethod = PsiTreeUtil.getParentOfType(originalPlace, GrMethod.class); assert originalMethod != null : containingFile .getOriginalFile() .getText() .substring( Math.max(0, methodOffset - 50), Math.min(methodOffset + 50, containingFile.getOriginalFile().getText().length())); } else { originalMethod = method; } Map<String, SpockVariableDescriptor> cachedValue = KEY.getCachedValue(originalMethod); if (cachedValue == null) { cachedValue = createVariableMap(originalMethod); cachedValue = KEY.putCachedValue(originalMethod, cachedValue); } return cachedValue; } // See org.spockframework.compiler.WhereBlockRewriter public static Map<String, SpockVariableDescriptor> createVariableMap(GrMethod method) { GrOpenBlock block = method.getBlock(); if (block == null) return Collections.emptyMap(); PsiElement elementUnderLabel = null; PsiElement elementAfterLabel = null; main: for (PsiElement e = block.getFirstChild(); e != null; e = e.getNextSibling()) { if (e instanceof GrLabeledStatement) { GrLabeledStatement l = (GrLabeledStatement) e; elementAfterLabel = l.getNextSibling(); while (true) { GrStatement statement = l.getStatement(); if ("where".equals(l.getName())) { elementUnderLabel = statement; break main; } if (statement instanceof GrLabeledStatement) { l = (GrLabeledStatement) statement; continue; } break; } } } if (elementUnderLabel == null) return Collections.emptyMap(); Map<String, SpockVariableDescriptor> res = new HashMap<>(); PsiElement e = elementUnderLabel; while (e != null) { if (e instanceof GrBinaryExpression && ((GrBinaryExpression) e).getOperationTokenType() == GroovyElementTypes.COMPOSITE_LSHIFT_SIGN) { GrBinaryExpression shift = (GrBinaryExpression) e; GrExpression leftOperand = shift.getLeftOperand(); GrExpression rightOperand = shift.getRightOperand(); if (leftOperand instanceof GrReferenceExpression) { String name = getNameByReference(leftOperand); if (name != null) { SpockVariableDescriptor descriptor = new SpockVariableDescriptor(leftOperand, name); descriptor.addExpressionOfCollection(rightOperand); res.put(name, descriptor); } } else if (leftOperand instanceof GrListOrMap) { GrExpression[] variableDefinitions = ((GrListOrMap) leftOperand).getInitializers(); SpockVariableDescriptor[] variables = createVariables(res, Arrays.asList(variableDefinitions)); if (rightOperand instanceof GrListOrMap) { for (GrExpression expression : ((GrListOrMap) rightOperand).getInitializers()) { if (expression instanceof GrListOrMap) { add(variables, Arrays.asList(((GrListOrMap) expression).getInitializers())); } else { for (SpockVariableDescriptor variable : variables) { if (variable != null) { variable.addExpressionOfCollection(expression); } } } } } } } else if (e instanceof GrAssignmentExpression) { GrAssignmentExpression assExpr = (GrAssignmentExpression) e; GrExpression lValue = assExpr.getLValue(); String name = getNameByReference(lValue); if (name != null) { res.put( name, new SpockVariableDescriptor(lValue, name).addExpression(assExpr.getRValue())); } } else if (isOrStatement(e)) { // See org.spockframework.compiler.WhereBlockRewriter#rewriteTableLikeParameterization() List<GrExpression> variableDefinitions = new ArrayList<>(); splitOr(variableDefinitions, (GrExpression) e); SpockVariableDescriptor[] variables = createVariables(res, variableDefinitions); List<GrExpression> row = new ArrayList<>(); PsiElement rowElement = getNext(e, elementUnderLabel, elementAfterLabel); while (isOrStatement(rowElement)) { row.clear(); splitOr(row, (GrExpression) rowElement); add(variables, row); rowElement = getNext(rowElement, elementUnderLabel, elementAfterLabel); } e = rowElement; continue; } e = getNext(e, elementUnderLabel, elementAfterLabel); } return res; } private static SpockVariableDescriptor[] createVariables( Map<String, SpockVariableDescriptor> map, List<GrExpression> variableDefinitions) { SpockVariableDescriptor[] variables = new SpockVariableDescriptor[variableDefinitions.size()]; for (int i = 0; i < variableDefinitions.size(); i++) { GrExpression expression = variableDefinitions.get(i); String name = getNameByReference(expression); if (name == null) continue; SpockVariableDescriptor variableDescriptor = new SpockVariableDescriptor(expression, name); map.put(name, variableDescriptor); variables[i] = variableDescriptor; } return variables; } private static void add(SpockVariableDescriptor[] variables, List<GrExpression> expressions) { for (int i = 0, end = Math.min(variables.length, expressions.size()); i < end; i++) { if (variables[i] != null) { // variables[i] can be null. variables[i].addExpression(expressions.get(i)); } } } private static boolean isOrStatement(PsiElement element) { if (element instanceof GrBinaryExpression) { IElementType type = ((GrBinaryExpression) element).getOperationTokenType(); return type == GroovyTokenTypes.mBOR || type == GroovyTokenTypes.mLOR; } return false; } @Nullable private static PsiElement getNext( @NotNull PsiElement current, PsiElement elementUnderLabel, PsiElement elementAfterLabel) { PsiElement e = current; do { if (e == elementUnderLabel) { e = elementAfterLabel; } else { e = e.getNextSibling(); } } while (PsiImplUtil.isLeafElementOfType(e, TokenSets.WHITE_SPACES_OR_COMMENTS)); if (e instanceof GrLabeledStatement) return null; return e; } @Nullable public static String getNameByReference(@Nullable PsiElement expression) { if (!(expression instanceof GrReferenceExpression)) return null; PsiElement firstChild = expression.getFirstChild(); if (firstChild != expression.getLastChild() || !PsiImplUtil.isLeafElementOfType(firstChild, GroovyTokenTypes.mIDENT)) return null; GrReferenceExpression ref = (GrReferenceExpression) expression; if (ref.isQualified()) return null; return ref.getReferenceName(); } // See org.spockframework.compiler.WhereBlockRewriter#splitRow() private static void splitOr(List<GrExpression> res, GrExpression element) { if (isOrStatement(element)) { GrBinaryExpression be = (GrBinaryExpression) element; splitOr(res, be.getLeftOperand()); splitOr(res, be.getRightOperand()); } else { res.add(element); } } }