class Filter implements SootMethodFilter {

    @Override
    public boolean want(SootMethod method) {
      // could be optimized with HashSet....
      String c = method.getDeclaringClass().toString();
      String m = method.getName();
      for (String[] element : PurityInterproceduralAnalysis.pureMethods) {
        if (m.equals(element[1]) && c.startsWith(element[0])) {
          return false;
        }
      }
      for (String[] element : PurityInterproceduralAnalysis.impureMethods) {
        if (m.equals(element[1]) && c.startsWith(element[0])) {
          return false;
        }
      }
      for (String[] element : PurityInterproceduralAnalysis.alterMethods) {
        if (m.equals(element[1]) && c.startsWith(element[0])) {
          return false;
        }
      }
      return true;
    }
  }

  /**
   * The constructor does it all!
   */
  PurityInterproceduralAnalysis(CallGraph cg, Iterator<SootMethod> heads, PurityOptions opts) {
    super(cg, new Filter(), heads, opts.dump_cg());

    if (opts.dump_cg()) {
      logger.debug("[AM] Dumping empty .dot call-graph");
      drawAsOneDot("EmptyCallGraph");
    }

    {
      Date start = new Date();
      logger.debug("[AM] Analysis began");
      doAnalysis(opts.verbose());
      logger.debug("[AM] Analysis finished");
      Date finish = new Date();
      long runtime = finish.getTime() - start.getTime();
      logger.debug("[AM] run time: " + runtime / 1000. + " s");
    }

    if (opts.dump_cg()) {
      logger.debug("[AM] Dumping annotated .dot call-graph");
      drawAsOneDot("CallGraph");
    }

    if (opts.dump_summaries()) {
      logger.debug("[AM] Dumping .dot summaries of analysed methods");
      drawAsManyDot("Summary_", false);
    }

    if (opts.dump_intra()) {
      logger.debug("[AM] Dumping .dot full intra-procedural method analyses");
      // relaunch the interprocedural analysis once on each method
      // to get a purity graph at each statement, not only summaries
      for (Iterator<SootMethod> it = getAnalysedMethods(); it.hasNext();) {
        SootMethod method = it.next();
        if (opts.verbose()) {
          logger.debug("  |- " + method);
        }
        ExceptionalUnitGraph graph = ExceptionalUnitGraphFactory.createExceptionalUnitGraph(method.retrieveActiveBody());
        PurityIntraproceduralAnalysis r = new PurityIntraproceduralAnalysis(graph, this);
        r.drawAsOneDot("Intra_", method.toString());
        r.copyResult(new PurityGraphBox());
      }
    }

    {
      logger.debug("[AM] Annotate methods. ");
      for (Iterator<SootMethod> it = getAnalysedMethods(); it.hasNext();) {
        SootMethod m = it.next();
        PurityGraphBox b = getSummaryFor(m);

        // purity
        boolean isPure = m.toString().contains("<init>") ? b.g.isPureConstructor() : b.g.isPure();

        /*
         * m.addTag(new GenericAttribute("isPure", (new String(isPure?"yes":"no")).getBytes()));
         */
        m.addTag(new StringTag("purity: " + (isPure ? "pure" : "impure")));
        if (isPure && opts.annotate()) {
          m.addTag(new GenericAttribute("Pure", new byte[0]));
        }
        if (opts.print()) {
          logger.debug("  |- method " + m.toString() + " is " + (isPure ? "pure" : "impure"));
        }

        // param & this ro / safety
        if (!m.isStatic()) {
          String s;
          switch (b.g.thisStatus()) {
            case PurityGraph.PARAM_RW:
              s = "read/write";
              break;
            case PurityGraph.PARAM_RO:
              s = "read-only";
              break;
            case PurityGraph.PARAM_SAFE:
              s = "Safe";
              break;
            default:
              s = "unknown";
          }
          /*
           * m.addTag(new GenericAttribute("thisStatus",s.getBytes()));
           */
          m.addTag(new StringTag("this: " + s));
          if (opts.print()) {
            logger.debug("  |   |- this is " + s);
          }
        }

        int i = 0;
        for (Type t : m.getParameterTypes()) {
          if (t instanceof RefLikeType) {
            String s;
            switch (b.g.paramStatus(i)) {
              case PurityGraph.PARAM_RW:
                s = "read/write";
                break;
              case PurityGraph.PARAM_RO:
                s = "read-only";
                break;
              case PurityGraph.PARAM_SAFE:
                s = "safe";
                break;
              default:
                s = "unknown";
            }
            /*
             * m.addTag(new GenericAttribute("param"+i+"Status", s.getBytes()));
             */
            m.addTag(new StringTag("param" + i + ": " + s));
            if (opts.print()) {
              logger.debug("  |   |- param " + i + " is " + s);
            }
          }
          i++;
        }
      }
    }

  }

  @Override
  protected PurityGraphBox newInitialSummary() {
    return new PurityGraphBox();
  }

  @Override
  protected void merge(PurityGraphBox in1, PurityGraphBox in2, PurityGraphBox out) {
    if (out != in1) {
      out.g = new PurityGraph(in1.g);
    }
    out.g.union(in2.g);
  }

  @Override
  protected void copy(PurityGraphBox source, PurityGraphBox dest) {
    dest.g = new PurityGraph(source.g);
  }

  @Override
  protected void analyseMethod(SootMethod method, PurityGraphBox dst) {
    new PurityIntraproceduralAnalysis(ExceptionalUnitGraphFactory.createExceptionalUnitGraph(method.retrieveActiveBody()),
        this).copyResult(dst);
  }

  /**
   * @return
   *
   * @see PurityGraph.conservativeGraph
   * @see PurityGraph.freshGraph
   */
  @Override
  protected PurityGraphBox summaryOfUnanalysedMethod(SootMethod method) {<FILL_FUNCTION_BODY>

    PurityGraphBox b = new PurityGraphBox();
    String c = method.getDeclaringClass().toString();
    String m = method.getName();

    // impure with side-effect, unless otherwise specified
    b.g = PurityGraph.conservativeGraph(method, true);

    for (String[] element : PurityInterproceduralAnalysis.pureMethods) {
      if (m.equals(element[1]) && c.startsWith(element[0])) {
        b.g = PurityGraph.freshGraph(method);
      }
    }

    for (String[] element : PurityInterproceduralAnalysis.alterMethods) {
      if (m.equals(element[1]) && c.startsWith(element[0])) {
        b.g = PurityGraph.conservativeGraph(method, false);
      }
    }

    return b;
  
<methods>public void <init>(soot.jimple.toolkits.callgraph.CallGraph, soot.jimple.toolkits.annotation.purity.SootMethodFilter, Iterator<soot.SootMethod>, boolean) ,public void analyseCall(soot.jimple.toolkits.annotation.purity.PurityGraphBox, soot.jimple.Stmt, soot.jimple.toolkits.annotation.purity.PurityGraphBox) ,public void drawAsManyDot(java.lang.String, boolean) ,public void drawAsOneDot(java.lang.String) ,public Iterator<soot.SootMethod> getAnalysedMethods() ,public soot.jimple.toolkits.annotation.purity.PurityGraphBox getSummaryFor(soot.SootMethod) <variables>protected final non-sealed soot.jimple.toolkits.callgraph.CallGraph cg,protected final non-sealed Map<soot.SootMethod,soot.jimple.toolkits.annotation.purity.PurityGraphBox> data,protected final non-sealed DirectedGraph<soot.SootMethod> dg,public static final boolean doCheck,private static final org.slf4j.Logger logger,protected final non-sealed Map<soot.SootMethod,java.lang.Integer> order,protected final non-sealed Map<soot.SootMethod,soot.jimple.toolkits.annotation.purity.PurityGraphBox> unanalysed