Just thought I'd post the source of the Bantam ClassDepAnalyzer for discussion:

The Bantam Class Dependency Analyser is much smaller that I expected, considering the small size, were probably better rolling our own, this is interesting though:

-Peter.

/**
* * Copyright 2006 Skill Corporation * * 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 com.skillcorp.bantam.startup.annotation;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.rmi.RemoteException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.logging.Logger;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

public class ClassDepAnalyzer extends ClassLoader {
        private static final Logger logger = 
Logger.getLogger("com.skillcorp.bantam.startup.annotation");

        public ClassDepAnalyzer() {
                super();
        }

        public static void main(String[] args) throws Exception {
                Set<String> mainClasses = new HashSet<String>();
                
mainClasses.add("com.skillcorp.bantam.service.examples.admin.TestInterface");
                
mainClasses.add("com.skillcorp.bantam.service.examples.admin.TestAdminInterface");
                Set<String> includedPackages = new HashSet<String>();
                Set<String> excludedPackages = new HashSet<String>();
                excludedPackages.addAll(Arrays.asList(new String[] { "java", "javax", 
"com.sun", "net.jini" }));
                includedPackages.addAll(Arrays.asList(new String[] { 
"com.skillcorp.bantam.service", }));
                URLClassLoader urlc = new URLClassLoader(new URL[] { new File(
                                
"c:\\workspaces\\mayflowerClean\\bantam.dev.java.net\\bantam-examples\\build\\WEB-INF\\classes\\")
                                .toURL() }, 
ClassDepAnalyzer.class.getClass().getClassLoader());
                new ClassDepAnalyzer().createClassDepJar("test.jar", 
includedPackages, excludedPackages, mainClasses, urlc);
        }

        public void test(final Class classToRemote) throws IOException {
                String className = classToRemote.getName() + "Intfc";
                String jarName = classToRemote.getSimpleName() + "-dl.jar";
                createRemoteAndJar(className, classToRemote, jarName);
                try {
                        File createdJarFile = new File(jarName);
                        URLClassLoader u = new URLClassLoader(new URL[] { 
createdJarFile.toURL() }, null);
                        u.loadClass(className);
                }
                catch (MalformedURLException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
                catch (ClassNotFoundException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
        }

        public String createRemoteAndJar(Class classToRemote) throws 
ClassFormatError, FileNotFoundException, IOException {
                return createRemoteAndJar(null, classToRemote, null);
        }

        public String createRemoteAndJar(String remoteName, final Class 
classToRemote, String jarName)
                        throws ClassFormatError, IOException, 
FileNotFoundException {
                if (remoteName == null) {
                        remoteName = classToRemote.getName() + "Intfc";
                }
                if (jarName == null) {
                        jarName = classToRemote.getSimpleName() + "-dl.jar";
                }
                byte[] classBytes = createRemote(remoteName, classToRemote);
                Class remoteClass = defineClass(remoteName, classBytes, 0, 
classBytes.length);
                assert (java.rmi.Remote.class.isAssignableFrom(remoteClass));
                Map<String, byte[]> createdResources = new HashMap<String, 
byte[]>();
                createdResources.put(remoteName.replace('.', '/'), classBytes);
                InputStream classStream = new ByteArrayInputStream(classBytes);
                createClassDepJar(jarName, createdResources, classStream);
                return jarName;
        }

        private void createClassDepJar(String jarName, Map<String, byte[]> 
createdResources, InputStream classStream)
                        throws IOException, FileNotFoundException {
                Set<String> classes = analyzeDependencies(classStream, null, 
null, Thread.currentThread()
                                .getContextClassLoader());
                logger.fine("jar contents: " + classes.toString());
                createJar(jarName, classes, createdResources, 
Thread.currentThread().getContextClassLoader());
        }

        public void createClassDepJar(String jarName, Set<String> includePackages, 
Set<String> excludePackages,
                        Set<String> mainClasses, ClassLoader cl) throws 
IOException {
                Set<String> allDependencies = new HashSet<String>();
                for (String mainClass : mainClasses) {
                        logger.fine("analyzing main class " + mainClass);
                        String resourceName = mainClass.replace('.', '/') + 
".class";
                        Set<String> dependencies = 
analyzeDependencies(cl.getResourceAsStream(resourceName), includePackages,
                                        excludePackages, cl);
                        allDependencies.addAll(dependencies);
                }
                logger.fine("jar contents: " + allDependencies.toString());
                Map<String, byte[]> empty = Collections.emptyMap();
                createJar(jarName, allDependencies, empty, cl);
        }

        private byte[] createRemote(String remoteName, final Class 
classToRemote) {
                ClassWriter cw = new ClassWriter(false);
                String remoteResourceName = remoteName.replace('.', '/');
                cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC + 
Opcodes.ACC_ABSTRACT + Opcodes.ACC_INTERFACE, remoteResourceName,
                                null, "java/lang/Object", new String[] { 
"java/rmi/Remote" });
                Method[] methods = classToRemote.getDeclaredMethods();
                for (Method method : methods) {
                        if (Modifier.isPublic(method.getModifiers())) {
                                Class[] exceptionTypes = 
method.getExceptionTypes();
                                String[] exceptions = new 
String[exceptionTypes.length];
                                boolean throwsRemote = false;
                                for (int j = 0; j < exceptionTypes.length; j++) 
{
                                        Class ex = exceptionTypes[j];
                                        if (ex.equals(RemoteException.class)) {
                                                throwsRemote = true;
                                        }
                                        exceptions[j] = 
ex.getName().replace('.', '/');
                                }
                                if (!throwsRemote) {
                                        String[] moreExceptions = new 
String[exceptions.length + 1];
                                        System.arraycopy(exceptions, 0, 
moreExceptions, 0, exceptions.length);
                                        moreExceptions[exceptions.length] = 
RemoteException.class.getName().replace('.', '/');
                                        exceptions = moreExceptions;
                                }
                                cw.visitMethod(Opcodes.ACC_PUBLIC + 
Opcodes.ACC_ABSTRACT, method.getName(), Type
                                                .getMethodDescriptor(method), 
null, exceptions);
                        }
                }
                cw.visitEnd();
                return cw.toByteArray();
        }

        private Set<String> analyzeDependencies(InputStream classStream, 
Set<String> includedPackages,
                        Set<String> excludedPackages, ClassLoader cl) throws 
IOException {
                DependencyVisitor v = new DependencyVisitor(includedPackages, 
excludedPackages);
                new ClassReader(classStream).accept(v, false);
                Set<String> dependentClasses = new 
HashSet<String>(v.getDependencies());
                Set<String> newDependentClasses = new 
HashSet<String>(dependentClasses);
                Set<String> evaluatedClasses = new HashSet<String>();
                while (!newDependentClasses.isEmpty()) {
                        for (String dependency : dependentClasses) {
                                if (!evaluatedClasses.contains(dependency)) {
                                        String dependentClass = dependency + 
".class";
                                        InputStream is = 
cl.getResourceAsStream(dependentClass);
                                        if (is != null) {
                                                new ClassReader(is).accept(v, 
false);
                                                
newDependentClasses.addAll(v.getDependencies());
                                        }
                                        evaluatedClasses.add(dependency);
                                }
                        }
                        newDependentClasses.removeAll(dependentClasses);
                        dependentClasses.addAll(newDependentClasses);
                }
                return v.getDependencies();
        }

        private void createJar(String jarName, Set<String> includedClasses, 
Map<String, byte[]> createdResources,
                        ClassLoader cl) throws IOException, 
FileNotFoundException {
                JarOutputStream jos = new JarOutputStream(new 
FileOutputStream(jarName));
                byte[] buf = new byte[1024];
                for (String dependency : includedClasses) {
                        String resourceName = dependency + ".class";
                        InputStream is = cl.getResourceAsStream(resourceName);
                        int br = 0;
                        if (is != null) {
                                JarEntry entry = new JarEntry(resourceName);
                                jos.putNextEntry(entry);
                                while ((br = is.read(buf, 0, buf.length)) != 
-1) {
                                        jos.write(buf, 0, br);
                                }
                                is.close();
                        }
                        else if (createdResources.containsKey(dependency)) {
                                JarEntry entry = new JarEntry(resourceName);
                                jos.putNextEntry(entry);
                                jos.write(createdResources.get(dependency));
                        }
                        jos.closeEntry();
                }
                jos.close();
        }
}


Reply via email to