Monday, June 28, 2010

Klipsch ProMedia Ultra 2.0 Pot Fix

One of my favorite audio companies for years now has been Klipsch. They've offered unparalleled THX certified sound for the computer audio enthusiast and as a result I've happily purchased multiple sets of speakers from them. Unfortunately, I've come to find that the volume and bass potentiometers (a.k.a. pots or rheostats) on the ProMedia Ultra 2.0 series are faulty. I put up with the irritations that go along with the knob adjustment crackling and popping sounds for a while, but finally had to do something about the issue when the sound started to cut out altogether.

I concluded there to be a couple options in this case since I'm out of warranty by at least a couple years and Klipsch no longer produces these speakers. I could have certainly attempted to switch out the pots with new, perhaps higher quality ones but this option didn't thrill me all that much because it's invasive, time consuming, and a last ditch effort in the event there are no other options. Fortunately, I was able to find evidence that people were successfully fixing the pots by opening up the speaker enclosure, spraying compressed air onto the pot, and subsequently lubricating the knob with a light oil of some variety. This solution seemed a lot better to me so that's what I went ahead and did. Sure enough, the speakers are already performing more reliably than they had been before this fix. However, I must report that as I write this they have already cut out one time and I had to turn the speakers off and on again in order to get the faulty pot to work. We'll see how things go moving forward. Who knows? I may soon find myself writing an article about replacing the potentiometer with a new one...

Mike

Friday, June 4, 2010

Compiling Java From the Command Line - Part 1

Introduction
More often than not Java developers end up using an IDE like Eclipse or Netbeans for writing, compiling, and packaging their projects. There's also a good chance a tool such as Ant or Maven is being leveraged in this environment. These are all great tools for productive development which I actually use myself, but they shield a developer from what's going on under the hood. This is generally a good thing, but for those occasions when something goes wrong or you're having a hard time understanding why class Foo is not being picked up by the compiler, it's good to have an understanding of what magic is really happening underneath all the bells and whistles.

In this article we'll be discussing the ins and outs of the javac compiler and how it's used with the sourcepath and classpath arguments as well as how the compilation process is affected by code packaging schemes. This article assumes you already have java 1.6 installed and properly configured on your machine and that you're using Windows XP. While some of the commands are OS-specific, the lessons are fairly universal so Unix/Linux users shouldn't have much trouble following along as well. By no means will this be an exhaustive discussion, but it should have you well on your way to using the command prompt (or shell) for compiling Java applications.

Javac Me Gently
Let's start with a simple example to get the ball rolling. First, open up a command prompt and get your project structure in order. Here's my sample project structure:
C:\>cd DevProjects\ExampleProject
C:\>tree /F
C:.
├───classes
└───src
        MainClassNoPkg.java

I *strongly* urge you to put your code in a location that has no spaces in the folder names. It's possible to get around this, but the headaches you'll get especially with more complicated projects and tool dependencies make it not worth the hassle.

and the java class, MainClassNoPkg.java, is as follows:
// MainClassNoPkg.java
// Default package

class MainClassNoPkg {
    MainClassNoPkg() {}
    
    public static void main(String[] args) {
        System.out.println("Msg from MainClassNoPkg");
    }
}

For a text editor I've grown quite fond of gvim. It's a Windows port of the popular and ubiquitous Unix editor, Vi, and has many powerful text editing features. I highly recommend learning an editor like this, but it's not something you pick up overnight so feel free to use any plain text editor you like for this tutorial.

The last thing I'd like you to do here is clear out the CLASSPATH environment variable. More on why I'm having you do this later, but for now just go with it and run the following in your command shell:
C:\DevProjects\ExampleProject>set CLASSPATH=

Now let's run javac and see what happens...
C:\DevProjects\ExampleProject>javac -verbose src\MainClassNoPkg.java
[parsing started src\MainClassNoPkg.java]
[parsing completed 31ms]
[search path for source files: .]
[search path for class files: C:\java\jdk1.6.0_18\jre\lib\resources.jar,C:\java\
jdk1.6.0_18\jre\lib\rt.jar,C:\java\jdk1.6.0_18\jre\lib\sunrsasign.jar,C:\java\jd
k1.6.0_18\jre\lib\jsse.jar,C:\java\jdk1.6.0_18\jre\lib\jce.jar,C:\java\jdk1.6.0_
18\jre\lib\charsets.jar,C:\java\jdk1.6.0_18\jre\classes,C:\java\jdk1.6.0_18\jre\
lib\ext\dnsns.jar,C:\java\jdk1.6.0_18\jre\lib\ext\localedata.jar,C:\java\jdk1.6.
0_18\jre\lib\ext\sunjce_provider.jar,C:\java\jdk1.6.0_18\jre\lib\ext\sunmscapi.j
ar,C:\java\jdk1.6.0_18\jre\lib\ext\sunpkcs11.jar,.]
[loading java\lang\Object.class(java\lang:Object.class)]
[loading java\lang\String.class(java\lang:String.class)]
[checking MainClassNoPkg]
[loading java\lang\System.class(java\lang:System.class)]
[loading java\io\PrintStream.class(java\io:PrintStream.class)]
[loading java\io\FilterOutputStream.class(java\io:FilterOutputStream.class)]
[loading java\io\OutputStream.class(java\io:OutputStream.class)]
[wrote src\MainClassNoPkg.class]
[total 297ms]

That might look overwhelming at first, but bear with me and all we'll walk through it. On the first line we're actually executing javac. I've intentionally included the -verbose argument so we can take a look at what's going on under the hood, but normally you can feel free to leave it out. Immediately following you will see the reference to our MainClassNoPkg.java class file we wrote. Everything else that follows is output from the javac compiler.

First, you can see that the compiler picked up our java source file and parsed it. Notice that immediately following the compiler mentions searching paths for source and class files. The default for both sourcepath and classpath is the current directory. In addition, the compiler will add the bootstrap and extension classes that shipped with the javac compiler being used, though you can modify this behavior with the -bootclasspath and -extdirs options if you wish. However, we won't be changing these options in this article. You can see all the bootstrap and ext classes listed in the output for the class file search path. Finally, the output shows the compiler loading the classes MainClassNoPkg references.

For one simple java source file that was pretty painless, right? You could even simplify this further by using wildcards
C:\DevProjects\ExampleProject>javac src\*.java

But we've only compiled one file, you might say. The standard Java tutorial shows me how to do that, there must be more to it than this, right? Indeed, there is. Next we'll discuss compiling multiple source files at once and using the -sourcepath and -classpath options.

Let's Get To the Source

In this section I'm going to cover how the compiler searches for your uncompiled Java source files and turns them into executable bytecode. We're going to add to our example now by creating another class appropriately named AnotherClass with the following contents
// AnotherClass.java
// Default package

class AnotherClass {
    AnotherClass() {}

    public void printMessage(String msg) {
        System.out.println("The message is: " + msg);
    }
}

Next, modify the MainClassNoPkg.java to use the newly created class.
// MainClassNoPkg.java
// Default package

class MainClassNoPkg {
    MainClassNoPkg() {}
    
    public static void main(String[] args) {
        System.out.println("Msg from MainClassNoPkg");
        AnotherClass lAnother = new AnotherClass();
        lAnother.printMessage("Hello Java developers!");
    }
}

Your source tree should look as follows, assuming you've cleaned up the previously created .class file.
C:\DevProjects\ExampleProject>tree /F
Folder PATH listing
Volume serial number is
C:.
├───classes
└───src
        AnotherClass.java
        MainClassNoPkg.java

Now run javac as we did before, specifying the main class name. Don't use the wildcard character this time.
C:\DevProjects\ExampleProject>javac src\MainClassNoPkg.java
src\MainClassNoPkg.java:8: cannot find symbol
symbol  : class AnotherClass
location: class MainClassNoPkg
        AnotherClass lAnother = new AnotherClass();
        ^
src\MainClassNoPkg.java:8: cannot find symbol
symbol  : class AnotherClass
location: class MainClassNoPkg
        AnotherClass lAnother = new AnotherClass();
                                    ^
2 errors

Uh-oh what happened!? That is most definitely NOT what we want to happen, but no worries as this was just a way to make a point about how the javac compiler finds the classes it needs during compilation. Remember when we cleared out the CLASSPATH environment variable? By doing so we instructed javac to default it's classpath to the current directory (normally denoted by a period). The compiler also takes a -sourcepath argument and when we don't specify this the default is also the current directory.

The point to make here is that while we specified a java file to compile in the src directory, the compiler couldn't find the referenced AnotherClass.java file. Why, you might ask? But isn't the compiler smart enough to grab other files in the same directory as the source file we instructed it to compile? Well, no actually. The Java compiler does not really do any searching based on the source files (and their paths) you pass as arguments to javac. If you want javac to find AnotherClass.java you have to handle it like this:
C:\DevProjects\ExampleProject>javac -sourcepath src src\MainClassNoPkg.java

or this
C:\DevProjects\ExampleProject>javac -cp src src\MainClassNoPkg.java

or this
C:\DevProjects\ExampleProject>cd src
C:\DevProjects\ExampleProject\src>javac MainClassNoPkg.java

In the first two examples we specified a location for the compiler to find other ancillary classes by passing the src directory as the -sourcepath or -cp (same as -classpath) options. In the third example we cd'ed into the src directory and executed the javac command. Specifying a sourcepath or classpath was unnecessary because remember that the compiler uses the current directory as the default when those options aren't specified. If you need to pass more than one directory, simply separate them with a semicolon (;), no spaces. Also note that we could have set the CLASSPATH environment variable instead of using the command line arguments. This is particularly well-suited for long path definitions or for occasions when you'll need to run the javac command more than once.

A more complete order of operations for searching for source files is as follows and additional details can be found in the javac documentation listed in the references at the end of this article.
  1. paths listed via the -sourcepath option are searched
  2. -cp or -classpath arguments are searched
  3. The CLASSPATH environment variable is searched
  4. The current directory
OK, now assuming you've successfully run one of the examples above, the compiler should spit out a couple .class files and you should be able to run your program without any errors as follows:
C:\DevProjects\ExampleProject>java -cp src MainClassNoPkg
Msg from MainClassNoPkg
The message is: Hello Java developers!

The point in this section's example is to show that you must include the relative or absolute path in your source file list and you must include at least one reference to a source file, whether by wildcard or explicit declaration, regardless of what you include as arguments to -cp and -sourcepath. The following, while seemingly clever enough and seemingly within javac's rules, just simply will not work!
C:\DevProjects\ExampleProject>javac -sourcepath src *.java
javac: file not found: *.java
Usage: javac <options> <source files>
use -help for a list of possible options

C:\DevProjects\ExampleProject>javac -sourcepath src MainClassNoPkg.java
javac: file not found: MainClassNoPkg.java
Usage: javac <options> <source files>
use -help for a list of possible options

Furthermore, the compiler will oblige you to grab source files referenced by any source files you are compiling, but only if it can find them on the source or class paths. There is simply no way to instruct javac to search recursively for a source or class file. This comes into play when we deal with packages, but we'll discuss this further in the next section. Now onto our next lesson, compiling using references to source that's already been compiled.

Javac and Source Files Vs. Class Files and JARs - What the Diff?

We've already compiled a number of example projects so now let's expand on this code to illustrate another point - how javac treats java source files differently from previously-compiled class files (java bytecode in a file with a .class extension).

Imagine you've been coding a project for a while now and have numerous source files distributed across multiple packages (we'll cover this later) and projects and are using a couple external libraries. Obviously, there are times that we might not want to compile every last bit of code in our project, but more importantly there are times when this might not be feasible. Think of the situation where you've purchased a jar file from an external company and all you've received is compiled java bytecode. In other words, you don't have the source code because it's proprietary. But you still want to use the wonderful API/library that, perhaps ClevelandFlash, has developed for you. Will ClevelandFlash let it be so? Can this be done? Of course it can! Otherwise we wouldn't be here and instead developers would be off using some other language for its code reuse facilities. Even if you have access to the source code, which is par for the course for open source projects (thus the term "open source!"), you don't want to be compiling them from source unless absolutely necessary. Leveraging previously compiled code is in fact pretty simple to do, but first let's modify our code so that it's not using the yucky default package anymore.

Hopefully you're familiar with Java packages as I'm not going to go into the details here, but if not no worries as you should still be able to run the examples. I do, however highly recommend your taking the time to check out the Java Tutorials basic package explanation and package trail at your earliest convenience. Okay, onto the examples now. First, let's move our java source files to their new package locations and rename MainClassNoPkg to MainClass. I'm going to be using com.clevelandflash as my root package name to stick with convention, but you're free to use your own values here if you wish. Just be sure to adjust the source code and command prompt commands accordingly to reflect these changes!

My tree now looks like the following
C:\DevProjects\ExampleProject>tree /F
Folder PATH listing
Volume serial number is
C:.
├───classes
└───src
    └───com
        └───clevelandflash
            └───javac_tut
                ├───core
                │       MainClass.java
                │
                └───helper
                        AnotherClass.java

And here are the updated source files:
// MainClass.java
// No longer the default package
package com.clevelandflash.javac_tut.core;

import com.clevelandflash.javac_tut.helper.AnotherClass;

class MainClass {
    MainClass() {}
    
    public static void main(String[] args) {
        System.out.println("Msg from MainClass");
        AnotherClass lAnother = new AnotherClass();
        lAnother.printMessage("Hello Java developers!");
    }
}

// AnotherClass.java
// No longer the default package
package com.clevelandflash.javac_tut.helper;

public class AnotherClass {
    public AnotherClass() {}

    public void printMessage(String msg) {
        System.out.println("The message is: " + msg);
    }
}


Now, run the following command
C:\DevProjects\ExampleProject>javac -d classes -sourcepath src src\com\cleveland
flash\javac_tut\core\MainClass.java

Here we've used the -d option with a value of classes to instruct the Java compiler to output the .class files to the classes directory rather than alongside the .java source files, as is the default. The compiler does something additionally useful in that it automatically generates the package folder structure in the output directory. Cool!
C:\DevProjects\ExampleProject>tree /F
Folder PATH listing
Volume serial number is
C:.
├───classes
│   └───com
│       └───clevelandflash
│           └───javac_tut
│               ├───core
│               │       MainClass.class
│               │
│               └───helper
│                       AnotherClass.class
│
└───src
    └───com
        └───clevelandflash
            └───javac_tut
                ├───core
                │       MainClass.java
                │
                └───helper
                        AnotherClass.java

This is really good considering the Java language requires you to match the folder structure to your package names. One small exception, though not considered a best practice, would be if you had a source file in the core directory but left it declared as part of the default package. Javac would compile it just fine, but when it writes out the .class file it would put it in .\classes instead of in .\classes\com\clevelandflash\javac_tut\core. Another important thing to note is that unless you use the *.java wildcard on a each directory or enumerate each file that needs compiling, the compiler will NOT pick up non-referenced source files. So for instance, if you had a file IO helper class (just thinking of something random here) in the helper directory that was atomic from the rest of the code, you'd have to be sure and include it in your javac command explicitly, otherwise it won't get compiled. The compiler only picks up necessary references (i.e. import statements in code) or files you explicitly declared in the source files list when calling javac. Make sense so far? Good! Let's just run our sample real quick to make sure everything works with our new package structure.
C:\DevProjects\ExampleProject>java -cp classes com.clevelandflash.javac_tut.core
.MainClass
Msg from MainClass
The message is: Hello Java developers!

Looks good, but I do want to draw attention to the fact that you need to specify two things when running the program so that the JVM can find your program. First, you must specify the classpath to include the classes directory, and second, call the class that contains the main method (MainClass) by its full qualified name. If you went through the packages trail on the Java tuts site (if you didn't, now's a great time) you would immediately notice this is done simply by appending the class's package name as a prefix to the class name. If you've made it this far, you're well on your way to understanding Java compilation and classpaths from the command line. Thanks for reading!

But You Didn't Cover JAR and External Class Files Yet!@#%

The last section was just getting so long and it seemed only appropriate to wrap it up. Alright, on with the show as promised. In this final section we'll be compiling source code against some external libraries as well as some previously compiled code that we wrote ourselves. First thing's first, we need to setup another project directory and download a good library to use for our code examples.

Modify your folder setup so that you now have another project named LibProject at the same level as ExamplProject. Mine looks like this:
C:\DevProjects\LibProject>tree /F
Folder PATH listing
Volume serial number is
C:.
├───classes
├───lib
├───resources
└───src
    └───com
        └───clevelandflash
            └───goodtimes
                ├───core
                │       ClassForXerces.java
                │
                └───model
                        Company.java

Go ahead and also create the additional subdirectories and files as I have shown above. We'll work out the source code for the two java files shortly but first we need to find a library.

Just for fun, let's go with the Xerces XML parser. XML parsing, while potentially memory intensive, is a very popular and necessary activity for programmers these days. You can download it for yourself here - Xerces2 Java Parser. I'm not going to go into setup instructions, but at the very least you'll need the following jar files:
  • resolver.jar
  • serializer.jar
  • xercesImpl.jar
  • xml-apis.jar
Let's put these in the lib directory located in the LibProject you just created. Also, we're going to need an XML file to work with. Seeing as the focus of this article is not XML nor Xerces, we'll save the complicated examples for another day and use something simple. This is the XML file, companies.xml.
<companies> 
    <company status="spectacular">ClevelandFlash</company> 
    <company status="good">Some Company</company> 
    <company status="bad">Terrible Corporation</company> 
</companies>

You can also download it here if you're so inclined companies.xml. Place this in resources directory in your LibProject folder.

Okay, so let's code something already. We'll put the XML parsing code in ClassForXerces.java and the model structure in Company.java. This is an admittedly contrived example with very little if any emphasis on elegant design patterns, but we're not really concerned with that right now.

Here's ClassForXerces.java
package com.clevelandflash.goodtimes.core;

import java.io.IOException;

import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.SAXException;

import com.clevelandflash.goodtimes.model.Company;

public class ClassForXerces extends DefaultHandler {
    private Company mCompany;
    private StringBuffer mCurrentName;
    private String mCurrentStatus;

    public ClassForXerces() {
        this.mCompany = new Company();
    }
    
    public Company getCompany() {
        return this.mCompany;
    }

    public void parseFile(String pFileToParse) {
        SAXParserFactory lSpf = SAXParserFactory.newInstance();
        try {
            SAXParser lSp = lSpf.newSAXParser();
            lSp.parse(pFileToParse, this);
        } catch(FactoryConfigurationError fce) {
            fce.printStackTrace();
        } catch(ParserConfigurationException pce) {
            pce.printStackTrace();
        } catch(SAXException se) {
            se.printStackTrace();
        } catch(IOException ioe) {
            ioe.printStackTrace();
        }
    }

    // Handle events from SAX parsing
    
    public void startDocument () {
        System.out.println("Start document");
    }


    public void endDocument () {
        System.out.println("End document");
    }
    
    public void startElement(String pUri, String pName, String pQName, Attributes pAtts) {
        this.mCurrentName = new StringBuffer();
        this.mCurrentStatus = "";
        if(pQName.equalsIgnoreCase("company")) {
            this.mCurrentStatus = pAtts.getValue("status");
            if(this.mCurrentStatus.equalsIgnoreCase("spectacular")) {
                this.mCompany.setStatus(pAtts.getValue("status"));
            }
        }
    }

    public void characters(char pCh[], int pStart, int pLength) {
        this.mCurrentName.append(new String(pCh, pStart, pLength));
    }

    public void endElement(String pUri, String pName, String pQName) {
        if(pQName.equalsIgnoreCase("company")) {
            if(this.mCurrentStatus.equals("spectacular")) {
                this.mCompany.setName(mCurrentName.toString());
            }
        }
    }
     
}

and Company.java, our model class that will act as a container for Company data:
package com.clevelandflash.goodtimes.model;

public class Company {
    private String mStatus;
    private String mName;

    public Company() {}

    public Company(String pStatus, String pName) {
        this.mStatus = pStatus;
        this.mName = pName;
    }

    // Getters and Setters

    public void setStatus(String pStatus) {
        this.mStatus = pStatus;
    }

    public String getStatus() {
        return this.mStatus;
    }

    public void setName(String pName) {
        this.mName = pName;
    }

    public String getName() {
        return this.mName;
    }

    // Override toString so we have something pretty to show
    public String toString() {
        StringBuffer lSb = new StringBuffer();
        lSb.append("Company detail: ");
        lSb.append("name=" + getName() + ", ");
        lSb.append("status=" + getStatus());

        return lSb.toString();
    }
}

Also go ahead and update MainClass.java to use the Xerces parser classes we created in LibProject:
// No longer the default package
package com.clevelandflash.javac_tut.core;

import com.clevelandflash.goodtimes.core.ClassForXerces;
import com.clevelandflash.goodtimes.model.Company;
import com.clevelandflash.javac_tut.helper.AnotherClass;

class MainClass {
    MainClass() {}
    
    public static void main(String[] args) {
        System.out.println("Msg from MainClass");
        AnotherClass lAnother = new AnotherClass();
        lAnother.printMessage("Hello Java developers!");

        // Use Xerces!
        ClassForXerces lParser = new ClassForXerces();
        lParser.parseFile("..\\LibProject\\resources\\Companies.xml");
        Company lComp = lParser.getCompany();
        System.out.println("Found a company here...");
        System.out.println(lComp.toString());
    }
}

Now that you've typed, copied/pasted, or dictated your way through getting that code where it needs to be we're ready to compile. The first logical thing we'll want to do is compile the LibProject source code and this will be our first chance to include some external Java libraries (jars) in our compilation. There are no other code dependencies, unlike ExampleProject which depends on LibProject, so it's safe to start here when compiling. At this time, go ahead and clear out any previously compiled .class files from your projects as well as the entire directory tree under the classes directories so we have a fresh start. Your project directories in their clean state should look like the following:
C:\DevProjects\LibProject>tree /F
Folder PATH listing
Volume serial number is
C:.
├───classes
├───lib
│       resolver.jar
│       serializer.jar
│       xercesImpl.jar
│       xml-apis.jar
│
├───resources
│       companies.xml
│
└───src
    └───com
        └───clevelandflash
            └───goodtimes
                ├───core
                │       ClassForXerces.java
                │
                └───model
                        Company.java

C:\DevProjects\ExampleProject>tree /F
Folder PATH listing
Volume serial number is
C:.
├───classes
└───src
    └───com
        └───clevelandflash
            └───javac_tut
                ├───core
                │       MainClass.java
                │
                └───helper
                        AnotherClass.java

Now, navigate to the LibProject directory and execute the following:
C:\DevProjects\LibProject>javac -d classes -cp "lib/*" -sourcepath src src\com\c
levelandflash\goodtimes\core\ClassForXerces.java

You shouldn't receive any errors. If you do, you should first do a thorough job reading the error output to make sure it's not something simple like a misspelled word (case sensitivity matters in Java, if you haven't run into this yet) or missing semicolon.

The first argument to javac you should recognize from earlier - we're just telling the compiler again to output compiled bytecode to the classes directory instead of alongside our source files. The next argument, -cp, is where the dependency magic really happens. Here we're telling the compiler to include lib directory on the classpath. Even better, the compiler allows us to use the wildcard symbol "*" to automatically include any and all jar files that happen to be in that directory. This saves us from having to type every single jar file in the classpath, though you could just as easily do that if you so desire. One more thing to note is that in using the wildcard symbol I'm forced to surround my classpath with quotes. This isn't true for all environments but it is for my Windows XP machine as it's currently configured. Again, check the javac documentation in the references below for more information about using the -classpath argument. The next argument, -sourcepath, is nothing special and again we include the full relative (that's relative from where we're executing javac) path to the root class we want to compile.

Now, navigate to the ExampleProject directory and execute the following:
C:\DevProjects\ExampleProject>javac -d classes -cp ..\LibProject\classes -source
path src src\com\clevelandflash\javac_tut\core\MainClass.java

This time we're leveraging the classes we just compiled from LibProject, thus we include their relative root compiled classes location as a classpath argument to javac. Unlike when we compiled LibProject there's no need to include the Xerces jar files on the classpath. Why? Because while the project ultimately needs those jars to execute, the dependency on them is further down the chain and we've already compiled the code that depends on those jars directly. In other words, since ExampleProject's source code doesn't directly rely on the Xerces jars, we don't need to worry about them when compiling ExampleProject.

The last thing for us to do is run our little experiment with the java tool. So finally, type the following in your command prompt and hit enter.
C:\DevProjects\ExampleProject>java -cp ..\LibProject\classes;..\LibProject\lib\*
;classes com.clevelandflash.javac_tut.core.MainClass
Msg from MainClass
The message is: Hello Java developers!
Start document
End document
Found a company here...
Company detail: name=ClevelandFlash, status=spectacular

Assuming all goes as planned, you should see the output as I've shown above. This time, the classpath includes references to the classes directories for both projects as well as the lib directory from the LibProject. Remember when I said you'd still need to think about that? Now, had our code not actually invoked any classes/methods that depended on those Xerces jar files we would not run into any problems. However, if you want to see what happens, rerun the example excluding the jar directory on the classpath, and you will be greeted with a grotesque error message from the JVM complaining about not being able to find such and such class. Last thing to note - the path you include to the Companies.xml file is relative to where you are actually call "java" from. Just something to keep in mind if you decide to do some of your own tinkering (which I encourage). Sorry, just one more thing... Those Xerces jars I had you download? Well, they come packaged with Java as of JDK 1.5, so really we probably didn't need to download them separately. But then again, that would have ruined the jar dependency lesson ;-) Okay, that's about it for this article on using the Java compiler and Java execution tool from the command line.

Conclusion

Give yourself a hand! In the world of advanced tooling and IDEs, many developers never get a chance to learn the basics and sometimes they just plain forget them. Now that you understand the Java compiler, go download Ant, or better yet, Maven!

Thank you for reading and I hope this article leaves you much more informed in compiling and running your own projects. Stay tuned for the next part in this series when I show you how to create your own class libraries in conjunction with the jar command!

Cheers,
Mike

Reference: