
package AST;

public class ASTNode<T extends ASTNode> implements Cloneable, Iterable<T> {

  public void traverse() {
    for(int i = 0; i < getNumChild(); i++) {
      getChild(i).traverse();
    }
  }

  public static String treeToString(ASTNode node) {
    StringBuffer buf = new StringBuffer();
    buf.append("(" + nodeToString(node));
    buf.append("," + nodeToString(node.parent));
    buf.append(",(");
    for (int i = 0; i < node.numChildren; i++) {
      buf.append(treeToString(node.children[i]));
      if (i < node.numChildren-1) {
        buf.append(",");
      }
    } 
    buf.append("))");
    return buf.toString();
  }

  public static String nodeToString(ASTNode node) {
    return (node != null ? node.getClass().getSimpleName() : "null");
  }

  @SuppressWarnings({"unchecked", "cast"})
  public ASTNode<T> clone() throws CloneNotSupportedException {
    ASTNode node = (ASTNode) super.clone();
    node.extra_visited = -1;
    node.in$Circle(false);
    node.is$Final(false);
    return node;
  }

  @SuppressWarnings({"unchecked", "cast"})
  public ASTNode<T> copy() {
    try {
      Tracer.traceCopy(this, nodeToString(this));
      ASTNode node = (ASTNode) clone();
      node.parent = null;
      if(children != null) {
        node.children = (ASTNode[]) children.clone();
      }
      return node;
    } catch (CloneNotSupportedException e) {
      throw new Error("Error: clone not supported for " + getClass().getName());
    }
  }

  @SuppressWarnings({"unchecked", "cast"})
  public ASTNode<T> fullCopy() {
    ASTNode tree = (ASTNode) copy();
    if (children != null) {
      for (int i = 0; i < children.length; ++i) {
        ASTNode child = (ASTNode) children[i];
        if(child != null) {
          child = child.fullCopy();
          tree.setChild(child, i);
        }
      }
    }
    return tree;
  }

  public String dumpTree(int j) {
    StringBuffer s = new StringBuffer();
    for(int i = 0; i < j; i++) {
      s.append("  ");
    }
    s.append(getClass().getName() + extra() + "\n");
    for(int i = 0; i < getNumChild(); i++) {
      s.append(getChild(i).dumpTree(j + 1));
    }
    return s.toString();
  }

  public ASTNode() {
    super();
    init$Children();
  }

  public void init$Children() {
  }
  
  private int childIndex;

  public int getIndexOfChild(ASTNode node) {
    if (node != null && node.childIndex < numChildren
        && node == children[node.childIndex]) {
      return node.childIndex;
    }
    for(int i = 0; children != null && i < children.length; i++) {
      if(children[i] == node) {
        node.childIndex = i;
        return i;
      }
    }
    return -1;
  }

  public static final boolean generatedWithCircularEnabled = true;
  public static final boolean generatedWithCacheCycle = true;
  public static final boolean generatedWithComponentCheck = false;

  protected ASTNode parent;

  protected ASTNode[] children;

  protected static ASTNode$State state = new ASTNode$State();

  public final ASTNode$State state() {
    return state;
  }

  public boolean in$Circle = false;

  public boolean in$Circle() {
    return in$Circle;
  }

  public void in$Circle(boolean b) {
    in$Circle = b;
  }

  public boolean is$Final = false;

  public boolean is$Final() { return is$Final; }

  public void is$Final(boolean b) { is$Final = b; }

  @SuppressWarnings("cast") 
  public T getChild(int i) {

    ASTNode node = this.getChildNoTransform(i);
    if(node == null) {
      return null;
    }
    if(node.is$Final()) {
      return (T) node;
    }
    if(!node.mayHaveRewrite()) {
      node.is$Final(this.is$Final());
      return (T) node;
    }
    if(!node.in$Circle()) {
      Tracer.traceEnterRewriteCase1(this, "ASTNode.child", "" + i, nodeToString(node));
      int rewriteState;
      int num = this.state().boundariesCrossed;
      do {
        this.state().push(ASTNode$State.REWRITE_CHANGE);
        ASTNode oldNode = node;
        oldNode.in$Circle(true);
        node = node.rewriteTo();
        if(node != oldNode) {
          Tracer.traceRewriteChange(this, "ASTNode.child", "" + i, 
              nodeToString(oldNode) + "->" + nodeToString(node));
          this.setChild(node, i);
        }
        oldNode.in$Circle(false);
        rewriteState = this.state().pop();
      } while(rewriteState == ASTNode$State.REWRITE_CHANGE);
      if(rewriteState == ASTNode$State.REWRITE_NOCHANGE && this.is$Final() && num == this.state().boundariesCrossed) {
        node.is$Final(true);
        this.state().boundariesCrossed = num;
        Tracer.traceCached(this, "ASTNode.child", "" + i, nodeToString(node));
      }
      Tracer.traceExitRewriteCase1(this, "ASTNode.child", "" + i, nodeToString(node));
    } else if(this.is$Final() != node.is$Final()) {
      Tracer.traceExitRewriteCase2(this, "ASTNode.child", "" + i, nodeToString(node));
      this.state().boundariesCrossed++;
    } else {
      Tracer.traceExitRewriteCase3(this, "ASTNode.child", "" + i, nodeToString(node));
    }
    return (T) node;
  }

  public void addChild(T node) {
    setChild(node, getNumChildNoTransform());
  }

  @SuppressWarnings("cast")
  public final T getChildNoTransform(int i) {
      if (children == null) {
      return null;
    }
    T child = (T)children[i];
    return child;        
  }

  protected int numChildren;

  protected int numChildren() {
    return numChildren;
  }

  public int getNumChild() {
    return numChildren();
  }

  public final int getNumChildNoTransform() {
    return numChildren();
  }

  public void setChild(ASTNode node, int i) {
    if(children == null) {
      children = new ASTNode[(i+1>4 || !(this instanceof List))?i+1:4];
    } else if (i >= children.length) {
      ASTNode c[] = new ASTNode[i << 1];
      System.arraycopy(children, 0, c, 0, children.length);
      children = c;
    }
    children[i] = node;
    if(i >= numChildren) {
      numChildren = i+1;
    }
    if(node != null) { 
      node.setParent(this); 
      node.childIndex = i; 
    }
  }

  public void insertChild(ASTNode node, int i) {
    if(children == null) {
      children = new ASTNode[(i+1>4 || !(this instanceof List))?i+1:4];
      children[i] = node;
    } else {
      ASTNode c[] = new ASTNode[children.length + 1];
      System.arraycopy(children, 0, c, 0, i);
      c[i] = node;
      if(i < children.length) {
        System.arraycopy(children, i, c, i+1, children.length-i);
        for(int j = i+1; j < c.length; ++j) {
          if(c[j] != null) {
            c[j].childIndex = j;
          }
        }
      }
      children = c;
    }
    numChildren++;
    if(node != null) { 
      node.setParent(this); 
      node.childIndex = i; 
    }
  }

  public void removeChild(int i) {
    if(children != null) {
      ASTNode child = (ASTNode)children[i];
      if(child != null) {
        child.parent = null;
        child.childIndex = -1;
      }
      if (this instanceof List || this instanceof Opt) {
        System.arraycopy(children, i+1, children, i, children.length-i-1);
        children[children.length-1] = null;
        numChildren--;
        for(int j = i; j < numChildren; ++j) {
          if(children[j] != null) {
            child = (ASTNode) children[j];
            child.childIndex = j;
          }
        }
      } else {
        children[i] = null;
      }
    }
  }

  public ASTNode getParent() {
    if(parent != null && ((ASTNode)parent).is$Final() != is$Final()) {
      state().boundariesCrossed++;
    }
    return (ASTNode)parent;
  }

  public void setParent(ASTNode node) {
    parent = node;
  }

  protected boolean duringJavaDemoNames() {
    if(state().duringJavaDemoNames == 0) {
      return false;
    } else {
      state().pop();
      state().push(ASTNode$State.REWRITE_INTERRUPT);
      return true;
    }
  }

  public java.util.Iterator<T> iterator() {
    return new java.util.Iterator<T>() {
      private int counter = 0;
      public boolean hasNext() {
        return counter < getNumChild();
      }
      @SuppressWarnings("unchecked") public T next() {
        if(hasNext())
          return (T)getChild(counter++);
        else
          return null;
      }
      public void remove() {
        throw new UnsupportedOperationException();
      }
    };
  }

  public boolean mayHaveRewrite() {
    return false;
  }

  public void flushCache() {
    extra_visited = -1;
  }

  public void flushCollectionCache() {
  }

  protected int extra_visited = -1;

  public String extra() {
    ASTNode$State state = state();
    if (extra_visited == state().boundariesCrossed) {
      throw new RuntimeException("Circular definition of attr: extra in class: ast.AST.SynDecl");
    }          
    extra_visited = state().boundariesCrossed;      
    try {  return "";  }
    finally {
      extra_visited = -1;
    }
  }

  public ASTNode rewriteTo() {    
    if(state().peek() == ASTNode$State.REWRITE_CHANGE) {
      state().pop();
      state().push(ASTNode$State.REWRITE_NOCHANGE);
    }
    return this;
  }

  public ClassDecl Define_ClassDecl_unknownType(ASTNode caller, ASTNode child) {
    Tracer.traceInhAttrEval(this, "ASTNode.child.unknownType", "", nodeToString(caller));
    return getParent().Define_ClassDecl_unknownType(this, caller);
  }

  public Variable Define_Variable_lookupVariable(ASTNode caller, ASTNode child, String name) {
    Tracer.traceInhAttrEval(this, "ASTNode.child.lookupVariable", name, nodeToString(caller));
    return getParent().Define_Variable_lookupVariable(this, caller, name);
  }

  public ClassDecl Define_ClassDecl_lookupCanonical(ASTNode caller, ASTNode child, String pack, String type) {
    Tracer.traceInhAttrEval(this, "ASTNode.child.lookupCanonical", "[" + pack + "," + type + "]", nodeToString(caller));
    return getParent().Define_ClassDecl_lookupCanonical(this, caller, pack, type);
  }

  public ClassDecl Define_ClassDecl_lookupType(ASTNode caller, ASTNode child, String name) {
    Tracer.traceInhAttrEval(this, "ASTNode.child.lookupType", name, nodeToString(caller));
    return getParent().Define_ClassDecl_lookupType(this, caller, name);
  }

  public Kind Define_Kind_kind(ASTNode caller, ASTNode child) {
    Tracer.traceInhAttrEval(this, "ASTNode.child.kind", "", nodeToString(caller));
    return getParent().Define_Kind_kind(this, caller);
  }

  public boolean Define_boolean_hasPackage(ASTNode caller, ASTNode child, String name) {
    Tracer.traceInhAttrEval(this, "ASTNode.child.hasPackage", name, nodeToString(caller));
    return getParent().Define_boolean_hasPackage(this, caller, name);
  }
}
