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) {