diff --git a/teavm-classlib/pom.xml b/teavm-classlib/pom.xml index 1bf13a0ec..409012dec 100644 --- a/teavm-classlib/pom.xml +++ b/teavm-classlib/pom.xml @@ -58,6 +58,34 @@ + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + + java + + process-test-classes + + + + org.teavm.classlib.impl.JCLComparisonBuilder + + java.lang + java.lang.annotation + java.lang.reflect + java.io + java.net + java.util + java.util.logging + java.util.concurrent + -output + ${project.build.directory}/jcl-report/jcl-comparision.json + + + diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLComparisonBuilder.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLComparisonBuilder.java new file mode 100644 index 000000000..a997a64e0 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLComparisonBuilder.java @@ -0,0 +1,121 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * 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.teavm.classlib.impl; + +import java.io.*; +import java.net.URL; +import java.util.HashSet; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import org.objectweb.asm.ClassReader; +import org.teavm.parsing.ClasspathClassHolderSource; + +/** + * + * @author Alexey Andreev + */ +public class JCLComparisonBuilder { + private static final String JAR_PREFIX = "jar:file:"; + private static final String JAR_SUFFIX = "!/java/lang/Object.class"; + private static final String CLASS_SUFFIX = ".class"; + private Set packages = new HashSet<>(); + private ClassLoader classLoader = JCLComparisonBuilder.class.getClassLoader(); + private JCLComparisonVisitor visitor; + private String outputFile; + + public ClassLoader getClassLoader() { + return classLoader; + } + + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + public Set getPackages() { + return packages; + } + + public String getOutputFile() { + return outputFile; + } + + public void setOutputFile(String outputFile) { + this.outputFile = outputFile; + } + + public static void main(String[] args) throws IOException { + if (args.length == 0) { + System.err.println("Usage: package.name [package.name2 ...]"); + System.exit(1); + } + JCLComparisonBuilder builder = new JCLComparisonBuilder(); + + for (int i = 0; i < args.length; ++i) { + if (args[i].equals("-output")) { + builder.setOutputFile(args[++i]); + } else { + builder.getPackages().add(args[i].replace('.', '/')); + } + } + + builder.buildComparisonReport(); + } + + public void buildComparisonReport() throws IOException { + URL url = classLoader.getResource("java/lang/Object" + CLASS_SUFFIX); + String path = url.toString(); + if (!path.startsWith(JAR_PREFIX) || !path.endsWith(JAR_SUFFIX)) { + throw new RuntimeException("Can't find JCL classes"); + } + ClasspathClassHolderSource classSource = new ClasspathClassHolderSource(classLoader); + path = path.substring(JAR_PREFIX.length(), path.length() - JAR_SUFFIX.length()); + File outDir = new File(outputFile).getParentFile(); + if (!outDir.exists()) { + outDir.mkdirs(); + } + try (JarInputStream jar = new JarInputStream(new FileInputStream(path)); + PrintStream out = new PrintStream(new FileOutputStream(outputFile))) { + out.println("{"); + visitor = new JCLComparisonVisitor(classSource, out); + while (true) { + JarEntry entry = jar.getNextJarEntry(); + if (entry == null) { + break; + } + if (validateName(entry.getName())) { + compareClass(jar); + } + jar.closeEntry(); + } + out.println(); + out.println("}"); + } + } + + private boolean validateName(String name) { + if (!name.endsWith(CLASS_SUFFIX)) { + return false; + } + int slashIndex = name.lastIndexOf('/'); + return packages.contains(name.substring(0, slashIndex)); + } + + private void compareClass(InputStream input) throws IOException { + ClassReader reader = new ClassReader(input); + reader.accept(visitor, 0); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLComparisonVisitor.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLComparisonVisitor.java new file mode 100644 index 000000000..ea412d88a --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLComparisonVisitor.java @@ -0,0 +1,143 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * 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.teavm.classlib.impl; + +import java.io.PrintStream; +import org.objectweb.asm.*; +import org.teavm.model.*; +import org.teavm.model.ClassReader; + +/** + * + * @author Alexey Andreev + */ +class JCLComparisonVisitor implements ClassVisitor { + private PrintStream out; + private ClassReaderSource classSource; + private boolean first = true; + private boolean firstItem; + private boolean pass; + private boolean ended; + private ClassReader classReader; + + public JCLComparisonVisitor(ClassReaderSource classSource, PrintStream out) { + this.classSource = classSource; + this.out = out; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + if ((access & Opcodes.ACC_PUBLIC) == 0) { + return; + } + String javaName = name.replace('/', '.'); + if (!first) { + out.println(","); + } + first = false; + out.println(" \"" + javaName + "\" : {"); + classReader = classSource.get(javaName); + if (classReader == null) { + out.println(" \"implemented\" : false"); + pass = true; + } else { + out.println(" \"implemented\" : true,"); + out.println(" \"items\" : ["); + pass = false; + } + ended = false; + firstItem = true; + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + if (pass) { + return null; + } + if ((access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) == 0) { + return null; + } + if (!firstItem) { + out.println(","); + } + firstItem = false; + out.println(" {"); + out.println(" \"type\" : \"field\","); + out.println(" \"name\" : \"" + name + "\","); + out.println(" \"descriptor\" : \"" + desc + "\","); + FieldReader field = classReader.getField(name); + out.println(" \"implemented\" : \"" + (field != null ? "true" : "false") + "\","); + out.print(" }"); + return null; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (pass) { + return null; + } + if ((access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) == 0) { + return null; + } + if (!firstItem) { + out.println(","); + } + firstItem = false; + out.println(" {"); + out.println(" \"type\" : \"method\","); + out.println(" \"name\" : \"" + name + "\","); + out.println(" \"descriptor\" : \"" + desc + "\","); + MethodReader method = classReader.getMethod(MethodDescriptor.parse(name + desc)); + out.println(" \"implemented\" : \"" + (method != null ? "true" : "false") + "\","); + out.print(" }"); + return null; + } + + @Override + public void visitSource(String source, String debug) { + } + + @Override + public void visitOuterClass(String owner, String name, String desc) { + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return null; + } + + @Override + public void visitAttribute(Attribute attr) { + } + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + } + + @Override + public void visitEnd() { + if (!ended) { + if (!pass) { + if (!firstItem) { + out.println(); + } + out.println(" ]"); + } + out.print(" }"); + ended = true; + } + } +} diff --git a/teavm-core/src/main/java/org/teavm/parsing/ClasspathResourceMapper.java b/teavm-core/src/main/java/org/teavm/parsing/ClasspathResourceMapper.java index 3adb19233..19ac6e3dc 100644 --- a/teavm-core/src/main/java/org/teavm/parsing/ClasspathResourceMapper.java +++ b/teavm-core/src/main/java/org/teavm/parsing/ClasspathResourceMapper.java @@ -93,7 +93,7 @@ public class ClasspathResourceMapper implements Mapper { if (name.startsWith(transformation.packageName)) { int index = name.lastIndexOf('.'); String className = name.substring(index + 1); - String packageName = name.substring(0, index); + String packageName = index > 0 ? name.substring(0, index) : ""; ClassHolder classHolder = innerMapper.map(transformation.packagePrefix + packageName + "." + transformation.classPrefix + className); if (classHolder != null) {