import java.io.*;
import java.util.*;

aspect Java_CodeGenEnv {

  // Environment wrapper for Java-code generation
  // handles indentation, file writing,

  public class Java_env {

    public final int version;  //labcomm version to generate code for
    public final String verStr;
    private int indent;
    private int depth;
    private Java_printer printer;
    private HashMap unique = new HashMap();

    final private class Java_printer {

      private boolean newline = true;
      private File file;
      private PrintStream out;
      private IOException exception;


      public Java_printer(File f) {
  	file = f;
        File parentFile = f.getParentFile();
        if(parentFile != null) {
            parentFile.mkdirs();
        }
      }

     public Java_printer(PrintStream out) {
        this.out = out;
      }

      public void close() throws IOException {
	if (out != null) {
  	  out.close();
        }
	if (exception != null) {
	  throw exception;
        }
      }

      public PrintStream getPrintStream() {
	return(out);
      }

      public void checkOpen() {
	if (out == null && exception == null) {
          try {
    	    out = new PrintStream(new FileOutputStream(file));
          } catch (IOException e) {
	    exception = e;
          }
        }
      }

      public void print(Java_env env, String s) {
	checkOpen();
        if (newline) {
          newline = false;
          for (int i = 0 ; i < env.indent ; i++) {
            out.print("  ");
          }
        }
        out.print(s);
      }

      public void println(Java_env env, String s) {
	checkOpen();
        print(env, s);
        out.println();
        newline = true;
      }
    }

    private Java_env(int version, int indent) {
      this.version = version;
      this.verStr = "2014";
      this.indent = indent;
    }

    private Java_env(int version, Java_printer printer) {
      this(version, 0);
      this.printer = printer;
    }

    public Java_env(int version, File f) {
      this(version, 0);
      this.printer = new Java_printer(f);
    }

    public Java_env(int version, PrintStream out) {
      this(version, 0);
      this.printer = new Java_printer(out);
    }

    public void close() throws IOException {
      printer.close();
    }

    public PrintStream getPrintStream() {
      return printer.getPrintStream();
    }
    public void indent(int amount) {
      indent += amount;
    }

    public void indent() {
      indent(1);
    }

    public void unindent(int amount) {
      indent -= amount;
    }

    public void unindent() {
      unindent(1);
    }

    public void print(String s) {
      printer.print(this, s);
    }

    public void println(String s) {
      printer.println(this, s);
    }

    public void println() {
      printer.println(this, "");
    }

    public int getDepth() {
      return depth;
    }

    public String print_for_begin(String limit) {
      print("for (int i_" + depth + " = 0 ; ");
      print("i_" + depth + " < " + limit + " ; ");
      println("i_" + depth + "++) {");
      indent();
      depth++;
      return "[i_" + (depth - 1) + "]";
    }

    public void print_for_end() {
      depth--;
      unindent();
      println("}");
    }

    public void print_block_begin() {
      println("{");
      indent();
    }

    public void print_block_end() {
      unindent();
      println("}");
    }

    public String getUnique(Object o) {
      String result = (String)unique.get(o);
      if (result == null) {
   	result = "_" + (unique.size() + 1) + "_";
      }
      unique.put(o, result);
      return result;
    }

  }

}

aspect Java_StructName {

  inh int Decl.Java_Depth();
  inh int Type.Java_Depth();
  eq Program.getDecl(int i).Java_Depth() = 0;
  eq StructType.getField(int i).Java_Depth() = Java_Depth() + 1;

  inh String Type.Java_structName();
  eq Program.getDecl(int i).Java_structName() = getDecl(i).getName();
  eq StructType.getField(int i).Java_structName() {
    if (Java_Depth() == 0) {
      return "struct_" + getField(i).getName();
    } else {
      return Java_structName() + "_" + getField(i).getName();
    }
  }
}

aspect Java_Void {

  syn boolean Decl.isVoid() = getType().isVoid();
  syn boolean UserType.isVoid() = decl().isVoid();
  syn boolean Type.isVoid() = false;
  syn boolean VoidType.isVoid() = true;

}

aspect Java_CodeGen {

  public void Program.J_gen(PrintStream ps, String pack, int version) throws IOException {
    Java_env env;
    env = new Java_env(version, ps);
    for (int i = 0; i < getNumDecl(); i++) {
      Decl d = getDecl(i);
      try {
        d.Java_emitClass(env, pack);
      } catch (Error e) {
	System.err.println(d.getName());
	throw e;
      }
    }
    env.close();
  }

  public void Program.J_gen(String dir, String pack, int version) throws IOException {
    Java_env env;
    for (int i = 0; i < getNumDecl(); i++) {
      Decl d = getDecl(i);
      try {
        env = new Java_env(version, new File(dir, d.getName() + ".java"));
        d.Java_emitClass(env, pack);
        env.close();
      } catch (Error e) {
	System.err.println(d.getName());
	throw e;
      }
    }
  }

  /** Experimental method for generating code to a map <classname, source>
    */
  public void Program.J_gen(Map<String,String> src, String pack, int version) throws IOException {
    Java_env env;
    for (int i = 0; i < getNumDecl(); i++) {
      Decl d = getDecl(i);
      try {
        ByteArrayOutputStream bs = new ByteArrayOutputStream();
        PrintStream out = new PrintStream(bs);
        env = new Java_env(version, out);
        d.Java_emitClass(env, pack);
        env.close();
        src.put(d.getName(), bs.toString());
      } catch (Error e) {
	System.err.println(d.getName());
	throw e;
      }
    }
  }

}

aspect Java_Class {

  public void Decl.Java_emitClass(Java_env env, String pack) {
    throw new Error(this.getClass().getName() +
		    ".Java_emitClass(Java_env env, String pack)" +
		    " not declared");
  }

  public void Decl.Java_emitDeclPP(Java_env env) {
      // Hackish prettyprint preamble
      env.println("/* ");
      pp(env.getPrintStream());

      Java_emitUserTypeDeps(env, null, false);
      Java_emitUserTypeRefs(env, null, false);
      env.println("*/");

  }

  public void Decl.Java_emitUserTypeDeps(Java_env env, String via, boolean outputCode) {
  // XXX TODO will generate unnecessary recursion for types. fix this per commented out code
  // XXX      but ensure that types with references actually register themselves.. (i.e., add "nested" argument)
  //public abstract void Decl.Java_emitUserTypeDeps(Java_env env, String via, boolean outputCode);

  //public void TypeDecl.Java_emitUserTypeDeps(Java_env env, String via, boolean outputCode) {
  //  // do nothing for type decls; sampledecl iterates over all dependencies and outputs
  //  // all type decls
  //}
  //public void SampleDecl.Java_emitUserTypeDeps(Java_env env, String via, boolean outputCode) {
//  if(env.versionHasMetaData() && hasDependencies() || isReferenced() ) {
//      if(env.versionHasMetaData() && isSampleDecl() && outputCode) {
//         env.println("if(sendMetaData){");
//         env.indent();
//      }
        Iterator<Decl> it = type_dependencies().iterator();
        while(it.hasNext()) {
            Decl t = it.next();

            t.Java_emitUserTypeDeps(env, t.getName(), outputCode);
            if( outputCode){// && t.getType().isUserType() ) {
               env.println(t.getName()+".register(e);");
            } else {  // Just output a comment
	        String refpath = (via == null) ? "directly" : "indirectly via "+via;
	       env.println(" //Depends ("+refpath+") on "+t.getName() );
            }
        }
//      if(env.versionHasMetaData() && isSampleDecl() && outputCode) {
//         env.unindent();
//         env.println("}");
//      }
//  }
  }
  public void Decl.Java_emitUserTypeRefs(Java_env env, String via, boolean outputCode) {
    if( isReferenced() ) {
        Iterator<Decl> it = type_references().iterator();
        while(it.hasNext()) {
            Decl t = it.next();

            t.Java_emitUserTypeRefs(env, t.getName(), outputCode);
            if(outputCode) {
               env.println(t.getName()+".register(e);");
            } else {  // Just output a comment
	        String refpath = (via == null) ? "directly" : "indirectly via "+via;
	       env.println(" //Is referenced ("+refpath+")  by "+t.getName() );
            }
        }
    }
 }


  public void Decl.Java_emitRegisterEncoder(Java_env env) {
    env.println("public static void register(Encoder e) throws IOException {");
    env.indent();
    env.println("register(e, true);");
    env.unindent();
    env.println("}");
    env.println();
    env.println("public static void register(Encoder e, boolean sendMetaData) throws IOException {");
    env.indent();
    Java_emitUserTypeDeps(env, null, true);
    env.println("e.register(dispatcher);");
    env.unindent();
    env.println("}");
    env.println("public static void registerSampleRef(Encoder e) throws IOException{");
    env.indent();
    env.println("e.registerSampleRef(dispatcher);");
    env.unindent();
    env.println("}");
    env.println();
  }

  public void TypeDecl.Java_emitClass(Java_env env, String pack) {
      Java_emitDeclPP(env);
      if (pack != null && pack.length() > 0) {
          env.println("package " + pack + ";");
      }

      env.println("import se.lth.control.labcomm"+env.verStr+".Constant;");
      env.println("import se.lth.control.labcomm"+env.verStr+".SampleType;");

      if (getType().Java_needInstance() || hasDependencies() || isReferenced()) {
          env.println("import se.lth.control.labcomm"+env.verStr+".Encoder;");
          env.println("import se.lth.control.labcomm"+env.verStr+".SampleDispatcher;");
          env.println("import se.lth.control.labcomm"+env.verStr+".SampleHandler;");
//          env.println();
//      }
//
//      if (getType().Java_needInstance()) {
          env.println("import java.io.IOException;");
          env.println("import se.lth.control.labcomm"+env.verStr+".Decoder;");
      }
      // For types without type_dependencies and not needing an instance,
      // currently just an empty class is generated

      env.println("public class " + getName() + " implements SampleType {");
      env.println();

      env.indent();
      if (getType().Java_needInstance()) {
        getType().Java_emitInstance(env);
        Java_emitEncoder(env);
        Java_emitDecoder(env);
      }

      //if(hasDependencies() || isReferenced()) {
      //if( getType().isUserType() && isReferenced()) {
      if( isReferenced()) {
        Java_emitRegisterEncoder(env);
        Java_emitDispatcher(env, false);
      }
      Java_emitSignature(env);

      env.println("}");
      env.unindent();
      env.println();
  }


  public void SampleDecl.Java_emitClass(Java_env env, String pack) {
    Java_emitDeclPP(env);

    if (pack != null && pack.length() > 0) {
      env.println("package " + pack + ";");
    }

    env.println("import java.io.IOException;");
    env.println("import se.lth.control.labcomm"+env.verStr+".Constant;");
    env.println("import se.lth.control.labcomm"+env.verStr+".Decoder;");
    env.println("import se.lth.control.labcomm"+env.verStr+".SampleDispatcher;");
    env.println("import se.lth.control.labcomm"+env.verStr+".Encoder;");
    env.println("import se.lth.control.labcomm"+env.verStr+".SampleHandler;");
    env.println("import se.lth.control.labcomm"+env.verStr+".Sample;");
    env.println();
    env.print("public class " + getName());
//  TODO: ?
//  Code for making samples of user types extend their type
//  currently commented out. Is this a good idea or not?
//
//    if(getType().isUserType()) {
//        env.print(" extends "+getType().getTypeName());
//    }
    env.println(" implements Sample {");
    env.println();
    env.indent();
    getType().Java_emitInstance(env);
    env.println("public interface Handler extends SampleHandler {");
    env.print("  public void handle_" + getName() + "(");
    if (!isVoid()) {
      getType().Java_emitType(env);
      env.print(" value");
    }
    env.println(") throws Exception;");
    env.println("}");
    env.println();
    env.println("public static void register(Decoder d, Handler h) throws IOException {");
    env.indent();
    env.println("d.register(dispatcher, h);");
    env.unindent();
    env.println("}");
    env.println("public static void registerSampleRef(Decoder d) throws IOException {");
    env.indent();
    env.println("d.registerSampleRef(dispatcher);");
    env.unindent();
    env.println("}");
    env.println();


    Java_emitRegisterEncoder(env);
    Java_emitDispatcher(env, true);
    Java_emitEncoder(env);
    Java_emitDecoder(env);

    Java_emitSignature(env);
    env.unindent();
    env.println("}");
    env.println();
  }

  public void TypeDecl.Java_emitSignature(Java_env env) {
    Signature signature = getSignature();
    signature.Java_emitSignature(env, true);
  }

  public void Decl.Java_emitSignature(Java_env env) {
    //always emit the flat signature, as it is needed
    //for matching at the decoder side (which cannot know
    //the type_ids of dependent types. Therefore, flat sigs
    //are used for matching
    Java_emitFlatSignature(env);
    if(isReferenced() || (isSampleDecl() && hasDependencies() )){
      Signature signature = getSignature();
      signature.Java_emitSignature(env, !isSampleDecl());
    }
  }

  public void Decl.Java_emitFlatSignature(Java_env env){
    env.println("private static byte[] signature = new byte[] {");
      env.indent();
      SignatureList signature = flatSignature(env.version);
      for (int i = 0 ; i < signature.size() ; i++) {
        String comment = signature.getComment(i);
        if (comment != null) {
          env.println(signature.getIndent(i) + "// " + comment);
        }
        byte[] data = signature.getData(i, env.version);
        if (data != null) {
          env.print(signature.getIndent(i));
          for (int j = 0 ; j < data.length ; j++) {
          env.print(data[j] + ", ");
        }
        env.println();
      }
    }
    env.unindent();
    env.println("};");
    env.unindent();
    env.println();
  }

  //XXX TODO: refactor: split into a static class ("TypeDefSingleton"?)and a (smaller) dispatcher
  public void Decl.Java_emitDispatcher(Java_env env, boolean isSample) {
    // String genericStr = ""; //env.versionHasMetaData()?"<"+getName()+">":""; 
    String genericStr = "<"+getName()+">"; 
    env.println("private static Dispatcher dispatcher = new Dispatcher();");
    env.println();
    env.println("public SampleDispatcher getDispatcher() {");
    env.indent();
    env.println("return dispatcher;");
    env.unindent();
    env.println("}");
    env.println();
    env.println("private static class Dispatcher implements SampleDispatcher "+genericStr+"{");
    env.indent();
    env.println();
    env.println("public Class"+genericStr+" getSampleClass() {");
    env.indent();
    env.println("return " + getName() + ".class;");
    env.unindent();
    env.println("}");
    env.println();
    env.println("public String getName() {");
    env.indent();
    env.println("return \"" + getName() + "\";");
    env.unindent();
    env.println("}");
    env.println();
    env.println("public byte getTypeDeclTag() {");
    env.indent();
    if(env.version == 2006) {
      if(isSample) {
        env.println("return Constant.SAMPLE;");
      } else {
        env.println("return Constant.TYPEDEF;");
      }
    } else {
      if(isSample) {
        env.println("return Constant.SAMPLE_DEF;");
      } else {
        env.println("return Constant.TYPE_DEF;");
      }
    }
    env.unindent();
    env.println("}");
    env.println();
    env.println("public boolean isSample() {");
    env.indent();
    env.println("return "+isSample+";");
    env.unindent();
    env.println("}");
    env.println("public boolean hasDependencies() {");
    env.indent();
    env.println("return "+hasDependencies()+";");
    env.unindent();
    env.println("}");
    env.println();
    env.println("/** return the flat signature. */");
    env.println("public byte[] getSignature() {");
    env.indent();
    if(isSample) {
        env.println("return signature;");
    } else {
        env.println("throw new Error(\"a TYPE_DEF has no flat signature\");");
    }
    env.unindent();
    env.println("}");
    env.println();
//    env.println("public void encodeSignature(Encoder e) throws IOException{");
//    env.indent();
//    env.println("emitSignature(e);");
//    env.unindent();
//    env.println("}");
//    env.println();
    env.println("public void encodeTypeDef(Encoder e, int index) throws IOException{");
    env.indent();
    if(!isSample || hasDependencies()) {
      env.println("emitSignature(e);");
    } else {
      env.println("// the type has no dependencies, do nothing");
    }
    env.unindent();
    env.println("}");
    env.println();
    env.println("public boolean canDecodeAndHandle() {");
    env.indent();
    env.println("return "+isSample+";");
    env.unindent();
    env.println("}");
    env.println();
    env.println("public void decodeAndHandle(Decoder d,");
    env.println("                            SampleHandler h) throws Exception {");
    env.indent();
    if( isSample) {
        if (isVoid()) {
          env.println(getName() + ".decode(d);");
          env.println("((Handler)h).handle_" + getName() + "();");
        } else {
          env.println("((Handler)h).handle_" + getName() + "(" + getName() + ".decode(d));");
        }
    } else {
        env.println("throw new Exception(\"A typedef has no handler, the corresponding method on the sample class should be called.\");");
    }
    env.unindent();
    env.println("}");
    env.println("");
    env.unindent();
    env.println("}");
    env.println("");

 }


  public void TypeDecl.Java_emitEncoder(Java_env env) {
    env.print("public static void encode(Encoder e");
    if (!isVoid()) {
      env.print(", ");
      getType().Java_emitType(env);
      env.print(" value");
    }
    env.println(") throws IOException {");
    env.indent();
    getType().Java_emitEncoder(env, "value");
    env.unindent();
    env.println("}");
    env.println();
  }

  public void SampleDecl.Java_emitEncoder(Java_env env) {
    env.print("public static void encode(Encoder e");
    if (!isVoid()) {
      env.print(", ");
      getType().Java_emitType(env);
      env.print(" value");
    }
    env.println(") throws IOException {");
    env.indent();
    env.println("e.begin(" + getName() + ".class);");
    getType().Java_emitEncoder(env, "value");
    env.println("e.end(" + getName() + ".class);");
    env.unindent();
    env.println("}");
    env.println();
  }

  public void Type.Java_emitEncoder(Java_env env, String name) {
    throw new Error(this.getClass().getName() +
		    ".Java_emitEncoder(Java_env env, String name)" +
		    " not declared");
  }

  public void VoidType.Java_emitEncoder(Java_env env, String name) {
  }

  public void PrimType.Java_emitEncoder(Java_env env, String name) {
    switch (getToken()) {
      case LABCOMM_BOOLEAN: { env.print("e.encodeBoolean"); } break;
      case LABCOMM_BYTE: { env.print("e.encodeByte"); } break;
      case LABCOMM_SHORT: { env.print("e.encodeShort"); } break;
      case LABCOMM_INT: { env.print("e.encodeInt"); } break;
      case LABCOMM_LONG: { env.print("e.encodeLong"); } break;
      case LABCOMM_FLOAT: { env.print("e.encodeFloat"); } break;
      case LABCOMM_DOUBLE: { env.print("e.encodeDouble"); } break;
      case LABCOMM_STRING: { env.print("e.encodeString"); } break;
      case LABCOMM_SAMPLE: { env.print("e.encodeSampleRef"); } break;
    }
    env.println("(" + name + ");");
  }

  public void ArrayType.Java_emitEncoder(Java_env env, String name) {
    int baseDepth = env.getDepth();
    String prefix = "";
    for (int i = 0 ; i < getNumExp() ; i++) {
      String limit = getExp(i).Java_emitEncoder(env, name + prefix);
      env.print_block_begin();
      env.println("int i_" + (baseDepth + i) + "_max = " + limit + ";");
      prefix = prefix + "[0]";
    }
    for (int i = 0 ; i < getNumExp() ; i++) {
      String limit = "i_" + (baseDepth + i) + "_max";
      name = name + env.print_for_begin(limit);
    }
    getType().Java_emitEncoder(env, name);
    for (int i = 0 ; i < getNumExp() ; i++) {
      env.print_for_end();
      env.print_block_end();
    }
  }

  public String Exp.Java_emitEncoder(Java_env env, String name) {
    throw new Error(this.getClass().getName() +
		    ".Java_emitEncoder(Java_env env, String name)" +
		    " not declared");
  }

  public String IntegerLiteral.Java_emitEncoder(Java_env env, String name) {
    return getValue();
  }

  public String VariableSize.Java_emitEncoder(Java_env env, String name) {
    env.println("e.encodePacked32(" + name + ".length);");
    return name + ".length";
  }

  public void StructType.Java_emitEncoder(Java_env env, String name) {
    for (int i = 0 ; i < getNumField() ; i++) {
      Field f = getField(i);
      f.getType().Java_emitEncoder(env, name + "." + f.getName());
    }
  }

  public void UserType.Java_emitEncoder(Java_env env, String name) {
    if (Java_needInstance()) {
      env.println(getName() + ".encode(e, " + name + ");");
    } else {
      decl().getType().Java_emitEncoder(env, name);
    }
  }

  public void Decl.Java_emitDecoder(Java_env env) {
    env.print("public static ");
    getType().Java_emitType(env);
    env.println(" decode(Decoder d) throws IOException {");
    env.indent();
    if (!isVoid()) {
      getType().Java_emitType(env);
      env.println(" result;");
      getType().Java_emitDecoder(env, "result");
      env.println("return result;");
    }
    env.unindent();
    env.println("}");
    env.println();
  }

  public void Type.Java_emitDecoder(Java_env env, String name) {
    throw new Error(this.getClass().getName() +
		    ".Java_emitDecoder(Java_env env, String name)" +
		    " not declared");
  }

  public void VoidType.Java_emitDecoder(Java_env env, String name) {
  }

  public void PrimType.Java_emitDecoder(Java_env env, String name) {
    env.print(name + " = ");
    switch (getToken()) {
      case LABCOMM_BOOLEAN: { env.println("d.decodeBoolean();"); } break;
      case LABCOMM_BYTE: { env.println("d.decodeByte();"); } break;
      case LABCOMM_SHORT: { env.println("d.decodeShort();"); } break;
      case LABCOMM_INT: { env.println("d.decodeInt();"); } break;
      case LABCOMM_LONG: { env.println("d.decodeLong();"); } break;
      case LABCOMM_FLOAT: { env.println("d.decodeFloat();"); } break;
      case LABCOMM_DOUBLE: { env.println("d.decodeDouble();"); } break;
      case LABCOMM_STRING: { env.println("d.decodeString();"); } break;
      case LABCOMM_SAMPLE: { env.println("d.decodeSampleRef();"); } break;
    }
  }

  public void ArrayType.Java_emitDecoder(Java_env env, String name) {
    env.println("{");
    env.indent();
    int baseDepth = env.getDepth();
    for (int i = 0 ; i < getNumExp() ; i++) {
      env.print("int i_" + (baseDepth + i) + "_max = ");
      getExp(i).Java_emitDecoder(env);
      env.println(";");
    }
    for (int i = 0 ; i < getNumExp() ; i++) {
      String limit = "i_" + (baseDepth + i) + "_max";
      env.print(name + " = ");
      Java_emitNew(env, limit, getNumExp() - i);
      env.println(";");
      name = name + env.print_for_begin(limit);
    }
    getType().Java_emitDecoder(env, name);
    for (int i = 0 ; i < getNumExp() ; i++) {
      env.print_for_end();
    }
    env.unindent();
    env.println("}");
  }

  public void Exp.Java_emitDecoder(Java_env env) {
    throw new Error(this.getClass().getName() +
		    ".Java_emitDecoder(Java_env env)" +
		    " not declared");
  }

  public void IntegerLiteral.Java_emitDecoder(Java_env env) {
    env.print(getValue());
  }

  public void VariableSize.Java_emitDecoder(Java_env env) {
    env.print("d.decodePacked32()");
  }

  public void StructType.Java_emitDecoder(Java_env env, String name) {
    env.print(name + " = new ");
    Java_emitType(env);
    env.println("();");
    for (int i = 0 ; i < getNumField() ; i++) {
      Field f = getField(i);
      f.getType().Java_emitDecoder(env, name + "." + f.getName());
    }
  }

  public void UserType.Java_emitDecoder(Java_env env, String name) {
    if (Java_needInstance()) {
      env.println(name + " = " + getName() + ".decode(d);");
    } else {
      decl().getType().Java_emitDecoder(env, name);
    }
  }

  public void Type.Java_emitNew(Java_env env, String size) {
    throw new Error(this.getClass().getName() +
		    ".Java_emitNew(Java_env env, String size)" +
		    " not declared");
  }

  public void ArrayType.Java_emitNew(Java_env env, String size, int depth) {
    env.print("new ");
    getType().Java_emitTypePrefix(env);
    env.print("[" + size + "]");
    getType().Java_emitTypeSuffix(env);
    for (int i = 1 ; i < depth ; i++) {
      env.print("[]");
    }
  }

  public void Type.Java_emitTypePrefix(Java_env env) {
    throw new Error(this.getClass().getName() +
		    ".Java_emitTypePrefix(Java_env env)" +
		    " not declared");
  }

  public void PrimType.Java_emitTypePrefix(Java_env env) {
    switch (getToken()) {
      case LABCOMM_STRING: { env.print("String"); } break;
      case LABCOMM_SAMPLE: { env.print("Class"); } break;
      default: { env.print(getName()); } break;
    }
  }

  public void UserType.Java_emitTypePrefix(Java_env env) {
    if (Java_needInstance()) {
      env.print(getName());
    } else {
      decl().getType().Java_emitTypePrefix(env);
    }
  }

  public void ArrayType.Java_emitTypePrefix(Java_env env){
    getType().Java_emitTypePrefix(env);
  }

  public void StructType.Java_emitTypePrefix(Java_env env){
    env.print(Java_structName());
  }

  public void Type.Java_emitTypeSuffix(Java_env env) {
  }

  public void UserType.Java_emitTypeSuffix(Java_env env) {
    if (! Java_needInstance()) {
      decl().getType().Java_emitTypeSuffix(env);
    }
  }

  public void ArrayType.Java_emitTypeSuffix(Java_env env){
    getType().Java_emitTypeSuffix(env);
    for (int i = 0 ; i < getNumExp() ; i++) {
      env.print("[]");
    }
  }

  public boolean Type.Java_needInstance() {
    throw new Error(this.getClass().getName() +
		    ".Java_needInstance()" +
		    " not declared");
  }

  public boolean VoidType.Java_needInstance() {
    return false;
  }

  public boolean PrimType.Java_needInstance() {
    return false;
  }

  public boolean UserType.Java_needInstance() {
    return decl().getType().Java_needInstance();
  }

  public boolean StructType.Java_needInstance() {
    return true;
  }

  public boolean ArrayType.Java_needInstance() {
    return getType().Java_needInstance();
  }

  public boolean Type.Java_isPrimitive() {
    return false;
  }

  public boolean PrimType.Java_isPrimitive() {
    return true;
  }

  public void Type.Java_emitInstance(Java_env env) {
    throw new Error(this.getClass().getName() +
		    ".Java_emitInstance(Java_env env)" +
		    " not declared");
  }

  public void VoidType.Java_emitInstance(Java_env env) {
  }

  public void PrimType.Java_emitInstance(Java_env env) {
  }

  public void ArrayType.Java_emitInstance(Java_env env) {
    getType().Java_emitInstance(env);
  }

  public void StructType.Java_emitInstance(Java_env env) {
    if (Java_Depth() > 0) {
      env.println("public static class " + Java_structName() + " {");
      env.indent();
    }
    for (int i = 0 ; i < getNumField() ; i++) {
      getField(i).getType().Java_emitInstance(env);
    }
    for (int i = 0 ; i < getNumField() ; i++) {
      getField(i).Java_emitField(env);
    }
    if (Java_Depth() > 0) {
      env.unindent();
      env.println("}");
    }
    env.println();
  }

  public void UserType.Java_emitInstance(Java_env env) {
  }

  public void Field.Java_emitField(Java_env env) {
    env.print("public ");
    getType().Java_emitType(env);
    env.println(" " + getName() + ";");
  }

  public void Type.Java_emitType(Java_env env) {
    throw new Error(this.getClass().getName() +
		    ".Java_emitType(Java_env env)" +
		    " not declared");
  }

  public void VoidType.Java_emitType(Java_env env) {
    env.print("void");
  }

  public void PrimType.Java_emitType(Java_env env) {
    switch (getToken()) {
      case LABCOMM_STRING: { env.print("String"); } break;
      case LABCOMM_SAMPLE: { env.print("Class"); } break;
      default: { env.print(getName()); } break;
    }
  }

  public void UserType.Java_emitType(Java_env env) {
    decl().getType().Java_emitType(env);
  }

  public void ArrayType.Java_emitType(Java_env env){
    getType().Java_emitType(env);
    for (int i = 0 ; i < getNumExp() ; i++) {
      env.print("[]");
    }
  }

  public void StructType.Java_emitType(Java_env env){
    env.print(Java_structName());
  }
}

aspect Java_Signature {
    public void Signature.Java_emitSignature(Java_env env, boolean decl){
      // XXX should sendOnlyFlatSignatures be kept somewhere?
      //SignatureList sl = (parentDecl().sendOnlyFlatSignatures(env)) ? getFlatSignatureList() : getSignatureList();
      SignatureList sl = getSignatureList();
      sl.Java_emitSignature(env, decl);
    }

//    public void Signature.Java_emitHierarchicalSignature(Java_env env, boolean decl){
//      SignatureList sl = getSignatureList();
//      sl.Java_emitSignature(env, decl);
//    }
//
    public abstract void SignatureLine.Java_emitSignature(Java_env env, boolean decl);

    public void TypeRefSignatureLine.Java_emitSignature(Java_env env, boolean isDecl){
      env.print(getIndentString());
      env.println("e.encodePacked32(e.getTypeId("+decl.getName()+".class));");
    }

    public void DataSignatureLine.Java_emitSignature(Java_env env, boolean decl){
        byte[] data = getData(env.version);
          if (data != null) {
              env.print(getIndentString());
              for (int j = 0 ; j < data.length ; j++) {
                  //env.print("e.encodeByte((byte)"+data[j]+");");
                  env.print("e.encodeByte((byte)"+ String.format("0x%02X ", data[j]) +"); ");
              }
              env.println();
          }
  }
    public void SignatureList.Java_emitSignature(Java_env env, boolean decl) {
      env.println("private static void emitSignature(Encoder e) throws IOException{");
      env.indent();
      for (int i = 0 ; i < size() ; i++) {
        String comment = getComment(i);
        if (comment != null && comment.length() > 0) {
            env.println(getIndent(i) + "// " + comment);
        }
        SignatureLine l = getSignatureLine(i);
        l.Java_emitSignature(env, decl);
      }
      env.println("}");
      env.unindent();
  }

}

aspect Java_Info {

  public void Program.Java_info(PrintStream out, int version) {
    Java_env env = new Java_env(version, out);
    for (int i = 0; i < getNumDecl(); i++) {
      getDecl(i).Java_info(env);
    }
  }

  public void Decl.Java_info(Java_env env) {
    throw new Error(this.getClass().getName() +
		    ".Java_info(Java_env env)" +
		    " not declared");
  }

  public void TypeDecl.Java_info(Java_env env) {
    env.print(",Java,typedef," + getName() + ",");
    getType().Java_emitType(env);
    env.print(",not_applicable_for_Java");
    env.println();
  }

  public void SampleDecl.Java_info(Java_env env) {
    env.print(",Java,sample," + getName() + ",");
    getType().Java_emitType(env);
    env.print(",not_applicable_for_Java");
    env.println();
  }

}