/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.codehaus.groovy.ast.decompiled; import org.objectweb.asm.*; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.net.URL; import java.util.*; /** * A utility class responsible for decompiling JVM class files and producing {@link ClassStub} objects reflecting their structure. * * @author Peter Gromov */ public abstract class AsmDecompiler { private static class StubCache { /** * Caches stubs per URL. This cache is useful when performing multiple compilations in the same JVM/class loader and in tests. * * It's synchronized "just in case". Occasional misses are expected if several threads attempt to load the same class, * but this shouldn't result in serious memory issues. */ static final Map<URL, SoftReference<ClassStub>> map = Collections.synchronizedMap(new HashMap<URL, SoftReference<ClassStub>>()); } /** * Loads the URL contents and parses them with ASM, producing a {@link ClassStub} object representing the structure of * the corresponding class file. Stubs are cached and reused if queried several times with equal URLs. * * @param url an URL from a class loader, most likely a file system file or a JAR entry. * @return the class stub * @throws IOException if reading from this URL is impossible */ public static ClassStub parseClass(URL url) throws IOException { SoftReference<ClassStub> ref = StubCache.map.get(url); ClassStub stub = ref == null ? null : ref.get(); if (stub == null) { DecompilingVisitor visitor = new DecompilingVisitor(); InputStream stream = url.openStream(); try { new ClassReader(new BufferedInputStream(stream)).accept(visitor, ClassReader.SKIP_FRAMES); } finally { stream.close(); } stub = visitor.result; StubCache.map.put(url, new SoftReference<ClassStub>(stub)); } return stub; } private static class DecompilingVisitor extends ClassVisitor { private static final String[] EMPTY_STRING_ARRAY = new String[0]; private ClassStub result; public DecompilingVisitor() { super(Opcodes.ASM5); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaceNames) { result = new ClassStub(fromInternalName(name), access, signature, superName, interfaceNames); } @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { result.innerClassModifiers.put(innerName, access); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (!"<clinit>".equals(name)) { final MethodStub stub = new MethodStub(name, access, desc, signature, exceptions != null ? exceptions : EMPTY_STRING_ARRAY); if (result.methods == null) result.methods = new ArrayList<MethodStub>(1); result.methods.add(stub); return new MethodVisitor(api) { @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return readAnnotationMembers(stub.addAnnotation(desc)); } @Override public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { if (stub.parameterAnnotations == null) stub.parameterAnnotations = new HashMap<Integer, List<AnnotationStub>>(1); List<AnnotationStub> list = stub.parameterAnnotations.get(parameter); if (list == null) { stub.parameterAnnotations.put(parameter, list = new ArrayList<AnnotationStub>()); } AnnotationStub annotationStub = new AnnotationStub(desc); list.add(annotationStub); return readAnnotationMembers(annotationStub); } @Override public AnnotationVisitor visitAnnotationDefault() { return new AnnotationReader() { @Override void visitAttribute(String name, Object value) { stub.annotationDefault = value; } }; } }; } return null; } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return readAnnotationMembers(result.addAnnotation(desc)); } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { final FieldStub stub = new FieldStub(name, access, desc, signature); if (result.fields == null) result.fields = new ArrayList<FieldStub>(1); result.fields.add(stub); return new FieldVisitor(api) { @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return readAnnotationMembers(stub.addAnnotation(desc)); } }; } } private static AnnotationReader readAnnotationMembers(final AnnotationStub stub) { return new AnnotationReader() { @Override void visitAttribute(String name, Object value) { stub.members.put(name, value); } }; } static String fromInternalName(String name) { return name.replace('/', '.'); } private abstract static class AnnotationReader extends AnnotationVisitor { public AnnotationReader() { super(Opcodes.ASM5); } abstract void visitAttribute(String name, Object value); @Override public void visit(String name, Object value) { visitAttribute(name, value instanceof Type ? new TypeWrapper(((Type) value).getDescriptor()) : value); } @Override public void visitEnum(String name, String desc, String value) { visitAttribute(name, new EnumConstantWrapper(desc, value)); } @Override public AnnotationVisitor visitAnnotation(String name, String desc) { AnnotationStub stub = new AnnotationStub(desc); visitAttribute(name, stub); return readAnnotationMembers(stub); } @Override public AnnotationVisitor visitArray(String name) { final List<Object> list = new ArrayList<Object>(); visitAttribute(name, list); return new AnnotationReader() { @Override void visitAttribute(String name, Object value) { list.add(value); } }; } } }