/* Soot - a J*va Optimization Framework
 * Copyright (C) 2007 Manu Sridharan
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
package soot.jimple.spark.ondemand;

import java.util.HashSet;
import java.util.Set;


import soot.RefType;
import soot.Scene;
import soot.SootField;
import soot.jimple.spark.internal.TypeManager;
import soot.jimple.spark.ondemand.genericutil.Util;
import soot.jimple.spark.ondemand.pautil.SootUtil;
import soot.jimple.spark.pag.ArrayElement;
import soot.jimple.spark.pag.SparkField;

public class InnerTypesIncrementalHeuristic implements FieldCheckHeuristic {

    private final TypeManager manager;

    private final Set<RefType> typesToCheck = new HashSet<RefType>();

    private String newTypeOnQuery = null;

    private final Set<RefType> bothEndsTypes = new HashSet<RefType>();

    private final Set<RefType> notBothEndsTypes = new HashSet<RefType>();

    private int numPasses = 0;

    private final int passesInDirection;

    private boolean allNotBothEnds = false;

    public InnerTypesIncrementalHeuristic(TypeManager manager, int maxPasses) {
        this.manager = manager;
        this.passesInDirection = maxPasses / 2;
    }

    public boolean runNewPass() {
        numPasses++;
        if (numPasses == passesInDirection) {
            return switchToNotBothEnds();
        } else {
            if (newTypeOnQuery != null) {
                String topLevelTypeStr = Util.topLevelTypeString(newTypeOnQuery);
                RefType refType = Scene.v().getRefType(topLevelTypeStr);
                boolean added = typesToCheck.add(refType);
                newTypeOnQuery = null;
                return added;
            } else {
                return switchToNotBothEnds();
            }
        }
    }

    private boolean switchToNotBothEnds() {
        if (!allNotBothEnds) {
            numPasses = 0;
            allNotBothEnds = true;
            newTypeOnQuery = null;
            typesToCheck.clear();
            return true;
        } else {
            return false;
        }
    }

    public boolean validateMatchesForField(SparkField field) {
        if (field instanceof ArrayElement) {
            return true;
        }
        SootField sootField = (SootField) field;
        RefType declaringType = sootField.getDeclaringClass().getType();
        String declaringTypeStr = declaringType.toString();
        String topLevel = Util.topLevelTypeString(declaringTypeStr);
        RefType refType = Scene.v().getRefType(topLevel);
        for (RefType checkedType : typesToCheck) {
            if (manager.castNeverFails(checkedType, refType)) {
                // System.err.println("validate " + declaringTypeStr);
                return true;
            }
        }
        if (newTypeOnQuery == null) {
            newTypeOnQuery = declaringTypeStr;
        }
        return false;
    }

    public boolean validFromBothEnds(SparkField field) {
        if (allNotBothEnds) {
            return false;
        }
        if (field instanceof ArrayElement) {
            return true;
        }
        SootField sootField = (SootField) field;
        RefType declaringType = sootField.getDeclaringClass().getType();
        if (bothEndsTypes.contains(declaringType)) {
            return true;
        } else if (notBothEndsTypes.contains(declaringType)) {
            return false;
        } else {
            if (SootUtil.hasRecursiveField(declaringType.getSootClass())) {
                notBothEndsTypes.add(declaringType);
                return false;
            } else {
                bothEndsTypes.add(declaringType);
                return true;
            }
        }
    }

    @Override
    public String toString() {
        return typesToCheck.toString();
    }
}

@@ -3162,42 +3162,160 @@
 Type
- = Scene.v().getRefType(topLevel);
+;%0A        if(Scene.v().containsType(topLevel)) %7B%0A            refType = Scene.v().getRefType(topLevel);%0A        %7D else %7B%0A        %09refType=null;%0A        %7D
 %0A   

fix to heuristic (adapt to new checks in Soot API)