/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.ConstantUtf8;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.classfile.Utility;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.GETSTATIC;
import org.apache.bcel.generic.INVOKESPECIAL;
import org.apache.bcel.generic.INVOKEVIRTUAL;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.PUSH;

/**
 * Read class file(s) and patch all of its methods, so that they print
 * "hello" and their name and signature before doing anything else.
 *
 * @version $Id$
 * @author <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
 */
public final class helloify implements Constants {
  private static String          class_name;
  private static ConstantPoolGen cp;
  private static int             out;     // reference to System.out
  private static int             println; // reference to PrintStream.println

  public static void main(String[] argv) { 
    try {
      for(int i=0; i < argv.length; i++) {
    if(argv[i].endsWith(".class")) {
          JavaClass       java_class = new ClassParser(argv[i]).parse();
      ConstantPool    constants  = java_class.getConstantPool();
          String          file_name  = argv[i].substring(0, argv[i].length() - 6) +
        "_hello.class";
      cp = new ConstantPoolGen(constants);

      helloifyClassName(java_class);

      out     = cp.addFieldref("java.lang.System", "out",
                   "Ljava/io/PrintStream;");
      println = cp.addMethodref("java.io.PrintStream", "println",
                    "(Ljava/lang/String;)V");
      /* Patch all methods.
       */
          Method[] methods = java_class.getMethods();

      for(int j=0; j < methods.length; j++) {
        methods[j] = helloifyMethod(methods[j]);
    }

      /* Finally dump it back to a file.
       */
      java_class.setConstantPool(cp.getFinalConstantPool());
      java_class.dump(file_name);
    }
      }
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
  
  /** Change class name to <old_name>_hello
   */
  private static void helloifyClassName(JavaClass java_class ) {
    class_name = java_class.getClassName() + "_hello";
    int index = java_class.getClassNameIndex();
    
    index = ((ConstantClass)cp.getConstant(index)).getNameIndex();
    cp.setConstant(index, new ConstantUtf8(class_name.replace('.', '/')));
  }

  /**
   * Patch a method.
   */
  private static Method helloifyMethod(Method m) {
    Code   code  = m.getCode();
    int    flags = m.getAccessFlags();
    String name  = m.getName();
    
    // Sanity check
    if(m.isNative() || m.isAbstract() || (code == null)) {
        return m;
    }
    
    /* Create instruction list to be inserted at method start.
     */
    String mesg = "Hello from " + Utility.methodSignatureToString(m.getSignature(),
                                  name, 
                                  Utility.accessToString(flags));
    InstructionList patch  = new InstructionList();
    patch.append(new GETSTATIC(out));
    patch.append(new PUSH(cp, mesg));
    patch.append(new INVOKEVIRTUAL(println));
    
    MethodGen           mg  = new MethodGen(m, class_name, cp);
    InstructionList     il  = mg.getInstructionList();
    InstructionHandle[] ihs = il.getInstructionHandles();

    if(name.equals("<init>")) { // First let the super or other constructor be called
      for(int j=1; j < ihs.length; j++) {
    if(ihs[j].getInstruction() instanceof INVOKESPECIAL) {
      il.append(ihs[j], patch); // Should check: method name == "<init>"
      break;
    }
      }
    } else {
        il.insert(ihs[0], patch);
    }

    /* Stack size must be at least 2, since the println method takes 2 argument.
     */
    if(code.getMaxStack() < 2) {
        mg.setMaxStack(2);
    }

    m = mg.getMethod();

    il.dispose(); // Reuse instruction handles
    
    return m;
  }
}
