diff options
author | Roel Spilker <r.spilker@gmail.com> | 2010-08-05 23:46:34 +0200 |
---|---|---|
committer | Roel Spilker <r.spilker@gmail.com> | 2010-08-05 23:46:34 +0200 |
commit | b14eef7eed8703824773467606f3be0c03a04b33 (patch) | |
tree | 101fbcba9df98c9d5756fddd070508d6816ad7f8 | |
parent | d41a282e50a8348b904951893d98239095a7ccab (diff) | |
download | lombok-b14eef7eed8703824773467606f3be0c03a04b33.tar.gz lombok-b14eef7eed8703824773467606f3be0c03a04b33.tar.bz2 lombok-b14eef7eed8703824773467606f3be0c03a04b33.zip |
Created utility class to casually inspect class files on the usage of classes, fields and methods
-rw-r--r-- | src/core/lombok/bytecode/ClassFileMetaData.java | 244 | ||||
-rw-r--r-- | src/core/lombok/bytecode/PostCompilerApp.java | 6 | ||||
-rw-r--r-- | src/core/lombok/bytecode/SneakyThrowsRemover.java | 2 | ||||
-rw-r--r-- | test/bytecode/resource/Bar.java | 3 | ||||
-rw-r--r-- | test/bytecode/resource/Baz.java | 2 | ||||
-rw-r--r-- | test/bytecode/resource/Buux.java | 10 | ||||
-rw-r--r-- | test/bytecode/resource/Foo.java | 9 | ||||
-rw-r--r-- | test/bytecode/src/lombok/bytecode/ClassFileMetaDataTest.java | 196 |
8 files changed, 469 insertions, 3 deletions
diff --git a/src/core/lombok/bytecode/ClassFileMetaData.java b/src/core/lombok/bytecode/ClassFileMetaData.java new file mode 100644 index 00000000..df50969a --- /dev/null +++ b/src/core/lombok/bytecode/ClassFileMetaData.java @@ -0,0 +1,244 @@ +/* + * 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 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(8) + 1; + offsets = new int[maxPoolSize]; + types = new byte[maxPoolSize]; + utf8s = new String[maxPoolSize]; + int position = 10; + for (int i = 1; i < maxPoolSize - 1; 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; + 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 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; + } + + 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 index 3a413d67..e1b177ba 100644 --- a/src/core/lombok/bytecode/PostCompilerApp.java +++ b/src/core/lombok/bytecode/PostCompilerApp.java @@ -130,8 +130,8 @@ public class PostCompilerApp implements LombokApp { else if (f.getName().endsWith(".class")) filesToProcess.add(f); } } - - private static byte[] readFile(File file) throws IOException { + + static byte[] readFile(File file) throws IOException { byte[] buffer = new byte[1024]; ByteArrayOutputStream bytes = new ByteArrayOutputStream(); FileInputStream fileInputStream = new FileInputStream(file); @@ -144,7 +144,7 @@ public class PostCompilerApp implements LombokApp { return bytes.toByteArray(); } - private static void writeFile(File file, byte[] transformed) throws IOException { + 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 index 4d69ab34..fde29924 100644 --- a/src/core/lombok/bytecode/SneakyThrowsRemover.java +++ b/src/core/lombok/bytecode/SneakyThrowsRemover.java @@ -72,6 +72,8 @@ public class SneakyThrowsRemover implements PostCompilerTransformation { } @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); diff --git a/test/bytecode/resource/Bar.java b/test/bytecode/resource/Bar.java new file mode 100644 index 00000000..13cbf425 --- /dev/null +++ b/test/bytecode/resource/Bar.java @@ -0,0 +1,3 @@ +public interface Bar extends java.util.RandomAccess, java.util.Map { + String getName(); +}
\ No newline at end of file diff --git a/test/bytecode/resource/Baz.java b/test/bytecode/resource/Baz.java new file mode 100644 index 00000000..2525ed00 --- /dev/null +++ b/test/bytecode/resource/Baz.java @@ -0,0 +1,2 @@ +public interface Baz { +}
\ No newline at end of file diff --git a/test/bytecode/resource/Buux.java b/test/bytecode/resource/Buux.java new file mode 100644 index 00000000..33b352e2 --- /dev/null +++ b/test/bytecode/resource/Buux.java @@ -0,0 +1,10 @@ +public class Buux extends java.util.ArrayList { + public Buux() { + super(7); + addSomething(); + } + + public void addSomething() { + super.add("H\u3404l\0"); + } +}
\ No newline at end of file diff --git a/test/bytecode/resource/Foo.java b/test/bytecode/resource/Foo.java new file mode 100644 index 00000000..95a2c820 --- /dev/null +++ b/test/bytecode/resource/Foo.java @@ -0,0 +1,9 @@ +public class Foo implements java.util.RandomAccess { + private static final String ONE = "Eén"; + + { + String value = toString(); + System.out.print(value); + System.out.print("Two" + "Four"); + } +}
\ No newline at end of file diff --git a/test/bytecode/src/lombok/bytecode/ClassFileMetaDataTest.java b/test/bytecode/src/lombok/bytecode/ClassFileMetaDataTest.java new file mode 100644 index 00000000..a2fa919f --- /dev/null +++ b/test/bytecode/src/lombok/bytecode/ClassFileMetaDataTest.java @@ -0,0 +1,196 @@ +/* + * 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 static org.junit.Assert.*; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.tools.JavaCompiler; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; +import javax.tools.JavaCompiler.CompilationTask; + +import lombok.Lombok; + +import org.junit.Test; + +public class ClassFileMetaDataTest { + + private ClassFileMetaData foo = create(new File("test/bytecode/resource/Foo.java")); + private ClassFileMetaData bar = create(new File("test/bytecode/resource/Bar.java")); + private ClassFileMetaData baz = create(new File("test/bytecode/resource/Baz.java")); + private ClassFileMetaData buux = create(new File("test/bytecode/resource/Buux.java")); + + @Test + public void testGetClassName() { + assertTrue(foo.containsUtf8("Foo")); + assertEquals("Foo", foo.getClassName()); + + assertTrue(bar.containsUtf8("Bar")); + assertEquals("Bar", bar.getClassName()); + + assertTrue(baz.containsUtf8("Baz")); + assertEquals("Baz", baz.getClassName()); + } + + @Test + public void testGetSuperClassName() { + assertTrue(foo.containsUtf8("java/lang/Object")); + assertEquals("java/lang/Object", foo.getSuperClassName()); + + assertEquals("java/lang/Object", bar.getSuperClassName()); + assertEquals("java/lang/Object", baz.getSuperClassName()); + + assertEquals("java/util/ArrayList", buux.getSuperClassName()); + } + + + + @Test + public void testUsesClass() { + assertTrue(foo.usesClass("java/lang/System")); +// assertTrue(foo.usesClass("java/lang/String")); + } + + @Test + public void testUsesField() { + assertTrue(foo.usesField("java/lang/System", "out")); + } + + @Test + public void testUsesMethodWithName() { + assertTrue(foo.usesMethod("java/io/PrintStream", "print")); + + assertTrue(buux.usesMethod("java/util/ArrayList", "<init>")); + assertTrue(buux.usesMethod("java/util/ArrayList", "add")); + assertTrue(buux.usesMethod("Buux", "addSomething")); + } + + @Test + public void testUsesMethodWithNameAndDescriptor() { + assertTrue(foo.usesMethod("java/io/PrintStream", "print", "(Ljava/lang/String;)V")); + + assertTrue(buux.usesMethod("java/util/ArrayList", "<init>", "(I)V")); + assertTrue(buux.usesMethod("java/util/ArrayList", "add", "(Ljava/lang/Object;)Z")); + assertTrue(buux.usesMethod("Buux", "addSomething", "()V")); + } + + @Test + public void testGetInterfaces() { + assertTrue(foo.containsUtf8("java/util/RandomAccess")); + + List<String> fooInterfaces = foo.getInterfaces(); + assertEquals(1, fooInterfaces.size()); + assertEquals("java/util/RandomAccess", fooInterfaces.get(0)); + + assertTrue(bar.containsUtf8("java/util/RandomAccess")); + assertTrue(bar.containsUtf8("java/util/Map")); + + List<String> barInterfaces = bar.getInterfaces(); + assertEquals(2, barInterfaces.size()); + assertEquals("java/util/RandomAccess", barInterfaces.get(0)); + assertEquals("java/util/Map", barInterfaces.get(1)); + } + + @Test + public void testContainsStringConstant() { + assertTrue(foo.containsStringConstant("Eén")); + assertTrue(foo.containsStringConstant("TwoFour")); + + assertTrue(buux.containsStringConstant("H\u3404l\0")); + } + + private ClassFileMetaData create(File file) { + return new ClassFileMetaData(compile(file)); + } + + private byte[] compile(File file) { + try { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + File tempDir = getTempDir(); + tempDir.mkdirs(); + List<String> options = Arrays.asList("-nowarn", "-proc:none", "-d", tempDir.getAbsolutePath()); + StringWriter captureWarnings = new StringWriter(); + CompilationTask task = compiler.getTask(captureWarnings, null, null, options, null, Collections.singleton(new ContentBasedJavaFileObject(file.getPath(), readFileAsString(file)))); + assertTrue(task.call()); + return PostCompilerApp.readFile(new File(tempDir, file.getName().replaceAll("\\.java$", ".class"))); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + } + + private File getTempDir() { + String[] rawDirs = { + System.getProperty("java.io.tmpdir"), + "/tmp", + "C:\\Windows\\Temp" + }; + + for (String dir : rawDirs) { + if (dir == null) continue; + File f = new File(dir); + if (!f.isDirectory()) continue; + return new File(f, "lombok.bytecode-test"); + } + + return new File("./build/tmp"); + } + + static class ContentBasedJavaFileObject extends SimpleJavaFileObject { + private final String content; + + protected ContentBasedJavaFileObject(String name, String content) { + super(new File(name).toURI(), Kind.SOURCE); + this.content = content; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return content; + } + } + + private String readFileAsString(File file) { + try { + BufferedReader reader = new BufferedReader(new FileReader(file)); + StringWriter writer = new StringWriter(); + String line = reader.readLine(); + while(line != null) { + writer.append(line).append("\n"); + line = reader.readLine(); + } + reader.close(); + writer.close(); + return writer.toString(); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + } +} |