blob: 73a315218beba939077ae8a76056e1ae21e2a457 [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.dataFlow;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.PsiIntersectionType;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiType;
import com.intellij.psi.util.TypeConversionUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.NegatingGotoInstruction;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.ConditionInstruction;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
/**
* @author Max Medvedev
*/
public class DFAType {
private static class Mixin {
private final int ID;
private final PsiType myType;
private final ConditionInstruction myCondition;
private final boolean myNegated;
private Mixin(PsiType type, ConditionInstruction condition, boolean negated) {
this(-1, type, condition, negated);
}
private Mixin(int ID, PsiType type, ConditionInstruction condition, boolean negated) {
if (ID == -1) ID = hashCode();
this.ID = ID;
myType = type;
myCondition = condition;
myNegated = negated;
}
Mixin negate() {
return new Mixin(ID, myType, myCondition, !myNegated);
}
@Override
public String toString() {
return "Mixin{" +
"ID=" + ID +
", myType=" + myType +
", myCondition=" + myCondition +
", myNegated=" + myNegated +
'}';
}
}
private final PsiType primary;
private final List<Mixin> mixins = new ArrayList<Mixin>();
private DFAType(@Nullable PsiType primary) {
this.primary = primary;
}
public void addMixin(@Nullable PsiType mixin, ConditionInstruction instruction) {
if (mixin == null) {
return;
}
mixins.add(new Mixin(mixin, instruction, false));
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (!(obj instanceof DFAType)) return false;
final DFAType other = (DFAType)obj;
if (!eq(primary, other.primary)) return false;
if (mixins.size() != other.mixins.size()) return false;
for (Mixin mixin1 : mixins) {
boolean contains = false;
for (Mixin mixin2 : other.mixins) {
if (mixin1.ID == mixin2.ID) {
contains = mixin1.myNegated == mixin2.myNegated;
break;
}
}
if (!contains) return false;
}
return true;
}
public DFAType negate(@NotNull Instruction instruction) {
final DFAType type = new DFAType(primary);
for (Mixin mixin : mixins) {
type.mixins.add(mixin);
}
for (NegatingGotoInstruction negation: instruction.getNegatingGotoInstruction()) {
final Set<ConditionInstruction> conditionsToNegate = negation.getCondition().getDependentConditions();
for (ListIterator<Mixin> iterator = type.mixins.listIterator(); iterator.hasNext(); ) {
Mixin mixin = iterator.next();
if (conditionsToNegate.contains(mixin.myCondition)) {
iterator.set(mixin.negate());
}
}
}
return type;
}
@Nullable
public PsiType getResultType() {
if (mixins.isEmpty()) return primary;
List<PsiType> types = new ArrayList<PsiType>();
if (primary != null) {
types.add(primary);
}
for (Mixin mixin : mixins) {
if (!mixin.myNegated) {
types.add(mixin.myType);
}
}
if (types.isEmpty()) return null;
return PsiIntersectionType.createIntersection(types.toArray(PsiType.createArray(types.size())));
}
public static DFAType create(@Nullable PsiType type) {
return new DFAType(type);
}
private static boolean eq(PsiType t1, PsiType t2) {
return t1 == t2 || Comparing.equal(TypeConversionUtil.erasure(t1), TypeConversionUtil.erasure(t2));
}
@Nullable
public static DFAType create(DFAType t1, DFAType t2, PsiManager manager) {
if (t1.equals(t2)) return t1;
final PsiType primary = TypesUtil.getLeastUpperBoundNullable(t1.primary, t2.primary, manager);
final DFAType type = new DFAType(primary);
for (Mixin mixin1 : t1.mixins) {
for (Mixin mixin2 : t2.mixins) {
if (mixin1.ID == mixin2.ID && mixin1.myNegated == mixin2.myNegated) {
type.mixins.add(mixin1);
}
}
}
return type;
}
@Override
public String toString() {
return "DFAType{" +
"primary=" + primary +
", mixins=" + mixins +
'}';
}
}