| /* |
| * 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 + |
| '}'; |
| } |
| } |