import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.net.URL;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* I built this class to perform the introspection necessary to generate the
* packages.yml file, which is then used by package-generator.pl to generate
* each of the wrapper objects for the JCR classes.
*
* Copyright 2006 Andrew Sterling Hanenkamp (hanenkamp@cpan.org). All Rights
* Reserved.
*
* This module is free software; you can redistribute it and/or modify it under
* the same terms as Perl.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE.
*
* @author Andrew Sterling Hanenkamp (hanenkamp@cpan.org)
*/
public class JCRPackageGenerator {
/**
* Print the usage message.
*/
public static void usage() {
System.err.println("usage: java [options] " + JCRPackageGenerator.class.getName() + " perl-blib-directory");
}
/**
* Print an error message and then the usage message.
*
* @param message the error message to display
*/
public static void usage(String message) {
System.err.println("error: " + message);
System.err.println();
usage();
}
/**
* Loads a {@link JarFile} object for the JCR library.
*
* @return an object representing the JCR library's JAR file.
* @throws URISyntaxException if an error occurs parsing the JAR file's path
* as discovered from the class loader
* @throws IOException if an error occurs locating and creating the JAR file
* object
*/
public static JarFile getJCRJar() throws URISyntaxException, IOException {
ClassLoader loader = JCRPackageGenerator.class.getClassLoader();
URL repositoryURL = loader.getResource("javax/jcr/Repository.class");
String repositoryPath = repositoryURL.getPath();
int splitAt = repositoryPath.indexOf('!');
URI jarURI = new URI(repositoryPath.substring(0, splitAt));
JarFile jarFile = new JarFile(new File(jarURI));
return jarFile;
}
/**
* Reads all the class files found in the JCR JAR file and returns the
* classes found in a list. Any class file found in the same JAR file as
* {@link javax.jcr.Repository} will be returned.
*
* @param jarFile the JAR file object returned by {@link #getJCRJar()}
* @return a {@link List} of {@link String}s, each containing the fully
* qualified name of a Java class found in the JCR JAR
*/
public static List getJCRClasses(JarFile jarFile) {
List jcrClasses = new ArrayList();
Enumeration entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = (JarEntry) entries.nextElement();
String entryName = entry.getName();
if (entryName.matches(".*?\\.class$")) {
String className
= entryName.replaceAll("\\.class$", "")
.replaceAll("/", ".");
jcrClasses.add(className);
}
}
return jcrClasses;
}
/**
* This method outputs a class' type name in a more friently format. If the
* class represents an array type, it is printed as "Array:innerType" rather
* than "[LinnerType;".
*
* @param clazz the {@link Class} to turn into a a {@link String}
* @return a {@link String} containing the fully qualified type name with
* array types abstracted as described above
*/
public static String classAsString(Class clazz) {
if (clazz.isArray()) {
return "Array:" + classAsString(clazz.getComponentType());
}
else {
return clazz.getName();
}
}
/**
* Takes a list of signatures and converts that into the YAML needed to
* describe the return types and parameters. This method isn't currently
* used, but I've left it because it might be used in the future.
*
* Output is sent to standard out.
*
* @param methodList a {@link List} containing {@link List}s containing
* {@link Class} types. These represent all of the possible method
* signatures of a given method name. The first element of the nested list
* is the return type. The rest of the elements (if any) represent the
* paraemter types.
*/
public static void generateParametersConfig(List methodList) {
Iterator methodIter = methodList.iterator();
while (methodIter.hasNext()) {
List signatureList = (List) methodIter.next();
System.out.println(" -");
Iterator signatureIter = signatureList.iterator();
while (signatureIter.hasNext()) {
Class parameter = (Class) signatureIter.next();
System.out.println(" - " + classAsString(parameter));
}
}
}
/**
* Creates the YAML required to represent each method and it's return type.
*
* Output is sent to standard out.
*
* @param methods the array of methods to output
*/
public static void generateMethodsConfig(Method[] methods) {
Map staticMethods = new HashMap();
Map instanceMethods = new HashMap();
for (int i = 0; i < methods.length; ++i) {
Method thisMethod = methods[i];
// Skip some of the mundane
String name = thisMethod.getName();
if ("hashCode".equals(name)) {
continue;
}
else if ("getClass".equals(name)) {
continue;
}
else if ("equals".equals(name)) {
continue;
}
else if ("wait".equals(name)) {
continue;
}
else if ("notify".equals(name)) {
continue;
}
else if ("notifyAll".equals(name)) {
continue;
}
Map methodMap = (thisMethod.getModifiers() & Modifier.STATIC) > 0
? staticMethods : instanceMethods;
List methodList = methodMap.containsKey(thisMethod.getName())
? (List) methodMap.get(thisMethod.getName())
: new ArrayList();
List signatureList = new ArrayList();
signatureList.add(thisMethod.getReturnType());
Collections.addAll(signatureList, thisMethod.getParameterTypes());
methodList.add(signatureList);
methodMap.put(thisMethod.getName(), methodList);
}
if (!staticMethods.isEmpty()) {
System.out.println(" static:");
Iterator iter = staticMethods.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
System.out.println(" " + entry.getKey() + ": " +
classAsString((Class) ((List) ((List) entry.getValue()).get(0)).get(0)));
// generateParametersConfig((List) entry.getValue());
}
}
if (!instanceMethods.isEmpty()) {
System.out.println(" instance:");
Iterator iter = instanceMethods.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
System.out.println(" " + entry.getKey() + ": " +
classAsString((Class) ((List) ((List) entry.getValue()).get(0)).get(0)));
// generateParametersConfig((List) entry.getValue());
}
}
}
/**
* Generates the YAML configuration required to generate Perl wrapper
* packages for each JCR class.
*
* All output is sent to standard out.
*
* @param classes this is the list of classes returned by {@link
* getJCRClasses(JarFile)}
* @throws ClassNotFoundException if an error occurs instantiating one of
* the classes in the given list
*/
public static void generateJCRForPerlConfig(List classes)
throws ClassNotFoundException {
System.out.println("---");
Iterator classIter = classes.iterator();
while (classIter.hasNext()) {
String className = (String) classIter.next();
Class clazz = Class.forName(className);
System.out.println(className + ":");
if (clazz.getSuperclass() != null
|| clazz.getInterfaces().length > 0) {
System.out.println(" isa:");
if (clazz.getSuperclass() != null) {
System.out.println(" - " +
classAsString(clazz.getSuperclass()));
}
Class[] interfaces = clazz.getInterfaces();
for (int i = 0; i < interfaces.length; ++i) {
System.out.println(" - " +
classAsString(interfaces[i]));
}
}
if (clazz.getConstructors().length > 0) {
System.out.println(" has_constructors: 1");
}
else {
System.out.println(" has_constructors: 0");
}
Field[] fields = clazz.getFields();
if (fields.length > 0) {
System.out.println(" static_fields:");
for (int i = 0; i < fields.length; ++i) {
System.out.println(" - " + fields[i].getName());
}
}
System.out.println(" methods:");
generateMethodsConfig(clazz.getMethods());
System.out.println();
}
}
/**
* Main program: finds the JAR file, extracts the list of classes, and
* generates the YAML file containing information about the classes found.
*
* @param args unused
* @throws IOException see {@link #getJCRJar()}
* @throws URISyntaxException see {@link #getJCRJar()}
* @throws ClassNotFoundException see {@link #getJCRClasses(JarFile)}
*/
public static void main(String[] args) throws IOException,
URISyntaxException, ClassNotFoundException {
// Load the classes
JarFile jcrJar = getJCRJar();
List jcrClasses = getJCRClasses(jcrJar);
// Generate the configuration
generateJCRForPerlConfig(jcrClasses);
}
}