You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@river.apache.org by Peter Firmstone <ji...@zeus.net.au> on 2009/03/19 06:07:16 UTC

RIVER-272 Bantam ClassDepAnalyzer.java

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();
	}
}