src/share/classes/sun/module/tools/JRepo.java

Print this page




  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22  * CA 95054 USA or visit www.sun.com if you need additional information or
  23  * have any questions.
  24  */
  25 
  26 package sun.module.tools;
  27 
  28 import java.io.BufferedReader;
  29 import java.io.File;
  30 import java.io.FileNotFoundException;
  31 import java.io.IOException;
  32 import java.io.InputStreamReader;

  33 import java.io.PrintStream;
  34 import java.io.PrintWriter;
  35 import java.io.StringWriter;
  36 import java.net.MalformedURLException;
  37 import java.net.URI;
  38 import java.net.URL;
  39 import java.net.URISyntaxException;
  40 import java.module.*;

  41 import java.security.AccessController;

  42 import java.text.DateFormat;
  43 import java.util.Arrays;
  44 import java.util.ArrayList;

  45 import java.util.Date;
  46 import java.util.HashMap;

  47 import java.util.LinkedHashMap;
  48 import java.util.List;
  49 import java.util.Map;
  50 import sun.module.JamUtils;
  51 import sun.module.repository.RepositoryConfig;
  52 import sun.security.action.GetPropertyAction;
  53 import sun.tools.jar.CommandLine;
  54 
  55 /**
  56  * Java Modules Repository Management Tool
  57  * @since 1.7
  58  */
  59 public class JRepo {
  60     public static final boolean DEBUG;
  61 
  62     static {
  63         DEBUG = (AccessController.doPrivileged(new GetPropertyAction("sun.module.tools.debug")) != null);
  64     }
  65 
  66     private static void debug(String s) {
  67         System.err.println(s);
  68     }
  69 
  70     /** For printing user output. */
  71     private final Messenger msg;
  72 
  73     /** Optional command line argument used by {@code list()}. */
  74     private String moduleName;
  75 
  76     /** Map from this tool's command names to the commands themselves. */
  77     private static final Map<String, Command> commands = new LinkedHashMap<String, Command>();
  78 
  79     /** Usage message created from each command's usage. */
  80     private static String usage = null;
  81 
  82     /** Format for module name & version. */
  83     private static final String MAIFormat = "%-20s %-20s";
  84 
  85     /** Format for additional/verbose module information. */
  86     private static final String MAIFormatVerbose = " %-9s %-7s %-17s %s";
  87 






  88     /** String containing column headings for name & version. */
  89     private static final String maiHeading;
  90 
  91     /** String containing column headings for additional/verbose module information. */
  92     private static final String maiHeadingVerbose;
  93 
  94     /** Indicates that parent repositories should be used by a command. */
  95     private static final Flag parentFlag = new Flag('p');
  96 
  97     /** Indicates that command output should be verbose. */
  98     private static final Flag verboseFlag = new Flag('v');
  99 
 100     /** Location of repository; if not given uses system repository. */
 101     private static final RepositoryFlag repositoryFlag = new RepositoryFlag();
 102 






 103     /** Contains the flags that are common to all commands. */
 104     private static final HashMap<Character, Flag> commonFlags = new HashMap<Character, Flag>();
 105 
 106     static {
 107         repositoryFlag.register(commonFlags);
 108         verboseFlag.register(commonFlags);
 109 
 110         StringWriter sw = new StringWriter();
 111         PrintWriter pw = new PrintWriter(sw);
 112         pw.printf(MAIFormat, "Name", "Version"); // XXX i18n
 113         maiHeading = sw.toString();
 114 
 115         sw = new StringWriter();
 116         pw = new PrintWriter(sw);
 117         pw.printf(MAIFormat, "Name", "Version"); // XXX i18n
 118         pw.printf(MAIFormatVerbose, "Platform", "Arch", "Modified", "Filename"); // XXX i18n
 119         maiHeadingVerbose = sw.toString();
 120     }
 121 




 122     public JRepo(PrintStream out, PrintStream err, BufferedReader reader) {
 123         msg = new Messenger("jrepo", out, err, reader);
 124         synchronized(commands) {
 125             if (commands.isEmpty()) {
 126                 new ListCommand().register(commands);
 127                 new InstallCommand().register(commands);
 128                 new UninstallCommand().register(commands);

 129             }
 130         }
 131         reset();
 132     }
 133 
 134     public static void main(String[] args) {
 135         JRepo jrepo = new JRepo(
 136             System.out, System.err,
 137             new BufferedReader(new InputStreamReader(System.in)));
 138         System.exit(jrepo.run(args) ? 0 : 1);
 139     }
 140 
 141     public synchronized boolean run(String[] args) {
 142         reset();
 143 
 144         if (DEBUG) { for (String s : args) debug("arg: '" + s + "'"); }
 145         Command cmd = parseArgs(args);
 146         if (DEBUG && cmd != null) debug("running " + cmd);
 147         if (cmd == null) {
 148             return false;


 172         Repository rc = null;
 173 
 174         String repositoryLocation = repositoryFlag.getLocation();
 175 
 176         if (repositoryLocation == null) {
 177             rc = Repository.getSystemRepository();
 178         } else {
 179             // If repositoryLocation is a URL use URLRepository else LocalRepository.
 180             try {
 181                 URL u = new URL(repositoryLocation);
 182                 rc = Modules.newURLRepository(
 183                     "jrepo", u, null, RepositoryConfig.getSystemRepository());
 184             } catch (MalformedURLException ex) {
 185                 File f = new File(repositoryLocation);
 186                 if (f.exists() && f.canRead()) {
 187                     rc = Modules.newLocalRepository(
 188                         "jrepo",
 189                         f.getCanonicalFile(), null,
 190                         RepositoryConfig.getSystemRepository());
 191                 } else {
 192                     throw new IOException("Cannot access repository at " + repositoryLocation);

 193                 }
 194             }
 195         }
 196         return rc;
 197     }
 198 
 199     /** Reset instance state to default values. */
 200     private void reset() {
 201         moduleName = null;
 202         parentFlag.reset();
 203         repositoryFlag.reset();
 204         verboseFlag.reset();
 205         for (Command cmd : commands.values()) {
 206             cmd.reset();
 207         }
 208     }
 209 
 210 
 211     /** Parse command line arguments.*/
 212     private Command parseArgs(String[] args) {


 245             try {
 246                 if (cmd.parseArgs(args, msg)) {
 247                     return cmd;
 248                 } else {
 249                     usageError();
 250                     return null;
 251                 }
 252             } catch (ArrayIndexOutOfBoundsException e) {
 253                 usageError();
 254             }
 255         } catch (IllegalArgumentException ex) {
 256             msg.error(ex.getMessage());
 257             usageError();
 258             return null;
 259         }
 260         return cmd;
 261     }
 262 
 263 
 264     /** Returns a user-grokkable description of the repository. */
 265     private String getRepositoryText(Repository repo) {
 266         String rc;
 267         URI u = repo.getSourceLocation();
 268         if (u == null) {
 269             rc = "Bootstrap repository";
 270         } else {
 271             try {
 272                 rc = "Repository " + u.toURL().toExternalForm();
 273             } catch (MalformedURLException ex) {
 274                 rc = "Repository unknown";
 275             }
 276         }
 277         return rc;
 278     }
 279 
 280     private String getMAIText(ModuleArchiveInfo mai) {
 281         StringWriter sw = new StringWriter();
 282         PrintWriter pw = new PrintWriter(sw);
 283         pw.printf(MAIFormat, mai.getName(), mai.getVersion());
 284         if (verboseFlag.isEnabled()) {
 285             long t = mai.getLastModified();
 286             String lastMod = null;
 287             if (t != 0) {
 288                 lastMod = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(new Date(t));
 289             }
 290             pw.printf(MAIFormatVerbose,
 291                 mai.getPlatform() == null ? "generic" : mai.getPlatform(),
 292                 mai.getArch() == null ? "generic" : mai.getArch(),
 293                 lastMod == null ? "n/a" : lastMod,
 294                 mai.getFileName() == null ? "n/a" : mai.getFileName()
 295                 );
 296         }
 297         return sw.toString();
 298     }
 299 

















 300     void usageError() {
 301         usageError(msg);
 302     }
 303 
 304     static void usageError(Messenger msg) {
 305         if (usage == null) {
 306             StringBuilder ub = new StringBuilder(
 307                 "Usage: jrepo <command>\nwhere <command> includes:");
 308             for (Command c : commands.values()) {
 309                 String u = c.usage();
 310                 if (u != null) {
 311                     ub.append("\n    ").append(c.usage());
 312                 }
 313             }
 314             usage = ub.toString();
 315         }
 316         msg.error(usage);
 317     }
 318 
 319     /**
 320      * Represents a flag given on the command line.  Flags always are 2
 321      * characters long, and start with a '-'.  They can have additional
 322      * associated arguments (cf RepositoryFlag).
 323      */
 324     private static class Flag {
 325         private final char name;
 326 
 327         private boolean enabled = false;
 328 
 329         private static final Map<Character, Flag> flags = new HashMap<Character, Flag>();
 330 
 331         Flag(char name) {
 332             this.name = name;
 333             flags.put(new Character(name), this);
 334         }
 335 
 336         void register(Map<Character, Flag> registry) {
 337             registry.put(new Character(name), this);
 338         }
 339 
 340         char getName() {
 341             return name;
 342         }
 343 
 344         /** @return the number of arguments consumed by this Flag. */
 345         int set(String[] args, int pos) {



 346             enabled = true;
 347             return 1;
 348         }
 349 
 350         void reset() {
 351             enabled = false;
 352         }
 353 
 354         boolean isEnabled() {
 355             return enabled;
 356         }
 357 
 358         static Flag get(char c) {
 359             return flags.get(new Character(c));
 360         }
 361     }
 362 
 363     private static class RepositoryFlag extends Flag {
 364         String location = null;
 365 
 366         RepositoryFlag() {
 367             super('r');
 368         }
 369 
 370         int set(String[] args, int pos) {

 371             int rc = super.set(args, pos);
 372             location = args[pos + 1];
 373             return rc + 1;
 374         }
 375 
 376         String getLocation() {
 377             return location;
 378         }
 379 

 380         void reset() {
 381             super.reset();
 382             location = null;
 383         }
 384     }
 385 





































 386     /*
 387      * Command types: An abstract base class, plus one concrete class for
 388      * each Command.
 389      */
 390 
 391     /*
 392      * Represents a Command.
 393      */
 394     private static abstract class Command {
 395         private final String name;
 396 
 397         Command(String name) {
 398             this.name = name;
 399         }
 400 
 401         /** Adds this command to the given registry. */
 402         void register(Map<String, Command> registry) {
 403             registry.put(name, this);
 404         }
 405 


 413         /** Parses the arguments particular to this command. */
 414         abstract boolean parseArgs(String[] args, Messenger msg);
 415 
 416         /**
 417          * Parse the Flags for this command.
 418          * @return number of flags found.
 419          * @throws IllegalArgumentException if an invalid flag is given.
 420          */
 421         int parseFlags(String[] args, Map<Character, Flag> flags) {
 422             int rc = 0;
 423             int i = 0;
 424             while (i < args.length) {
 425                 String s = args[i];
 426                 if (s.length() == 2 && s.charAt(0) == '-') {
 427                     Flag f = flags.get(s.charAt(1));
 428                     if (f != null) {
 429                         int numConsumed = f.set(args, i);
 430                         i += numConsumed;  // Increases at each iteration.
 431                         rc += numConsumed; // Increases only when the arg is a flag.
 432                     } else {
 433                         throw new IllegalArgumentException("unrecognized flag: " + args[i]);

 434                     }
 435                 } else {
 436                     i++;
 437                 }
 438             }
 439             return rc;
 440         }
 441 
 442         /** Represents the actual behavior of the command. */
 443         abstract boolean run(Repository repo, Messenger msg);
 444 
 445         /** Returns a usage string describing this command, or null if the
 446          * command is a synonym for another command.
 447          */
 448         abstract String usage();
 449 

 450         public String toString() { return name; }
 451 
 452         /**
 453          * RepositoryVisitor types walk a parent chain of repositories, invoking
 454          * {@code doit} in each one.  The abstract base class provides the recursion;
 455          * each concrete subclass provides the per-repository behavior.  The
 456          * recursion is such that the bootstrap repository is visited first,
 457          * and the system repository is last.
 458          */
 459         abstract class RepositoryVisitor {
 460             abstract void doit(Repository repo, Messenger msg);
 461 
 462             void doBefore(Messenger msg) { }
 463 
 464             void doAfter(Messenger msg) { }
 465 
 466             final void run(Repository repo, Messenger msg) {
 467                 visit(repo, msg);
 468             }
 469 
 470             private final void visit(Repository repo, Messenger msg) {
 471                 Repository parent = parentFlag.isEnabled() ? repo.getParent() : null;
 472                 if (parent != null) {
 473                     visit(parent, msg);
 474                 }
 475                 doBefore(msg);
 476                 doit(repo, msg);
 477                 doAfter(msg);
 478             }
 479         }
 480     }
 481 











































































































































































 482     /** Installs a JAM into a repository. */
 483     private class InstallCommand extends Command {
 484         private String jamName;
 485 
 486         InstallCommand() {
 487             super("install");
 488         }
 489 
 490         boolean parseArgs(String[] args, Messenger msg) {
 491             boolean rc = false;
 492             if (!parentFlag.isEnabled()
 493                     && repositoryFlag.getLocation() != null
 494                     && args.length == 1) {
 495                 jamName = args[0];
 496                 rc = true;
 497             }
 498             return rc;
 499         }
 500 
 501         boolean run(Repository repo, Messenger msg) {


 519                     jamURL = jamName;
 520                 }
 521                 try {
 522                     ModuleArchiveInfo mai = repo.install(new URL(jamURL).toURI());
 523                     if (verboseFlag.isEnabled()) {
 524                         msg.println("Installed " + jamName + ": " + getMAIText(mai));
 525                     }
 526                     return true;
 527                 } catch (URISyntaxException ex) {
 528                     msg.error("Cannot install " + jamName + ": no such file, or malformed URI");
 529                 } catch (MalformedURLException ex) {
 530                     msg.error("Cannot install " + jamName + ": no such file, or malformed URL");
 531                 } catch (IOException ex) {
 532                     msg.error("Cannot install " + jamName + ": " + ex.getMessage());
 533                 }
 534             }
 535             return false;
 536         }
 537 
 538         String usage() {
 539             return "install [-v] -r repositoryLocation jamFile | jamURL\n"
 540                 +  "        installs a module into a repository";
 541         }
 542     }
 543 
 544     /** Uninstalls a module from a repository. */
 545     private class UninstallCommand extends Command {
 546         @SuppressWarnings("unchecked")
 547         private final Map<Character, Flag> myFlags = (Map<Character, Flag>) commonFlags.clone();
 548 
 549         private Flag forceFlag = new Flag('f');
 550 
 551         private Flag interactiveFlag = new Flag('i');
 552 
 553         private String moduleName;
 554 
 555         private Version version;
 556 
 557         private String platformBinding;
 558 
 559         UninstallCommand() {
 560             super("uninstall");
 561             forceFlag.register(myFlags);
 562             interactiveFlag.register(myFlags);
 563         }
 564 

 565         void reset() {
 566             version = null;
 567             platformBinding = null;
 568             forceFlag.reset();
 569             interactiveFlag.reset();
 570         }
 571 

 572         int parseFlags(String[] args, Map<Character, Flag> flags) {
 573             return super.parseFlags(args, myFlags);
 574         }
 575 
 576         boolean parseArgs(String[] args, Messenger msg) {
 577             if (repositoryFlag.getLocation() == null) {
 578                 return false;
 579             }
 580 
 581             if (forceFlag.isEnabled() && interactiveFlag.isEnabled()) {
 582                 // Doesn't make sense for these to both be enabled
 583                 msg.error("uninstall cannot simultaneously use both -i and -f: choose one or the other"); // XXX i81n
 584                 return false;
 585             }
 586 
 587             if (args.length >= 1) {
 588                 moduleName = args[0];
 589                 if (args.length >= 2 && args.length < 4) {
 590                     try {
 591                         version = Version.valueOf(args[1]);


 638                         }
 639                     }
 640                 } else if (forceFlag.isEnabled()) {
 641                     if (DEBUG) debug("forced uninstall of multiple matches");
 642                     rc = true;
 643                     for (ModuleArchiveInfo mai : found) {
 644                         if (!uninstall(repo, mai, msg)) {
 645                             if (DEBUG) debug("uninstall failed for " + getMAIText(mai) + " in " + repo.getSourceLocation());
 646                             rc = false;
 647                             break;
 648                         }
 649                     }
 650                 } else if (interactiveFlag.isEnabled()) {
 651                     rc = uninstallInteractive(repo, found, msg);
 652                 }
 653             }
 654             return rc;
 655         }
 656 
 657         String usage() {
 658             return "uninstall [-v] [-f | -i] -r repositoryLocation moduleName [moduleVersion] [modulePlatformBinding]\n"

 659                 +  "        removes a module from a repository, along with associated files.";
 660         }
 661 
 662         /** Uninstall the ModuleArchiveInfo from the Repository. */
 663         private boolean uninstall(Repository repo, ModuleArchiveInfo mai, Messenger msg) {
 664             boolean rc = false;
 665             if (DEBUG) debug("Uninstalling " + getMAIText(mai));
 666             try {
 667                 rc = repo.uninstall(mai);
 668                 if (verboseFlag.isEnabled()) {
 669                     if (rc) {
 670                         msg.println("Uninstalled " + getMAIText(mai));
 671                     } else {
 672                         msg.error("Failed to uninstall " + getMAIText(mai));
 673                     }
 674                 }
 675             } catch (Exception ex) {
 676                 msg.error("Exception while uninstalling " + getInfo()
 677                           + ": " + ex.getMessage());
 678             }
 679             return rc;
 680         }
 681 
 682         /** Uninstall one of the ModuleArchiveInfos from the Repository. */
 683         private boolean uninstallInteractive(
 684                 Repository repo,
 685                 List<ModuleArchiveInfo> found,
 686                 Messenger msg) {
 687             boolean rc = false;
 688 
 689             String spaces = "          ";
 690             String fmt = "%" + found.size() + "d %s\n";
 691 
 692             StringWriter sw = new StringWriter();


 760             if (version != null) {
 761                 s += " with version " + version;
 762             }
 763             if (platformBinding != null) {
 764                 s += " and platform-binding of " + platformBinding;
 765             }
 766             return s;
 767         }
 768     }
 769 
 770     /** Prints information about modules found in repositories. */
 771     private class ListCommand extends Command {
 772         @SuppressWarnings("unchecked")
 773         private final Map<Character, Flag> myFlags = (Map<Character, Flag>) commonFlags.clone();
 774 
 775         ListCommand() {
 776             super("list");
 777             parentFlag.register(myFlags);
 778         }
 779 

 780         void reset() {
 781             parentFlag.reset();
 782         }
 783 

 784         int parseFlags(String[] args, Map<Character, Flag> flags) {
 785             return super.parseFlags(args, myFlags);
 786         }
 787 
 788         boolean parseArgs(String[] args, Messenger msg){
 789             boolean rc = true;
 790             if (args.length == 0) {
 791                 moduleName = null;
 792             } else if (args.length == 1) {
 793                 moduleName = args[0];
 794             } else {
 795                 rc = false;
 796             }
 797             return rc;
 798         }
 799 
 800         boolean run(Repository repo, Messenger msg) {
 801             ListRepositoryVisitor visitor = new ListRepositoryVisitor();
 802             visitor.run(repo, msg);
 803             boolean found = visitor.wasFound();
 804             if (verboseFlag.isEnabled() && !found) {
 805                 if (moduleName != null) {
 806                     msg.error("Could not find module name starting with '" + moduleName + "'");


 807                 } else {
 808                     msg.error("Could not find any modules");
 809                 }
 810             }
 811             return found;
 812         }
 813 
 814         String usage() {
 815             return "list [-v] [-p] [-r repositoryLocation] moduleName\n"

 816                 +  "        lists the modules in the repository";
 817         }
 818 
 819         class ListRepositoryVisitor extends RepositoryVisitor {
 820             private boolean found = false;
 821 
 822             boolean wasFound() { return found; }
 823 
 824             void doit(Repository repo, Messenger msg) {
 825                 boolean printedHeader = false;





 826                 for (ModuleArchiveInfo mai : repo.list()) {
 827                     if (moduleName == null || mai.getName().startsWith(moduleName)) {
 828                         if (!printedHeader) {
 829                             msg.println(getRepositoryText(repo));
 830                             msg.println(verboseFlag.isEnabled() ? maiHeadingVerbose : maiHeading);
 831                             printedHeader = true;
 832                         }
 833                         msg.println(getMAIText(mai));
 834                         found = true;
 835                     }
 836                 }

 837             }
 838         }
 839     }
 840 }


  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22  * CA 95054 USA or visit www.sun.com if you need additional information or
  23  * have any questions.
  24  */
  25 
  26 package sun.module.tools;
  27 
  28 import java.io.BufferedReader;
  29 import java.io.File;
  30 import java.io.FileNotFoundException;
  31 import java.io.IOException;
  32 import java.io.InputStreamReader;
  33 import java.io.OutputStream;
  34 import java.io.PrintStream;
  35 import java.io.PrintWriter;
  36 import java.io.StringWriter;
  37 import java.net.MalformedURLException;
  38 import java.net.URI;
  39 import java.net.URL;
  40 import java.net.URISyntaxException;
  41 import java.module.*;
  42 import java.module.annotation.ImportPolicyClass;
  43 import java.security.AccessController;
  44 import java.security.PrivilegedAction;
  45 import java.text.DateFormat;
  46 import java.util.Arrays;
  47 import java.util.ArrayList;
  48 import java.util.Collections;
  49 import java.util.Date;
  50 import java.util.HashMap;
  51 import java.util.HashSet;
  52 import java.util.LinkedHashMap;
  53 import java.util.List;
  54 import java.util.Map;
  55 import java.util.Set;
  56 import sun.module.repository.RepositoryConfig;
  57 import sun.security.action.GetPropertyAction;
  58 import sun.tools.jar.CommandLine;
  59 
  60 /**
  61  * Java Modules Repository Management Tool
  62  * @since 1.7
  63  */
  64 public class JRepo {
  65     public static final boolean DEBUG;
  66 
  67     static {
  68         DEBUG = (AccessController.doPrivileged(new GetPropertyAction("sun.module.tools.debug")) != null);
  69     }
  70 
  71     private static void debug(String s) {
  72         System.err.println(s);
  73     }
  74 
  75     /** For printing user output. */
  76     private final Messenger msg;
  77 
  78     /** Optional command line argument used by {@code list()}. */
  79     private String moduleName;
  80 
  81     /** Map from this tool's command names to the commands themselves. */
  82     private static final Map<String, Command> commands = new LinkedHashMap<String, Command>();
  83 
  84     /** Usage message created from each command's usage. */
  85     private static String usage = null;
  86 
  87     /** Format for ModuleArchiveInfo name & version. */
  88     private static final String MAIFormat = "%-20s %-20s";
  89 
  90     /** Format for additional/verbose ModuleArchiveInfo details. */
  91     private static final String MAIFormatVerbose = " %-9s %-7s %-17s %s";
  92 
  93     /** Format for ModuleDefinition name & version. */
  94     private static final String MDFormat = "%s-%s";
  95 
  96     /** Format for additional/verbose ModuleDefinition details. */
  97     private static final String MDFormatVerbose = " %s";
  98 
  99     /** String containing column headings for name & version. */
 100     private static final String maiHeading;
 101 
 102     /** String containing column headings for additional/verbose module information. */
 103     private static final String maiHeadingVerbose;
 104 
 105     /** Indicates that parent repositories should be used by a command. */
 106     private static final Flag parentFlag = new Flag('p');
 107 
 108     /** Indicates that command output should be verbose. */
 109     private static final Flag verboseFlag = new Flag('v');
 110 
 111     /** Location of repository; if not given uses system repository. */
 112     private static final RepositoryFlag repositoryFlag = new RepositoryFlag();
 113 
 114     /** Indicates dependencies command should display info on core modules. */
 115     private static final Flag javaseFlag = new Flag('j');
 116 
 117     /** Provides way to specify platform binding fo dependencies command. */
 118     private static final BindingFlag bindingFlag = new BindingFlag();
 119 
 120     /** Contains the flags that are common to all commands. */
 121     private static final HashMap<Character, Flag> commonFlags = new HashMap<Character, Flag>();
 122 
 123     static {
 124         repositoryFlag.register(commonFlags);
 125         verboseFlag.register(commonFlags);
 126 
 127         StringWriter sw = new StringWriter();
 128         PrintWriter pw = new PrintWriter(sw);
 129         pw.printf(MAIFormat, "Name", "Version"); // XXX i18n
 130         maiHeading = sw.toString();
 131 
 132         sw = new StringWriter();
 133         pw = new PrintWriter(sw);
 134         pw.printf(MAIFormat, "Name", "Version"); // XXX i18n
 135         pw.printf(MAIFormatVerbose, "Platform", "Arch", "Modified", "Filename"); // XXX i18n
 136         maiHeadingVerbose = sw.toString();
 137     }
 138 
 139     public JRepo(OutputStream out, OutputStream err, BufferedReader reader) {
 140         this(new PrintStream(out), new PrintStream(err), reader);
 141     }
 142 
 143     public JRepo(PrintStream out, PrintStream err, BufferedReader reader) {
 144         msg = new Messenger("jrepo", out, err, reader);
 145         synchronized(commands) {
 146             if (commands.isEmpty()) {
 147                 new ListCommand().register(commands);
 148                 new InstallCommand().register(commands);
 149                 new UninstallCommand().register(commands);
 150                 new DependenciesCommand().register(commands);
 151             }
 152         }
 153         reset();
 154     }
 155 
 156     public static void main(String[] args) {
 157         JRepo jrepo = new JRepo(
 158             System.out, System.err,
 159             new BufferedReader(new InputStreamReader(System.in)));
 160         System.exit(jrepo.run(args) ? 0 : 1);
 161     }
 162 
 163     public synchronized boolean run(String[] args) {
 164         reset();
 165 
 166         if (DEBUG) { for (String s : args) debug("arg: '" + s + "'"); }
 167         Command cmd = parseArgs(args);
 168         if (DEBUG && cmd != null) debug("running " + cmd);
 169         if (cmd == null) {
 170             return false;


 194         Repository rc = null;
 195 
 196         String repositoryLocation = repositoryFlag.getLocation();
 197 
 198         if (repositoryLocation == null) {
 199             rc = Repository.getSystemRepository();
 200         } else {
 201             // If repositoryLocation is a URL use URLRepository else LocalRepository.
 202             try {
 203                 URL u = new URL(repositoryLocation);
 204                 rc = Modules.newURLRepository(
 205                     "jrepo", u, null, RepositoryConfig.getSystemRepository());
 206             } catch (MalformedURLException ex) {
 207                 File f = new File(repositoryLocation);
 208                 if (f.exists() && f.canRead()) {
 209                     rc = Modules.newLocalRepository(
 210                         "jrepo",
 211                         f.getCanonicalFile(), null,
 212                         RepositoryConfig.getSystemRepository());
 213                 } else {
 214                     throw new IOException("Cannot access repository at " // XXX i18n
 215                                           + repositoryLocation); 
 216                 }
 217             }
 218         }
 219         return rc;
 220     }
 221 
 222     /** Reset instance state to default values. */
 223     private void reset() {
 224         moduleName = null;
 225         parentFlag.reset();
 226         repositoryFlag.reset();
 227         verboseFlag.reset();
 228         for (Command cmd : commands.values()) {
 229             cmd.reset();
 230         }
 231     }
 232 
 233 
 234     /** Parse command line arguments.*/
 235     private Command parseArgs(String[] args) {


 268             try {
 269                 if (cmd.parseArgs(args, msg)) {
 270                     return cmd;
 271                 } else {
 272                     usageError();
 273                     return null;
 274                 }
 275             } catch (ArrayIndexOutOfBoundsException e) {
 276                 usageError();
 277             }
 278         } catch (IllegalArgumentException ex) {
 279             msg.error(ex.getMessage());
 280             usageError();
 281             return null;
 282         }
 283         return cmd;
 284     }
 285 
 286 
 287     /** Returns a user-grokkable description of the repository. */
 288     private static String getRepositoryText(Repository repo) {
 289         String rc;
 290         URI u = repo.getSourceLocation();
 291         if (u == null) {
 292             rc = "Bootstrap repository";
 293         } else {
 294             try {
 295                 rc = "Repository " + u.toURL().toExternalForm();
 296             } catch (MalformedURLException ex) {
 297                 rc = "Repository unknown";
 298             }
 299         }
 300         return rc;
 301     }
 302 
 303     private static String getMAIText(ModuleArchiveInfo mai) {
 304         StringWriter sw = new StringWriter();
 305         PrintWriter pw = new PrintWriter(sw);
 306         pw.printf(MAIFormat, mai.getName(), mai.getVersion());
 307         if (verboseFlag.isEnabled()) {
 308             long t = mai.getLastModified();
 309             String lastMod = null;
 310             if (t != 0) {
 311                 lastMod = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(new Date(t));
 312             }
 313             pw.printf(MAIFormatVerbose,
 314                 mai.getPlatform() == null ? "generic" : mai.getPlatform(),
 315                 mai.getArch() == null ? "generic" : mai.getArch(),
 316                 lastMod == null ? "n/a" : lastMod,
 317                 mai.getFileName() == null ? "n/a" : mai.getFileName()
 318                 );
 319         }
 320         return sw.toString();
 321     }
 322 
 323     private static String getMText(Module m) {
 324         return getMDText(m.getModuleDefinition());
 325     }
 326     
 327     private static String getMDText(ModuleDefinition md) {
 328         StringWriter sw = new StringWriter();
 329         PrintWriter pw = new PrintWriter(sw);
 330         pw.printf(MDFormat, md.getName(), md.getVersion());
 331         if (verboseFlag.isEnabled()) {
 332             pw.printf(MDFormatVerbose,
 333                       md.getRepository() == Repository.getBootstrapRepository()
 334                       ? "bootstrap"
 335                       : md.getRepository().getSourceLocation().toString());
 336         }
 337         return sw.toString();
 338     }
 339 
 340     void usageError() {
 341         usageError(msg);
 342     }
 343 
 344     static void usageError(Messenger msg) {
 345         if (usage == null) {
 346             StringBuilder ub = new StringBuilder(
 347                 "Usage: jrepo <command>\nwhere <command> includes:"); // XXX i18n
 348             for (Command c : commands.values()) {
 349                 String u = c.usage();
 350                 if (u != null) {
 351                     ub.append("\n    ").append(c.usage());
 352                 }
 353             }
 354             usage = ub.toString();
 355         }
 356         msg.error(usage);
 357     }
 358 
 359     /**
 360      * Represents a flag given on the command line.  Flags always are 2
 361      * characters long, and start with a '-'.  They can have additional
 362      * associated arguments (cf {@link #RepositoryFlag}).
 363      */
 364     private static class Flag {
 365         private final char name;
 366 
 367         private boolean enabled = false;
 368 
 369         private static final Map<Character, Flag> flags = new HashMap<Character, Flag>();
 370 
 371         Flag(char name) {
 372             this.name = name;
 373             flags.put(new Character(name), this);
 374         }
 375 
 376         void register(Map<Character, Flag> registry) {
 377             registry.put(new Character(name), this);
 378         }
 379 
 380         char getName() {
 381             return name;
 382         }
 383 
 384         /**
 385          * @return the number of arguments consumed by this Flag
 386          * @throws IllegalArgumentException if invalid args are given
 387          */
 388         int set(String[] args, int pos) throws IllegalArgumentException {
 389             enabled = true;
 390             return 1;
 391         }
 392 
 393         void reset() {
 394             enabled = false;
 395         }
 396 
 397         boolean isEnabled() {
 398             return enabled;
 399         }
 400 
 401         static Flag get(char c) {
 402             return flags.get(new Character(c));
 403         }
 404     }
 405 
 406     private static class RepositoryFlag extends Flag {
 407         String location = null;
 408 
 409         RepositoryFlag() {
 410             super('r');
 411         }
 412 
 413         @Override
 414         int set(String[] args, int pos) throws IllegalArgumentException {
 415             int rc = super.set(args, pos);
 416             location = args[pos + 1];
 417             return rc + 1;
 418         }
 419 
 420         String getLocation() {
 421             return location;
 422         }
 423 
 424         @Override
 425         void reset() {
 426             super.reset();
 427             location = null;
 428         }
 429     }
 430 
 431     private static class BindingFlag extends Flag {
 432         String platform;
 433         String arch;
 434 
 435         BindingFlag() {
 436             super('b');
 437         }
 438 
 439         @Override
 440         int set(String[] args, int pos) throws IllegalArgumentException {
 441             int rc = super.set(args, pos);
 442             String[] binding = args[pos + 1].split("-");
 443             if (binding.length != 2) {
 444                 throw new IllegalArgumentException(
 445                     "Must 2 and only 2 elements for platform-arch");
 446             }
 447             platform = binding[0];
 448             arch = binding[1];
 449             return rc + 1;
 450         }
 451 
 452         String getPlatform() {
 453             return platform;
 454         }
 455 
 456         String getArch() {
 457             return arch;
 458         }
 459 
 460         @Override
 461         void reset() {
 462             super.reset();
 463             platform = null;
 464             arch = null;
 465         }
 466     }
 467 
 468     /*
 469      * Command types: An abstract base class, plus one concrete class for
 470      * each Command.
 471      */
 472 
 473     /*
 474      * Represents a Command.
 475      */
 476     private static abstract class Command {
 477         private final String name;
 478 
 479         Command(String name) {
 480             this.name = name;
 481         }
 482 
 483         /** Adds this command to the given registry. */
 484         void register(Map<String, Command> registry) {
 485             registry.put(name, this);
 486         }
 487 


 495         /** Parses the arguments particular to this command. */
 496         abstract boolean parseArgs(String[] args, Messenger msg);
 497 
 498         /**
 499          * Parse the Flags for this command.
 500          * @return number of flags found.
 501          * @throws IllegalArgumentException if an invalid flag is given.
 502          */
 503         int parseFlags(String[] args, Map<Character, Flag> flags) {
 504             int rc = 0;
 505             int i = 0;
 506             while (i < args.length) {
 507                 String s = args[i];
 508                 if (s.length() == 2 && s.charAt(0) == '-') {
 509                     Flag f = flags.get(s.charAt(1));
 510                     if (f != null) {
 511                         int numConsumed = f.set(args, i);
 512                         i += numConsumed;  // Increases at each iteration.
 513                         rc += numConsumed; // Increases only when the arg is a flag.
 514                     } else {
 515                         throw new IllegalArgumentException("unrecognized flag: " // XXX i18n
 516                                                            + args[i]);
 517                     }
 518                 } else {
 519                     i++;
 520                 }
 521             }
 522             return rc;
 523         }
 524 
 525         /** Represents the actual behavior of the command. */
 526         abstract boolean run(Repository repo, Messenger msg);
 527 
 528         /** Returns a usage string describing this command, or null if the
 529          * command is a synonym for another command.
 530          */
 531         abstract String usage();
 532 
 533         @Override
 534         public String toString() { return name; }
 535 
 536         /**
 537          * RepositoryVisitor types walk a parent chain of repositories, invoking
 538          * {@code doit} in each one.  The abstract base class provides the recursion;
 539          * each concrete subclass provides the per-repository behavior.  The
 540          * recursion is such that the bootstrap repository is visited first,
 541          * and the system repository is last.
 542          */
 543         abstract class RepositoryVisitor {
 544             abstract void doit(Repository repo, Messenger msg);
 545 
 546             void preVisit(Messenger msg) { }
 547 
 548             void postVisit(Messenger msg) { }
 549 
 550             final void run(Repository repo, Messenger msg) {
 551                 visit(repo, msg);
 552             }
 553 
 554             private final void visit(Repository repo, Messenger msg) {
 555                 Repository parent = parentFlag.isEnabled() ? repo.getParent() : null;
 556                 if (parent != null) {
 557                     visit(parent, msg);
 558                 }
 559                 preVisit(msg);
 560                 doit(repo, msg);
 561                 postVisit(msg);
 562             }
 563         }
 564     }
 565 
 566 
 567     /** Lists dependencies of a module. */
 568     private class DependenciesCommand extends Command {
 569         @SuppressWarnings("unchecked")
 570         private final Map<Character, Flag> myFlags = (Map<Character, Flag>) commonFlags.clone();
 571 
 572         private String name;
 573         private Version version;
 574 
 575         // For printing module name, version.
 576         private String moduleString;
 577 
 578         DependenciesCommand() {
 579             super("dependencies");
 580             javaseFlag.register(myFlags);
 581             bindingFlag.register(myFlags);
 582         }
 583 
 584         @Override
 585         void reset() {
 586             javaseFlag.reset();
 587             bindingFlag.reset();
 588         }
 589         
 590         @Override
 591         int parseFlags(String[] args, Map<Character, Flag> flags) {
 592             return super.parseFlags(args, myFlags);
 593         }
 594 
 595         boolean parseArgs(String[] args, Messenger msg) {
 596             if (args.length > 0) {
 597                 name = args[0];
 598             }
 599             if (args.length > 1) {
 600                 try {
 601                     version = Version.valueOf(args[1]);
 602                 } catch (IllegalArgumentException ex) {
 603                     return false;
 604                 }
 605             }
 606             if (args.length > 2) {
 607                 return false;
 608             }
 609             
 610             if (DEBUG) debug("name: " + name + " ver: " + version
 611                              + " plat: " + bindingFlag.getPlatform()
 612                              + " arch: " + bindingFlag.getArch());
 613             return true;
 614         }
 615 
 616         boolean run(Repository repo, Messenger msg) {
 617             boolean rc = true;
 618             if (name != null) {
 619                 VersionConstraint vc = (version == null
 620                                         ? VersionConstraint.DEFAULT
 621                                         : version.toVersionConstraint());
 622                 printHeader(name, vc);
 623                 rc = depend(repo, name, vc,
 624                             bindingFlag.getPlatform(), bindingFlag.getArch());
 625                 printTrailer(null);
 626             } else {
 627                 for (ModuleArchiveInfo mai : repo.list()) {
 628                     reset();
 629                     String maiName = mai.getName();
 630                     VersionConstraint vc = mai.getVersion().toVersionConstraint();
 631                     printHeader(maiName, vc);
 632                     rc &= depend(repo, maiName, vc,
 633                                  mai.getPlatform(), mai.getArch());
 634                     printTrailer("\n");
 635                 }
 636             }
 637             return rc;
 638         }
 639 
 640         boolean depend(Repository repo, String name, VersionConstraint constraint,
 641                        String platform, String arch) {
 642             ImportTraverser traverser = new ImportTraverser();
 643             ImportTraverser.Visitor visitor = new ImportVisitor(traverser, msg);
 644             try {
 645                 traverser.traverse(visitor, repo, name, constraint, platform, arch);
 646                 if (traverser.traversedAny()) {
 647                     return true;
 648                 }
 649                 if (verboseFlag.isEnabled()) {
 650                     msg.error("Cannot find module " + name + " in " // XXX i18n
 651                               + getRepositoryText(repo));
 652                 }
 653                 return false;
 654             } catch (ModuleInitializationException ex) {
 655                 msg.error("Cannot instantiate module for " + name // XXX i18n
 656                           + ": " + ex);
 657                 return false;
 658             }
 659         }
 660 
 661         
 662         void printHeader(String name, VersionConstraint vc) {
 663             if (verboseFlag.isEnabled()) {
 664                 msg.println("Dependencies for " // XXXi18n
 665                             + name + "-" + vc + ":");
 666             }
 667         }
 668 
 669         void printTrailer(String s) {
 670             if (verboseFlag.isEnabled()) {
 671                 msg.print(s);
 672             }
 673         }
 674         
 675         // TBD support platform binding.  Ideally, want to allow specifying
 676         // version, or binding, or both.
 677         String usage() {
 678             return "dependencies [-v] [-r repositoryLocation] [-b platform-arch]"// XXX i18n
 679                 + " [moduleName [moduleVersion] ]\n"
 680                 + "        Lists all modules on which identified modules depend.\n"
 681                 + "        If no moduleName is given, lists dependencies of all"
 682                 + " module archives in the repository\n"
 683                 + "        which are instantiable on the current platform.";
 684         }
 685     }
 686             
 687 
 688     private static class ImportVisitor extends ImportTraverser.Visitor {
 689         private final Messenger msg;
 690         
 691         private static final String INDENT = "    ";
 692         private static final int INDENT_LENGTH = INDENT.length();
 693         
 694         private String indent = "";
 695 
 696         ImportVisitor(ImportTraverser traverser, Messenger msg) {
 697             super(traverser);
 698             this.msg = msg;
 699         }
 700 
 701         @Override
 702         protected void init(Module m) {
 703             printModule(m);
 704         }
 705 
 706         @Override
 707         protected boolean preVisit(Module m) {
 708             if (javaseFlag.isEnabled() == false
 709                 && m.getModuleDefinition().getName().startsWith("java.se")) {
 710                 return false;
 711             } else {
 712                 indent += INDENT;
 713                 return true;
 714             }
 715         }
 716 
 717         @Override
 718         protected void visit(Module m) {
 719             printModule(m);
 720         }
 721 
 722         @Override
 723         protected void postVisit(Module m) {
 724             if (javaseFlag.isEnabled() == false
 725                 && m.getModuleDefinition().getName().startsWith("java.se")) {
 726                 // empty
 727             } else {
 728                 indent = indent.substring(INDENT_LENGTH);
 729             }
 730         }
 731 
 732         void printModule(Module m) {
 733             msg.println(indent + getMText(m));
 734         }
 735     }
 736 
 737     /** Installs a JAM into a repository. */
 738     private class InstallCommand extends Command {
 739         private String jamName;
 740 
 741         InstallCommand() {
 742             super("install");
 743         }
 744 
 745         boolean parseArgs(String[] args, Messenger msg) {
 746             boolean rc = false;
 747             if (!parentFlag.isEnabled()
 748                     && repositoryFlag.getLocation() != null
 749                     && args.length == 1) {
 750                 jamName = args[0];
 751                 rc = true;
 752             }
 753             return rc;
 754         }
 755 
 756         boolean run(Repository repo, Messenger msg) {


 774                     jamURL = jamName;
 775                 }
 776                 try {
 777                     ModuleArchiveInfo mai = repo.install(new URL(jamURL).toURI());
 778                     if (verboseFlag.isEnabled()) {
 779                         msg.println("Installed " + jamName + ": " + getMAIText(mai));
 780                     }
 781                     return true;
 782                 } catch (URISyntaxException ex) {
 783                     msg.error("Cannot install " + jamName + ": no such file, or malformed URI");
 784                 } catch (MalformedURLException ex) {
 785                     msg.error("Cannot install " + jamName + ": no such file, or malformed URL");
 786                 } catch (IOException ex) {
 787                     msg.error("Cannot install " + jamName + ": " + ex.getMessage());
 788                 }
 789             }
 790             return false;
 791         }
 792 
 793         String usage() {
 794             return "install [-v] -r repositoryLocation jamFile | jamURL\n" // XXX i18n
 795                 +  "        installs a module into a repository";
 796         }
 797     }
 798 
 799     /** Uninstalls a module from a repository. */
 800     private class UninstallCommand extends Command {
 801         @SuppressWarnings("unchecked")
 802         private final Map<Character, Flag> myFlags = (Map<Character, Flag>) commonFlags.clone();
 803 
 804         private Flag forceFlag = new Flag('f');
 805 
 806         private Flag interactiveFlag = new Flag('i');
 807 
 808         private String moduleName;
 809 
 810         private Version version;
 811 
 812         private String platformBinding;
 813 
 814         UninstallCommand() {
 815             super("uninstall");
 816             forceFlag.register(myFlags);
 817             interactiveFlag.register(myFlags);
 818         }
 819 
 820         @Override
 821         void reset() {
 822             version = null;
 823             platformBinding = null;
 824             forceFlag.reset();
 825             interactiveFlag.reset();
 826         }
 827 
 828         @Override
 829         int parseFlags(String[] args, Map<Character, Flag> flags) {
 830             return super.parseFlags(args, myFlags);
 831         }
 832 
 833         boolean parseArgs(String[] args, Messenger msg) {
 834             if (repositoryFlag.getLocation() == null) {
 835                 return false;
 836             }
 837 
 838             if (forceFlag.isEnabled() && interactiveFlag.isEnabled()) {
 839                 // Doesn't make sense for these to both be enabled
 840                 msg.error("uninstall cannot simultaneously use both -i and -f: choose one or the other"); // XXX i81n
 841                 return false;
 842             }
 843 
 844             if (args.length >= 1) {
 845                 moduleName = args[0];
 846                 if (args.length >= 2 && args.length < 4) {
 847                     try {
 848                         version = Version.valueOf(args[1]);


 895                         }
 896                     }
 897                 } else if (forceFlag.isEnabled()) {
 898                     if (DEBUG) debug("forced uninstall of multiple matches");
 899                     rc = true;
 900                     for (ModuleArchiveInfo mai : found) {
 901                         if (!uninstall(repo, mai, msg)) {
 902                             if (DEBUG) debug("uninstall failed for " + getMAIText(mai) + " in " + repo.getSourceLocation());
 903                             rc = false;
 904                             break;
 905                         }
 906                     }
 907                 } else if (interactiveFlag.isEnabled()) {
 908                     rc = uninstallInteractive(repo, found, msg);
 909                 }
 910             }
 911             return rc;
 912         }
 913 
 914         String usage() {
 915             return "uninstall [-v] [-f | -i] -r repositoryLocation moduleName"// XXX i18n
 916                 + " [moduleVersion] [modulePlatformBinding]\n"
 917                 +  "        removes a module from a repository, along with associated files.";
 918         }
 919 
 920         /** Uninstall the ModuleArchiveInfo from the Repository. */
 921         private boolean uninstall(Repository repo, ModuleArchiveInfo mai, Messenger msg) {
 922             boolean rc = false;
 923             if (DEBUG) debug("Uninstalling " + getMAIText(mai));
 924             try {
 925                 rc = repo.uninstall(mai);
 926                 if (verboseFlag.isEnabled()) {
 927                     if (rc) {
 928                         msg.println("Uninstalled " + getMAIText(mai)); // XXX i18n
 929                     } else {
 930                         msg.error("Failed to uninstall " + getMAIText(mai)); // XXX i18n
 931                     }
 932                 }
 933             } catch (Exception ex) {
 934                 msg.error("Exception while uninstalling " + getInfo()
 935                           + ": " + ex.getMessage());
 936             }
 937             return rc;
 938         }
 939 
 940         /** Uninstall one of the ModuleArchiveInfos from the Repository. */
 941         private boolean uninstallInteractive(
 942                 Repository repo,
 943                 List<ModuleArchiveInfo> found,
 944                 Messenger msg) {
 945             boolean rc = false;
 946 
 947             String spaces = "          ";
 948             String fmt = "%" + found.size() + "d %s\n";
 949 
 950             StringWriter sw = new StringWriter();


1018             if (version != null) {
1019                 s += " with version " + version;
1020             }
1021             if (platformBinding != null) {
1022                 s += " and platform-binding of " + platformBinding;
1023             }
1024             return s;
1025         }
1026     }
1027 
1028     /** Prints information about modules found in repositories. */
1029     private class ListCommand extends Command {
1030         @SuppressWarnings("unchecked")
1031         private final Map<Character, Flag> myFlags = (Map<Character, Flag>) commonFlags.clone();
1032 
1033         ListCommand() {
1034             super("list");
1035             parentFlag.register(myFlags);
1036         }
1037 
1038         @Override
1039         void reset() {
1040             parentFlag.reset();
1041         }
1042 
1043         @Override
1044         int parseFlags(String[] args, Map<Character, Flag> flags) {
1045             return super.parseFlags(args, myFlags);
1046         }
1047 
1048         boolean parseArgs(String[] args, Messenger msg){
1049             boolean rc = true;
1050             if (args.length == 0) {
1051                 moduleName = null;
1052             } else if (args.length == 1) {
1053                 moduleName = args[0];
1054             } else {
1055                 rc = false;
1056             }
1057             return rc;
1058         }
1059 
1060         boolean run(Repository repo, Messenger msg) {
1061             ListRepositoryVisitor visitor = new ListRepositoryVisitor();
1062             visitor.run(repo, msg);
1063             boolean found = visitor.wasFound();
1064             if (verboseFlag.isEnabled() && !found) {
1065                 if (moduleName != null) {
1066                     msg.error("Could not find module name starting with '"// 
1067                                                                           // XXX i18n
1068                               + moduleName + "'");
1069                 } else {
1070                     msg.error("Could not find any modules"); // XXX i18n
1071                 }
1072             }
1073             return found;
1074         }
1075 
1076         String usage() {
1077             return "list [-v] [-p] [-r repositoryLocation] moduleName\n"// 
1078                                                                         // XXX i18n
1079                 +  "        lists the modules in the repository";
1080         }
1081 
1082         class ListRepositoryVisitor extends RepositoryVisitor {
1083             private boolean found = false;
1084 
1085             boolean wasFound() { return found; }
1086 
1087             void doit(Repository repo, Messenger msg) {
1088                 boolean printedHeader = false;
1089                 List<ModuleArchiveInfo> maiList = repo.list();
1090                 if (maiList.size() == 0 && verboseFlag.isEnabled()) {
1091                     msg.println(getRepositoryText(repo));
1092                     msg.println("   empty");
1093                 } else {
1094                     for (ModuleArchiveInfo mai : repo.list()) {
1095                         if (moduleName == null || mai.getName().startsWith(moduleName)) {
1096                             if (!printedHeader) {
1097                                 msg.println(getRepositoryText(repo));
1098                                 msg.println(verboseFlag.isEnabled() ? maiHeadingVerbose : maiHeading);
1099                                 printedHeader = true;
1100                             }
1101                             msg.println(getMAIText(mai));
1102                             found = true;
1103                         }
1104                     }
1105                 }
1106             }
1107         }
1108     }
1109 }