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.URL;
  38 import java.module.*;

  39 import java.security.AccessController;

  40 import java.text.DateFormat;
  41 import java.util.Arrays;
  42 import java.util.ArrayList;

  43 import java.util.Date;
  44 import java.util.HashMap;

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






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






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




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

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


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

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


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
















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

 365             int rc = super.set(args, pos);
 366             location = args[pos + 1];
 367             return rc + 1;
 368         }
 369 
 370         String getLocation() {
 371             return location;
 372         }
 373 

 374         void reset() {
 375             super.reset();
 376             location = null;
 377         }
 378     }
 379 





































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


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

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

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






































































































































































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


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

 557         void reset() {
 558             version = null;
 559             platformBinding = null;
 560             forceFlag.reset();
 561             interactiveFlag.reset();
 562         }
 563 

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


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

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


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

 772         void reset() {
 773             parentFlag.reset();
 774         }
 775 

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


 799                 } else {
 800                     msg.error("Could not find any modules");
 801                 }
 802             }
 803             return found;
 804         }
 805 
 806         String usage() {
 807             return "list [-v] [-p] [-r repositoryLocation] moduleName\n"

 808                 +  "        lists the modules in the repository";
 809         }
 810 
 811         class ListRepositoryVisitor extends RepositoryVisitor {
 812             private boolean found = false;
 813 
 814             boolean wasFound() { return found; }
 815 
 816             void doit(Repository repo, Messenger msg) {
 817                 boolean printedHeader = false;





 818                 for (ModuleArchiveInfo mai : repo.list()) {
 819                     if (moduleName == null || mai.getName().startsWith(moduleName)) {
 820                         if (!printedHeader) {
 821                             msg.println(getRepositoryText(repo));
 822                             msg.println(verboseFlag.isEnabled() ? maiHeadingVerbose : maiHeading);
 823                             printedHeader = true;
 824                         }
 825                         msg.println(getMAIText(mai));
 826                         found = true;
 827                     }
 828                 }

 829             }
 830         }
 831     }
 832 }


  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.URL;
  39 import java.module.*;
  40 import java.module.annotation.ImportPolicyClass;
  41 import java.security.AccessController;
  42 import java.security.PrivilegedAction;
  43 import java.text.DateFormat;
  44 import java.util.Arrays;
  45 import java.util.ArrayList;
  46 import java.util.Collections;
  47 import java.util.Date;
  48 import java.util.HashMap;
  49 import java.util.HashSet;
  50 import java.util.LinkedHashMap;
  51 import java.util.List;
  52 import java.util.Map;
  53 import java.util.Set;
  54 import sun.module.repository.RepositoryConfig;
  55 import sun.security.action.GetPropertyAction;
  56 import sun.tools.jar.CommandLine;
  57 
  58 /**
  59  * Java Modules Repository Management Tool
  60  * @since 1.7
  61  */
  62 public class JRepo {
  63     public static final boolean DEBUG;
  64 
  65     static {
  66         DEBUG = (AccessController.doPrivileged(new GetPropertyAction("sun.module.tools.debug")) != null);
  67     }
  68 
  69     private static void debug(String s) {
  70         System.err.println(s);
  71     }
  72 
  73     /** For printing user output. */
  74     private final Messenger msg;
  75 
  76     /** Optional command line argument used by {@code list()}. */
  77     private String moduleName;
  78 
  79     /** Map from this tool's command names to the commands themselves. */
  80     private static final Map<String, Command> commands = new LinkedHashMap<String, Command>();
  81 
  82     /** Usage message created from each command's usage. */
  83     private static String usage = null;
  84 
  85     /** Format for ModuleArchiveInfo name & version. */
  86     private static final String MAIFormat = "%-20s %-20s";
  87 
  88     /** Format for additional/verbose ModuleArchiveInfo details. */
  89     private static final String MAIFormatVerbose = " %-9s %-7s %-17s %s";
  90 
  91     /** Format for ModuleDefinition name & version. */
  92     private static final String MDFormat = "%s-%s";
  93 
  94     /** Format for additional/verbose ModuleDefinition details. */
  95     private static final String MDFormatVerbose = " %s";
  96 
  97     /** String containing column headings for name & version. */
  98     private static final String maiHeading;
  99 
 100     /** String containing column headings for additional/verbose module information. */
 101     private static final String maiHeadingVerbose;
 102 
 103     /** Indicates that parent repositories should be used by a command. */
 104     private static final Flag parentFlag = new Flag('p');
 105 
 106     /** Indicates that command output should be verbose. */
 107     private static final Flag verboseFlag = new Flag('v');
 108 
 109     /** Location of repository; if not given uses system repository. */
 110     private static final RepositoryFlag repositoryFlag = new RepositoryFlag();
 111 
 112     /** Indicates dependencies command should display info on core modules. */
 113     private static final Flag javaseFlag = new Flag('j');
 114 
 115     /** Provides way to specify platform binding fo dependencies command. */
 116     private static final BindingFlag bindingFlag = new BindingFlag();
 117 
 118     /** Contains the flags that are common to all commands. */
 119     private static final HashMap<Character, Flag> commonFlags = new HashMap<Character, Flag>();
 120 
 121     static {
 122         repositoryFlag.register(commonFlags);
 123         verboseFlag.register(commonFlags);
 124 
 125         StringWriter sw = new StringWriter();
 126         PrintWriter pw = new PrintWriter(sw);
 127         pw.printf(MAIFormat, "Name", "Version"); // XXX i18n
 128         maiHeading = sw.toString();
 129 
 130         sw = new StringWriter();
 131         pw = new PrintWriter(sw);
 132         pw.printf(MAIFormat, "Name", "Version"); // XXX i18n
 133         pw.printf(MAIFormatVerbose, "Platform", "Arch", "Modified", "Filename"); // XXX i18n
 134         maiHeadingVerbose = sw.toString();
 135     }
 136 
 137     public JRepo(OutputStream out, OutputStream err, BufferedReader reader) {
 138         this(new PrintStream(out), new PrintStream(err), reader);
 139     }
 140 
 141     public JRepo(PrintStream out, PrintStream err, BufferedReader reader) {
 142         msg = new Messenger("jrepo", out, err, reader);
 143         synchronized(commands) {
 144             if (commands.isEmpty()) {
 145                 new ListCommand().register(commands);
 146                 new InstallCommand().register(commands);
 147                 new UninstallCommand().register(commands);
 148                 new DependenciesCommand().register(commands);
 149             }
 150         }
 151         reset();
 152     }
 153 
 154     public static void main(String[] args) {
 155         JRepo jrepo = new JRepo(
 156             System.out, System.err,
 157             new BufferedReader(new InputStreamReader(System.in)));
 158         System.exit(jrepo.run(args) ? 0 : 1);
 159     }
 160 
 161     public synchronized boolean run(String[] args) {
 162         reset();
 163 
 164         if (DEBUG) { for (String s : args) debug("arg: '" + s + "'"); }
 165         Command cmd = parseArgs(args);
 166         if (DEBUG && cmd != null) debug("running " + cmd);
 167         if (cmd == null) {
 168             return false;


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


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


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


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


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


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