Note that this method caches its results once they are computed + * in the expectation that it will be used in a "one shot" manner + * (meaning the JVM that instantiates and uses this class is not long + * lived). Once a dependency is resolved, it does not go back and + * reresolve it, thus it could become out of date if the library (or + * its dependencies) is updated between the time that this method is + * called and the time that the calling JVM exits. + */ + public boolean isSatisified () + { + // TBD + return false; + } + + /** + * Returns true if we have a local copy of the library and it is the + * most recent release of that library. This method results in an HTTP + * request being issued to download the latest library descriptor for + * the library + * + *
Note that this method caches its results once they are computed
+ * in the expectation that it will be used in a "one shot" manner
+ * (meaning the JVM that instantiates and uses this class is not long
+ * lived). Once a dependency is resolved, it does not go back and
+ * reresolve it, thus it could become out of date if the library (or
+ * its dependencies) is updated between the time that this method is
+ * called and the time that the calling JVM exits.
+ */
+ public boolean isUpToDate ()
+ throws IOException
+ {
+ // TBD
+ return false;
+ }
+
+ /**
+ * Downloads the library descriptor for the library referenced by this
+ * dependency and returns it. Note that once the descriptor has been
+ * downloaded once, it is cached for the remainder of the lifetime of
+ * the JVM, hiding updates that take place between the time that this
+ * method is called and the time that the calling JVM exists. This is
+ * considered to be more desirable than making an HTTP request every
+ * time this method is called.
+ */
+ public LibraryDescriptor getLibraryDescriptor ()
+ throws IOException, FormatException
+ {
+ if (_remoteDescriptor != null) {
+ return _remoteDescriptor;
+ }
+
+ // download the descriptor data from the publishing site
+ URLConnection uconn = _descriptorURL.openConnection();
+ _remoteDescriptor = LibraryDescriptorParser.parseLibraryDescriptor(
+ uconn.getInputStream());
+ return _remoteDescriptor;
+ }
+
+ /**
+ * Downloads the library descriptor for the library referenced by this
+ * dependency and reports the most recent version available of said
+ * library.
+ *
+ * @see #getLibraryDescriptor
+ */
+ public Version getRemoteVersion ()
+ throws IOException, FormatException
+ {
+ LibraryDescriptor descriptor = getLibraryDescriptor();
+ return descriptor.getLatestRelease().getVersion();
+ }
+
+ /**
+ * Returns a string representation of this instance.
+ */
+ public String toString ()
+ {
+ return "[name=" + _libraryName + ", version=" + _requiredVersion +
+ ", url=" + _descriptorURL + "]";
+ }
+
+ /**
+ * Constructs the file name of our local copy of the jar file.
+ */
+ protected String constructJarName ()
+ {
+ return _libraryName + LIBRARY_SUFFIX;
+ }
+
+ /**
+ * Loads up the library stamp from our local copy of the library and
+ * extracts the version information from it.
+ */
+ protected Version resolveLocalVersion ()
+ throws IOException, FormatException
+ {
+ File jar = getLocalJar();
+ // TBD
+ return null;
+ }
+
+ /** The name of the library referenced by this dependency. */
+ protected String _libraryName;
+
+ /** The version required by this dependency. */
+ protected Version _requiredVersion;
+
+ /** The URL for the descriptor of the library referenced by this
+ * dependency. */
+ protected URL _descriptorURL;
+
+ /** The path to our local library repository. */
+ protected File _repository;
+
+ /** The version of our local copy of the library or null if we have no
+ * local copy. */
+ protected Version _localVersion;
+
+ /** A parsed library descriptor, obtained from our local copy of the
+ * library. */
+ protected LibraryDescriptor _localDescriptor;
+
+ /** A parsed library descriptor, downloaded from the site that
+ * publishes this library. */
+ protected LibraryDescriptor _remoteDescriptor;
+
+ /** All libraries end with this suffix: .jar */
+ protected static final String LIBRARY_SUFFIX = ".jar";
+}
diff --git a/projects/depends/src/java/com/samskivert/depends/DependencyParser.java b/projects/depends/src/java/com/samskivert/depends/DependencyParser.java
new file mode 100644
index 00000000..e4b960f5
--- /dev/null
+++ b/projects/depends/src/java/com/samskivert/depends/DependencyParser.java
@@ -0,0 +1,122 @@
+//
+// $Id: DependencyParser.java,v 1.1 2002/04/11 07:23:59 mdb Exp $
+
+package com.samskivert.depends;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.net.URL;
+import java.net.MalformedURLException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Parses XML dependency definitions.
+ */
+public class DependencyParser
+{
+ /**
+ * Parses the specified dependency definition file into a list of
+ * {@link Dependency} instances.
+ */
+ public static List parseDependencies (File definition)
+ throws FormatException, IOException
+ {
+ try {
+ ArrayList list = new ArrayList();
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ SAXParser parser = factory.newSAXParser();
+ DependencyHandler handler = new DependencyHandler(list);
+ parser.parse(definition, handler);
+ return list;
+
+ } catch (SAXException saxe) {
+ throw new FormatException(saxe.getMessage());
+
+ } catch (ParserConfigurationException pce) {
+ throw new FormatException(
+ "Unable to load parser: " + pce.getMessage());
+ }
+ }
+
+ public static void main (String[] args)
+ {
+ try {
+ parseDependencies(new File(args[0]));
+ } catch (Exception e) {
+ Log.warning("Chokey choke-opolis", e);
+ }
+ }
+
+ /**
+ * Used to parse the dependency specification.
+ */
+ protected static class DependencyHandler extends DefaultHandler
+ {
+ public DependencyHandler (List list)
+ {
+ _list = list;
+ }
+
+ public void startElement (String namespaceURI, String localName,
+ String qName, Attributes attrs)
+ {
+ if (qName.equals("library")) {
+ String name = null;
+ String version = null;
+ String url = null;
+
+ for (int i = 0; i < attrs.getLength(); i++) {
+ String attrName = attrs.getQName(i);
+ if (attrName.equals("name")) {
+ name = attrs.getValue(i);
+ } else if (attrName.equals("version")) {
+ version = attrs.getValue(i);
+ } else if (attrName.equals("url")) {
+ url = attrs.getValue(i);
+ }
+ }
+
+ if (!validateAttribute("name", name) ||
+ !validateAttribute("version", version) ||
+ !validateAttribute("url", url)) {
+ return;
+ }
+
+ try {
+ Dependency dep = new Dependency(
+ name, new Version(version), new URL(url));
+ Log.info("Parsed dependency " + dep + ".");
+ _list.add(dep);
+
+ } catch (MalformedURLException mue) {
+ Log.warning("Skipping dependency with invalid 'url' " +
+ "specification [url=" + url + "]: " + mue);
+ }
+ }
+ }
+
+ protected boolean validateAttribute (String name, String value)
+ {
+ if (value == null || value.length() == 0) {
+ Log.warning("Skipping dependency declaration that is " +
+ "missing required attribute '" + name + "'.");
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ protected List _list;
+ }
+}
diff --git a/projects/depends/src/java/com/samskivert/depends/FormatException.java b/projects/depends/src/java/com/samskivert/depends/FormatException.java
new file mode 100644
index 00000000..1c319067
--- /dev/null
+++ b/projects/depends/src/java/com/samskivert/depends/FormatException.java
@@ -0,0 +1,15 @@
+//
+// $Id: FormatException.java,v 1.1 2002/04/11 07:23:59 mdb Exp $
+
+package com.samskivert.depends;
+
+/**
+ * Used to communicate file format exceptions.
+ */
+public class FormatException extends Exception
+{
+ public FormatException (String message)
+ {
+ super(message);
+ }
+}
diff --git a/projects/depends/src/java/com/samskivert/depends/LibraryDescriptor.java b/projects/depends/src/java/com/samskivert/depends/LibraryDescriptor.java
new file mode 100644
index 00000000..44cd320b
--- /dev/null
+++ b/projects/depends/src/java/com/samskivert/depends/LibraryDescriptor.java
@@ -0,0 +1,61 @@
+//
+// $Id: LibraryDescriptor.java,v 1.1 2002/04/11 07:23:59 mdb Exp $
+
+package com.samskivert.depends;
+
+import java.net.URL;
+
+import java.util.List;
+
+/**
+ * Describes a library.
+ */
+public class LibraryDescriptor
+{
+ public String getLibraryName ()
+ {
+ return _libraryName;
+ }
+
+ public String getCopyright ()
+ {
+ return _copyright;
+ }
+
+ public String getContact ()
+ {
+ return _contact;
+ }
+
+ public URL getLibraryURL ()
+ {
+ return _libraryURL;
+ }
+
+ public URL getDescriptorURL ()
+ {
+ return _descriptorURL;
+ }
+
+ public List getReleases ()
+ {
+ return _releases;
+ }
+
+ public Release getLatestRelease ()
+ {
+ return null;
+ }
+
+ protected String _libraryName;
+
+ protected String _copyright;
+
+ protected String _contact;
+
+ protected URL _libraryURL;
+
+ protected URL _descriptorURL;
+
+ protected List _releases;
+}
diff --git a/projects/depends/src/java/com/samskivert/depends/LibraryDescriptorParser.java b/projects/depends/src/java/com/samskivert/depends/LibraryDescriptorParser.java
new file mode 100644
index 00000000..41c4b1ff
--- /dev/null
+++ b/projects/depends/src/java/com/samskivert/depends/LibraryDescriptorParser.java
@@ -0,0 +1,97 @@
+//
+// $Id: LibraryDescriptorParser.java,v 1.1 2002/04/11 07:23:59 mdb Exp $
+
+package com.samskivert.depends;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.net.URL;
+import java.net.MalformedURLException;
+
+/**
+ * Parses XML library descriptor definitions.
+ */
+public class LibraryDescriptorParser
+{
+ /**
+ * Parses the specified library descriptor file.
+ */
+ public static LibraryDescriptor parseLibraryDescriptor (File descriptor)
+ throws FormatException, IOException
+ {
+ return parseLibraryDescriptor(new FileInputStream(descriptor));
+ }
+
+ /**
+ * Parses the library descriptor definition provided via the supplied
+ * input stream.
+ */
+ public static LibraryDescriptor parseLibraryDescriptor (InputStream source)
+ throws FormatException, IOException
+ {
+ try {
+ LibraryDescriptor ld = new LibraryDescriptor();
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ SAXParser parser = factory.newSAXParser();
+ DescriptorHandler handler = new DescriptorHandler(ld);
+ parser.parse(source, handler);
+ return ld;
+
+ } catch (SAXException saxe) {
+ throw new FormatException(saxe.getMessage());
+
+ } catch (ParserConfigurationException pce) {
+ throw new FormatException(
+ "Unable to load parser: " + pce.getMessage());
+ }
+ }
+
+ public static void main (String[] args)
+ {
+ try {
+ parseLibraryDescriptor(new File(args[0]));
+ } catch (Exception e) {
+ Log.warning("Chokey choke-opolis", e);
+ }
+ }
+
+ /**
+ * Used to parse a library descriptor specification.
+ */
+ protected static class DescriptorHandler extends DefaultHandler
+ {
+ public DescriptorHandler (LibraryDescriptor descriptor)
+ {
+ _descriptor = descriptor;
+ }
+
+ public void startElement (String namespaceURI, String localName,
+ String qName, Attributes attrs)
+ {
+ }
+
+ protected boolean validateAttribute (String name, String value)
+ {
+ if (value == null || value.length() == 0) {
+ Log.warning("Skipping dependency declaration that is " +
+ "missing required attribute '" + name + "'.");
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ protected LibraryDescriptor _descriptor;
+ }
+}
diff --git a/projects/depends/src/java/com/samskivert/depends/Log.java b/projects/depends/src/java/com/samskivert/depends/Log.java
new file mode 100644
index 00000000..04a5923d
--- /dev/null
+++ b/projects/depends/src/java/com/samskivert/depends/Log.java
@@ -0,0 +1,66 @@
+//
+// $Id: Log.java,v 1.1 2002/04/11 07:23:59 mdb Exp $
+
+package com.samskivert.depends;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Provides convenient access to our logging instance.
+ */
+public class Log
+{
+ /** A reference to our logger. */
+ public static Logger log = Logger.getLogger("com.samskivert.depends");
+
+ /**
+ * Passes through to {@link Logger#fine(String)}.
+ */
+ public static void fine (String message)
+ {
+ log.fine(message);
+ }
+
+ /**
+ * Passes through to {@link Logger#info(String)}.
+ */
+ public static void info (String message)
+ {
+ log.info(message);
+ }
+
+ /**
+ * Passes through to {@link Logger#warning(String)}.
+ */
+ public static void warning (String message)
+ {
+ log.warning(message);
+ }
+
+ /**
+ * Passes through to {@link Logger#log} with level
+ * WARNING.
+ */
+ public static void warning (String message, Exception e)
+ {
+ log.log(Level.WARNING, message, e);
+ }
+
+ /**
+ * Passes through to {@link Logger#severe(String)}.
+ */
+ public static void severe (String message)
+ {
+ log.severe(message);
+ }
+
+ /**
+ * Passes through to {@link Logger#log} with level
+ * SEVERE.
+ */
+ public static void severe (String message, Exception e)
+ {
+ log.log(Level.SEVERE, message, e);
+ }
+}
diff --git a/projects/depends/src/java/com/samskivert/depends/Release.java b/projects/depends/src/java/com/samskivert/depends/Release.java
new file mode 100644
index 00000000..1129536a
--- /dev/null
+++ b/projects/depends/src/java/com/samskivert/depends/Release.java
@@ -0,0 +1,43 @@
+//
+// $Id: Release.java,v 1.1 2002/04/11 07:23:59 mdb Exp $
+
+package com.samskivert.depends;
+
+import java.net.URL;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Describes a particular release of a library.
+ */
+public class Release
+{
+ public Version getVersion ()
+ {
+ return _version;
+ }
+
+ public Date getDate ()
+ {
+ return _date;
+ }
+
+ public URL getJarURL ()
+ {
+ return _jarURL;
+ }
+
+ public List getDependencies ()
+ {
+ return _dependencies;
+ }
+
+ protected Version _version;
+
+ protected Date _date;
+
+ protected URL _jarURL;
+
+ protected List _dependencies;
+}
diff --git a/projects/depends/src/java/com/samskivert/depends/ReleaseDescriptor.java b/projects/depends/src/java/com/samskivert/depends/ReleaseDescriptor.java
new file mode 100644
index 00000000..5a889c85
--- /dev/null
+++ b/projects/depends/src/java/com/samskivert/depends/ReleaseDescriptor.java
@@ -0,0 +1,11 @@
+//
+// $Id: ReleaseDescriptor.java,v 1.1 2002/04/11 07:23:59 mdb Exp $
+
+package com.samskivert.depends;
+
+/**
+ * Does something extraordinary.
+ */
+public class ReleaseDescriptor
+{
+}
diff --git a/projects/depends/src/java/com/samskivert/depends/Version.java b/projects/depends/src/java/com/samskivert/depends/Version.java
new file mode 100644
index 00000000..b60118a4
--- /dev/null
+++ b/projects/depends/src/java/com/samskivert/depends/Version.java
@@ -0,0 +1,48 @@
+//
+// $Id: Version.java,v 1.1 2002/04/11 07:23:59 mdb Exp $
+
+package com.samskivert.depends;
+
+/**
+ * Represents a library version. Version strings are not constrained to a
+ * particular format, but must meet a couple of criterion: first, they
+ * must contain only valid filename characters as they will be used to
+ * create the filename of our local copy of the library's jar file.
+ * Second, if a dependency's version specification ends with a
+ * '+', the dependency is considered satisfied by any library
+ * release newer than the specified version. Thus, if a version string
+ * ends in plus, wackiness ensues. Fortunately, we can issue a warning if
+ * anyone tries to stamp a library with a descriptor that declares its
+ * version to end with a plus.
+ */
+public class Version
+{
+ /**
+ * Constructs a version instance from the supplied version
+ * specification string.
+ */
+ public Version (String version)
+ {
+ _version = version;
+ }
+
+ /**
+ * Returns true if this version specification allows replacement by
+ * newer versions, false otherwise.
+ */
+ public boolean openEnded ()
+ {
+ return _version.endsWith("+");
+ }
+
+ /**
+ * Returns a string representation of this instance.
+ */
+ public String toString ()
+ {
+ return _version;
+ }
+
+ /** Our version specification string. */
+ protected String _version;
+}
diff --git a/projects/depends/src/java/com/samskivert/depends/tasks/CheckDependsTask.java b/projects/depends/src/java/com/samskivert/depends/tasks/CheckDependsTask.java
new file mode 100644
index 00000000..5c5690da
--- /dev/null
+++ b/projects/depends/src/java/com/samskivert/depends/tasks/CheckDependsTask.java
@@ -0,0 +1,46 @@
+//
+// $Id: CheckDependsTask.java,v 1.1 2002/04/11 07:23:59 mdb Exp $
+
+package com.samskivert.depends.tasks;
+
+import java.util.List;
+
+import org.apache.tools.ant.BuildException;
+
+import com.samskivert.depends.Dependency;
+
+/**
+ * An ant task for checking that a set of dependencies are satisfied by
+ * the jar files that currently exist in our local library repository.
+ */
+public class CheckDependsTask extends DependsTask
+{
+ /**
+ * Performs the actual work of the task.
+ */
+ public void execute ()
+ throws BuildException
+ {
+ // load and resolve our dependencies
+ List depends = loadDependencies();
+ resolveDependencies(depends);
+
+ int dcount = depends.size();
+ for (int i = 0; i < dcount; i++) {
+ Dependency dep = (Dependency)depends.get(i);
+ try {
+ if (!dep.isUpToDate()) {
+ System.out.println("Library '" + dep.getLibraryName() +
+ "' has newer version " +
+ "[have=" + dep.getLocalVersion() +
+ ", latest=" + dep.getRemoteVersion() +
+ "].");
+ }
+
+ } catch (Exception e) {
+ System.err.println("Error checking dependency " +
+ "[dep=" + dep + ", error=" + e + "].");
+ }
+ }
+ }
+}
diff --git a/projects/depends/src/java/com/samskivert/depends/tasks/DependsTask.java b/projects/depends/src/java/com/samskivert/depends/tasks/DependsTask.java
new file mode 100644
index 00000000..8e7d8e65
--- /dev/null
+++ b/projects/depends/src/java/com/samskivert/depends/tasks/DependsTask.java
@@ -0,0 +1,103 @@
+//
+// $Id: DependsTask.java,v 1.1 2002/04/11 07:23:59 mdb Exp $
+
+package com.samskivert.depends.tasks;
+
+import java.io.File;
+import java.util.List;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+
+import com.samskivert.depends.Dependency;
+import com.samskivert.depends.DependencyParser;
+
+/**
+ * A base class that handles functionality common to all dependency
+ * related ant tasks.
+ */
+public abstract class DependsTask extends Task
+{
+ /**
+ * Called by ant to set the 'deps' attribute.
+ */
+ public void setDeps (File deps)
+ {
+ _deps = deps;
+ }
+
+ /**
+ * Called by ant to set the 'repository' attribute.
+ */
+ public void setRepository (File repository)
+ {
+ _repository = repository;
+ }
+
+ /**
+ * Throws a build exception using the supplied error message if the
+ * supplied value is null.
+ */
+ protected void ensureSet (Object value, String errmsg)
+ throws BuildException
+ {
+ if (value == null) {
+ throw new BuildException(errmsg);
+ }
+ }
+
+ /**
+ * Loads up a set of dependencies from the dependency file specified
+ * via the 'deps' task attribute.
+ */
+ protected List loadDependencies ()
+ throws BuildException
+ {
+ ensureSet(_deps, "Missing 'deps' attribute which should " +
+ "reference the dependency definition file.");
+ try {
+ return DependencyParser.parseDependencies(_deps);
+ } catch (Exception e) {
+ throw new BuildException("Error parsing dependency " +
+ "definition file.", e);
+ }
+ }
+
+ /**
+ * Configures the supplied set of dependencies with the local library
+ * repository specified via the 'repository' task attribute.
+ */
+ protected void resolveDependencies (List depends)
+ throws BuildException
+ {
+ // make sure the repository directory was specified...
+ ensureSet(_repository, "Missing 'repository' attribute which should " +
+ "reference the local library repository directory.");
+
+ // ...exists...
+ if (!_repository.exists()) {
+ String errmsg = "Local library repository directory '" +
+ _repository.getPath() + "' does not exist.";
+ throw new BuildException(errmsg);
+ }
+
+ // ...and is actually a directory
+ if (!_repository.isDirectory()) {
+ String errmsg = "Specified local library repository '" +
+ _repository.getPath() + "' is not a directory.";
+ throw new BuildException(errmsg);
+ }
+
+ int dcount = depends.size();
+ for (int i = 0; i < dcount; i++) {
+ Dependency dep = (Dependency)depends.get(i);
+ dep.setLocalRepository(_repository);
+ }
+ }
+
+ /** The dependency definition file. */
+ protected File _deps;
+
+ /** The local library repository directory. */
+ protected File _repository;
+}
diff --git a/projects/depends/src/java/com/samskivert/depends/tasks/IncludeDependsTask.java b/projects/depends/src/java/com/samskivert/depends/tasks/IncludeDependsTask.java
new file mode 100644
index 00000000..3b738eca
--- /dev/null
+++ b/projects/depends/src/java/com/samskivert/depends/tasks/IncludeDependsTask.java
@@ -0,0 +1,11 @@
+//
+// $Id: IncludeDependsTask.java,v 1.1 2002/04/11 07:23:59 mdb Exp $
+
+package com.samskivert.depends.tasks;
+
+/**
+ * Does something extraordinary.
+ */
+public class IncludeDependsTask
+{
+}
diff --git a/projects/depends/src/java/com/samskivert/depends/tasks/UpdateDependsTask.java b/projects/depends/src/java/com/samskivert/depends/tasks/UpdateDependsTask.java
new file mode 100644
index 00000000..b8d43fe8
--- /dev/null
+++ b/projects/depends/src/java/com/samskivert/depends/tasks/UpdateDependsTask.java
@@ -0,0 +1,11 @@
+//
+// $Id: UpdateDependsTask.java,v 1.1 2002/04/11 07:23:59 mdb Exp $
+
+package com.samskivert.depends.tasks;
+
+/**
+ * Does something extraordinary.
+ */
+public class UpdateDependsTask
+{
+}