# MethodInstrumenter.java

    /*******************************************************************************
     * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
     * All rights reserved. This program and the accompanying materials
     * are made available under the terms of the Eclipse Public License v1.0
     * which accompanies this distribution, and is available at
     * http://www.eclipse.org/legal/epl-v10.html
     *
     * Contributors:
     *    Marc R. Hoffmann - initial API and implementation
     *
     *******************************************************************************/
    package org.jacoco.core.internal.instr;

    import org.jacoco.core.internal.flow.MethodProbesVisitor;
    import org.jacoco.core.internal.flow.LabelInfo;
    import org.objectweb.asm.Label;
    import org.objectweb.asm.MethodVisitor;
    import org.objectweb.asm.Opcodes;

    /**
     * This method adapter inserts probes as requested by the
     * {@link MethodProbesVisitor} events.
     */
    class MethodInstrumenter extends MethodProbesVisitor {

    	private final IProbeInserter probeInserter;
    	private final IFrameInserter frameInserter;

    	/**
    	 * Create a new instrumenter instance for the given method.
    	 *
    	 * @param mv
    	 *            next method visitor in the chain
    	 * @param probeInserter
    	 *            call-back to insert probes where required
    	 * @param frameInserter
    	 *            call-back to insert additional frames where required
    	 */
    	public MethodInstrumenter(final MethodVisitor mv,
    			final IProbeInserter probeInserter,
    			final IFrameInserter frameInserter) {
    		super(mv);
    		this.probeInserter = probeInserter;
    		this.frameInserter = frameInserter;
    	}

    	// === IMethodProbesVisitor ===

    	@Override
    	public void visitProbe(final int probeId) {
    		probeInserter.insertProbe(probeId);
    	}

    	@Override
    	public void visitInsnWithProbe(final int opcode, final int probeId) {
    		probeInserter.insertProbe(probeId);
    		mv.visitInsn(opcode);
    	}

    	@Override
    	public void visitJumpInsnWithProbe(final int opcode, final Label label,
    			final int probeId) {
    		if (opcode == Opcodes.GOTO) {
    			probeInserter.insertProbe(probeId);
    			mv.visitJumpInsn(Opcodes.GOTO, label);
    		} else {
    			final Label intermediate = new Label();
    			mv.visitJumpInsn(getInverted(opcode), intermediate);
    			probeInserter.insertProbe(probeId);
    			mv.visitJumpInsn(Opcodes.GOTO, label);
    			mv.visitLabel(intermediate);
    			frameInserter.insertFrame();
    		}
    	}

    	private int getInverted(final int opcode) {
    		switch (opcode) {
    		case Opcodes.IFEQ:
    			return Opcodes.IFNE;
    		case Opcodes.IFNE:
    			return Opcodes.IFEQ;
    		case Opcodes.IFLT:
    			return Opcodes.IFGE;
    		case Opcodes.IFGE:
    			return Opcodes.IFLT;
    		case Opcodes.IFGT:
    			return Opcodes.IFLE;
    		case Opcodes.IFLE:
    			return Opcodes.IFGT;
    		case Opcodes.IF_ICMPEQ:
    			return Opcodes.IF_ICMPNE;
    		case Opcodes.IF_ICMPNE:
    			return Opcodes.IF_ICMPEQ;
    		case Opcodes.IF_ICMPLT:
    			return Opcodes.IF_ICMPGE;
    		case Opcodes.IF_ICMPGE:
    			return Opcodes.IF_ICMPLT;
    		case Opcodes.IF_ICMPGT:
    			return Opcodes.IF_ICMPLE;
    		case Opcodes.IF_ICMPLE:
    			return Opcodes.IF_ICMPGT;
    		case Opcodes.IF_ACMPEQ:
    			return Opcodes.IF_ACMPNE;
    		case Opcodes.IF_ACMPNE:
    			return Opcodes.IF_ACMPEQ;
    		case Opcodes.IFNULL:
    			return Opcodes.IFNONNULL;
    		case Opcodes.IFNONNULL:
    			return Opcodes.IFNULL;
    		}
    		throw new IllegalArgumentException();
    	}

    	@Override
    	public void visitTableSwitchInsnWithProbes(final int min, final int max,
    			final Label dflt, final Label[] labels) {
    		// 1. Calculate intermediate labels:
    		LabelInfo.resetDone(dflt);
    		LabelInfo.resetDone(labels);
    		final Label newDflt = createIntermediate(dflt);
    		final Label[] newLabels = createIntermediates(labels);
    		mv.visitTableSwitchInsn(min, max, newDflt, newLabels);

    		// 2. Insert probes:
    		insertIntermediateProbes(dflt, labels);
    	}

    	@Override
    	public void visitLookupSwitchInsnWithProbes(final Label dflt,
    			final int[] keys, final Label[] labels) {
    		// 1. Calculate intermediate labels:
    		LabelInfo.resetDone(dflt);
    		LabelInfo.resetDone(labels);
    		final Label newDflt = createIntermediate(dflt);
    		final Label[] newLabels = createIntermediates(labels);
    		mv.visitLookupSwitchInsn(newDflt, keys, newLabels);

    		// 2. Insert probes:
    		insertIntermediateProbes(dflt, labels);
    	}

    	private Label[] createIntermediates(final Label[] labels) {
    		final Label[] intermediates = new Label[labels.length];
    		for (int i = 0; i < labels.length; i++) {
    			intermediates[i] = createIntermediate(labels[i]);
    		}
    		return intermediates;
    	}

    	private Label createIntermediate(final Label label) {
    		final Label intermediate;
    		if (LabelInfo.getProbeId(label) == LabelInfo.NO_PROBE) {
    			intermediate = label;
    		} else {
    			if (LabelInfo.isDone(label)) {
    				intermediate = LabelInfo.getIntermediateLabel(label);
    			} else {
    				intermediate = new Label();
    				LabelInfo.setIntermediateLabel(label, intermediate);
    				LabelInfo.setDone(label);
    			}
    		}
    		return intermediate;
    	}

    	private void insertIntermediateProbe(final Label label) {
    		final int probeId = LabelInfo.getProbeId(label);
    		if (probeId != LabelInfo.NO_PROBE && !LabelInfo.isDone(label)) {
    			mv.visitLabel(LabelInfo.getIntermediateLabel(label));
    			frameInserter.insertFrame();
    			probeInserter.insertProbe(probeId);
    			mv.visitJumpInsn(Opcodes.GOTO, label);
    			LabelInfo.setDone(label);
    		}
    	}

    	private void insertIntermediateProbes(final Label dflt, final Label[] labels) {
    		LabelInfo.resetDone(dflt);
    		LabelInfo.resetDone(labels);
    		insertIntermediateProbe(dflt);
    		for (final Label l : labels) {
    			insertIntermediateProbe(l);
    		}
    	}

    }