org.nuclos.server.customcode.codegenerator.ClassDebugAdapter.java Source code

Java tutorial

Introduction

Here is the source code for org.nuclos.server.customcode.codegenerator.ClassDebugAdapter.java

Source

//Copyright (C) 2010  Novabit Informationssysteme GmbH
//
//This file is part of Nuclos.
//
//Nuclos is free software: you can redistribute it and/or modify
//it under the terms of the GNU Affero General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//Nuclos 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 Affero General Public License for more details.
//
//You should have received a copy of the GNU Affero General Public License
//along with Nuclos.  If not, see <http://www.gnu.org/licenses/>.

package org.nuclos.server.customcode.codegenerator;

import static org.objectweb.asm.Opcodes.*;

import java.util.HashMap;
import java.util.Map;

import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;

/**
 * An ASM ClassAdapter that logs each method body instruction line of the processed
 * class to log4j. This serves as poor men debugging facility for Nuclos rules.
 *
 * @author Thomas Pasch (javadoc)
 */
class ClassDebugAdapter extends ClassAdapter {

    private Map<String, String> varLookup = new HashMap<String, String>();
    private String className;
    private int headerCount;

    public ClassDebugAdapter(ClassVisitor cv, int headerCount) {
        super(cv);
        this.headerCount = headerCount;
    }

    public static byte[] weaveDebugInterceptors(byte[] bytes, int headerCount) {
        ClassReader cr = new ClassReader(bytes);
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
        ClassAdapter ca = new ClassDebugAdapter(cw, headerCount);
        cr.accept(ca, 0);
        return cw.toByteArray();
    }

    @Override
    public void visit(final int version, final int access, final String name, final String signature,
            final String superName, final String[] interfaces) {
        this.className = name;
        cv.visit(version, access, name, signature, superName, interfaces);
        insertDebugOutMethod();
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv;
        mv = cv.visitMethod(access, name, desc, signature, exceptions);
        if (mv != null && !name.equals("<init>")) {
            mv = new MethodDebugAdapter(className, varLookup, mv, name, desc);
        }
        return mv;
    }

    @Override
    public void visitEnd() {
        insertDebugResolveMethod();
        cv.visitEnd();
    }

    private void insertDebugOutMethod() {
        /*
        Old code:
          private static void __out(String prefix, String varKey, Object o) {
             String var = __resolve(varKey);
             String value = "<null>";
             if(o != null)
          value = o.toString();
             __LOG.info(prefix + var + "=> " + value);
          }
              
          New code:
          private static void __out(String prefix, String varKey, Object o) {
             String var = __resolve(varKey);
             RuleDebugLoggerSingleton.getInstance().log(prefix, var, o);      
          }
              
          ASM code:
        L0
        ALOAD 1
        INVOKESTATIC org/nuclos/server/customcode/codegenerator/ClassDebugAdapter.__resolve(Ljava/lang/String;)Ljava/lang/String;
        ASTORE 3
        L1
        INVOKESTATIC org/nuclos/server/customcode/codegenerator/RuleDebugLoggerSingleton.getInstance()Lorg/nuclos/server/customcode/codegenerator/RuleDebugLoggerSingleton;
        ALOAD 0
        ALOAD 3
        ALOAD 2
        INVOKEVIRTUAL org/nuclos/server/customcode/codegenerator/RuleDebugLoggerSingleton.log(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V
        L2
        RETURN
        L3
        LOCALVARIABLE prefix Ljava/lang/String; L0 L3 0
        LOCALVARIABLE varKey Ljava/lang/String; L0 L3 1
        LOCALVARIABLE o Ljava/lang/Object; L0 L3 2
        LOCALVARIABLE var Ljava/lang/String; L1 L3 3
        MAXSTACK = 4
        MAXLOCALS = 4       
         */
        MethodVisitor mv = cv.visitMethod(ACC_PRIVATE + ACC_STATIC, "__out",
                "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V", null, null);
        mv.visitCode();
        Label l0 = new Label();
        mv.visitLabel(l0);
        mv.visitVarInsn(ALOAD, 1);
        mv.visitMethodInsn(INVOKESTATIC, className, "__resolve", "(Ljava/lang/String;)Ljava/lang/String;");
        mv.visitVarInsn(ASTORE, 3);
        Label l1 = new Label();
        mv.visitLabel(l1);
        mv.visitMethodInsn(INVOKESTATIC, "org/nuclos/server/customcode/codegenerator/RuleDebugLoggerSingleton",
                "getInstance", "()Lorg/nuclos/server/customcode/codegenerator/RuleDebugLoggerSingleton;");
        mv.visitVarInsn(ALOAD, 0);
        mv.visitVarInsn(ALOAD, 3);
        mv.visitVarInsn(ALOAD, 2);
        mv.visitMethodInsn(INVOKEVIRTUAL, "org/nuclos/server/customcode/codegenerator/RuleDebugLoggerSingleton",
                "log", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V");
        Label l2 = new Label();
        mv.visitLabel(l2);
        mv.visitInsn(RETURN);
        mv.visitMaxs(4, 4);
        mv.visitEnd();
    }

    private void insertDebugResolveMethod() {
        /*
          private String __resolve(String methodAndIndex) {
             if(methodAndIndex.equals("method1"))
          return "var1";
             if(methodAndIndex.equals("method2"))
          return "var2";
             [ ... ]
             if(methodAndIndex.equals("methodX"))
          return "varX";
             return "-";
          }
         */
        MethodVisitor mv = cv.visitMethod(ACC_PRIVATE + ACC_STATIC, "__resolve",
                "(Ljava/lang/String;)Ljava/lang/String;", null, null);
        mv.visitCode();
        Label l0 = new Label();
        mv.visitLabel(l0);
        for (String mapKey : varLookup.keySet()) {
            mv.visitVarInsn(ALOAD, 0);
            mv.visitLdcInsn(mapKey);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z");
            Label l1 = new Label();
            mv.visitJumpInsn(IFEQ, l1);
            Label l2 = new Label();
            mv.visitLabel(l2);
            mv.visitLdcInsn(varLookup.get(mapKey));
            mv.visitInsn(ARETURN);
            mv.visitLabel(l1);
            mv.visitFrame(F_SAME, 0, null, 0, null);
        }
        mv.visitLdcInsn("-");
        mv.visitInsn(ARETURN);
        mv.visitMaxs(2, 2);
        mv.visitEnd();
    }

    private class MethodDebugAdapter extends MethodAdapter {
        private String className;
        private Map<String, String> varLookup = new HashMap<String, String>();
        private String methodName;
        private String desc;
        private int line;

        public MethodDebugAdapter(String className, Map<String, String> varLookup, MethodVisitor mv, String name,
                String desc) {
            super(mv);
            this.className = className;
            this.varLookup = varLookup;
            this.methodName = name;
            this.desc = desc;
        }

        @Override
        public void visitCode() {
            mv.visitCode();
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn(">>> " + className + "." + methodName + desc);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
        }

        @Override
        public void visitInsn(final int opcode) {
            if (opcode == RETURN) {
                mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv.visitLdcInsn("<<< " + className + "." + methodName + desc);
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
            }
            mv.visitInsn(opcode);
        }

        @Override
        public void visitVarInsn(final int opcode, final int var) {
            mv.visitVarInsn(opcode, var);
            switch (opcode) {
            case ISTORE:
                insertDebugOutput(var, ILOAD, "java/lang/Integer", "(I)Ljava/lang/Integer;");
                break;
            case LSTORE:
                insertDebugOutput(var, LLOAD, "java/lang/Long", "(J)Ljava/lang/Long;");
                break;
            case FSTORE:
                insertDebugOutput(var, FLOAD, "java/lang/Float", "(F)Ljava/lang/Float;");
                break;
            case DSTORE:
                insertDebugOutput(var, DLOAD, "java/lang/Double", "(D)Ljava/lang/Double;");
                break;
            case ASTORE:
                insertDebugOutput(var, ALOAD, null, null);
                break;
            }
        }

        @Override
        public void visitLocalVariable(String name, String desc, String signature, Label start, Label end,
                int index) {
            super.visitLocalVariable(name, desc, signature, start, end, index);
            if (index > 0)
                varLookup.put(methodName + index, getType(desc) + " " + name);
        }

        @Override
        public void visitLineNumber(int line, Label start) {
            super.visitLineNumber(line, start);
            this.line = line - headerCount;
        }

        @Override
        public void visitIincInsn(int var, int increment) {
            mv.visitIincInsn(var, increment);
            insertDebugOutput(var, ILOAD, "java/lang/Integer", "(I)Ljava/lang/Integer;");
        }

        private void insertDebugOutput(int var, int opcode, String valueClassName, String valueClassDesc) {
            // Method '__out' is now static, thus the stack size would change 
            // if we load the class instance here. (Thomas Pasch)
            // mv.visitVarInsn(ALOAD, 0);
            mv.visitLdcInsn("\t" + className + "." + methodName + desc + " [" + (this.line) + "] => ");
            mv.visitLdcInsn(methodName + var);
            mv.visitVarInsn(opcode, var);
            if (valueClassName != null && valueClassDesc != null)
                mv.visitMethodInsn(INVOKESTATIC, valueClassName, "valueOf", valueClassDesc);
            mv.visitMethodInsn(INVOKESTATIC, className, "__out",
                    "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V");
        }

        private String getType(String desc) {
            if (desc == null)
                return null;
            if (desc.startsWith("L"))
                return desc.substring(desc.lastIndexOf("/") + 1, desc.length() - 1);
            else if (desc.equals("I"))
                return "int";
            else if (desc.equals("F"))
                return "float";
            else if (desc.equals("J"))
                return "long";
            else if (desc.equals("D"))
                return "double";
            else if (desc.equals("Z"))
                return "boolean";
            return desc;
        }
    }

}