/* * JBoss, Home of Professional Open Source * Copyright 2014, Red Hat, Inc., and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * 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 org.jboss.weld.bean.proxy; import static org.jboss.classfilewriter.util.DescriptorUtils.makeDescriptor; import static org.jboss.classfilewriter.util.DescriptorUtils.methodDescriptor; import static org.jboss.weld.util.bytecode.BytecodeUtils.DOUBLE_CLASS_DESCRIPTOR; import static org.jboss.weld.util.bytecode.BytecodeUtils.LONG_CLASS_DESCRIPTOR; import static org.jboss.weld.util.bytecode.BytecodeUtils.VOID_CLASS_DESCRIPTOR; import org.jboss.classfilewriter.ClassMethod; import org.jboss.classfilewriter.code.BranchEnd; import org.jboss.classfilewriter.code.CodeAttribute; import org.jboss.classfilewriter.code.ExceptionHandler; import org.jboss.weld.bean.proxy.InterceptionDecorationContext.Stack; /** * Generates bytecode that wraps {@link #doWork(CodeAttribute, ClassMethod)} within {@link InterceptionDecorationContext#startInterceptorContextIfNotEmpty()} * and {@link InterceptionDecorationContext#endInterceptorContext()} * * @author Stuart Douglas * @author Jozef Hartinger * */ abstract class RunWithinInterceptionDecorationContextGenerator { static final String INTERCEPTION_DECORATION_CONTEXT_CLASS_NAME = InterceptionDecorationContext.class.getName(); static final String START_INTERCEPTOR_CONTEXT_IF_NOT_EMPTY_METHOD_NAME = "startIfNotEmpty"; static final String START_INTERCEPTOR_CONTEXT_IF_NOT_ON_TOP_METHOD_NAME = "startIfNotOnTop"; static final String END_INTERCEPTOR_CONTEXT_METHOD_NAME = "end"; private static final String STACK_DESCRIPTOR = makeDescriptor(Stack.class); private static final String EMPTY_PARENTHESES = "()"; private static final String RETURNS_STACK_DESCRIPTOR = EMPTY_PARENTHESES + STACK_DESCRIPTOR; static final String START_INTERCEPTOR_CONTEXT_IF_NOT_ON_TOP_METHOD_SIGNATURE = methodDescriptor( new String[] { makeDescriptor(CombinedInterceptorAndDecoratorStackMethodHandler.class) }, STACK_DESCRIPTOR); private final ClassMethod classMethod; private final CodeAttribute b; private final ProxyFactory<?> factory; RunWithinInterceptionDecorationContextGenerator(ClassMethod classMethod, ProxyFactory<?> factory) { this.classMethod = classMethod; this.b = classMethod.getCodeAttribute(); this.factory = factory; } abstract void doWork(CodeAttribute b, ClassMethod method); abstract void doReturn(CodeAttribute b, ClassMethod method); void startIfNotEmpty(CodeAttribute b, ClassMethod method) { b.invokestatic(INTERCEPTION_DECORATION_CONTEXT_CLASS_NAME, START_INTERCEPTOR_CONTEXT_IF_NOT_EMPTY_METHOD_NAME, RETURNS_STACK_DESCRIPTOR); // store the outcome so that we know later whether to end the context or not storeToLocalVariable(0); } void startIfNotOnTop(CodeAttribute b, ClassMethod method) { b.aload(0); factory.getMethodHandlerField(method.getClassFile(), b); b.dup(); // if handler != null (may happen inside constructor calls) final BranchEnd handlerNull = b.ifnull(); b.invokestatic(INTERCEPTION_DECORATION_CONTEXT_CLASS_NAME, START_INTERCEPTOR_CONTEXT_IF_NOT_ON_TOP_METHOD_NAME, START_INTERCEPTOR_CONTEXT_IF_NOT_ON_TOP_METHOD_SIGNATURE); final BranchEnd endOfIfStatement = b.gotoInstruction(); b.branchEnd(handlerNull); // else started = false // keeping null handler on top of stack b.branchEnd(endOfIfStatement); storeToLocalVariable(0); } void withinCatchBlock(CodeAttribute b, ClassMethod method) { final ExceptionHandler start = b.exceptionBlockStart(Throwable.class.getName()); doWork(b, method); // end the interceptor context, everything was fine endIfStarted(b, method); // jump over the catch block BranchEnd gotoEnd = b.gotoInstruction(); // create catch block b.exceptionBlockEnd(start); b.exceptionHandlerStart(start); // end the interceptor context if there was an exception endIfStarted(b, method); b.athrow(); // update the correct address to jump over the catch block b.branchEnd(gotoEnd); doReturn(b, method); } /** * Ends interception context if it was previously stated. This is indicated by a local variable with index 0. */ void endIfStarted(CodeAttribute b, ClassMethod method) { b.aload(getLocalVariableIndex(0)); b.dup(); final BranchEnd ifnotnull = b.ifnull(); b.checkcast(Stack.class); b.invokevirtual(Stack.class.getName(), END_INTERCEPTOR_CONTEXT_METHOD_NAME, EMPTY_PARENTHESES + VOID_CLASS_DESCRIPTOR); BranchEnd ifnull = b.gotoInstruction(); b.branchEnd(ifnotnull); b.pop(); // remove null Stack b.branchEnd(ifnull); } /** * Generates bytecode that invokes {@link InterceptionDecorationContext#startIfNotEmpty()} and stores the result in a local variable. Then, the bytecode * generated by {@link #doWork(CodeAttribute, ClassMethod)} is added. Lastly, bytecode that conditionally calls {@link InterceptionDecorationContext} based * on the value of the local variable is added. This is done within a catch block so that the context is ended no matter if the bytecode generated by * {@link #doWork(CodeAttribute, ClassMethod)} yields an exception or not. */ void runStartIfNotEmpty() { startIfNotEmpty(b, classMethod); withinCatchBlock(b, classMethod); } /** * Generates bytecode that loads the "methodHandler" field, invokes * {@link InterceptionDecorationContext#startIfNotOnTop(CombinedInterceptorAndDecoratorStackMethodHandler)} and stores the result in a local variable. Then, * the bytecode generated by {@link #doWork(CodeAttribute, ClassMethod)} is added. Lastly, bytecode that conditionally calls * {@link InterceptionDecorationContext} based on the value of the local variable is added. This is done within a catch block so that the context is ended * no matter if the bytecode generated by {@link #doWork(CodeAttribute, ClassMethod)} yields an exception or not. */ void runStartIfNotOnTop() { startIfNotOnTop(b, classMethod); withinCatchBlock(b, classMethod); } void storeToLocalVariable(int i) { b.astore(getLocalVariableIndex(0)); } /** * Gets the index of a local variable (the first index after method parameters). Indexes start with 0. */ private int getLocalVariableIndex(int i) { int index = classMethod.isStatic() ? 0 : 1; for (String type : classMethod.getParameters()) { if (type.equals(DOUBLE_CLASS_DESCRIPTOR) || type.equals(LONG_CLASS_DESCRIPTOR)) { index += 2; } else { index++; } } return index + i; } }