diff options
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/lombok/bytecode/ClassFileMetaData.java | 362 | ||||
-rw-r--r-- | src/core/lombok/bytecode/PostCompilerApp.java | 152 | ||||
-rw-r--r-- | src/core/lombok/bytecode/SneakyThrowsRemover.java | 206 | ||||
-rw-r--r-- | src/core/lombok/core/DiagnosticsReceiver.java | 40 | ||||
-rw-r--r-- | src/core/lombok/core/LombokNode.java | 8 | ||||
-rw-r--r-- | src/core/lombok/core/PostCompiler.java | 83 | ||||
-rw-r--r-- | src/core/lombok/core/PostCompilerTransformation.java | 26 | ||||
-rw-r--r-- | src/core/lombok/javac/apt/InterceptingJavaFileManager.java | 104 | ||||
-rw-r--r-- | src/core/lombok/javac/apt/InterceptingJavaFileObject.java | 106 | ||||
-rw-r--r-- | src/core/lombok/javac/apt/MessagerDiagnosticsReceiver.java | 43 | ||||
-rw-r--r-- | src/core/lombok/javac/apt/Processor.java | 38 |
11 files changed, 1159 insertions, 9 deletions
diff --git a/src/core/lombok/bytecode/ClassFileMetaData.java b/src/core/lombok/bytecode/ClassFileMetaData.java new file mode 100644 index 00000000..693a9ad5 --- /dev/null +++ b/src/core/lombok/bytecode/ClassFileMetaData.java @@ -0,0 +1,362 @@ +/* + * Copyright © 2010 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.bytecode; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ClassFileMetaData { + + private static final byte UTF8 = 1; + private static final byte INTEGER = 3; + private static final byte FLOAT = 4; + private static final byte LONG = 5; + private static final byte DOUBLE = 6; + private static final byte CLASS = 7; + private static final byte STRING = 8; + private static final byte FIELD = 9; + private static final byte METHOD = 10; + private static final byte IMETHOD = 11; + private static final byte NAME_TYPE = 12; + + private static final int NOT_FOUND = -1; + private static final int START_OF_CONSTANT_POOL = 8; + + private final byte[] byteCode; + + private final int maxPoolSize; + private final int[] offsets; + private final byte[] types; + private final String[] utf8s; + private final int endOfPool; + + public ClassFileMetaData(byte[] byteCode) { + this.byteCode = byteCode; + + maxPoolSize = readValue(START_OF_CONSTANT_POOL); + offsets = new int[maxPoolSize]; + types = new byte[maxPoolSize]; + utf8s = new String[maxPoolSize]; + int position = 10; + for (int i = 1; i < maxPoolSize; i++) { + byte type = byteCode[position]; + types[i] = type; + position++; + offsets[i] = position; + switch (type) { + case UTF8: + int length = readValue(position); + position += 2; + utf8s[i] = decodeString(position, length); + position += length; + break; + case CLASS: + case STRING: + position += 2; + break; + case INTEGER: + case FLOAT: + case FIELD: + case METHOD: + case IMETHOD: + case NAME_TYPE: + position += 4; + break; + case LONG: + case DOUBLE: + position += 8; + i++; + break; + case 0: + break; + default: + throw new AssertionError("Unknown constant pool type " + type); + } + } + endOfPool = position; + } + + private String decodeString(int pos, int size) { + int end = pos + size; + + // the resulting string might be smaller + StringBuilder result = new StringBuilder(size); + while (pos < end) { + int first = (byteCode[pos++] & 0xFF); + if (first < 0x80) { + result.append((char)first); + } else if ((first & 0xE0) == 0xC0) { + int x = (first & 0x1F) << 6; + int y = (byteCode[pos++] & 0x3F); + result.append((char)(x | y)); + } else { + int x = (first & 0x0F) << 12; + int y = (byteCode[pos++] & 0x3F) << 6; + int z = (byteCode[pos++] & 0x3F); + result.append((char)(x | y | z)); + } + } + return result.toString(); + } + + public boolean containsUtf8(String value) { + return findUtf8(value) != NOT_FOUND; + } + + public boolean usesClass(String className) { + return findClass(className) != NOT_FOUND; + } + + public boolean usesField(String className, String fieldName) { + int classIndex = findClass(className); + if (classIndex == NOT_FOUND) return false; + int fieldNameIndex = findUtf8(fieldName); + if (fieldNameIndex == NOT_FOUND) return false; + + for (int i = 1; i < maxPoolSize; i++) { + if (types[i] == FIELD && readValue(offsets[i]) == classIndex) { + int nameAndTypeIndex = readValue(offsets[i] + 2); + if (readValue(offsets[nameAndTypeIndex]) == fieldNameIndex) return true; + } + } + return false; + } + + public boolean usesMethod(String className, String methodName) { + int classIndex = findClass(className); + if (classIndex == NOT_FOUND) return false; + int methodNameIndex = findUtf8(methodName); + if (methodNameIndex == NOT_FOUND) return false; + + for (int i = 1; i < maxPoolSize; i++) { + if (isMethod(i) && readValue(offsets[i]) == classIndex) { + int nameAndTypeIndex = readValue(offsets[i] + 2); + if (readValue(offsets[nameAndTypeIndex]) == methodNameIndex) return true; + } + } + return false; + } + + public boolean usesMethod(String className, String methodName, String descriptor) { + int classIndex = findClass(className); + if (classIndex == NOT_FOUND) return false; + int nameAndTypeIndex = findNameAndType(methodName, descriptor); + if (nameAndTypeIndex == NOT_FOUND) return false; + + for (int i = 1; i < maxPoolSize; i++) { + if (isMethod(i) && + readValue(offsets[i]) == classIndex && + readValue(offsets[i] + 2) == nameAndTypeIndex) return true; + } + return false; + } + + public boolean containsStringConstant(String value) { + int index = findUtf8(value); + if (index == NOT_FOUND) return false; + for (int i = 1; i < maxPoolSize; i++) { + if (types[i] == STRING && readValue(offsets[i]) == index) return true; + } + return false; + } + + public boolean containsLong(long value) { + for (int i = 1; i < maxPoolSize; i++) { + if (types[i] == LONG && readLong(i) == value) return true; + } + return false; + } + + public boolean containsDouble(double value) { + boolean isNan = Double.isNaN(value); + for (int i = 1; i < maxPoolSize; i++) { + if (types[i] == DOUBLE) { + double d = readDouble(i); + if (d == value || (isNan && Double.isNaN(d))) return true; + } + } + return false; + } + + public boolean containsInteger(int value) { + for (int i = 1; i < maxPoolSize; i++) { + if (types[i] == INTEGER && readInteger(i) == value) return true; + } + return false; + } + + public boolean containsFloat(float value) { + boolean isNan = Float.isNaN(value); + for (int i = 1; i < maxPoolSize; i++) { + if (types[i] == FLOAT) { + float f = readFloat(i); + if (f == value || (isNan && Float.isNaN(f))) return true; + } + } + return false; + } + + private long readLong(int index) { + int pos = offsets[index]; + return ((long)read32(pos)) << 32 | read32(pos + 4); + } + + private double readDouble(int index) { + int pos = offsets[index]; + long bits = ((long)read32(pos)) << 32 | (read32(pos + 4) & 0x00000000FFFFFFFF); + return Double.longBitsToDouble(bits); + } + + private long readInteger(int index) { + return read32(offsets[index]); + } + + private float readFloat(int index) { + return Float.intBitsToFloat(read32(offsets[index])); + } + + private int read32(int pos) { + return (byteCode[pos] & 0xFF) << 24 | (byteCode[pos + 1] & 0xFF) << 16 | (byteCode[pos + 2] & 0xFF) << 8 | (byteCode[pos + 3] &0xFF); + } + + public String getClassName() { + return getClassName(readValue(endOfPool + 2)); + } + + public String getSuperClassName() { + return getClassName(readValue(endOfPool + 4)); + } + + public List<String> getInterfaces() { + int size = readValue(endOfPool + 6); + if (size == 0) return Collections.emptyList(); + + List<String> result = new ArrayList<String>(); + for (int i = 0; i < size; i++) { + result.add(getClassName(readValue(endOfPool + 8 + (i * 2)))); + } + return result; + } + + public String poolContent() { + StringBuilder result = new StringBuilder(); + for (int i = 1; i < maxPoolSize; i++) { + result.append(String.format("#%02d: ", i)); + int pos = offsets[i]; + switch(types[i]) { + case UTF8: + result.append("Utf8 ").append(utf8s[i]); + break; + case CLASS: + result.append("Class ").append(getClassName(i)); + break; + case STRING: + result.append("String \"").append(utf8s[readValue(pos)]).append("\""); + break; + case INTEGER: + result.append("int ").append(readInteger(i)); + break; + case FLOAT: + result.append("float ").append(readFloat(i)); + break; + case FIELD: + appendAccess(result.append("Field "), i); + break; + case METHOD: + case IMETHOD: + appendAccess(result.append("Method "), i); + break; + case NAME_TYPE: + appendNameAndType(result.append("Name&Type "), i); + break; + case LONG: + result.append("long ").append(readLong(i)); + break; + case DOUBLE: + result.append("double ").append(readDouble(i)); + break; + case 0: + result.append("(cont.)"); + break; + } + result.append("\n"); + } + return result.toString(); + } + + private void appendAccess(StringBuilder result, int index) { + int pos = offsets[index]; + result.append(getClassName(readValue(pos))).append("."); + appendNameAndType(result, readValue(pos + 2)); + } + + private void appendNameAndType(StringBuilder result, int index) { + int pos = offsets[index]; + result.append(utf8s[readValue(pos)]).append(":").append(utf8s[readValue(pos + 2)]); + } + + private String getClassName(int classIndex) { + if (classIndex < 1) return null; + return utf8s[readValue(offsets[classIndex])]; + } + + private boolean isMethod(int i) { + byte type = types[i]; + return type == METHOD || type == IMETHOD; + } + + private int findNameAndType(String name, String descriptor) { + int nameIndex = findUtf8(name); + if (nameIndex == NOT_FOUND) return NOT_FOUND; + int descriptorIndex = findUtf8(descriptor); + if (descriptorIndex == NOT_FOUND) return NOT_FOUND; + for (int i = 1; i < maxPoolSize; i++) { + if (types[i] == NAME_TYPE && + readValue(offsets[i]) == nameIndex && + readValue(offsets[i] + 2) == descriptorIndex) return i; + } + return NOT_FOUND; + } + + private int findUtf8(String value) { + for (int i = 1; i < maxPoolSize; i++) { + if (value.equals(utf8s[i])) { + return i; + } + } + return NOT_FOUND; + } + + private int findClass(String className) { + int index = findUtf8(className); + if (index == -1) return NOT_FOUND; + for (int i = 1; i < maxPoolSize; i++) { + if (types[i] == CLASS && readValue(offsets[i]) == index) return i; + } + return NOT_FOUND; + } + + private int readValue(int position) { + return ((byteCode[position] & 0xFF) << 8) | (byteCode[position + 1] & 0xFF); + } +}
\ No newline at end of file diff --git a/src/core/lombok/bytecode/PostCompilerApp.java b/src/core/lombok/bytecode/PostCompilerApp.java new file mode 100644 index 00000000..e1b177ba --- /dev/null +++ b/src/core/lombok/bytecode/PostCompilerApp.java @@ -0,0 +1,152 @@ +/* + * Copyright © 2010 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.bytecode; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import lombok.core.DiagnosticsReceiver; +import lombok.core.LombokApp; +import lombok.core.PostCompiler; + +import org.mangosdk.spi.ProviderFor; + +import com.zwitserloot.cmdreader.CmdReader; +import com.zwitserloot.cmdreader.Description; +import com.zwitserloot.cmdreader.InvalidCommandLineException; +import com.zwitserloot.cmdreader.Mandatory; +import com.zwitserloot.cmdreader.Sequential; +import com.zwitserloot.cmdreader.Shorthand; + +@ProviderFor(LombokApp.class) +public class PostCompilerApp implements LombokApp { + @Override public List<String> getAppAliases() { + return Arrays.asList("post", "postcompile"); + } + + @Override public String getAppDescription() { + return "Runs registered post compiler handlers to against existing class files, modifying them in the process."; + } + + @Override public String getAppName() { + return "post-compile"; + } + + public static class CmdArgs { + @Sequential + @Mandatory + @Description("paths to class files to be converted. If a directory is named, all files (recursively) in that directory will be converted.") + private List<String> classFiles = new ArrayList<String>(); + + @Shorthand("v") + @Description("Prints lots of status information as the post compiler runs") + boolean verbose = false; + + @Shorthand({"h", "?"}) + @Description("Shows this help text") + boolean help = false; + } + + @Override public int runApp(List<String> raw) throws Exception { + CmdReader<CmdArgs> reader = CmdReader.of(CmdArgs.class); + CmdArgs args; + try { + args = reader.make(raw.toArray(new String[0])); + if (args.help) { + System.out.println(reader.generateCommandLineHelp("java -jar lombok.jar post-compile")); + return 0; + } + } catch (InvalidCommandLineException e) { + System.err.println(e.getMessage()); + System.err.println(reader.generateCommandLineHelp("java -jar lombok.jar post-compile")); + return 1; + } + + List<File> filesToProcess = new ArrayList<File>(); + for (String f : args.classFiles) addFiles(filesToProcess, f); + + int filesVisited = 0, filesTouched = 0; + for (File file : filesToProcess) { + if (!file.exists() || !file.isFile()) { + System.out.printf("Cannot find file '%s'\n", file); + continue; + } + filesVisited++; + if (args.verbose) System.out.println("Processing " + file.getAbsolutePath()); + byte[] original = readFile(file); + byte[] clone = original.clone(); + byte[] transformed = PostCompiler.applyTransformations(clone, file.toString(), DiagnosticsReceiver.CONSOLE); + if (clone != transformed && !Arrays.equals(clone, transformed)) { + filesTouched++; + if (args.verbose) System.out.println("Rewriting " + file.getAbsolutePath()); + writeFile(file, transformed); + } + } + + if (args.verbose) { + System.out.printf("Total files visited: %d total files changed: %d\n", filesVisited, filesTouched); + } + + return filesVisited == 0 ? 1 : 0; + } + + private void addFiles(List<File> filesToProcess, String f) { + File file = new File(f); + if (file.isDirectory()) { + addRecursively(filesToProcess, file); + } else { + filesToProcess.add(file); + } + } + + private void addRecursively(List<File> filesToProcess, File file) { + for (File f : file.listFiles()) { + if (f.isDirectory()) addRecursively(filesToProcess, f); + else if (f.getName().endsWith(".class")) filesToProcess.add(f); + } + } + + static byte[] readFile(File file) throws IOException { + byte[] buffer = new byte[1024]; + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + FileInputStream fileInputStream = new FileInputStream(file); + while (true) { + int read = fileInputStream.read(buffer); + if (read == -1) break; + bytes.write(buffer, 0, read); + } + fileInputStream.close(); + return bytes.toByteArray(); + } + + static void writeFile(File file, byte[] transformed) throws IOException { + FileOutputStream out = new FileOutputStream(file); + out.write(transformed); + out.close(); + } +} diff --git a/src/core/lombok/bytecode/SneakyThrowsRemover.java b/src/core/lombok/bytecode/SneakyThrowsRemover.java new file mode 100644 index 00000000..fde29924 --- /dev/null +++ b/src/core/lombok/bytecode/SneakyThrowsRemover.java @@ -0,0 +1,206 @@ +/* + * Copyright © 2010 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.bytecode; + +import java.util.concurrent.atomic.AtomicBoolean; + +import lombok.core.DiagnosticsReceiver; +import lombok.core.PostCompilerTransformation; + +import org.mangosdk.spi.ProviderFor; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodAdapter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.commons.JSRInlinerAdapter; + +@ProviderFor(PostCompilerTransformation.class) +public class SneakyThrowsRemover implements PostCompilerTransformation { + private static class FixedClassWriter extends ClassWriter { + FixedClassWriter(ClassReader classReader, int flags) { + super(classReader, flags); + } + + @Override protected String getCommonSuperClass(String type1, String type2) { + //By default, ASM will attempt to live-load the class types, which will fail if meddling with classes in an + //environment with custom classloaders, such as Equinox. It's just an optimization; returning Object is always legal. + try { + return super.getCommonSuperClass(type1, type2); + } catch (Exception e) { + return "java/lang/Object"; + } + } + } + + protected byte[] fixJSRInlining(byte[] byteCode) { + ClassReader reader = new ClassReader(byteCode); + ClassWriter writer = new FixedClassWriter(reader, 0); + + ClassVisitor visitor = new ClassAdapter(writer) { + @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + return new JSRInlinerAdapter(super.visitMethod(access, name, desc, signature, exceptions), access, name, desc, signature, exceptions); + } + }; + + reader.accept(visitor, 0); + return writer.toByteArray(); + } + + @Override public byte[] applyTransformations(byte[] original, String className, DiagnosticsReceiver diagnostics) { + if (!new ClassFileMetaData(original).usesMethod("lombok/Lombok", "sneakyThrow")) return null; + + byte[] fixedByteCode = fixJSRInlining(original); + + ClassReader reader = new ClassReader(fixedByteCode); + ClassWriter writer = new FixedClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + + final AtomicBoolean changesMade = new AtomicBoolean(); + + class SneakyThrowsRemoverVisitor extends MethodAdapter { + boolean justAddedAthrow = false; + + SneakyThrowsRemoverVisitor(MethodVisitor mv) { + super(mv); + } + + @Override public void visitMethodInsn(int opcode, String owner, String name, String desc) { + boolean hit = true; + if (hit && opcode != Opcodes.INVOKESTATIC) hit = false; + if (hit && !"sneakyThrow".equals(name)) hit = false; + if (hit && !"lombok/Lombok".equals(owner)) hit = false; + if (hit && !"(Ljava/lang/Throwable;)Ljava/lang/RuntimeException;".equals(desc)) hit = false; + if (hit) { + changesMade.set(true); + justAddedAthrow = true; + super.visitInsn(Opcodes.ATHROW); + } else { + super.visitMethodInsn(opcode, owner, name, desc); + } + } + + @Override public void visitInsn(int opcode) { + if (!justAddedAthrow || opcode != Opcodes.ATHROW) { + super.visitInsn(opcode); + } + justAddedAthrow = false; + } + + @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + justAddedAthrow = false; + return super.visitAnnotation(desc, visible); + } + + @Override public AnnotationVisitor visitAnnotationDefault() { + justAddedAthrow = false; + return super.visitAnnotationDefault(); + } + + @Override public void visitAttribute(Attribute attr) { + justAddedAthrow = false; + super.visitAttribute(attr); + } + + @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { + justAddedAthrow = false; + super.visitFieldInsn(opcode, owner, name, desc); + } + + @Override public void visitIincInsn(int var, int increment) { + justAddedAthrow = false; + super.visitIincInsn(var, increment); + } + + @Override public void visitIntInsn(int opcode, int operand) { + justAddedAthrow = false; + super.visitIntInsn(opcode, operand); + } + + @Override public void visitJumpInsn(int opcode, Label label) { + justAddedAthrow = false; + super.visitJumpInsn(opcode, label); + } + + @Override public void visitLabel(Label label) { + justAddedAthrow = false; + super.visitLabel(label); + } + + @Override public void visitLdcInsn(Object cst) { + justAddedAthrow = false; + super.visitLdcInsn(cst); + } + + @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { + justAddedAthrow = false; + super.visitLocalVariable(name, desc, signature, start, end, index); + } + + @Override public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + justAddedAthrow = false; + super.visitLookupSwitchInsn(dflt, keys, labels); + } + + @Override public void visitMultiANewArrayInsn(String desc, int dims) { + justAddedAthrow = false; + super.visitMultiANewArrayInsn(desc, dims); + } + + @Override public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { + justAddedAthrow = false; + return super.visitParameterAnnotation(parameter, desc, visible); + } + + @Override public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { + justAddedAthrow = false; + super.visitTableSwitchInsn(min, max, dflt, labels); + } + + @Override public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + justAddedAthrow = false; + super.visitTryCatchBlock(start, end, handler, type); + } + + @Override public void visitTypeInsn(int opcode, String type) { + justAddedAthrow = false; + super.visitTypeInsn(opcode, type); + } + + @Override public void visitVarInsn(int opcode, int var) { + justAddedAthrow = false; + super.visitVarInsn(opcode, var); + } + } + + reader.accept(new ClassAdapter(writer) { + @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + return new SneakyThrowsRemoverVisitor(super.visitMethod(access, name, desc, signature, exceptions)); + } + }, 0); + return changesMade.get() ? writer.toByteArray() : null; + } +} diff --git a/src/core/lombok/core/DiagnosticsReceiver.java b/src/core/lombok/core/DiagnosticsReceiver.java new file mode 100644 index 00000000..2adfadaf --- /dev/null +++ b/src/core/lombok/core/DiagnosticsReceiver.java @@ -0,0 +1,40 @@ +/* + * Copyright © 2009-2010 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core; + +public interface DiagnosticsReceiver { + DiagnosticsReceiver CONSOLE = new DiagnosticsReceiver() { + @Override public void addError(String message) { + System.err.println("Error: " + message); + } + + @Override public void addWarning(String message) { + System.out.println("Warning: " + message); + } + }; + + /** Generate a compiler error on this node. */ + void addError(String message); + + /** Generate a compiler warning on this node. */ + void addWarning(String message); +} diff --git a/src/core/lombok/core/LombokNode.java b/src/core/lombok/core/LombokNode.java index 6fe37a6b..6d0f3147 100644 --- a/src/core/lombok/core/LombokNode.java +++ b/src/core/lombok/core/LombokNode.java @@ -38,7 +38,7 @@ import lombok.core.AST.Kind; * @param N The common type of all AST nodes in the internal representation of the target platform. * For example, JCTree for javac, and ASTNode for Eclipse. */ -public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, N> { +public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, N> implements DiagnosticsReceiver { protected final A ast; protected final Kind kind; protected final N node; @@ -316,12 +316,6 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, return (L) this; } - /** Generate a compiler error on this node. */ - public abstract void addError(String message); - - /** Generate a compiler warning on this node. */ - public abstract void addWarning(String message); - /** * Structurally significant means: LocalDeclaration, TypeDeclaration, MethodDeclaration, ConstructorDeclaration, * FieldDeclaration, Initializer, and CompilationUnitDeclaration. diff --git a/src/core/lombok/core/PostCompiler.java b/src/core/lombok/core/PostCompiler.java new file mode 100644 index 00000000..52568286 --- /dev/null +++ b/src/core/lombok/core/PostCompiler.java @@ -0,0 +1,83 @@ +/* + * Copyright © 2010 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collections; +import java.util.List; + +public final class PostCompiler { + private PostCompiler() {/* prevent instantiation*/}; + + private static List<PostCompilerTransformation> transformations; + + public static byte[] applyTransformations(byte[] original, String className, DiagnosticsReceiver diagnostics) { + init(diagnostics); + byte[] previous = original; + for (PostCompilerTransformation transformation : transformations) { + try { + byte[] next = transformation.applyTransformations(previous, className, diagnostics); + if (next != null) { + previous = next; + } + } catch (Exception e) { + diagnostics.addWarning(String.format("Error during the transformation of '%s'; post-compiler '%s' caused an exception: %s", className, transformation.getClass().getName(), e.getMessage())); + } + } + return previous; + } + + private static synchronized void init(DiagnosticsReceiver diagnostics) { + if (transformations != null) return; + try { + transformations = SpiLoadUtil.readAllFromIterator(SpiLoadUtil.findServices(PostCompilerTransformation.class, PostCompilerTransformation.class.getClassLoader())); + } catch (IOException e) { + transformations = Collections.emptyList(); + diagnostics.addWarning("Could not load post-compile transformers: " + e.getMessage()); + } + } + + public static OutputStream wrapOutputStream(final OutputStream originalStream, final String className, final DiagnosticsReceiver diagnostics) throws IOException { + return new ByteArrayOutputStream() { + @Override public void close() throws IOException { + // no need to call super + byte[] original = toByteArray(); + byte[] copy = null; + try { + copy = applyTransformations(original, className, diagnostics); + } catch (Exception e) { + diagnostics.addWarning(String.format("Error during the transformation of '%s'; no post-compilation has been applied", className)); + } + + if (copy == null) { + copy = original; + } + + // Exceptions below should bubble + originalStream.write(copy); + originalStream.close(); + } + }; + } +} diff --git a/src/core/lombok/core/PostCompilerTransformation.java b/src/core/lombok/core/PostCompilerTransformation.java new file mode 100644 index 00000000..e0770f53 --- /dev/null +++ b/src/core/lombok/core/PostCompilerTransformation.java @@ -0,0 +1,26 @@ +/* + * Copyright © 2010 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core; + +public interface PostCompilerTransformation { + byte[] applyTransformations(byte[] original, String className, DiagnosticsReceiver diagnostics); +} diff --git a/src/core/lombok/javac/apt/InterceptingJavaFileManager.java b/src/core/lombok/javac/apt/InterceptingJavaFileManager.java new file mode 100644 index 00000000..2b570eb0 --- /dev/null +++ b/src/core/lombok/javac/apt/InterceptingJavaFileManager.java @@ -0,0 +1,104 @@ +/* + * Copyright © 2010 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.apt; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Set; + +import javax.tools.FileObject; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileObject.Kind; + +import lombok.core.DiagnosticsReceiver; + +final class InterceptingJavaFileManager implements JavaFileManager { + private final JavaFileManager delegate; + private final DiagnosticsReceiver diagnostics; + + InterceptingJavaFileManager(JavaFileManager original, DiagnosticsReceiver diagnostics) { + this.delegate = original; + this.diagnostics = diagnostics; + } + + @Override public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException { + JavaFileObject fileObject = delegate.getJavaFileForOutput(location, className, kind, sibling); + if (kind != Kind.CLASS) { + return fileObject; + } + return new InterceptingJavaFileObject(fileObject, className, diagnostics); + } + + + + +/////////////////////// NOTHING CHANGED BELOW ////////////////////////////////////// + + @Override public void close() throws IOException { + delegate.close(); + } + + @Override public void flush() throws IOException { + delegate.flush(); + } + + @Override public ClassLoader getClassLoader(Location location) { + return delegate.getClassLoader(location); + } + + @Override public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { + return delegate.getFileForInput(location, packageName, relativeName); + } + + @Override public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException { + return delegate.getFileForOutput(location, packageName, relativeName, sibling); + } + + @Override public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException { + return delegate.getJavaFileForInput(location, className, kind); + } + + @Override public boolean handleOption(String current, Iterator<String> remaining) { + return delegate.handleOption(current, remaining); + } + + @Override public boolean hasLocation(Location location) { + return delegate.hasLocation(location); + } + + @Override public String inferBinaryName(Location location, JavaFileObject file) { + return delegate.inferBinaryName(location, file); + } + + @Override public boolean isSameFile(FileObject a, FileObject b) { + return delegate.isSameFile(a, b); + } + + @Override public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse) throws IOException { + return delegate.list(location, packageName, kinds, recurse); + } + + @Override public int isSupportedOption(String option) { + return delegate.isSupportedOption(option); + } +}
\ No newline at end of file diff --git a/src/core/lombok/javac/apt/InterceptingJavaFileObject.java b/src/core/lombok/javac/apt/InterceptingJavaFileObject.java new file mode 100644 index 00000000..c0d3565c --- /dev/null +++ b/src/core/lombok/javac/apt/InterceptingJavaFileObject.java @@ -0,0 +1,106 @@ +/* + * Copyright © 2010 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.apt; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.net.URI; + +import javax.lang.model.element.Modifier; +import javax.lang.model.element.NestingKind; +import javax.tools.JavaFileObject; + +import lombok.core.DiagnosticsReceiver; +import lombok.core.PostCompiler; + +final class InterceptingJavaFileObject implements JavaFileObject { + private final JavaFileObject delegate; + private final String className; + private final DiagnosticsReceiver diagnostics; + + public InterceptingJavaFileObject(JavaFileObject original, String className, DiagnosticsReceiver diagnostics) { + this.delegate = original; + this.className = className; + this.diagnostics = diagnostics; + } + + public OutputStream openOutputStream() throws IOException { + return PostCompiler.wrapOutputStream(delegate.openOutputStream(), className, diagnostics); + } + + public Writer openWriter() throws IOException { + throw new UnsupportedOperationException("Can't use a write for class files"); +// return original.openWriter(); + } + + + + +/////////////////////// NOTHING CHANGED BELOW ////////////////////////////////////// + + public boolean delete() { + return delegate.delete(); + } + + public Modifier getAccessLevel() { + return delegate.getAccessLevel(); + } + + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return delegate.getCharContent(ignoreEncodingErrors); + } + + public Kind getKind() { + return delegate.getKind(); + } + + public long getLastModified() { + return delegate.getLastModified(); + } + + public String getName() { + return delegate.getName(); + } + + public NestingKind getNestingKind() { + return delegate.getNestingKind(); + } + + public boolean isNameCompatible(String simpleName, Kind kind) { + return delegate.isNameCompatible(simpleName, kind); + } + + public InputStream openInputStream() throws IOException { + return delegate.openInputStream(); + } + + public Reader openReader(boolean ignoreEncodingErrors) throws IOException { + return delegate.openReader(ignoreEncodingErrors); + } + + public URI toUri() { + return delegate.toUri(); + } +} diff --git a/src/core/lombok/javac/apt/MessagerDiagnosticsReceiver.java b/src/core/lombok/javac/apt/MessagerDiagnosticsReceiver.java new file mode 100644 index 00000000..5b75b3c5 --- /dev/null +++ b/src/core/lombok/javac/apt/MessagerDiagnosticsReceiver.java @@ -0,0 +1,43 @@ +/* + * Copyright © 2009-2010 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.apt; + +import javax.annotation.processing.Messager; +import javax.tools.Diagnostic.Kind; + +import lombok.core.DiagnosticsReceiver; + +public class MessagerDiagnosticsReceiver implements DiagnosticsReceiver { + private final Messager messager; + + public MessagerDiagnosticsReceiver(Messager messager) { + this.messager = messager; + } + + @Override public void addWarning(String message) { + messager.printMessage(Kind.WARNING, message); + } + + @Override public void addError(String message) { + messager.printMessage(Kind.ERROR, message); + } +}
\ No newline at end of file diff --git a/src/core/lombok/javac/apt/Processor.java b/src/core/lombok/javac/apt/Processor.java index 31cedf03..ab445309 100644 --- a/src/core/lombok/javac/apt/Processor.java +++ b/src/core/lombok/javac/apt/Processor.java @@ -1,5 +1,5 @@ /* - * Copyright © 2009 Reinier Zwitserloot and Roel Spilker. + * Copyright © 2009-2010 Reinier Zwitserloot and Roel Spilker. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,10 +21,14 @@ */ package lombok.javac.apt; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.IdentityHashMap; +import java.util.Map; import java.util.Set; import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; @@ -32,14 +36,17 @@ import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; +import javax.tools.JavaFileManager; +import lombok.Lombok; +import lombok.core.DiagnosticsReceiver; import lombok.javac.JavacTransformer; import com.sun.source.util.TreePath; import com.sun.source.util.Trees; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; - +import com.sun.tools.javac.util.Context; /** * This Annotation Processor is the standard injection mechanism for lombok-enabling the javac compiler. @@ -54,6 +61,7 @@ import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; @SupportedAnnotationTypes("*") @SupportedSourceVersion(SourceVersion.RELEASE_6) public class Processor extends AbstractProcessor { + private JavacProcessingEnvironment processingEnv; private JavacTransformer transformer; private Trees trees; @@ -62,9 +70,35 @@ public class Processor extends AbstractProcessor { @Override public void init(ProcessingEnvironment procEnv) { super.init(procEnv); this.processingEnv = (JavacProcessingEnvironment) procEnv; + placePostCompileHook(); transformer = new JavacTransformer(procEnv.getMessager()); trees = Trees.instance(procEnv); } + + private void placePostCompileHook() { + Context context = processingEnv.getContext(); + + try { + Method keyMethod = Context.class.getDeclaredMethod("key", Class.class); + keyMethod.setAccessible(true); + Object key = keyMethod.invoke(context, JavaFileManager.class); + Field htField = Context.class.getDeclaredField("ht"); + htField.setAccessible(true); + @SuppressWarnings("unchecked") + Map<Object,Object> ht = (Map<Object,Object>) htField.get(context); + final JavaFileManager originalFiler = (JavaFileManager) ht.get(key); + + if (!(originalFiler instanceof InterceptingJavaFileManager)) { + final Messager messager = processingEnv.getMessager(); + DiagnosticsReceiver receiver = new MessagerDiagnosticsReceiver(messager); + + JavaFileManager newFiler = new InterceptingJavaFileManager(originalFiler, receiver); + ht.put(key, newFiler); + } + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + } /** {@inheritDoc} */ @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { |