Find Java classes in the classpath

My brother Oskar needed to read some Java classes from disk, so I whipped up some code to show a possible way. Then I figured you might like it as well.

Here it is (and find the latest version at GitHub where you can submit your improved fork)

Ain’t java short and sweet? ;)


package com.palbrattberg.classpathtools;

import java.io.File;
import java.io.FilenameFilter;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Find classes in the classpath (reads JARs and classpath folders).
 *
 * @author Pål Brattberg, brattberg@gmail.com
 * @see http://gist.github.com/pal
 */
@SuppressWarnings("unchecked")
public class ClasspathInspector {
	static boolean DEBUG = false;

	public static List<Class> getAllKnownClasses() {
		List<Class> classFiles = new ArrayList<Class>();
		List<File> classLocations = getClassLocationsForCurrentClasspath();
		for (File file : classLocations) {
			classFiles.addAll(getClassesFromPath(file));
		}
		return classFiles;
	}

	public static List<Class> getMatchingClasses(Class interfaceOrSuperclass) {
		List<Class> matchingClasses = new ArrayList<Class>();
		List<Class> classes = getAllKnownClasses();
		log("checking %s classes", classes.size());
		for (Class clazz : classes) {
			if (interfaceOrSuperclass.isAssignableFrom(clazz)) {
				matchingClasses.add(clazz);
				log("class %s is assignable from %s", interfaceOrSuperclass, clazz);
			}
		}
		return matchingClasses;
	}

	public static List<Class> getMatchingClasses(String validPackagePrefix, Class interfaceOrSuperclass) {
		throw new IllegalStateException("Not yet implemented!");
	}

	public static List<Class> getMatchingClasses(String validPackagePrefix) {
		throw new IllegalStateException("Not yet implemented!");
	}

	private static Collection<? extends Class> getClassesFromPath(File path) {
		if (path.isDirectory()) {
			return getClassesFromDirectory(path);
		} else {
			return getClassesFromJarFile(path);
		}
	}

	private static String fromFileToClassName(final String fileName) {
		return fileName.substring(0, fileName.length() - 6).replaceAll("/|\\\\", "\\.");
	}

	private static List<Class> getClassesFromJarFile(File path) {
		List<Class> classes = new ArrayList<Class>();
		log("getClassesFromJarFile: Getting classes for %s", path);

		try {
			if (path.canRead()) {
				JarFile jar = new JarFile(path);
				Enumeration<JarEntry> en = jar.entries();
				while (en.hasMoreElements()) {
					JarEntry entry = en.nextElement();
					if (entry.getName().endsWith("class")) {
						String className = fromFileToClassName(entry.getName());
						log("\tgetClassesFromJarFile: found %s", className);
						loadClass(classes, className);
					}
				}
			}
		} catch (Exception e) {
			throw new RuntimeException("Failed to read classes from jar file: " + path, e);
		}

		return classes;
	}

	private static List<Class> getClassesFromDirectory(File path) {
		List<Class> classes = new ArrayList<Class>();
		log("getClassesFromDirectory: Getting classes for " + path);

		// get jar files from top-level directory
		List<File> jarFiles = listFiles(path, new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				return name.endsWith(".jar");
			}
		}, false);
		for (File file : jarFiles) {
			classes.addAll(getClassesFromJarFile(file));
		}

		// get all class-files
		List<File> classFiles = listFiles(path, new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				return name.endsWith(".class");
			}
		}, true);

		// List<URL> urlList = new ArrayList<URL>();
		// List<String> classNameList = new ArrayList<String>();
		int substringBeginIndex = path.getAbsolutePath().length() + 1;
		for (File classfile : classFiles) {
			String className = classfile.getAbsolutePath().substring(substringBeginIndex);
			className = fromFileToClassName(className);
			log("Found class %s in path %s: ", className, path);
			loadClass(classes, className);
		}

		return classes;
	}

	private static List<File> listFiles(File directory, FilenameFilter filter, boolean recurse) {
		List<File> files = new ArrayList<File>();
		File[] entries = directory.listFiles();

		// Go over entries
		for (File entry : entries) {
			// If there is no filter or the filter accepts the
			// file / directory, add it to the list
			if (filter == null || filter.accept(directory, entry.getName())) {
				files.add(entry);
			}

			// If the file is a directory and the recurse flag
			// is set, recurse into the directory
			if (recurse && entry.isDirectory()) {
				files.addAll(listFiles(entry, filter, recurse));
			}
		}

		// Return collection of files
		return files;
	}

	public static List<File> getClassLocationsForCurrentClasspath() {
		List<File> urls = new ArrayList<File>();
		String javaClassPath = System.getProperty("java.class.path");
		if (javaClassPath != null) {
			for (String path : javaClassPath.split(File.pathSeparator)) {
				urls.add(new File(path));
			}
		}
		return urls;
	}

	// todo: this is only partial, probably
	public static URL normalize(URL url) throws MalformedURLException {
		String spec = url.getFile();

		// get url base - remove everything after ".jar!/??" , if exists
		final int i = spec.indexOf("!/");
		if (i != -1) {
			spec = spec.substring(0, spec.indexOf("!/"));
		}

		// uppercase windows drive
		url = new URL(url, spec);
		final String file = url.getFile();
		final int i1 = file.indexOf(':');
		if (i1 != -1) {
			String drive = file.substring(i1 - 1, 2).toUpperCase();
			url = new URL(url, file.substring(0, i1 - 1) + drive + file.substring(i1));
		}

		return url;
	}

	private static void log(String pattern, final Object... args) {
		if (DEBUG)
			System.out.printf(pattern + "\n", args);
	}

	private static void loadClass(List<Class> classes, String className) {
		try {
			Class claz = Class.forName(className, false, ClassLoader.getSystemClassLoader());
			classes.add(claz);
		} catch (ClassNotFoundException cnfe) {
			log("ClassNotFoundException: Could not load class %s: %s", className, cnfe);
		} catch (NoClassDefFoundError e) {
			log("NoClassDefFoundError: Could not load class %s: %s", className, e);
		}
	}

	public static void main(String[] args) {
		// find all classes in classpath
		List<Class> allClasses = ClasspathInspector.getAllKnownClasses();
		System.out.printf("There are %s classes available in the classpath\n", allClasses.size());

		// find all classes that implement/subclass an interface/superclass
		List<Class> serializableClasses = ClasspathInspector.getMatchingClasses(Serializable.class);
		for (Class clazz : serializableClasses) {
			System.out.printf("%s is Serializable\n", clazz);
		}
		System.out.printf("There are %s Serializable classes available in the classpath\n", serializableClasses.size());
	}
}

Gimme closures baby!

Posted in Blog, Technology | 2 Comments

JUnit 4 @RunWith & Google App Engine problems

Quick heads-up if any of you are running the Google App Engine (GAE) for Java and doing test-driven development using JUnit 4+. I got a few problems from the DataNucleus enhancer in Eclipse.

Turns out that using the JUnit library supplied by Eclipse Ganymedes was no great idea. Using a standard junit 4+ jar worked much better as the supplied version seems to not contain the all-important org.junit.internal.runners.JUnit4ClassRunner class which I needed.

(I even managed to get an infinite loop from the enhancer, will see if I have time to boil it down to a simple, reproducible test case later)

Here’s a stacktrace for you googlers:


DataNucleus Enhancer (version 1.1.0) : Enhancement of classes

2009-apr-27 14:00:39 org.datanucleus.metadata.annotations.AnnotationManagerImpl getMetaDataForClass
VARNING: Class "bug.ShowBug" has annotations but there is no registered AnnotationReader. Please check your CLASSPATH and the annotations in the class for validity.

2009-apr-27 14:00:39 org.datanucleus.metadata.annotations.AnnotationManagerImpl getMetaDataForClass
VARNING: Class "bug.ShowBug" has annotations but there is no registered AnnotationReader. Please check your CLASSPATH and the annotations in the class for validity.

DataNucleus Enhancer completed with success for 0 classes. Timings : input=266 ms, enhance=0 ms, total=266 ms. Consult the log for full details
DataNucleus Enhancer completed and no classes were enhanced. Consult the log for full details

Posted in F/OSS, Technology | Leave a comment

Cucumber, Maven and Java

Madaspeak has a nice writeup on how to use Cucumber from Maven.

I ran into some problems with downloaded gems being read-only and this caused troubles as files seem to be opened in read-write mode.

I get the following error:

C:\Documents and Settings\palbra\Desktop\cucumber_try>mvn test
[INFO] Scanning for projects...
[INFO]
[INFO] Building cucumber_try
[INFO]    task-segment: [test]
[INFO]
[INFO] Preparing exec:java
[WARNING] Removing: java from forked lifecycle, to prevent recursive invocation.
[WARNING] Removing: java from forked lifecycle, to prevent recursive invocation.
[WARNING] Removing: java from forked lifecycle, to prevent recursive invocation.
[WARNING] Removing: java from forked lifecycle, to prevent recursive invocation.
[INFO] No goals needed for project - skipping
[INFO] [exec:java {execution: install-hoe-gem}]
JRuby limited openssl loaded. gem install jruby-openssl for full support.

http://wiki.jruby.org/wiki/JRuby_Builtin_OpenSSL

ERROR:  While executing gem ... (Errno::EACCES)
Permission denied - Permission denied - C:/Documents and Settings/palbra/.jruby/lib/ruby/gems/1.8/gems/hoe-1.8.2/History.txt

Removing the read-only flag fixes the problem though.

Another problem I’ve bumped into was

'cmd' is not recognized as an internal or external command,
operable program or batch file.
rake aborted!
Command failed with status (1): ["C:/Documents and Settings/palbra/.jruby/b...]

(See full trace by running task with --trace)

 

I had to add C:\WINDOWS\system32 before the cmd call in C:\Documents and Settings\palbra\.jruby\bin\jruby.bat

Not so hard, but hopefully it helps someone out there.

Posted in F/OSS, Technology | Tagged , , , , , , | Leave a comment

Access Sourceforge.net Shell services using PuTTY on Windows

Shell services at SourceForge have been changing recently and it’s not entirely intuitive how to access your shell account (at least it wasn’t for me).

Here’s how to log in to your shell account for your project using the excellent PuTTY package in Windows.

  1. Download and install PuTTy
  2. Run plink shell.sourceforge.net create
  3. Enter your sourceforge username and projectname using the form: username,projectname
  4. Enter your sourceforge password
  5. Run putty
  6. Enter host name as shell.sourceforge.net
  7. Set Connection > SSH > Remote command to create
  8. Set Session > Saved Sessions to shell.sourceforge.net
  9. Click Save
  10. Double-click shell.sourceforge.net and enter username and projectname using the same  form again: username,projectname
  11. Enter your password
  12. Enjoy!
This will work for several projects, just change the project name in step 10.
To give SourceForge feedback on the new Shell changes see the SourceForge shell service survey.
The information above is pulled from the support request PuTTY accesses restricted shell – ID: 2175546
Posted in Blog, Technology | Tagged , , | 3 Comments

From Best to Good – Re-evaluate your practices again

When looking to improve your work environment or pushing to use a new method, it’s not uncommon to go looking for what might be considered Best Practice in that area. It’s as natural and common as it is problematic and dangerous.

First of all, the very notion of a Best Practice is that there is some way of performing a task or method that is superior to all other possible ways of performing that task or method. It is also suggested that the practice is superior in all aspects, meaning it delivers the most value and costs the least resources (if applicable).

So, what’s the problem with wanting to find, and ultimately putting to use, best practices? Wouldn’t it be a great thing if we could find more best practices and let all interested parties in the world share as many best practices as possible? In theory; yes. In practice; no thanks.

The problem with many things marketed as best practices is that it’s not. It is extraordinarily uncommon for a technique och method to be so absolutely, overwhelmingly better than all other ways in which you could perform a task that it could be declared a true and undisputed Best Practice. That’s not to say there aren’t any Best Practices, I’m believe there are at least a few lurking about in this great universe of ours, it’s just that they’re not so common as we would think.

Take the field I’m working in (and probably you as well, since you’re stuck reading this): IT. Software Development. What’s the best way to develop software? What’s the best way to handle source code? What’s the best way to make sure we stay on track, do the right thing and always deliver maximum business value for the least amount of resources while boosting the user and developer experience and maximizing shareholder profits? Please let me know the best practice for that! No really, please do!

The obvious answer is that IT is a way to complex field to have any large, powerful Best Practices that all can agree upon. If you don’t agree, tell me the best computer language the world agreed upon. Or the best web framework. The catch being here that different people and organizations have different best practices, which serves to make them anything but unequivocally best. Of course, there is no single best computer language or web framework that is best for all cases.

So, if best practices are super-rare, bordering on non-existant, what should we do when we want to know how people smarter than us have solved a recurring problem we have just faced? We should do like other industries, look for Good Practices. In many industries there are several agreed upon Good Practices that form the basis for making sure we do not all spend obscene amount of times reinventing an inferior wheel.

Focusing on Good Practices instead of the mythical Best Practices has many benefits: A practice that has proven to work in a good fashion for a team doesn not have the burden of proof as to wether it is also the absolute best practice concievable meaning there should be more good practices to choose from. As there are many, competing good practices you are encouraged to constantly inspect, adapt and revise any decisions to go with any particular practice, which is great since that causes you to pay ongoing attention to how you work and how your practices are working for your particular environment and people. Focusing on good practices also removes the stigma of trying on a Best Practice and finding it doesn’t deliver for your needs. If it truly was best, you must be doing something wrong. If the practice you tried was merely good, you can just keep looking or adapting the practice.

To round up: stop looking for Best Practices and embrace imperfection and ongoing improvement by going for Good Practices. I’ll follow this up by posting some good practices I’ve used in projects over the following months. Stay tuned!

Posted in Articles, Blog | Tagged , , , , | Leave a comment

Initial Derby-support in DbFit

DbFit does heavy lifting!This just in: you can now run FitNesse tests with DbFit using Derby/JavaDB as your database of choice.

Have a go and download it from Subversion here: https://dbfit.svn.sourceforge.net/svnroot/dbfit/dbfit/dist/dbfit-20080530.jar

It’s not terribly complete which you can tell from the limited set of acceptance tests available, but it works for the basic cases.

Notable omission: Stored Procedures. My current project doesn’t need it so it’s not yet implemented.

If you are missing something, let me know and I will add it. (To get it fixed fast, also supply a failing DbFit-test case.

Posted in F/OSS, Technology | Tagged , , , , , , , | Leave a comment

JtestR 0.2 out now

Ola Bini+friends’ cool tool, JtestR has just been released in a 0.2 release that adds some nice features and fixes a few bugs as well.

JtestR, for those of you who haven’t heard about it yet, is a tool to enable testing of java code using alot of the cool ruby tools available for testing such as rspec, test/unit and more.

As if that wasn’t enough, it’s also integrated nicely with Ant and Maven and can also run your existing junit and testng tests.

Any day now I’ll try to find some time to put together a post on my experiences with using ruby to test java code. But for now, go take a look at jtestr!

Posted in F/OSS, Pointers, Technology | Tagged , , , , , , , , , | Leave a comment

Painless backups – solved for now

If you’re anything like me, you’ve dabbled in various solutions for your personal (and professional) backup of data. Being the geek that I am, these are some of the different solutions I have used in the past:

  • External harddrive (on my fifth (!) drive now, they keep crashing)
  • CD/DVD-backups (takes time and they’re fragile)
  • Extra internal drive (What was I thinking? Hacker’s paradise)
  • External server with plenty of storage space (Setup my own with 750 GB RAID’ed; linux, rsync, ssh, cron and all that jazz. But it also gets hacked…)
  • Flickr.com Pro (Good for my photos, but what about the rest?)
  • Cheap web hosting (Yeah, this works, although a bit unreliable, and messy to FTP every now and then)

So, I was thinking it was time for something new, something that doesn’t bother me and something that just works, automagically. What I’m using right now, and have been using for some months, is Mozy. It’s a service owned by EMC and it’s a no-frills automatic backup tool.

It has clients for Windows and Mac (found none for Linux yet) and just works. I has all the stuff I want such as encryption, incremental, block-level syncing, support for several computers on one account etc.

There’s a free version which I suggest you start with, to see if the program and service suits you (you get 2 GB for life) and the full version is about $5 / month for unlimited storage. I think it beats my other solutions and about the only other thing I would like now, is to have the client backup to both Mozy and Amazon S3 (for the paranoid person lurking inside me), but I’ll wait a bit for the S3-part. (There’s plenty of tools for doing just that though)

I’m not one to spam, but if you would like to try Mozy and get 256 MB extra, feel free to use this link. By using that link, I also get some extra love from Mozy.  On the other hand, if you don’t like me, just go to https://mozy.com/ and spare me the love! ;)

Posted in Pointers, Technology | Tagged , , , , , | Leave a comment

Manually adding MD5 checksum to files using Ant

I just added some JAR’s from the DctmUtils project to my local Maven repository to be able to use it like anyother dependency. In doing so I had to create a POM and the structure for the JAR. This in itself works like a charm, but if you do not supply valid checksum-files (SHA1 or MD5) for the POM and JAR Maven 2 will give you warnings.

Solution:
Save the following in a build.xml file in your folder and run ant md5 to generate MD5-files for all files in the folder. Easy peasy.

 

<project default="md5">
  <target name="md5">
    <checksum forceOverwrite="yes" fileext=".md5">
      <fileset dir="." excludes="**/*.md5"/>
    </checksum>
  </target>
</project>
Posted in F/OSS, Pointers, Technology | Tagged , , , , | Leave a comment

Getting a handle on custom WebTop components using Spring

When customizing an EMC Documentum WebTop application and using the Spring Framework to tie everything together, you find yourself wishing you could use Dependency Injection at all places in the existing WebTop source.

I don’t yet know of a workable way to achive that sort of plumbing with a WebTop application (especially for all types of existing components and tags) to just expose any new dependencies and let Spring handle it all.

Another way to achive interoperability with Spring and still use most of what’s good there is to allow our legacy components to have access to a Spring ApplicationContext, but setting one using Spring is not feasible because of the same reasons as above. Thus we introduce the StaticSpringApplicationContext.

The StaticSpringApplicationContext works simply by containing a static reference to the Spring ApplicationContext and exposing a static helper method to get beans configured by Spring.

Configure the helper class by defining it in your web.xml like so:

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

And at least the following in your WEB-INF/applicationContext.xml file:

<bean id="StaticSpringApplicationContext"
class="com.palbrattberg.spring.StaticSpringApplicationContext" />

This will make sure Spring itself configures the StaticSpringApplicationContext on startup.

Now you can simply get your configured beans (preferably in your constructor) like this:

MyBean myBean = (MyBean) StaticSpringApplicationContext.getBean("myBean");

Now you are free to use Spring for configuring all your future code.

Source code for StaticSpringApplicationContext

Posted in F/OSS, Technology | Tagged , , , , , , | 2 Comments