From: Shawn O. Pearce <hidden> Date: 2016-06-15 22:45:02
This series adds support to jgit to list commonly used subcommands
if the user just executes `jgit` with no subcommand requested:
$ jgit
jgit --git-dir GIT_DIR --help (-h) --show-stack-trace command [ARG ...]
The most commonly used commands are:
fetch Update remote refs from another repository
log View commit history
push Update remote repository from local refs
tag Create a tag
Commands inside of the pgm.debug package are automatically given
the debug- prefix, allowing debug-show-commands to be used to show
the command table.
Commands must be listed in the META-INF/services/org...TextBuiltin
file in order to be considered for execution. This means that we
must add (or remove) command class names from the listing each time
we introduce or remove a command line subcommand.
One advantage to this structure is additional commands can defined
in other packages, and are available so long as the classes are
reachable through the CLASSPATH. Since jgit.sh hardcodes the
CLASSPATH to only itself this is not fully supported yet, but does
open the door for users to extend jgit's command line support.
Shawn O. Pearce (9):
Switch jgit.pgm to J2SE-1.5 execution environment
Remove unnecessary duplicate if (help) test inside TextBuiltin
Create an optional documentation annotation for TextBuiltin
Create a lightweight registration wrapper for TextBuiltin
Create a catalog of CommandRefs for lookup and enumeration
Document some common commands with the new Command annotation
Include commonly used commands in main help output
Refactor SubcommandHandler to use CommandCatalog instead of
reflection
Add debug-show-commands to display the command table
org.spearce.jgit.pgm/.classpath | 2 +-
.../services/org.spearce.jgit.pgm.TextBuiltin | 14 ++
.../src/org/spearce/jgit/pgm/Command.java | 72 ++++++++
.../src/org/spearce/jgit/pgm/CommandCatalog.java | 188 ++++++++++++++++++++
.../src/org/spearce/jgit/pgm/CommandRef.java | 158 ++++++++++++++++
.../src/org/spearce/jgit/pgm/Fetch.java | 1 +
.../src/org/spearce/jgit/pgm/Log.java | 1 +
.../src/org/spearce/jgit/pgm/Main.java | 18 ++
.../src/org/spearce/jgit/pgm/Push.java | 1 +
.../src/org/spearce/jgit/pgm/Tag.java | 1 +
.../src/org/spearce/jgit/pgm/TextBuiltin.java | 17 +--
.../org/spearce/jgit/pgm/debug/ShowCommands.java | 78 ++++++++
.../spearce/jgit/pgm/opt/SubcommandHandler.java | 65 +------
13 files changed, 543 insertions(+), 73 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/Command.java
create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/CommandCatalog.java
create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/CommandRef.java
create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/ShowCommands.java
From: Shawn O. Pearce <hidden> Date: 2016-06-15 22:45:02
We have been keeping the jgit library itself on Java 5, and the
jgit command line tools should also be on Java 5 and avoid using
any Java 6 APIs (for now). Not all of our target platforms have
a Java 6 virtual machine available out of the box.
Since the pgm project broke out of the library project our code
already conforms to Java 5 APIs, so we just have to switch the
build path settings.
Signed-off-by: Shawn O. Pearce <redacted>
---
org.spearce.jgit.pgm/.classpath | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
From: Shawn O. Pearce <hidden> Date: 2016-06-15 22:45:02
This new annotation can be used for automatic command list creation,
or additional help generation by the default help generator.
Signed-off-by: Shawn O. Pearce <redacted>
---
.../src/org/spearce/jgit/pgm/Command.java | 72 ++++++++++++++++++++
1 files changed, 72 insertions(+), 0 deletions(-)
create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Command.java
From: Shawn O. Pearce <hidden> Date: 2016-06-15 22:45:02
Automatic command list generation requires knowing what commands
are available to this runtime, and what name those commands can be
called as by the end-user. This lightweight wrappers carries the
data from the Command annotation, possibly filling in the name of
the command by generating it from the implementation class name.
Signed-off-by: Shawn O. Pearce <redacted>
---
.../src/org/spearce/jgit/pgm/CommandRef.java | 151 ++++++++++++++++++++
1 files changed, 151 insertions(+), 0 deletions(-)
create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/CommandRef.java
From: Shawn O. Pearce <hidden> Date: 2016-06-15 22:45:02
This was caused by a copy-and-paste error as I borrowed the
global argument handling code in Main to help start writing
the command specific argument handling in TextBuiltin.
Signed-off-by: Shawn O. Pearce <redacted>
---
.../src/org/spearce/jgit/pgm/TextBuiltin.java | 9 ++++-----
1 files changed, 4 insertions(+), 5 deletions(-)
@@ -145,11 +145,10 @@ public abstract class TextBuiltin {clp.printSingleLineUsage(System.err);System.err.println();-if(help){-System.err.println();-clp.printUsage(System.err);-System.err.println();-}+System.err.println();+clp.printUsage(System.err);+System.err.println();+System.exit(1);}
From: Shawn O. Pearce <hidden> Date: 2016-06-15 22:45:02
Now that all commands are known to the CommandCatalog we do not need
to perform direct reflection inside of the SubcommandHandler. Instead
we can reuse the lookup table already known to the CommandCatalog.
Signed-off-by: Shawn O. Pearce <redacted>
---
.../src/org/spearce/jgit/pgm/TextBuiltin.java | 8 +--
.../spearce/jgit/pgm/opt/SubcommandHandler.java | 65 ++------------------
2 files changed, 6 insertions(+), 67 deletions(-)
@@ -83,13 +83,7 @@ public abstract class TextBuiltin {/** RevWalk used during command line parsing, if it was required. */protectedRevWalkargWalk;-/**-*Setthenamethiscommandcanbeinvokedasonthecommandline.-*-*@paramname-*thenameofthecommand.-*/-publicvoidsetCommandName(finalStringname){+finalvoidsetCommandName(finalStringname){commandName=name;}
@@ -80,65 +73,17 @@ public class SubcommandHandler extends OptionHandler<TextBuiltin> {@OverridepublicintparseArguments(finalParametersparams)throwsCmdLineException{finalStringname=params.getParameter(0);-finalStringBuilders=newStringBuilder();-s.append(mypackage());-s.append('.');-booleanupnext=true;-for(inti=0;i<name.length();i++){-finalcharc=name.charAt(i);-if(c=='-'){-upnext=true;-continue;-}-if(upnext)-s.append(Character.toUpperCase(c));-else-s.append(c);-upnext=false;-}--finalClass<?>clazz;-try{-clazz=Class.forName(s.toString());-}catch(ClassNotFoundExceptione){-thrownewCmdLineException(MessageFormat.format(-"{0} is not a jgit command",name));-}--if(!TextBuiltin.class.isAssignableFrom(clazz))+finalCommandRefcr=CommandCatalog.get(name);+if(cr==null)thrownewCmdLineException(MessageFormat.format("{0} is not a jgit command",name));-finalConstructor<?>cons;-try{-cons=clazz.getDeclaredConstructor();-}catch(SecurityExceptione){-thrownewCmdLineException("Cannot create "+name,e);-}catch(NoSuchMethodExceptione){-thrownewCmdLineException("Cannot create "+name,e);-}-cons.setAccessible(true);--finalTextBuiltincmd;-try{-cmd=(TextBuiltin)cons.newInstance();-}catch(InstantiationExceptione){-thrownewCmdLineException("Cannot create "+name,e);-}catch(IllegalAccessExceptione){-thrownewCmdLineException("Cannot create "+name,e);-}catch(InvocationTargetExceptione){-thrownewCmdLineException("Cannot create "+name,e);-}--cmd.setCommandName(name);-setter.addValue(cmd);-// Force option parsing to stop. Everything after us should// be arguments known only to this command and must not be// recognized by the current parser.//owner.stopOptionParsing();-+setter.addValue(cr.create());return1;}
@@ -120,6 +120,13 @@ public class CommandRef {}/**+*@returnloaderfor{@link#getImplementationClassName()}.+*/+publicClassLoadergetImplementationClassLoader(){+returnimpl.getClassLoader();+}++/***@returnanewinstanceofthecommandimplementation.*/publicTextBuiltincreate(){
@@ -0,0 +1,78 @@+packageorg.spearce.jgit.pgm.debug;++importjava.net.URL;++importorg.kohsuke.args4j.Option;+importorg.spearce.jgit.pgm.Command;+importorg.spearce.jgit.pgm.CommandCatalog;+importorg.spearce.jgit.pgm.CommandRef;+importorg.spearce.jgit.pgm.TextBuiltin;++@Command(usage="Display a list of all registered jgit commands")+classShowCommandsextendsTextBuiltin{+@Option(name="--pretty",usage="alter the detail shown")+privateFormatpretty=Format.USAGE;++@Override+protectedvoidrun()throwsException{+finalCommandRef[]list=CommandCatalog.all();++intwidth=0;+for(finalCommandRefc:list)+width=Math.max(width,c.getName().length());+width+=2;++for(finalCommandRefc:list){+System.err.print(c.isCommon()?'*':' ');+System.err.print(' ');++System.err.print(c.getName());+for(inti=c.getName().length();i<width;i++)+System.err.print(' ');++pretty.print(c);+System.err.println();+}+System.err.println();+}++staticenumFormat{+/** */+USAGE{+voidprint(finalCommandRefc){+System.err.print(c.getUsage());+}+},++/** */+CLASSES{+voidprint(finalCommandRefc){+System.err.print(c.getImplementationClassName());+}+},++/** */+URLS{+voidprint(finalCommandRefc){+finalClassLoaderldr=c.getImplementationClassLoader();++Stringcn=c.getImplementationClassName();+cn=cn.replace('.','/')+".class";++finalURLurl=ldr.getResource(cn);+if(url==null){+System.err.print("!! NOT FOUND !!");+return;+}++Stringrn=url.toExternalForm();+if(rn.endsWith(cn))+rn=rn.substring(0,rn.length()-cn.length());++System.err.print(rn);+}+};++abstractvoidprint(CommandRefc);+}+}
From: Shawn O. Pearce <hidden> Date: 2016-06-15 22:45:02
This way they are known to be common at runtime, by looking at
the annotation associated with the class instance. Right now
we do not use these annotations but they will be useful soon.
Signed-off-by: Shawn O. Pearce <redacted>
---
.../src/org/spearce/jgit/pgm/Fetch.java | 1 +
.../src/org/spearce/jgit/pgm/Log.java | 1 +
.../src/org/spearce/jgit/pgm/Push.java | 1 +
.../src/org/spearce/jgit/pgm/Tag.java | 1 +
4 files changed, 4 insertions(+), 0 deletions(-)
From: Shawn O. Pearce <hidden> Date: 2016-06-15 22:45:02
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 <redacted>
---
.../services/org.spearce.jgit.pgm.TextBuiltin | 12 ++
.../src/org/spearce/jgit/pgm/CommandCatalog.java | 188 ++++++++++++++++++++
2 files changed, 200 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
@@ -0,0 +1,188 @@+/*+*Copyright(C)2008,ShawnO.Pearce<spearce@spearce.org>+*+*Allrightsreserved.+*+*Redistributionanduseinsourceandbinaryforms,withor+*withoutmodification,arepermittedprovidedthatthefollowing+*conditionsaremet:+*+*-Redistributionsofsourcecodemustretaintheabovecopyright+*notice,thislistofconditionsandthefollowingdisclaimer.+*+*-Redistributionsinbinaryformmustreproducetheabove+*copyrightnotice,thislistofconditionsandthefollowing+*disclaimerinthedocumentationand/orothermaterialsprovided+*withthedistribution.+*+*-NeitherthenameoftheGitDevelopmentCommunitynorthe+*namesofitscontributorsmaybeusedtoendorseorpromote+*productsderivedfromthissoftwarewithoutspecificprior+*writtenpermission.+*+*THISSOFTWAREISPROVIDEDBYTHECOPYRIGHTHOLDERSAND+*CONTRIBUTORS"AS IS"ANDANYEXPRESSORIMPLIEDWARRANTIES,+*INCLUDING,BUTNOTLIMITEDTO,THEIMPLIEDWARRANTIES+*OFMERCHANTABILITYANDFITNESSFORAPARTICULARPURPOSE+*AREDISCLAIMED.INNOEVENTSHALLTHECOPYRIGHTOWNEROR+*CONTRIBUTORSBELIABLEFORANYDIRECT,INDIRECT,INCIDENTAL,+*SPECIAL,EXEMPLARY,ORCONSEQUENTIALDAMAGES(INCLUDING,BUT+*NOTLIMITEDTO,PROCUREMENTOFSUBSTITUTEGOODSORSERVICES;+*LOSSOFUSE,DATA,ORPROFITS;ORBUSINESSINTERRUPTION)HOWEVER+*CAUSEDANDONANYTHEORYOFLIABILITY,WHETHERINCONTRACT,+*STRICTLIABILITY,ORTORT(INCLUDINGNEGLIGENCEOROTHERWISE)+*ARISINGINANYWAYOUTOFTHEUSEOFTHISSOFTWARE,EVENIF+*ADVISEDOFTHEPOSSIBILITYOFSUCHDAMAGE.+*/++packageorg.spearce.jgit.pgm;++importjava.io.BufferedReader;+importjava.io.IOException;+importjava.io.InputStream;+importjava.io.InputStreamReader;+importjava.net.URL;+importjava.util.ArrayList;+importjava.util.Arrays;+importjava.util.Collection;+importjava.util.Comparator;+importjava.util.Enumeration;+importjava.util.HashMap;+importjava.util.Map;+importjava.util.Vector;++/**+*Listofallcommandsknownbyjgit'scommandlinetools.+*<p>+*Commandsareimplementationsof{@linkTextBuiltin},withanoptional+*{@linkCommand}classannotationtoinsertadditionaldocumentationor+*overridethedefaultcommandname(whichisguessedfromtheclassname).+*<p>+*CommandsmayberegisteredbyaddingthemtoaservicesfileinthesameJAR+*(orclassesdirectory)asthecommandimplementation.Theservicefilename+*is<code>META-INF/services/org.spearce.jgit.pgm.TextBuiltin</code>andit+*containsoneconcreteimplementationclassnameperline.+*<p>+*CommandregistrationisidenticaltoJava6'sservices,howeverthecatalog+*usesalightweightwrappertodelaycreatingacommandinstanceasmuchas+*possible.ThisavoidsinitializingtheAWTorSWTGUItoolkitsevenifthe+*command'sconstructormightrequirethem.+*/+publicclassCommandCatalog{+privatestaticfinalCommandCatalogINSTANCE=newCommandCatalog();++/**+*Locateasinglecommandbyitsuserfriendlyname.+*+*@paramname+*nameofthecommand.Typicallyindash-lower-case-form,which+*wasderivedfromtheDashLowerCaseFormclassname.+*@returnthecommandinstance;nullifnocommandexistsbythatname.+*/+publicstaticCommandRefget(finalStringname){+returnINSTANCE.commands.get(name);+}++/**+*@returnallknowncommands,sortedbycommandname.+*/+publicstaticCommandRef[]all(){+returntoSortedArray(INSTANCE.commands.values());+}++/**+*@returnallcommoncommands,sortedbycommandname.+*/+publicstaticCommandRef[]common(){+finalArrayList<CommandRef>common=newArrayList<CommandRef>();+for(finalCommandRefc:INSTANCE.commands.values())+if(c.isCommon())+common.add(c);+returntoSortedArray(common);+}++privatestaticCommandRef[]toSortedArray(finalCollection<CommandRef>c){+finalCommandRef[]r=c.toArray(newCommandRef[c.size()]);+Arrays.sort(r,newComparator<CommandRef>(){+publicintcompare(finalCommandRefo1,finalCommandRefo2){+returno1.getName().compareTo(o2.getName());+}+});+returnr;+}++privatefinalClassLoaderldr;++privatefinalMap<String,CommandRef>commands;++privateCommandCatalog(){+ldr=Thread.currentThread().getContextClassLoader();+commands=newHashMap<String,CommandRef>();++finalEnumeration<URL>catalogs=catalogs();+while(catalogs.hasMoreElements())+scan(catalogs.nextElement());+}++privateEnumeration<URL>catalogs(){+try{+finalStringpfx="META-INF/services/";+returnldr.getResources(pfx+TextBuiltin.class.getName());+}catch(IOExceptionerr){+returnnewVector<URL>().elements();+}+}++privatevoidscan(finalURLcUrl){+finalBufferedReadercIn;+try{+finalInputStreamin=cUrl.openStream();+cIn=newBufferedReader(newInputStreamReader(in,"UTF-8"));+}catch(IOExceptionerr){+// If we cannot read from the service list, go to the next.+//+return;+}++try{+Stringline;+while((line=cIn.readLine())!=null){+if(line.length()>0&&!line.startsWith("#"))+load(line);+}+}catch(IOExceptionerr){+// If we failed during a read, ignore the error.+//+}finally{+try{+cIn.close();+}catch(IOExceptione){+// Ignore the close error; we are only reading.+}+}+}++privatevoidload(finalStringcn){+finalClass<?extendsTextBuiltin>clazz;+try{+clazz=Class.forName(cn,false,ldr).asSubclass(TextBuiltin.class);+}catch(ClassNotFoundExceptionnotBuiltin){+// Doesn't exist, even though the service entry is present.+//+return;+}catch(ClassCastExceptionnotBuiltin){+// Isn't really a builtin, even though its listed as such.+//+return;+}++finalCommandRefcr;+finalCommanda=clazz.getAnnotation(Command.class);+if(a!=null)+cr=newCommandRef(clazz,a);+else+cr=newCommandRef(clazz);++commands.put(cr.getName(),cr);+}+}
From: Shawn O. Pearce <hidden> Date: 2016-06-15 22:45:02
If the main loop did not get a subcommand during parsing of the
command line then we should offer up a list of commonly used
commands and their one-line usage summary, to help the user make
a decision about which command they should try to execute.
Signed-off-by: Shawn O. Pearce <redacted>
---
.../src/org/spearce/jgit/pgm/Main.java | 18 ++++++++++++++++++
1 files changed, 18 insertions(+), 0 deletions(-)
@@ -121,6 +121,24 @@ public class Main {System.err.println();clp.printUsage(System.err);System.err.println();+}elseif(subcommand==null){+System.err.println();+System.err.println("The most commonly used commands are:");+finalCommandRef[]common=CommandCatalog.common();+intwidth=0;+for(finalCommandRefc:common)+width=Math.max(width,c.getName().length());+width+=2;++for(finalCommandRefc:common){+System.err.print(' ');+System.err.print(c.getName());+for(inti=c.getName().length();i<width;i++)+System.err.print(' ');+System.err.print(c.getUsage());+System.err.println();+}+System.err.println();}System.exit(1);}
From: Shawn O. Pearce <hidden> Date: 2016-06-15 22:45:02
"Shawn O. Pearce" [off-list ref] wrote:
This series adds support to jgit to list commonly used subcommands
if the user just executes `jgit` with no subcommand requested:
$ jgit
jgit --git-dir GIT_DIR --help (-h) --show-stack-trace command [ARG ...]
The most commonly used commands are:
fetch Update remote refs from another repository
log View commit history
push Update remote repository from local refs
tag Create a tag
Scratch that. This series is busted if you install jgit and actually
try to use it. No subcommands get registered. I suspect it is due
to the shell script+ZIP file we have in the CLASSPATH confusing the
JRE and making it impossible to read correctly.
--
Shawn.
From: Shawn O. Pearce <hidden> Date: 2016-06-15 22:45:02
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 <redacted>
---
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
@@ -0,0 +1,188 @@+/*+*Copyright(C)2008,ShawnO.Pearce<spearce@spearce.org>+*+*Allrightsreserved.+*+*Redistributionanduseinsourceandbinaryforms,withor+*withoutmodification,arepermittedprovidedthatthefollowing+*conditionsaremet:+*+*-Redistributionsofsourcecodemustretaintheabovecopyright+*notice,thislistofconditionsandthefollowingdisclaimer.+*+*-Redistributionsinbinaryformmustreproducetheabove+*copyrightnotice,thislistofconditionsandthefollowing+*disclaimerinthedocumentationand/orothermaterialsprovided+*withthedistribution.+*+*-NeitherthenameoftheGitDevelopmentCommunitynorthe+*namesofitscontributorsmaybeusedtoendorseorpromote+*productsderivedfromthissoftwarewithoutspecificprior+*writtenpermission.+*+*THISSOFTWAREISPROVIDEDBYTHECOPYRIGHTHOLDERSAND+*CONTRIBUTORS"AS IS"ANDANYEXPRESSORIMPLIEDWARRANTIES,+*INCLUDING,BUTNOTLIMITEDTO,THEIMPLIEDWARRANTIES+*OFMERCHANTABILITYANDFITNESSFORAPARTICULARPURPOSE+*AREDISCLAIMED.INNOEVENTSHALLTHECOPYRIGHTOWNEROR+*CONTRIBUTORSBELIABLEFORANYDIRECT,INDIRECT,INCIDENTAL,+*SPECIAL,EXEMPLARY,ORCONSEQUENTIALDAMAGES(INCLUDING,BUT+*NOTLIMITEDTO,PROCUREMENTOFSUBSTITUTEGOODSORSERVICES;+*LOSSOFUSE,DATA,ORPROFITS;ORBUSINESSINTERRUPTION)HOWEVER+*CAUSEDANDONANYTHEORYOFLIABILITY,WHETHERINCONTRACT,+*STRICTLIABILITY,ORTORT(INCLUDINGNEGLIGENCEOROTHERWISE)+*ARISINGINANYWAYOUTOFTHEUSEOFTHISSOFTWARE,EVENIF+*ADVISEDOFTHEPOSSIBILITYOFSUCHDAMAGE.+*/++packageorg.spearce.jgit.pgm;++importjava.io.BufferedReader;+importjava.io.IOException;+importjava.io.InputStream;+importjava.io.InputStreamReader;+importjava.net.URL;+importjava.util.ArrayList;+importjava.util.Arrays;+importjava.util.Collection;+importjava.util.Comparator;+importjava.util.Enumeration;+importjava.util.HashMap;+importjava.util.Map;+importjava.util.Vector;++/**+*Listofallcommandsknownbyjgit'scommandlinetools.+*<p>+*Commandsareimplementationsof{@linkTextBuiltin},withanoptional+*{@linkCommand}classannotationtoinsertadditionaldocumentationor+*overridethedefaultcommandname(whichisguessedfromtheclassname).+*<p>+*CommandsmayberegisteredbyaddingthemtoaservicesfileinthesameJAR+*(orclassesdirectory)asthecommandimplementation.Theservicefilename+*is<code>META-INF/services/org.spearce.jgit.pgm.TextBuiltin</code>andit+*containsoneconcreteimplementationclassnameperline.+*<p>+*CommandregistrationisidenticaltoJava6'sservices,howeverthecatalog+*usesalightweightwrappertodelaycreatingacommandinstanceasmuchas+*possible.ThisavoidsinitializingtheAWTorSWTGUItoolkitsevenifthe+*command'sconstructormightrequirethem.+*/+publicclassCommandCatalog{+privatestaticfinalCommandCatalogINSTANCE=newCommandCatalog();++/**+*Locateasinglecommandbyitsuserfriendlyname.+*+*@paramname+*nameofthecommand.Typicallyindash-lower-case-form,which+*wasderivedfromtheDashLowerCaseFormclassname.+*@returnthecommandinstance;nullifnocommandexistsbythatname.+*/+publicstaticCommandRefget(finalStringname){+returnINSTANCE.commands.get(name);+}++/**+*@returnallknowncommands,sortedbycommandname.+*/+publicstaticCommandRef[]all(){+returntoSortedArray(INSTANCE.commands.values());+}++/**+*@returnallcommoncommands,sortedbycommandname.+*/+publicstaticCommandRef[]common(){+finalArrayList<CommandRef>common=newArrayList<CommandRef>();+for(finalCommandRefc:INSTANCE.commands.values())+if(c.isCommon())+common.add(c);+returntoSortedArray(common);+}++privatestaticCommandRef[]toSortedArray(finalCollection<CommandRef>c){+finalCommandRef[]r=c.toArray(newCommandRef[c.size()]);+Arrays.sort(r,newComparator<CommandRef>(){+publicintcompare(finalCommandRefo1,finalCommandRefo2){+returno1.getName().compareTo(o2.getName());+}+});+returnr;+}++privatefinalClassLoaderldr;++privatefinalMap<String,CommandRef>commands;++privateCommandCatalog(){+ldr=Thread.currentThread().getContextClassLoader();+commands=newHashMap<String,CommandRef>();++finalEnumeration<URL>catalogs=catalogs();+while(catalogs.hasMoreElements())+scan(catalogs.nextElement());+}++privateEnumeration<URL>catalogs(){+try{+finalStringpfx="META-INF/services/";+returnldr.getResources(pfx+TextBuiltin.class.getName());+}catch(IOExceptionerr){+returnnewVector<URL>().elements();+}+}++privatevoidscan(finalURLcUrl){+finalBufferedReadercIn;+try{+finalInputStreamin=cUrl.openStream();+cIn=newBufferedReader(newInputStreamReader(in,"UTF-8"));+}catch(IOExceptionerr){+// If we cannot read from the service list, go to the next.+//+return;+}++try{+Stringline;+while((line=cIn.readLine())!=null){+if(line.length()>0&&!line.startsWith("#"))+load(line);+}+}catch(IOExceptionerr){+// If we failed during a read, ignore the error.+//+}finally{+try{+cIn.close();+}catch(IOExceptione){+// Ignore the close error; we are only reading.+}+}+}++privatevoidload(finalStringcn){+finalClass<?extendsTextBuiltin>clazz;+try{+clazz=Class.forName(cn,false,ldr).asSubclass(TextBuiltin.class);+}catch(ClassNotFoundExceptionnotBuiltin){+// Doesn't exist, even though the service entry is present.+//+return;+}catch(ClassCastExceptionnotBuiltin){+// Isn't really a builtin, even though its listed as such.+//+return;+}++finalCommandRefcr;+finalCommanda=clazz.getAnnotation(Command.class);+if(a!=null)+cr=newCommandRef(clazz,a);+else+cr=newCommandRef(clazz);++commands.put(cr.getName(),cr);+}+}