
package soot.jimple.toolkits.infoflow;

/*-
 * #%L
 * Soot - a J*va Optimization Framework
 * %%
 * Copyright (C) 1997 - 2018 Raja Vallée-Rai and others
 * %%
 * This program 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 program 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 General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
 * #L%
 */

import java.util.ArrayList;
import java.util.List;

import soot.EquivalentValue;
import soot.RefLikeType;
import soot.jimple.InstanceFieldRef;
import soot.jimple.ParameterRef;
import soot.jimple.Ref;
import soot.jimple.StaticFieldRef;
import soot.jimple.ThisRef;

/**
 * CallLocalityContext written by Richard L. Halpert 2007-03-05 Acts as a container for the locality information collected
 * about a call site by one of the Local Objects Analyses.
 */

public class CallLocalityContext {
  List<EquivalentValue> nodes;
  List<Boolean> isNodeLocal;

  public CallLocalityContext(List<EquivalentValue> nodes) {
    this.nodes = new ArrayList<EquivalentValue>();
    this.nodes.addAll(nodes);

    isNodeLocal = new ArrayList<Boolean>(nodes.size());
    for (int i = 0; i < nodes.size(); i++) {
      isNodeLocal.add(i, Boolean.FALSE);
    }
  }

  public void setFieldLocal(EquivalentValue fieldRef) {
    for (int i = 0; i < nodes.size(); i++) {
      if (fieldRef.equals(nodes.get(i))) {
        isNodeLocal.remove(i);
        isNodeLocal.add(i, Boolean.TRUE);
        return;
      }
    }
    nodes.add(fieldRef);
    isNodeLocal.add(Boolean.TRUE);
    // throw new RuntimeException("Field " + fieldRef + " is not present in CallLocalityContext\n" + toString());
    // return false;
  }

  public void setFieldShared(EquivalentValue fieldRef) {
    for (int i = 0; i < nodes.size(); i++) {
      if (fieldRef.equals(nodes.get(i))) {
        isNodeLocal.remove(i);
        isNodeLocal.add(i, Boolean.FALSE);
        return;
      }
    }
    nodes.add(fieldRef);
    isNodeLocal.add(Boolean.FALSE);
    // throw new RuntimeException("Field " + fieldRef + " is not present in CallLocalityContext\n" + toString());
    // return false;
  }

  public void setAllFieldsLocal() {
    for (int i = 0; i < nodes.size(); i++) {
      Ref r = (Ref) ((EquivalentValue) nodes.get(i)).getValue();
      if (r instanceof InstanceFieldRef) {
        isNodeLocal.remove(i);
        isNodeLocal.add(i, Boolean.TRUE);
      }
    }
  }

  public void setAllFieldsShared() {
    for (int i = 0; i < nodes.size(); i++) {
      Ref r = (Ref) ((EquivalentValue) nodes.get(i)).getValue();
      if (r instanceof InstanceFieldRef) {
        isNodeLocal.remove(i);
        isNodeLocal.add(i, Boolean.FALSE);
      }
    }
  }

  public void setParamLocal(int index) {
    for (int i = 0; i < nodes.size(); i++) {
      Ref r = (Ref) ((EquivalentValue) nodes.get(i)).getValue();
      if (r instanceof ParameterRef) {
        ParameterRef pr = (ParameterRef) r;
        if (pr.getIndex() == index) {
          isNodeLocal.remove(i);
          isNodeLocal.add(i, Boolean.TRUE);
        }
      }
    }
  }

  public void setParamShared(int index) {
    for (int i = 0; i < nodes.size(); i++) {
      Ref r = (Ref) ((EquivalentValue) nodes.get(i)).getValue();
      if (r instanceof ParameterRef) {
        ParameterRef pr = (ParameterRef) r;
        if (pr.getIndex() == index) {
          isNodeLocal.remove(i);
          isNodeLocal.add(i, Boolean.FALSE);
        }
      }
    }
  }

  public void setAllParamsLocal() {
    for (int i = 0; i < nodes.size(); i++) {
      Ref r = (Ref) ((EquivalentValue) nodes.get(i)).getValue();
      if (r instanceof ParameterRef) {
        ParameterRef pr = (ParameterRef) r;
        if (pr.getIndex() != -1) {
          isNodeLocal.remove(i);
          isNodeLocal.add(i, Boolean.TRUE);
        }
      }
    }
  }

  public void setAllParamsShared() {
    for (int i = 0; i < nodes.size(); i++) {
      Ref r = (Ref) ((EquivalentValue) nodes.get(i)).getValue();
      if (r instanceof ParameterRef) {
        ParameterRef pr = (ParameterRef) r;
        if (pr.getIndex() != -1) {
          isNodeLocal.remove(i);
          isNodeLocal.add(i, Boolean.FALSE);
        }
      }
    }
  }

  public void setThisLocal() {
    for (int i = 0; i < nodes.size(); i++) {
      Ref r = (Ref) ((EquivalentValue) nodes.get(i)).getValue();
      if (r instanceof ThisRef) {
        isNodeLocal.remove(i);
        isNodeLocal.add(i, Boolean.TRUE);
      }
    }
  }

  public void setThisShared() {
    for (int i = 0; i < nodes.size(); i++) {
      Ref r = (Ref) ((EquivalentValue) nodes.get(i)).getValue();
      if (r instanceof ThisRef) {
        isNodeLocal.remove(i);
        isNodeLocal.add(i, Boolean.FALSE);
      }
    }
  }

  public void setReturnLocal() {
    setParamLocal(-1);
  }

  public void setReturnShared() {
    setParamShared(-1);
  }

  public List<Object> getLocalRefs() {
    List<Object> ret = new ArrayList<Object>();
    for (int i = 0; i < nodes.size(); i++) {
      if (isNodeLocal.get(i).booleanValue()) {
        ret.add(nodes.get(i));
      }
    }
    return ret;
  }

  public List<Object> getSharedRefs() {
    List<Object> ret = new ArrayList<Object>();
    for (int i = 0; i < nodes.size(); i++) {
      if (!isNodeLocal.get(i).booleanValue()) {
        ret.add(nodes.get(i));
      }
    }
    return ret;
  }

  public boolean isFieldLocal(EquivalentValue fieldRef) {
    for (int i = 0; i < nodes.size(); i++) {
      if (fieldRef.equals(nodes.get(i))) {
        return isNodeLocal.get(i).booleanValue();
      }
    }
    return false; // catches static fields that were not included in the original context
    // throw new RuntimeException("Field " + fieldRef + " is not present in CallLocalityContext\n" + toString());
    // return false;
  }

  public boolean containsField(EquivalentValue fieldRef) {
    for (int i = 0; i < nodes.size(); i++) {
      if (fieldRef.equals(nodes.get(i))) {
        return true;
      }
    }
    return false;
  }

  // merges two contexts into one... shared fields in either context are shared in the merged context
  // return true if merge causes this context to change
  public boolean merge(CallLocalityContext other) {
    boolean isChanged = false;
    if (other.nodes.size() > nodes.size()) {
      isChanged = true;
      for (int i = nodes.size(); i < other.nodes.size(); i++) {
        nodes.add(other.nodes.get(i));
        isNodeLocal.add(other.isNodeLocal.get(i));
      }
    }
    for (int i = 0; i < other.nodes.size(); i++) {
      Boolean temp = new Boolean(isNodeLocal.get(i).booleanValue() && other.isNodeLocal.get(i).booleanValue());
      if (!temp.equals(isNodeLocal.get(i))) {
        isChanged = true;
      }
      isNodeLocal.remove(i);
      isNodeLocal.add(i, temp);
    }
    return isChanged;
  }

  public boolean equals(Object o) {
    if (o instanceof CallLocalityContext) {
      CallLocalityContext other = (CallLocalityContext) o;
      return isNodeLocal.equals(other.isNodeLocal);// && nodes.equals(other.nodes);
    }
    return false;
  }

  public int hashCode() {
    return isNodeLocal.hashCode();
  }

  public boolean isAllShared(boolean refsOnly) {
    for (int i = 0; i < nodes.size(); i++) {
      if ((!refsOnly) && isNodeLocal.get(i).booleanValue()) {
        return false;
      } else if (((EquivalentValue) nodes.get(i)).getValue().getType() instanceof RefLikeType
          && isNodeLocal.get(i).booleanValue()) {
        return false;
      }
    }
    return true;
  }

  public String toString() {
    String fieldrefs = "";
    String staticrefs = "";
    String paramrefs = ""; // includes returnref
    String thisref = "";
    if (nodes.size() == 0) {
      return "Call Locality Context: NO NODES\n";
    }
    for (int i = 0; i < nodes.size(); i++) {
      Ref r = (Ref) ((EquivalentValue) nodes.get(i)).getValue();
      if (r instanceof InstanceFieldRef) {
        fieldrefs = fieldrefs + r + ": " + (isNodeLocal.get(i).booleanValue() ? "local" : "shared") + "\n";
      } else if (r instanceof StaticFieldRef) {
        staticrefs = staticrefs + r + ": " + (isNodeLocal.get(i).booleanValue() ? "local" : "shared") + "\n";
      } else if (r instanceof ParameterRef) {
        paramrefs = paramrefs + r + ": " + (isNodeLocal.get(i).booleanValue() ? "local" : "shared") + "\n";
      } else if (r instanceof ThisRef) {
        thisref = thisref + r + ": " + (isNodeLocal.get(i).booleanValue() ? "local" : "shared") + "\n";
      } else {
        return "Call Locality Context: HAS STRANGE NODE " + r + "\n";
      }
    }
    return "Call Locality Context: \n" + fieldrefs + paramrefs + thisref + staticrefs;
  }

  public String toShortString() {
    String ret = "[";
    for (int i = 0; i < nodes.size(); i++) {
      ret = ret + (isNodeLocal.get(i).booleanValue() ? "L" : "S");
    }
    return ret + "]";
  }
}
