blob: d1a1d7299d15a99a9164ad83f477e9a8daac4d3d [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 com.intellij.psi;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* Intersection types arise in a process of computing least upper bound.
*
* @author ven
*/
public class PsiIntersectionType extends PsiType.Stub {
private final PsiType[] myConjuncts;
private PsiIntersectionType(@NotNull PsiType[] conjuncts) {
super(PsiAnnotation.EMPTY_ARRAY);
myConjuncts = conjuncts;
}
@NotNull
public static PsiType createIntersection(@NotNull List<PsiType> conjuncts) {
return createIntersection(conjuncts.toArray(createArray(conjuncts.size())));
}
@NotNull
public static PsiType createIntersection(PsiType... conjuncts) {
assert conjuncts.length > 0;
conjuncts = flattenAndRemoveDuplicates(conjuncts);
if (conjuncts.length == 1) return conjuncts[0];
return new PsiIntersectionType(conjuncts);
}
private static PsiType[] flattenAndRemoveDuplicates(PsiType[] conjuncts) {
try {
Set<PsiType> flattened = flatten(conjuncts, ContainerUtil.<PsiType>newLinkedHashSet());
return flattened.toArray(createArray(flattened.size()));
}
catch (NoSuchElementException e) {
throw new RuntimeException(Arrays.toString(conjuncts), e);
}
}
private static Set<PsiType> flatten(PsiType[] conjuncts, Set<PsiType> types) {
for (PsiType conjunct : conjuncts) {
if (conjunct instanceof PsiIntersectionType) {
PsiIntersectionType type = (PsiIntersectionType)conjunct;
flatten(type.getConjuncts(), types);
}
else {
types.add(conjunct);
}
}
if (types.size() > 1) {
PsiType[] array = types.toArray(createArray(types.size()));
for (Iterator<PsiType> iterator = types.iterator(); iterator.hasNext(); ) {
PsiType type = iterator.next();
for (PsiType existing : array) {
if (type != existing) {
final boolean allowUncheckedConversion = type instanceof PsiClassType && ((PsiClassType)type).isRaw();
if (TypeConversionUtil.isAssignable(GenericsUtil.eliminateWildcards(type),
GenericsUtil.eliminateWildcards(existing), allowUncheckedConversion)) {
iterator.remove();
break;
}
}
}
}
if (types.isEmpty()) {
types.add(array[0]);
}
}
return types;
}
@NotNull
public PsiType[] getConjuncts() {
return myConjuncts;
}
@NotNull
@Override
public String getPresentableText() {
return StringUtil.join(myConjuncts, new Function<PsiType, String>() {
@Override
public String fun(PsiType psiType) {
return psiType.getPresentableText();
}
}, " & ");
}
@NotNull
@Override
public String getCanonicalText(boolean annotated) {
return myConjuncts[0].getCanonicalText(annotated);
}
@NotNull
@Override
public String getInternalCanonicalText() {
return StringUtil.join(myConjuncts, new Function<PsiType, String>() {
@Override
public String fun(PsiType psiType) {
return psiType.getInternalCanonicalText();
}
}, " & ");
}
@Override
public boolean isValid() {
for (PsiType conjunct : myConjuncts) {
if (!conjunct.isValid()) return false;
}
return true;
}
@Override
public boolean equalsToText(@NotNull String text) {
return false;
}
@Override
public <A> A accept(@NotNull PsiTypeVisitor<A> visitor) {
return visitor.visitIntersectionType(this);
}
@Override
public GlobalSearchScope getResolveScope() {
return myConjuncts[0].getResolveScope();
}
@Override
@NotNull
public PsiType[] getSuperTypes() {
return myConjuncts;
}
public PsiType getRepresentative() {
return myConjuncts[0];
}
public boolean equals(final Object obj) {
if (this == obj) return true;
if (!(obj instanceof PsiIntersectionType)) return false;
final PsiType[] first = getConjuncts();
final PsiType[] second = ((PsiIntersectionType)obj).getConjuncts();
if (first.length != second.length) return false;
//positional equality
for (int i = 0; i < first.length; i++) {
if (!first[i].equals(second[i])) return false;
}
return true;
}
public int hashCode() {
return myConjuncts[0].hashCode();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("PsiIntersectionType: ");
for (int i = 0; i < myConjuncts.length; i++) {
if (i > 0) sb.append(", ");
sb.append(myConjuncts[i].getPresentableText());
}
return sb.toString();
}
}