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 }
|