com.github.stokito.gag.agent.ThisHadBetterGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.github.stokito.gag.agent.ThisHadBetterGenerator.java

Source

/**
 * Copyright 2010 Google Inc.
 *
 * Licensed 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.
 */

package com.github.stokito.gag.agent;

import com.github.stokito.gag.annotation.enforceable.ThisHadBetterBe;
import com.github.stokito.gag.annotation.enforceable.ThisHadBetterNotBe;
import com.github.stokito.gag.enumeration.Property;
import com.github.stokito.gag.instrument.AnnotationStateError;
import com.github.stokito.gag.instrument.ClassGenerator;
import com.github.stokito.gag.instrument.info.AnnoInfo;
import com.github.stokito.gag.instrument.info.ClassInfo;
import com.github.stokito.gag.instrument.info.LocalVarInfo;
import com.github.stokito.gag.instrument.info.MethodInfo;
import com.github.stokito.gag.instrument.util.StringBuilderVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

import static com.github.stokito.gag.enumeration.Property.THE_BLUE_PILL;
import static com.github.stokito.gag.enumeration.Property.valueOf;
import static org.objectweb.asm.Opcodes.*;

public class ThisHadBetterGenerator extends ClassGenerator {

    private static final Type BETTER_BE_TYPE = Type.getType(ThisHadBetterBe.class);
    private static final Type BETTER_NOT_BE_TYPE = Type.getType(ThisHadBetterNotBe.class);

    private static final int TRUE = IFNE;
    private static final int FALSE = IFEQ;

    @Override
    protected boolean canInstrument(ClassInfo classInfo) {
        return classInfo.hasLocalVarAnnoAnywhere(BETTER_BE_TYPE)
                || classInfo.hasLocalVarAnnoAnywhere(BETTER_NOT_BE_TYPE);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String sig, String[] exceptions) {

        MethodVisitor mv = writer().visitMethod(access, name, desc, sig, exceptions);
        mv.visitCode();

        MethodInfo method = classInfo().getMethod(name, desc);
        for (LocalVarInfo param : method.getLocalVars()) {
            AnnoInfo anno = param.getAnnoFor(BETTER_BE_TYPE);
            if (anno != null) {
                visitThisHadBetter(mv, param, anno, true);
                setInstrumented(true);
            }
            anno = param.getAnnoFor(BETTER_NOT_BE_TYPE);
            if (anno != null) {
                visitThisHadBetter(mv, param, anno, false);
                setInstrumented(true);
            }
        }

        mv.visitEnd();
        return mv;
    }

    private static void visitThisHadBetter(MethodVisitor mv, LocalVarInfo param, AnnoInfo anno, boolean be) {

        Label okay = new Label();

        Property property = valueOf((String) anno.getValue("value"));
        switch (property) {
        case NEGATIVE:
        case POSITIVE:
        case ZERO:
            visitComparable(mv, be, property, param, okay);
            break;
        case NULL:
            visitNullCheck(mv, be, param, okay);
            break;
        case THE_BLUE_PILL:
        case THE_RED_PILL:
            visitPill(mv, be, property, param, okay);
            break;
        case THE_STOLEN_DEATH_STAR_PLANS:
            visitDeathStarPlans(mv, be, param, okay);
            break;
        default:
            throw new AnnotationStateError("Unsupported Property: " + property);
        }

        visitException(mv, param, " is" + (be ? " not " : " ") + format(property));
        mv.visitLabel(okay);
    }

    private static void visitComparable(MethodVisitor mv, boolean be, Property property, LocalVarInfo param,
            Label okay) {

        Type type = param.getType();
        int index = param.getIndex();

        switch (type.getSort()) {
        case Type.INT:
        case Type.SHORT:
            mv.visitVarInsn(ILOAD, index);
            break;
        case Type.LONG:
            mv.visitVarInsn(LLOAD, index);
            mv.visitInsn(LCONST_0);
            mv.visitInsn(LCMP);
            break;
        case Type.DOUBLE:
            mv.visitVarInsn(DLOAD, index);
            mv.visitInsn(DCONST_0);
            mv.visitInsn(DCMPG);
            break;
        case Type.FLOAT:
            mv.visitVarInsn(FLOAD, index);
            mv.visitInsn(FCONST_0);
            mv.visitInsn(FCMPG);
            break;
        case Type.OBJECT:
            if (type.equals(Type.getType(Integer.class))) {
                mv.visitVarInsn(ALOAD, index);
                mv.visitMethodInsn(INVOKEVIRTUAL, Type.getType(Integer.class).getInternalName(), "intValue", "()I");
            } else if (type.equals(Type.getType(Short.class))) {
                mv.visitVarInsn(ALOAD, index);
                mv.visitMethodInsn(INVOKEVIRTUAL, Type.getType(Short.class).getInternalName(), "intValue", "()I");
            } else if (type.equals(Type.getType(Long.class))) {
                mv.visitVarInsn(ALOAD, index);
                mv.visitMethodInsn(INVOKEVIRTUAL, Type.getType(Long.class).getInternalName(), "longValue", "()J");
                mv.visitInsn(LCONST_0);
                mv.visitInsn(LCMP);
            } else if (type.equals(Type.getType(Double.class))) {
                mv.visitVarInsn(ALOAD, index);
                mv.visitMethodInsn(INVOKEVIRTUAL, Type.getType(Double.class).getInternalName(), "doubleValue",
                        "()D");
                mv.visitInsn(DCONST_0);
                mv.visitInsn(DCMPG);
            } else if (type.equals(Type.getType(Float.class))) {
                mv.visitVarInsn(ALOAD, index);
                mv.visitMethodInsn(INVOKEVIRTUAL, Type.getType(Float.class).getInternalName(), "floatValue", "()F");
                mv.visitInsn(FCONST_0);
                mv.visitInsn(FCMPG);
            } else {
                throw new AnnotationStateError("Unsupported type: " + type);
            }
            break;
        default:
            throw new AnnotationStateError("Unsupported type: " + type);
        }

        switch (property) {
        case NEGATIVE:
            mv.visitJumpInsn(be ? IFLT : IFGE, okay);
            break;
        case POSITIVE:
            mv.visitJumpInsn(be ? IFGT : IFLE, okay);
            break;
        case ZERO:
            mv.visitJumpInsn(be ? IFEQ : IFNE, okay);
            break;
        }
    }

    private static void visitNullCheck(MethodVisitor mv, boolean be, LocalVarInfo param, Label okay) {
        switch (param.getType().getSort()) {
        case Type.ARRAY:
        case Type.OBJECT:
            mv.visitVarInsn(ALOAD, param.getIndex());
            break;
        default:
            throw new AnnotationStateError("Unsupported type: " + param.getType());
        }

        mv.visitJumpInsn(be ? IFNULL : IFNONNULL, okay);
    }

    /**
     * The given Property needs to be either {@link Property#THE_BLUE_PILL} or
     * {@link Property#THE_RED_PILL}.
     */
    private static void visitPill(MethodVisitor mv, boolean be, Property property, LocalVarInfo param, Label okay) {

        if (param.getType().getSort() != Type.OBJECT) {
            throw new AnnotationStateError("Unsupported type: " + param.getType());
        }

        // TODO: Also handle if parameter is null.

        Label notOkay = new Label();

        // See if the param type matches a Pill type
        mv.visitVarInsn(ALOAD, param.getIndex());
        mv.visitMethodInsn(INVOKEVIRTUAL, param.getType().getInternalName(), "getClass", "()Ljava/lang/Class;");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;");
        mv.visitLdcInsn("Pill|ThePill|.*[\\.$]Pill|.*[\\.$]ThePill");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "matches", "(Ljava/lang/String;)Z");

        if (be) {
            // If the param type does not match a Pill type, that's not okay, go to exception.
            mv.visitJumpInsn(FALSE, notOkay);
        } else {
            // If the param type does not match a Pill type, then that's okay, skip exception.
            mv.visitJumpInsn(FALSE, okay);
        }

        // At this point, the param type matches a Pill type.
        // So check if the param type is an enum type.
        mv.visitVarInsn(ALOAD, param.getIndex());
        mv.visitTypeInsn(INSTANCEOF, "java/lang/Enum");

        if (be) {
            // If the param type is not an enum, that's not okay, go to exception.
            mv.visitJumpInsn(FALSE, notOkay);
        } else {
            // If the param type is not an enum, that's okay, skip exception.
            mv.visitJumpInsn(FALSE, okay);
        }

        // Check that the Pill type has the property specified in the annotation.
        // First try to match on "BLUE" (or "RED").
        mv.visitVarInsn(ALOAD, param.getIndex());
        mv.visitTypeInsn(CHECKCAST, "java/lang/Enum");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Enum", "name", "()Ljava/lang/String;");
        mv.visitLdcInsn(property == THE_BLUE_PILL ? "BLUE" : "RED");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z");
        mv.visitJumpInsn(TRUE, be ? okay : notOkay);

        // Then try to see if the value ends with "BLUE_PILL" (or "RED_PILL").
        mv.visitVarInsn(ALOAD, param.getIndex());
        mv.visitTypeInsn(CHECKCAST, "java/lang/Enum");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Enum", "name", "()Ljava/lang/String;");
        mv.visitLdcInsn(property == THE_BLUE_PILL ? "BLUE_PILL" : "RED_PILL");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "endsWith", "(Ljava/lang/String;)Z");
        mv.visitJumpInsn(be ? TRUE : FALSE, okay);

        mv.visitLabel(notOkay);
    }

    private static void visitDeathStarPlans(MethodVisitor mv, boolean be, LocalVarInfo param, Label okay) {

        if (param.getType().getSort() != Type.OBJECT) {
            throw new AnnotationStateError("Unsupported type: " + param.getType());
        }

        Label notOkay = new Label();

        // See if the param type matches a DeathStarPlans type
        mv.visitVarInsn(ALOAD, param.getIndex());
        mv.visitMethodInsn(INVOKEVIRTUAL, param.getType().getInternalName(), "getClass", "()Ljava/lang/Class;");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;");
        mv.visitLdcInsn("DeathStarPlans|TheDeathStarPlans|.*[\\.$]DeathStarPlans|.*[\\.$]TheDeathStarPlans");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "matches", "(Ljava/lang/String;)Z");

        if (be) {
            // If the param type does not match a DeathStarPlans type, that's not okay, go to exception.
            mv.visitJumpInsn(FALSE, notOkay);
        } else {
            // If the param type does not match a DeathStarPlans type, then that's okay, skip exception.
            mv.visitJumpInsn(FALSE, okay);
        }

        // At this point, the param type matches a DeathStarPlans type.
        // So check if the param has an isStolen method.
        Label start = new Label();
        Label end = new Label();
        Label handler = new Label();
        mv.visitTryCatchBlock(start, end, handler, null);

        mv.visitLabel(start);
        mv.visitVarInsn(ALOAD, param.getIndex());
        mv.visitMethodInsn(INVOKEVIRTUAL, param.getType().getInternalName(), "getClass", "()Ljava/lang/Class;");
        mv.visitLdcInsn("isStolen");
        mv.visitInsn(ACONST_NULL);
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getMethod",
                "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;");

        mv.visitVarInsn(ALOAD, param.getIndex());
        mv.visitInsn(ACONST_NULL);
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
                "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
        mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z");

        mv.visitJumpInsn(be ? TRUE : FALSE, okay);

        mv.visitLabel(end);
        mv.visitJumpInsn(GOTO, notOkay);
        mv.visitLabel(handler);
        mv.visitInsn(POP);

        mv.visitLabel(notOkay);
    }

    private static String format(Property property) {
        switch (property) {
        case THE_BLUE_PILL:
            return "the Blue Pill";
        case THE_RED_PILL:
            return "the Red Pill";
        case THE_STOLEN_DEATH_STAR_PLANS:
            return "the stolen Death Star plans";
        default:
            return property.toString().toLowerCase().replace('_', ' ');
        }
    }

    private static void visitException(MethodVisitor mv, LocalVarInfo param, String message) {
        mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException");
        mv.visitInsn(DUP);

        StringBuilderVisitor sb = new StringBuilderVisitor(mv);
        sb.visitAppend(param.getName() + " = ");
        sb.visitAppend(param.getType(), param.getIndex());
        sb.visitAppend(message);
        sb.visitToString();

        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V");
        mv.visitInsn(ATHROW);
    }
}