Java tutorial
/* * Copyright 2010 the original author or authors. * * 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.gradle.initialization; import org.apache.commons.lang.StringUtils; import org.gradle.api.LocationAwareException; import org.gradle.api.internal.ClassGenerator; import org.gradle.api.internal.Contextual; import org.gradle.groovy.scripts.ScriptSource; import org.gradle.util.ReflectionUtil; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * A {@link ClassGenerator} which mixes {@link org.gradle.api.LocationAwareException} into the supplied exception * types. Uses {@link ExceptionHelper} to do the work. */ public class ExceptionDecoratingClassGenerator implements ClassGenerator { private static final Map<Class<?>, Class<?>> GENERATED_CLASSES = new HashMap<Class<?>, Class<?>>(); public <T> T newInstance(Class<T> type, Object... parameters) { Throwable throwable = ReflectionUtil.newInstance(generate(type), parameters); throwable.setStackTrace(((Throwable) parameters[0]).getStackTrace()); return type.cast(throwable); } public <T> Class<? extends T> generate(Class<T> type) { Class generated = GENERATED_CLASSES.get(type); if (generated == null) { generated = doGenerate(type); GENERATED_CLASSES.put(type, generated); } return generated; } private <T> Class<? extends T> doGenerate(Class<T> type) { ClassWriter visitor = new ClassWriter(ClassWriter.COMPUTE_MAXS); String typeName = StringUtils.substringBeforeLast(type.getName(), ".") + ".LocationAware" + type.getSimpleName(); Type generatedType = Type.getType("L" + typeName.replaceAll("\\.", "/") + ";"); Type superclassType = Type.getType(type); visitor.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, generatedType.getInternalName(), null, superclassType.getInternalName(), new String[] { Type.getType(LocationAwareException.class).getInternalName() }); Type helperType = Type.getType(ExceptionHelper.class); Type throwableType = Type.getType(Throwable.class); Type scriptSourceType = Type.getType(ScriptSource.class); Type integerType = Type.getType(Integer.class); // GENERATE private ExceptionHelper helper; visitor.visitField(Opcodes.ACC_PRIVATE, "helper", helperType.getDescriptor(), null, null); // GENERATE <init>(<type> target, ScriptSource source, Integer lineNumber) String methodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { superclassType, scriptSourceType, integerType }); MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "<init>", methodDescriptor, null, new String[0]); methodVisitor.visitCode(); boolean noArgsConstructor; try { type.getConstructor(type); noArgsConstructor = false; } catch (NoSuchMethodException e) { try { type.getConstructor(); noArgsConstructor = true; } catch (NoSuchMethodException e1) { throw new IllegalArgumentException(String.format( "Cannot create subtype for exception '%s'. It needs a zero-args or copy constructor.", type.getName())); } } if (noArgsConstructor) { // GENERATE super() methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[0])); // END super() } else { // GENERATE super(target) methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { superclassType })); // END super(target) } // GENERATE helper = new ExceptionHelper(this, target, source, lineNumber) methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitTypeInsn(Opcodes.NEW, helperType.getInternalName()); methodVisitor.visitInsn(Opcodes.DUP); methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, helperType.getInternalName(), "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { throwableType, throwableType, scriptSourceType, integerType })); methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, generatedType.getInternalName(), "helper", helperType.getDescriptor()); // END helper = new ExceptionHelper(target) methodVisitor.visitInsn(Opcodes.RETURN); methodVisitor.visitMaxs(0, 0); methodVisitor.visitEnd(); // END <init>(<type> target, ScriptSource source, Integer lineNumber) for (Method method : ExceptionHelper.class.getDeclaredMethods()) { // GENERATE public <type> <method>() { return helper.<method>(); } methodDescriptor = Type.getMethodDescriptor(Type.getType(method.getReturnType()), new Type[0]); methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, method.getName(), methodDescriptor, null, new String[0]); methodVisitor.visitCode(); methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitFieldInsn(Opcodes.GETFIELD, generatedType.getInternalName(), "helper", helperType.getDescriptor()); methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, helperType.getInternalName(), method.getName(), methodDescriptor); methodVisitor.visitInsn(Opcodes.ARETURN); methodVisitor.visitMaxs(0, 0); methodVisitor.visitEnd(); // END public <type> <method>() { return helper.<method>(); } } visitor.visitEnd(); byte[] bytecode = visitor.toByteArray(); return (Class<T>) ReflectionUtil.invoke(type.getClassLoader(), "defineClass", new Object[] { typeName, bytecode, 0, bytecode.length }); } public static class ExceptionHelper { private final Throwable target; private final ScriptSource source; private final Integer lineNumber; public ExceptionHelper(Throwable owner, Throwable target, ScriptSource source, Integer lineNumber) { if (owner.getCause() == null) { owner.initCause(target.getCause()); } this.target = target; this.source = source; this.lineNumber = lineNumber; } public String getOriginalMessage() { return target.getMessage(); } public Throwable getOriginalException() { return target; } public String getLocation() { if (source == null) { return null; } String sourceMsg = StringUtils.capitalize(source.getDisplayName()); if (lineNumber == null) { return sourceMsg; } return String.format("%s line: %d", sourceMsg, lineNumber); } public String getMessage() { String location = getLocation(); String message = target.getMessage(); if (location == null && message == null) { return null; } if (location == null) { return message; } if (message == null) { return location; } return String.format("%s%n%s", location, message); } public ScriptSource getScriptSource() { return source; } public Integer getLineNumber() { return lineNumber; } public List<Throwable> getReportableCauses() { ArrayList<Throwable> causes = new ArrayList<Throwable>(); for (Throwable t = target.getCause(); t != null; t = t.getCause()) { causes.add(t); if (t.getClass().getAnnotation(Contextual.class) == null) { break; } } return causes; } } }