/* * Copyright 2004-2015 the Seasar Foundation and the Others. * * 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.seasar.framework.aop.javassist; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import javassist.ClassPool; import javassist.CtClass; import org.seasar.framework.aop.InterType; import org.seasar.framework.util.MethodUtil; /** * コンポーネントのバイトコードをエンハンスするクラスです。 * * @author koichik */ public class EnhancedClassGenerator extends AbstractGenerator { /** * ターゲットクラス */ protected final Class targetClass; /** * エンハンスされるクラス名 */ protected final String enhancedClassName; /** * エンハンスされるクラス */ protected CtClass enhancedClass; /** * {@link EnhancedClassGenerator}を作成します。 * * @param classPool * @param targetClass * @param enhancedClassName */ public EnhancedClassGenerator(final ClassPool classPool, final Class targetClass, final String enhancedClassName) { super(classPool); this.targetClass = targetClass; this.enhancedClassName = enhancedClassName; setupClass(); setupInterface(); setupConstructor(); } /** * ターゲットのメソッドを作成します。 * * @param method * @param methodInvocationClassName */ public void createTargetMethod(final Method method, final String methodInvocationClassName) { createMethod(enhancedClass, method, createTargetMethodSource(method, methodInvocationClassName)); } /** * superクラスのメソッドを呼び出すためのメソッドを作成します。 * * @param method * @param invokeSuperMethodName */ public void createInvokeSuperMethod(final Method method, final String invokeSuperMethodName) { createMethod(enhancedClass, method.getModifiers(), method.getReturnType(), invokeSuperMethodName, method.getParameterTypes(), method.getExceptionTypes(), createInvokeSuperMethodSource(method)); } /** * {@link InterType}を適用します。 * * @param interType */ public void applyInterType(final InterType interType) { interType.introduce(targetClass, enhancedClass); } /** * CtClassをClassに変換します。 * * @param classLoader * @return */ public Class toClass(final ClassLoader classLoader) { final Class clazz = toClass(classLoader, enhancedClass); enhancedClass.detach(); enhancedClass = null; return clazz; } /** * CtClassをセットアップします。 */ public void setupClass() { final Class superClass = (targetClass.isInterface()) ? Object.class : targetClass; enhancedClass = createCtClass(enhancedClassName, superClass); } /** * インターフェース用のセットアップを行ないます。 */ public void setupInterface() { if (targetClass.isInterface()) { setInterface(enhancedClass, targetClass); } } /** * {@link Constructor}のセットアップを行ないます。 */ public void setupConstructor() { final Constructor[] constructors = targetClass .getDeclaredConstructors(); if (constructors.length == 0) { createDefaultConstructor(enhancedClass); } else { for (int i = 0; i < constructors.length; ++i) { final int modifier = constructors[i].getModifiers(); final Package pkg = targetClass.getPackage(); if (Modifier.isPublic(modifier) || Modifier.isProtected(modifier) || (!Modifier.isPrivate(modifier) && !targetClass.getName().startsWith("java.") && (pkg == null || !pkg .isSealed()))) { createConstructor(enhancedClass, constructors[i]); } } } } /** * ターゲットメソッド用のソースコードを作成します。 * * @param method * @param methodInvocationClassName * @return ターゲットメソッド用のソースコード */ public static String createTargetMethodSource(final Method method, final String methodInvocationClassName) { final StringBuffer buf = new StringBuffer(200); buf.append("Object result = new ").append(methodInvocationClassName) .append("(this, $args).proceed();"); final Class returnType = method.getReturnType(); if (returnType.equals(void.class)) { buf.append("return;"); } else if (returnType.isPrimitive()) { buf.append("return ($r) ((result == null) ? "); if (returnType.equals(boolean.class)) { buf.append("false : "); } else { buf.append("0 : "); } buf.append(fromObject(returnType, "result")).append(");"); } else { buf.append("return ($r) result;"); } String code = new String(buf); final Class[] exceptionTypes = normalizeExceptionTypes(method .getExceptionTypes()); if (exceptionTypes.length != 1 || !exceptionTypes[0].equals(Throwable.class)) { code = aroundTryCatchBlock(exceptionTypes, code); } return "{" + code + "}"; } /** * superクラスのメソッドを呼び出すためのソースコードを作成します。 * * @param method * @return superクラスのメソッドを呼び出すためのソースコード */ public static String createInvokeSuperMethodSource(final Method method) { if (MethodUtil.isDefaultMethod(method)) { System.out.println("{" + "return ($r) " + method.getDeclaringClass().getName() + ".super." + method.getName() + "($$);" + "}"); return "{" + "return ($r) " + method.getDeclaringClass().getName() + ".super." + method.getName() + "($$);" + "}"; } return "{" + "return ($r) super." + method.getName() + "($$);" + "}"; } /** * 例外の型を正規化します。 * * @param exceptionTypes * @return */ public static Class[] normalizeExceptionTypes(final Class[] exceptionTypes) { final List list = new LinkedList(); outer: for (int i = 0; i < exceptionTypes.length; ++i) { final Class currentException = exceptionTypes[i]; final ListIterator it = list.listIterator(); while (it.hasNext()) { final Class comparisonException = (Class) it.next(); if (comparisonException.isAssignableFrom(currentException)) { continue outer; } if (currentException.isAssignableFrom(comparisonException)) { it.remove(); } } list.add(currentException); } return (Class[]) list.toArray(new Class[list.size()]); } /** * 元のソースコードをtry, cacheで囲んだソースコードを返します。 * * @param exceptionTypes * @param code * @return 元のソースコードをtry, cacheで囲んだソースコード */ public static String aroundTryCatchBlock(final Class[] exceptionTypes, final String code) { final TryBlockSupport tryBlock = new TryBlockSupport(code); boolean needRuntimeException = true; boolean needError = true; for (int i = 0; i < exceptionTypes.length; ++i) { final Class exceptionType = exceptionTypes[i]; tryBlock.addCatchBlock(exceptionType, "throw e;"); if (exceptionType.equals(RuntimeException.class)) { needRuntimeException = false; } if (exceptionType.equals(Error.class)) { needError = false; } } if (needRuntimeException) { tryBlock.addCatchBlock(RuntimeException.class, "throw e;"); } if (needError) { tryBlock.addCatchBlock(Error.class, "throw e;"); } tryBlock.addCatchBlock(Throwable.class, "throw new java.lang.reflect.UndeclaredThrowableException(e);"); return tryBlock.getSourceCode(); } }