The command catalog supports enumerating commands registered through a services list, converting each entry into a CommandRef and making that available to callers on demand. The CommandRef can be later used to create a command instance or just to obtain documentation about it. All current commands are listed in the service registration. Signed-off-by: Shawn O. Pearce <spearce@xxxxxxxxxxx> --- All that was missing was the services file entry in the built JAR. With this replacement for 5/9 the series should be fine. make_jgit.sh | 1 + .../services/org.spearce.jgit.pgm.TextBuiltin | 12 ++ .../src/org/spearce/jgit/pgm/CommandCatalog.java | 188 ++++++++++++++++++++ 3 files changed, 201 insertions(+), 0 deletions(-) create mode 100644 org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/CommandCatalog.java diff --git a/make_jgit.sh b/make_jgit.sh index bcb2df0..89df80c 100755 --- a/make_jgit.sh +++ b/make_jgit.sh @@ -71,6 +71,7 @@ sed s/@@use_self@@/1/ jgit.sh >$O+ && java org.spearce.jgit.pgm.build.JarLinkUtil \ `for p in $JARS ; do printf %s " -include $p" ;done` \ `for p in $PLUGINS; do printf %s " -include $p/bin2";done` \ + -file META-INF/services/org.spearce.jgit.pgm.TextBuiltin=org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin \ -file META-INF/MANIFEST.MF=$T_MF \ >>$O+ && chmod 555 $O+ && diff --git a/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin b/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin new file mode 100644 index 0000000..69ef046 --- /dev/null +++ b/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin @@ -0,0 +1,12 @@ +org.spearce.jgit.pgm.DiffTree +org.spearce.jgit.pgm.Fetch +org.spearce.jgit.pgm.Glog +org.spearce.jgit.pgm.IndexPack +org.spearce.jgit.pgm.Log +org.spearce.jgit.pgm.LsRemote +org.spearce.jgit.pgm.LsTree +org.spearce.jgit.pgm.MergeBase +org.spearce.jgit.pgm.Push +org.spearce.jgit.pgm.RevList +org.spearce.jgit.pgm.ShowRev +org.spearce.jgit.pgm.Tag diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/CommandCatalog.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/CommandCatalog.java new file mode 100644 index 0000000..13c585c --- /dev/null +++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/CommandCatalog.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce <spearce@xxxxxxxxxxx> + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Git Development Community nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spearce.jgit.pgm; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; + +/** + * List of all commands known by jgit's command line tools. + * <p> + * Commands are implementations of {@link TextBuiltin}, with an optional + * {@link Command} class annotation to insert additional documentation or + * override the default command name (which is guessed from the class name). + * <p> + * Commands may be registered by adding them to a services file in the same JAR + * (or classes directory) as the command implementation. The service file name + * is <code>META-INF/services/org.spearce.jgit.pgm.TextBuiltin</code> and it + * contains one concrete implementation class name per line. + * <p> + * Command registration is identical to Java 6's services, however the catalog + * uses a lightweight wrapper to delay creating a command instance as much as + * possible. This avoids initializing the AWT or SWT GUI toolkits even if the + * command's constructor might require them. + */ +public class CommandCatalog { + private static final CommandCatalog INSTANCE = new CommandCatalog(); + + /** + * Locate a single command by its user friendly name. + * + * @param name + * name of the command. Typically in dash-lower-case-form, which + * was derived from the DashLowerCaseForm class name. + * @return the command instance; null if no command exists by that name. + */ + public static CommandRef get(final String name) { + return INSTANCE.commands.get(name); + } + + /** + * @return all known commands, sorted by command name. + */ + public static CommandRef[] all() { + return toSortedArray(INSTANCE.commands.values()); + } + + /** + * @return all common commands, sorted by command name. + */ + public static CommandRef[] common() { + final ArrayList<CommandRef> common = new ArrayList<CommandRef>(); + for (final CommandRef c : INSTANCE.commands.values()) + if (c.isCommon()) + common.add(c); + return toSortedArray(common); + } + + private static CommandRef[] toSortedArray(final Collection<CommandRef> c) { + final CommandRef[] r = c.toArray(new CommandRef[c.size()]); + Arrays.sort(r, new Comparator<CommandRef>() { + public int compare(final CommandRef o1, final CommandRef o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + return r; + } + + private final ClassLoader ldr; + + private final Map<String, CommandRef> commands; + + private CommandCatalog() { + ldr = Thread.currentThread().getContextClassLoader(); + commands = new HashMap<String, CommandRef>(); + + final Enumeration<URL> catalogs = catalogs(); + while (catalogs.hasMoreElements()) + scan(catalogs.nextElement()); + } + + private Enumeration<URL> catalogs() { + try { + final String pfx = "META-INF/services/"; + return ldr.getResources(pfx + TextBuiltin.class.getName()); + } catch (IOException err) { + return new Vector<URL>().elements(); + } + } + + private void scan(final URL cUrl) { + final BufferedReader cIn; + try { + final InputStream in = cUrl.openStream(); + cIn = new BufferedReader(new InputStreamReader(in, "UTF-8")); + } catch (IOException err) { + // If we cannot read from the service list, go to the next. + // + return; + } + + try { + String line; + while ((line = cIn.readLine()) != null) { + if (line.length() > 0 && !line.startsWith("#")) + load(line); + } + } catch (IOException err) { + // If we failed during a read, ignore the error. + // + } finally { + try { + cIn.close(); + } catch (IOException e) { + // Ignore the close error; we are only reading. + } + } + } + + private void load(final String cn) { + final Class<? extends TextBuiltin> clazz; + try { + clazz = Class.forName(cn, false, ldr).asSubclass(TextBuiltin.class); + } catch (ClassNotFoundException notBuiltin) { + // Doesn't exist, even though the service entry is present. + // + return; + } catch (ClassCastException notBuiltin) { + // Isn't really a builtin, even though its listed as such. + // + return; + } + + final CommandRef cr; + final Command a = clazz.getAnnotation(Command.class); + if (a != null) + cr = new CommandRef(clazz, a); + else + cr = new CommandRef(clazz); + + commands.put(cr.getName(), cr); + } +} -- 1.6.0.rc0.182.gb96c7 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html