| /* |
| * 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 |
| * |
| * https://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. |
| */ |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.bcel.Const; |
| import org.apache.bcel.Repository; |
| import org.apache.bcel.classfile.ClassParser; |
| import org.apache.bcel.classfile.Code; |
| import org.apache.bcel.classfile.Constant; |
| import org.apache.bcel.classfile.ConstantClass; |
| import org.apache.bcel.classfile.ConstantPool; |
| import org.apache.bcel.classfile.ConstantUtf8; |
| import org.apache.bcel.classfile.JavaClass; |
| import org.apache.bcel.classfile.Method; |
| import org.apache.commons.lang3.stream.Streams; |
| |
| /** |
| * Read class file(s) and display its contents. The command line usage is: |
| * |
| * <pre> |
| * java ListClass [-constants] [-code] [-brief] [-dependencies] [-nocontents] [-recurse] class... [-exclude <list>] |
| * </pre> |
| * |
| * where |
| * <ul> |
| * <li>{@code -code} List byte code of methods</li> |
| * <li>{@code -brief} List byte codes briefly</li> |
| * <li>{@code -constants} Print constants table (constant pool)</li> |
| * <li>{@code -recurse} Usually intended to be used along with {@code -dependencies} When this flag is set, listclass |
| * will also print information about all classes which the target class depends on.</li> |
| * |
| * <li>{@code -dependencies} Setting this flag makes listclass print a list of all classes which the target class |
| * depends on. Generated from getting all CONSTANT_Class constants from the constant pool.</li> |
| * |
| * <li>{@code -exclude} All non-flag arguments after this flag are added to an 'exclusion list'. Target classes are |
| * compared with the members of the exclusion list. Any target class whose fully qualified name begins with a name in |
| * the exclusion list will not be analyzed/listed. This is meant primarily when using both {@code -recurse} to exclude |
| * java, javax, and sun classes, and is recommended as otherwise the output from {@code -recurse} gets quite long and |
| * most of it is not interesting. Note that {@code -exclude} prevents listing of classes, it does not prevent class |
| * names from being printed in the {@code -dependencies} list.</li> |
| * <li>{@code -nocontents} Do not print JavaClass.toString() for the class. I added this because sometimes I'm only |
| * interested in dependency information.</li> |
| * </ul> |
| * <p> |
| * Here's a couple examples of how I typically use ListClass: |
| * </p> |
| * |
| * <pre> |
| * java ListClass -code MyClass |
| * </pre> |
| * |
| * Print information about the class and the byte code of the methods |
| * |
| * <pre> |
| * java ListClass -nocontents -dependencies MyClass |
| * </pre> |
| * |
| * Print a list of all classes which MyClass depends on. |
| * |
| * <pre> |
| * java ListClass -nocontents -recurse MyClass -exclude java. javax. sun. |
| * </pre> |
| * |
| * Print a recursive listing of all classes which MyClass depends on. Do not analyze classes beginning with "java.", |
| * "javax.", or "sun.". |
| * |
| * <pre> |
| * java ListClass -nocontents -dependencies -recurse MyClass -exclude java.javax. sun. |
| * </pre> |
| * <p> |
| * Print a recursive listing of dependency information for MyClass and its dependents. Do not analyze classes beginning |
| * with "java.", "javax.", or "sun." |
| * </p> |
| * |
| * <a href="mailto:twheeler@objectspace.com">Thomas Wheeler</A> |
| */ |
| public class ListClass { |
| |
| public static String[] getClassDependencies(final ConstantPool pool) { |
| final String[] tempArray = new String[pool.getLength()]; |
| int size = 0; |
| final StringBuilder buf = new StringBuilder(); |
| |
| for (int idx = 0; idx < pool.getLength(); idx++) { |
| final Constant c = pool.getConstant(idx); |
| if (c != null && c.getTag() == Const.CONSTANT_Class) { |
| final ConstantUtf8 c1 = (ConstantUtf8) pool.getConstant(((ConstantClass) c).getNameIndex()); |
| buf.setLength(0); |
| buf.append(c1.getBytes()); |
| for (int n = 0; n < buf.length(); n++) { |
| if (buf.charAt(n) == '/') { |
| buf.setCharAt(n, '.'); |
| } |
| } |
| |
| tempArray[size++] = buf.toString(); |
| } |
| } |
| |
| final String[] dependencies = new String[size]; |
| System.arraycopy(tempArray, 0, dependencies, 0, size); |
| return dependencies; |
| } |
| |
| public static void main(final String[] argv) { |
| final List<String> fileName = new ArrayList<>(); |
| final List<String> excludeName = new ArrayList<>(); |
| boolean code = false; |
| boolean constants = false; |
| boolean verbose = true; |
| boolean classdep = false; |
| boolean nocontents = false; |
| boolean recurse = false; |
| boolean exclude = false; |
| String name; |
| |
| // Parse command line arguments. |
| for (final String arg : argv) { |
| if (arg.charAt(0) == '-') { // command line switch |
| switch (arg) { |
| case "-constants": |
| constants = true; |
| break; |
| case "-code": |
| code = true; |
| break; |
| case "-brief": |
| verbose = false; |
| break; |
| case "-dependencies": |
| classdep = true; |
| break; |
| case "-nocontents": |
| nocontents = true; |
| break; |
| case "-recurse": |
| recurse = true; |
| break; |
| case "-exclude": |
| exclude = true; |
| break; |
| case "-help": |
| // @formatter:off |
| System.out.println( |
| "Usage: java listclass [-constants] [-code] [-brief] [-dependencies] [-nocontents] [-recurse] class... [-exclude <list>]\n" |
| + "-constants Print constants table (constant pool)\n" |
| + "-code Dump byte code of methods\n" |
| + "-brief Brief listing\n" |
| + "-dependencies Show class dependencies\n" |
| + "-nocontents Do not print field/method information\n" |
| + "-recurse Recurse into dependent classes\n" |
| + "-exclude <list> Do not list classes beginning with strings in <list>"); |
| // @formatter:on |
| System.exit(0); |
| break; |
| default: |
| System.err.println("Unknown switch " + arg + " ignored."); |
| break; |
| } |
| } else if (exclude) { // add file name to list |
| excludeName.add(arg); |
| } else { |
| fileName.add(arg); |
| } |
| } |
| |
| if (fileName.isEmpty()) { |
| System.err.println("list: No input files specified"); |
| } else { |
| final ListClass listClass = new ListClass(code, constants, verbose, classdep, nocontents, recurse, excludeName); |
| |
| for (final String element : fileName) { |
| name = element; |
| |
| listClass.list(name); |
| } |
| } |
| } |
| |
| /** |
| * Dumps the list of classes this class is dependent on |
| */ |
| public static void printClassDependencies(final ConstantPool pool) { |
| System.out.println("Dependencies:"); |
| for (final String name : getClassDependencies(pool)) { |
| System.out.println("\t" + name); |
| } |
| } |
| |
| /** |
| * Dumps the disassembled code of all methods in the class. |
| */ |
| public static void printCode(final Method[] methods, final boolean verbose) { |
| Streams.of(methods).forEach(method -> { |
| System.out.println(method); |
| final Code code = method.getCode(); |
| if (code != null) { |
| System.out.println(code.toString(verbose)); |
| } |
| }); |
| } |
| |
| boolean code; |
| boolean constants; |
| boolean verbose; |
| boolean classDep; |
| boolean noContents; |
| boolean recurse; |
| Map<String, String> listedClasses; |
| List<String> excludeName; |
| |
| public ListClass(final boolean code, final boolean constants, final boolean verbose, final boolean classDep, final boolean noContents, |
| final boolean recurse, final List<String> excludeName) { |
| this.code = code; |
| this.constants = constants; |
| this.verbose = verbose; |
| this.classDep = classDep; |
| this.noContents = noContents; |
| this.recurse = recurse; |
| this.listedClasses = new HashMap<>(); |
| this.excludeName = excludeName; |
| } |
| |
| /** |
| * Print the given class on screen |
| */ |
| public void list(final String name) { |
| try { |
| JavaClass javaClass; |
| |
| if (listedClasses.get(name) != null || name.startsWith("[")) { |
| return; |
| } |
| |
| for (final String element : excludeName) { |
| if (name.startsWith(element)) { |
| return; |
| } |
| } |
| |
| if (name.endsWith(JavaClass.EXTENSION)) { |
| javaClass = new ClassParser(name).parse(); // May throw IOException |
| } else { |
| javaClass = Repository.lookupClass(name); |
| } |
| |
| if (noContents) { |
| System.out.println(javaClass.getClassName()); |
| } else { |
| System.out.println(javaClass); // Dump the contents |
| } |
| |
| if (constants) { |
| System.out.println(javaClass.getConstantPool()); |
| } |
| |
| if (code) { |
| printCode(javaClass.getMethods(), verbose); |
| } |
| |
| if (classDep) { |
| printClassDependencies(javaClass.getConstantPool()); |
| } |
| |
| listedClasses.put(name, name); |
| |
| if (recurse) { |
| final String[] dependencies = getClassDependencies(javaClass.getConstantPool()); |
| |
| for (final String dependency : dependencies) { |
| list(dependency); |
| } |
| } |
| } catch (final IOException e) { |
| System.out.println("Error loading class " + name + " (" + e.getMessage() + ")"); |
| } catch (final Exception e) { |
| System.out.println("Error processing class " + name + " (" + e.getMessage() + ")"); |
| } |
| } |
| } |