blob: a8bf629573326b5d6bf84376c152029ce5e631e9 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* 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.jetbrains.plugins.groovy.lang.psi.impl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.VolatileNotNullLazyValue;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrSafeCastExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrImplementsClause;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrReferenceList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeParameter;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeParameterList;
import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Created by Max Medvedev on 20/05/14
*/
public class GrTraitType extends PsiClassType {
private static final Logger LOG = Logger.getInstance(GrTraitType.class);
private final GrExpression myOriginal;
private final PsiClassType myExprType;
private final List<PsiClassType> myTraitTypes;
private final GlobalSearchScope myResolveScope;
private final VolatileNotNullLazyValue<PsiType[]> myParameters = new VolatileNotNullLazyValue<PsiType[]>() {
@NotNull
@Override
protected PsiType[] compute() {
List<PsiType> result = ContainerUtil.newArrayList();
ContainerUtil.addAll(result, myExprType.getParameters());
for (PsiClassType type : myTraitTypes) {
ContainerUtil.addAll(result, type.getParameters());
}
return result.toArray(new PsiType[result.size()]);
}
};
public GrTraitType(@NotNull GrExpression original,
@NotNull PsiClassType exprType,
@NotNull List<PsiClassType> traitTypes,
@NotNull GlobalSearchScope resolveScope, LanguageLevel languageLevel) {
super(languageLevel);
myOriginal = original;
myResolveScope = resolveScope;
myExprType = exprType;
myTraitTypes = ContainerUtil.newArrayList(traitTypes);
}
@NotNull
@Override
public String getPresentableText() {
return myExprType.getPresentableText() + " as " + StringUtil.join(ContainerUtil.map(myTraitTypes, new Function<PsiClassType, String>() {
@Override
public String fun(PsiClassType type) {
return type.getPresentableText();
}
}), ", ");
}
@NotNull
@Override
public String getCanonicalText() {
return myExprType.getCanonicalText();
}
@NotNull
@Override
public String getInternalCanonicalText() {
return myExprType.getCanonicalText() + " as " + StringUtil.join(ContainerUtil.map(myTraitTypes, new Function<PsiClassType, String>() {
@Override
public String fun(PsiClassType type) {
return type.getCanonicalText();
}
}), ", ");
}
@Override
public boolean isValid() {
return myExprType.isValid() && ContainerUtil.find(myTraitTypes, new Condition<PsiClassType>() {
@Override
public boolean value(PsiClassType type) {
return !type.isValid();
}
}) == null;
}
@Override
public boolean equalsToText(@NotNull @NonNls String text) {
return false;
}
@NotNull
@Override
public LanguageLevel getLanguageLevel() {
return myLanguageLevel;
}
@NotNull
@Override
public PsiClassType setLanguageLevel(@NotNull LanguageLevel languageLevel) {
return new GrTraitType(myOriginal, myExprType, myTraitTypes, myResolveScope, languageLevel);
}
@Nullable
@Override
public PsiClass resolve() {
return getMockTypeDefinition();
}
@Override
public String getClassName() {
return null;
}
@NotNull
@Override
public PsiType[] getParameters() {
return myParameters.getValue();
}
@NotNull
@Override
public PsiType[] getSuperTypes() {
PsiType[] result = new PsiType[myTraitTypes.size() + 1];
result[0] = myExprType;
ArrayUtil.copy(myTraitTypes, result, 1);
return result;
}
@NotNull
@Override
public ClassResolveResult resolveGenerics() {
return CachedValuesManager.getCachedValue(myOriginal, new CachedValueProvider<ClassResolveResult>() {
@Nullable
@Override
public Result<ClassResolveResult> compute() {
final GrTypeDefinition definition = new MockTypeBuilder().buildMockTypeDefinition();
final PsiSubstitutor substitutor = new SubstitutorBuilder(definition).buildSubstitutor();
return Result.<ClassResolveResult>create(new TraitResolveResult(definition, substitutor), PsiModificationTracker.MODIFICATION_COUNT);
}
});
}
@NotNull
@Override
public PsiClassType rawType() {
return new GrTraitType(myOriginal, myExprType.rawType(), ContainerUtil.map(myTraitTypes, new Function<PsiClassType, PsiClassType>() {
@Override
public PsiClassType fun(PsiClassType type) {
return type.rawType();
}
}), myResolveScope, myLanguageLevel);
}
@NotNull
@Override
public GlobalSearchScope getResolveScope() {
return myResolveScope;
}
@Nullable
public GrTypeDefinition getMockTypeDefinition() {
return CachedValuesManager.getCachedValue(myOriginal, new CachedValueProvider<GrTypeDefinition>() {
@Nullable
@Override
public Result<GrTypeDefinition> compute() {
return Result.create(new MockTypeBuilder().buildMockTypeDefinition(), PsiModificationTracker.MODIFICATION_COUNT);
}
});
}
public PsiClassType getExprType() {
return myExprType;
}
public List<PsiClassType> getTraitTypes() {
return Collections.unmodifiableList(myTraitTypes);
}
public GrTraitType erasure() {
PsiClassType exprType = (PsiClassType)TypeConversionUtil.erasure(myExprType);
List<PsiClassType> traitTypes = ContainerUtil.map(myTraitTypes, new Function<PsiClassType, PsiClassType>() {
@Override
public PsiClassType fun(PsiClassType type) {
return (PsiClassType)TypeConversionUtil.erasure(type);
}
});
return new GrTraitType(myOriginal, exprType, traitTypes, myResolveScope, LanguageLevel.JDK_1_5);
}
@Nullable
public static GrTraitType createTraitClassType(@NotNull GrSafeCastExpression safeCastExpression) {
GrExpression operand = safeCastExpression.getOperand();
PsiType exprType = operand.getType();
if (!(exprType instanceof PsiClassType)) return null;
GrTypeElement typeElement = safeCastExpression.getCastTypeElement();
if (typeElement == null) return null;
PsiType type = typeElement.getType();
if (!GrTraitUtil.isTrait(PsiTypesUtil.getPsiClass(type))) return null;
return new GrTraitType(safeCastExpression, ((PsiClassType)exprType), Collections.singletonList((PsiClassType)type), safeCastExpression.getResolveScope(),
LanguageLevel.JDK_1_5);
}
@NotNull
public static GrTraitType createTraitClassType(@NotNull GrExpression context,
@NotNull PsiClassType exprType,
@NotNull List<PsiClassType> traitTypes,
@NotNull GlobalSearchScope resolveScope) {
return new GrTraitType(context, exprType, traitTypes, resolveScope, LanguageLevel.JDK_1_5);
}
private static class TraitResolveResult implements ClassResolveResult {
private final GrTypeDefinition myDefinition;
private final PsiSubstitutor mySubstitutor;
public TraitResolveResult(GrTypeDefinition definition, PsiSubstitutor substitutor) {
myDefinition = definition;
mySubstitutor = substitutor;
}
@Override
public GrTypeDefinition getElement() {
return myDefinition;
}
@NotNull
@Override
public PsiSubstitutor getSubstitutor() {
return mySubstitutor;
}
@Override
public boolean isPackagePrefixPackageReference() {
return false;
}
@Override
public boolean isAccessible() {
return true;
}
@Override
public boolean isStaticsScopeCorrect() {
return true;
}
@Override
public PsiElement getCurrentFileResolveScope() {
return null;
}
@Override
public boolean isValidResult() {
return true;
}
}
private class MockTypeBuilder {
GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(myOriginal.getProject());
@Nullable
public GrTypeDefinition buildMockTypeDefinition() {
try {
StringBuilder buffer = new StringBuilder("class _____________Temp___________ ");
prepareGenerics(buffer);
buffer.append(" extends Super implements Trait {}");
GrTypeDefinition definition = factory.createTypeDefinition(buffer.toString());
replaceReferenceWith(definition.getExtendsClause(), myExprType);
addReferencesWith(definition.getImplementsClause(), myTraitTypes, myExprType.getParameterCount());
return definition;
}
catch (IncorrectOperationException e) {
return null;
}
}
private void prepareGenerics(StringBuilder buffer) {
int count = myExprType.getParameterCount();
for (PsiClassType trait : myTraitTypes) {
count += trait.getParameterCount();
}
if (count == 0) return;
buffer.append('<');
for (int i = 0; i < count; i++) {
buffer.append("T").append(i).append(",");
}
buffer.replace(buffer.length() - 1, buffer.length(), ">");
}
private void addReferencesWith(@Nullable GrImplementsClause clause, @NotNull List<PsiClassType> traitTypes, int parameterOffset) {
LOG.assertTrue(clause != null);
clause.getReferenceElementsGroovy()[0].delete();
for (PsiClassType type : traitTypes) {
processType(clause, type, parameterOffset);
parameterOffset += type.getParameterCount();
}
}
private void replaceReferenceWith(@Nullable GrReferenceList clause, @NotNull PsiClassType type) {
LOG.assertTrue(clause != null);
clause.getReferenceElementsGroovy()[0].delete();
processType(clause, type, 0);
}
private void processType(@NotNull GrReferenceList clause, @NotNull PsiClassType type, int parameterOffset) {
PsiClass resolved = type.resolve();
if (resolved != null) {
String qname = resolved.getQualifiedName();
StringBuilder buffer = new StringBuilder();
buffer.append(qname);
int parameterCount = type.getParameterCount();
if (parameterCount > 0) {
buffer.append('<');
for (int i = 0; i < parameterCount; i++) {
buffer.append("T").append(parameterOffset + i).append(',');
}
buffer.replace(buffer.length() - 1, buffer.length(), ">");
}
GrCodeReferenceElement ref = factory.createCodeReferenceElementFromText(buffer.toString());
clause.add(ref);
}
}
}
private class SubstitutorBuilder {
private final GrTypeParameter[] myParameters;
private int myOffset = 0;
public SubstitutorBuilder(@NotNull GrTypeDefinition definition) {
GrTypeParameterList typeParameterList = definition.getTypeParameterList();
myParameters = typeParameterList != null ? typeParameterList.getTypeParameters() : GrTypeParameter.EMPTY_ARRAY;
}
@NotNull
public PsiSubstitutor buildSubstitutor() {
if (myParameters.length == 0) return PsiSubstitutor.EMPTY;
Map<PsiTypeParameter, PsiType> map = ContainerUtil.newLinkedHashMap();
putMappingAndReturnOffset(map, myExprType);
for (PsiClassType type : myTraitTypes) {
putMappingAndReturnOffset(map, type);
}
return JavaPsiFacade.getElementFactory(myOriginal.getProject()).createSubstitutor(map);
}
private void putMappingAndReturnOffset(@NotNull Map<PsiTypeParameter, PsiType> map, @NotNull PsiClassType type) {
PsiType[] args = type.getParameters();
for (int i = 0; i < args.length; i++) {
map.put(myParameters[myOffset + i], args[i]);
}
myOffset += args.length;
}
}
}