13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 package sun.module.tools;
27
28 import java.io.BufferedReader;
29 import java.io.File;
30 import java.io.FileNotFoundException;
31 import java.io.IOException;
32 import java.io.InputStreamReader;
33 import java.io.PrintStream;
34 import java.io.PrintWriter;
35 import java.io.StringWriter;
36 import java.net.MalformedURLException;
37 import java.net.URI;
38 import java.net.URL;
39 import java.net.URISyntaxException;
40 import java.module.*;
41 import java.security.AccessController;
42 import java.text.DateFormat;
43 import java.util.Arrays;
44 import java.util.ArrayList;
45 import java.util.Date;
46 import java.util.HashMap;
47 import java.util.LinkedHashMap;
48 import java.util.List;
49 import java.util.Map;
50 import sun.module.JamUtils;
51 import sun.module.repository.RepositoryConfig;
52 import sun.security.action.GetPropertyAction;
53 import sun.tools.jar.CommandLine;
54
55 /**
56 * Java Modules Repository Management Tool
57 * @since 1.7
58 */
59 public class JRepo {
60 public static final boolean DEBUG;
61
62 static {
63 DEBUG = (AccessController.doPrivileged(new GetPropertyAction("sun.module.tools.debug")) != null);
64 }
65
66 private static void debug(String s) {
67 System.err.println(s);
68 }
69
70 /** For printing user output. */
71 private final Messenger msg;
72
73 /** Optional command line argument used by {@code list()}. */
74 private String moduleName;
75
76 /** Map from this tool's command names to the commands themselves. */
77 private static final Map<String, Command> commands = new LinkedHashMap<String, Command>();
78
79 /** Usage message created from each command's usage. */
80 private static String usage = null;
81
82 /** Format for module name & version. */
83 private static final String MAIFormat = "%-20s %-20s";
84
85 /** Format for additional/verbose module information. */
86 private static final String MAIFormatVerbose = " %-9s %-7s %-17s %s";
87
88 /** String containing column headings for name & version. */
89 private static final String maiHeading;
90
91 /** String containing column headings for additional/verbose module information. */
92 private static final String maiHeadingVerbose;
93
94 /** Indicates that parent repositories should be used by a command. */
95 private static final Flag parentFlag = new Flag('p');
96
97 /** Indicates that command output should be verbose. */
98 private static final Flag verboseFlag = new Flag('v');
99
100 /** Location of repository; if not given uses system repository. */
101 private static final RepositoryFlag repositoryFlag = new RepositoryFlag();
102
103 /** Contains the flags that are common to all commands. */
104 private static final HashMap<Character, Flag> commonFlags = new HashMap<Character, Flag>();
105
106 static {
107 repositoryFlag.register(commonFlags);
108 verboseFlag.register(commonFlags);
109
110 StringWriter sw = new StringWriter();
111 PrintWriter pw = new PrintWriter(sw);
112 pw.printf(MAIFormat, "Name", "Version"); // XXX i18n
113 maiHeading = sw.toString();
114
115 sw = new StringWriter();
116 pw = new PrintWriter(sw);
117 pw.printf(MAIFormat, "Name", "Version"); // XXX i18n
118 pw.printf(MAIFormatVerbose, "Platform", "Arch", "Modified", "Filename"); // XXX i18n
119 maiHeadingVerbose = sw.toString();
120 }
121
122 public JRepo(PrintStream out, PrintStream err, BufferedReader reader) {
123 msg = new Messenger("jrepo", out, err, reader);
124 synchronized(commands) {
125 if (commands.isEmpty()) {
126 new ListCommand().register(commands);
127 new InstallCommand().register(commands);
128 new UninstallCommand().register(commands);
129 }
130 }
131 reset();
132 }
133
134 public static void main(String[] args) {
135 JRepo jrepo = new JRepo(
136 System.out, System.err,
137 new BufferedReader(new InputStreamReader(System.in)));
138 System.exit(jrepo.run(args) ? 0 : 1);
139 }
140
141 public synchronized boolean run(String[] args) {
142 reset();
143
144 if (DEBUG) { for (String s : args) debug("arg: '" + s + "'"); }
145 Command cmd = parseArgs(args);
146 if (DEBUG && cmd != null) debug("running " + cmd);
147 if (cmd == null) {
148 return false;
172 Repository rc = null;
173
174 String repositoryLocation = repositoryFlag.getLocation();
175
176 if (repositoryLocation == null) {
177 rc = Repository.getSystemRepository();
178 } else {
179 // If repositoryLocation is a URL use URLRepository else LocalRepository.
180 try {
181 URL u = new URL(repositoryLocation);
182 rc = Modules.newURLRepository(
183 "jrepo", u, null, RepositoryConfig.getSystemRepository());
184 } catch (MalformedURLException ex) {
185 File f = new File(repositoryLocation);
186 if (f.exists() && f.canRead()) {
187 rc = Modules.newLocalRepository(
188 "jrepo",
189 f.getCanonicalFile(), null,
190 RepositoryConfig.getSystemRepository());
191 } else {
192 throw new IOException("Cannot access repository at " + repositoryLocation);
193 }
194 }
195 }
196 return rc;
197 }
198
199 /** Reset instance state to default values. */
200 private void reset() {
201 moduleName = null;
202 parentFlag.reset();
203 repositoryFlag.reset();
204 verboseFlag.reset();
205 for (Command cmd : commands.values()) {
206 cmd.reset();
207 }
208 }
209
210
211 /** Parse command line arguments.*/
212 private Command parseArgs(String[] args) {
245 try {
246 if (cmd.parseArgs(args, msg)) {
247 return cmd;
248 } else {
249 usageError();
250 return null;
251 }
252 } catch (ArrayIndexOutOfBoundsException e) {
253 usageError();
254 }
255 } catch (IllegalArgumentException ex) {
256 msg.error(ex.getMessage());
257 usageError();
258 return null;
259 }
260 return cmd;
261 }
262
263
264 /** Returns a user-grokkable description of the repository. */
265 private String getRepositoryText(Repository repo) {
266 String rc;
267 URI u = repo.getSourceLocation();
268 if (u == null) {
269 rc = "Bootstrap repository";
270 } else {
271 try {
272 rc = "Repository " + u.toURL().toExternalForm();
273 } catch (MalformedURLException ex) {
274 rc = "Repository unknown";
275 }
276 }
277 return rc;
278 }
279
280 private String getMAIText(ModuleArchiveInfo mai) {
281 StringWriter sw = new StringWriter();
282 PrintWriter pw = new PrintWriter(sw);
283 pw.printf(MAIFormat, mai.getName(), mai.getVersion());
284 if (verboseFlag.isEnabled()) {
285 long t = mai.getLastModified();
286 String lastMod = null;
287 if (t != 0) {
288 lastMod = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(new Date(t));
289 }
290 pw.printf(MAIFormatVerbose,
291 mai.getPlatform() == null ? "generic" : mai.getPlatform(),
292 mai.getArch() == null ? "generic" : mai.getArch(),
293 lastMod == null ? "n/a" : lastMod,
294 mai.getFileName() == null ? "n/a" : mai.getFileName()
295 );
296 }
297 return sw.toString();
298 }
299
300 void usageError() {
301 usageError(msg);
302 }
303
304 static void usageError(Messenger msg) {
305 if (usage == null) {
306 StringBuilder ub = new StringBuilder(
307 "Usage: jrepo <command>\nwhere <command> includes:");
308 for (Command c : commands.values()) {
309 String u = c.usage();
310 if (u != null) {
311 ub.append("\n ").append(c.usage());
312 }
313 }
314 usage = ub.toString();
315 }
316 msg.error(usage);
317 }
318
319 /**
320 * Represents a flag given on the command line. Flags always are 2
321 * characters long, and start with a '-'. They can have additional
322 * associated arguments (cf RepositoryFlag).
323 */
324 private static class Flag {
325 private final char name;
326
327 private boolean enabled = false;
328
329 private static final Map<Character, Flag> flags = new HashMap<Character, Flag>();
330
331 Flag(char name) {
332 this.name = name;
333 flags.put(new Character(name), this);
334 }
335
336 void register(Map<Character, Flag> registry) {
337 registry.put(new Character(name), this);
338 }
339
340 char getName() {
341 return name;
342 }
343
344 /** @return the number of arguments consumed by this Flag. */
345 int set(String[] args, int pos) {
346 enabled = true;
347 return 1;
348 }
349
350 void reset() {
351 enabled = false;
352 }
353
354 boolean isEnabled() {
355 return enabled;
356 }
357
358 static Flag get(char c) {
359 return flags.get(new Character(c));
360 }
361 }
362
363 private static class RepositoryFlag extends Flag {
364 String location = null;
365
366 RepositoryFlag() {
367 super('r');
368 }
369
370 int set(String[] args, int pos) {
371 int rc = super.set(args, pos);
372 location = args[pos + 1];
373 return rc + 1;
374 }
375
376 String getLocation() {
377 return location;
378 }
379
380 void reset() {
381 super.reset();
382 location = null;
383 }
384 }
385
386 /*
387 * Command types: An abstract base class, plus one concrete class for
388 * each Command.
389 */
390
391 /*
392 * Represents a Command.
393 */
394 private static abstract class Command {
395 private final String name;
396
397 Command(String name) {
398 this.name = name;
399 }
400
401 /** Adds this command to the given registry. */
402 void register(Map<String, Command> registry) {
403 registry.put(name, this);
404 }
405
413 /** Parses the arguments particular to this command. */
414 abstract boolean parseArgs(String[] args, Messenger msg);
415
416 /**
417 * Parse the Flags for this command.
418 * @return number of flags found.
419 * @throws IllegalArgumentException if an invalid flag is given.
420 */
421 int parseFlags(String[] args, Map<Character, Flag> flags) {
422 int rc = 0;
423 int i = 0;
424 while (i < args.length) {
425 String s = args[i];
426 if (s.length() == 2 && s.charAt(0) == '-') {
427 Flag f = flags.get(s.charAt(1));
428 if (f != null) {
429 int numConsumed = f.set(args, i);
430 i += numConsumed; // Increases at each iteration.
431 rc += numConsumed; // Increases only when the arg is a flag.
432 } else {
433 throw new IllegalArgumentException("unrecognized flag: " + args[i]);
434 }
435 } else {
436 i++;
437 }
438 }
439 return rc;
440 }
441
442 /** Represents the actual behavior of the command. */
443 abstract boolean run(Repository repo, Messenger msg);
444
445 /** Returns a usage string describing this command, or null if the
446 * command is a synonym for another command.
447 */
448 abstract String usage();
449
450 public String toString() { return name; }
451
452 /**
453 * RepositoryVisitor types walk a parent chain of repositories, invoking
454 * {@code doit} in each one. The abstract base class provides the recursion;
455 * each concrete subclass provides the per-repository behavior. The
456 * recursion is such that the bootstrap repository is visited first,
457 * and the system repository is last.
458 */
459 abstract class RepositoryVisitor {
460 abstract void doit(Repository repo, Messenger msg);
461
462 void doBefore(Messenger msg) { }
463
464 void doAfter(Messenger msg) { }
465
466 final void run(Repository repo, Messenger msg) {
467 visit(repo, msg);
468 }
469
470 private final void visit(Repository repo, Messenger msg) {
471 Repository parent = parentFlag.isEnabled() ? repo.getParent() : null;
472 if (parent != null) {
473 visit(parent, msg);
474 }
475 doBefore(msg);
476 doit(repo, msg);
477 doAfter(msg);
478 }
479 }
480 }
481
482 /** Installs a JAM into a repository. */
483 private class InstallCommand extends Command {
484 private String jamName;
485
486 InstallCommand() {
487 super("install");
488 }
489
490 boolean parseArgs(String[] args, Messenger msg) {
491 boolean rc = false;
492 if (!parentFlag.isEnabled()
493 && repositoryFlag.getLocation() != null
494 && args.length == 1) {
495 jamName = args[0];
496 rc = true;
497 }
498 return rc;
499 }
500
501 boolean run(Repository repo, Messenger msg) {
519 jamURL = jamName;
520 }
521 try {
522 ModuleArchiveInfo mai = repo.install(new URL(jamURL).toURI());
523 if (verboseFlag.isEnabled()) {
524 msg.println("Installed " + jamName + ": " + getMAIText(mai));
525 }
526 return true;
527 } catch (URISyntaxException ex) {
528 msg.error("Cannot install " + jamName + ": no such file, or malformed URI");
529 } catch (MalformedURLException ex) {
530 msg.error("Cannot install " + jamName + ": no such file, or malformed URL");
531 } catch (IOException ex) {
532 msg.error("Cannot install " + jamName + ": " + ex.getMessage());
533 }
534 }
535 return false;
536 }
537
538 String usage() {
539 return "install [-v] -r repositoryLocation jamFile | jamURL\n"
540 + " installs a module into a repository";
541 }
542 }
543
544 /** Uninstalls a module from a repository. */
545 private class UninstallCommand extends Command {
546 @SuppressWarnings("unchecked")
547 private final Map<Character, Flag> myFlags = (Map<Character, Flag>) commonFlags.clone();
548
549 private Flag forceFlag = new Flag('f');
550
551 private Flag interactiveFlag = new Flag('i');
552
553 private String moduleName;
554
555 private Version version;
556
557 private String platformBinding;
558
559 UninstallCommand() {
560 super("uninstall");
561 forceFlag.register(myFlags);
562 interactiveFlag.register(myFlags);
563 }
564
565 void reset() {
566 version = null;
567 platformBinding = null;
568 forceFlag.reset();
569 interactiveFlag.reset();
570 }
571
572 int parseFlags(String[] args, Map<Character, Flag> flags) {
573 return super.parseFlags(args, myFlags);
574 }
575
576 boolean parseArgs(String[] args, Messenger msg) {
577 if (repositoryFlag.getLocation() == null) {
578 return false;
579 }
580
581 if (forceFlag.isEnabled() && interactiveFlag.isEnabled()) {
582 // Doesn't make sense for these to both be enabled
583 msg.error("uninstall cannot simultaneously use both -i and -f: choose one or the other"); // XXX i81n
584 return false;
585 }
586
587 if (args.length >= 1) {
588 moduleName = args[0];
589 if (args.length >= 2 && args.length < 4) {
590 try {
591 version = Version.valueOf(args[1]);
638 }
639 }
640 } else if (forceFlag.isEnabled()) {
641 if (DEBUG) debug("forced uninstall of multiple matches");
642 rc = true;
643 for (ModuleArchiveInfo mai : found) {
644 if (!uninstall(repo, mai, msg)) {
645 if (DEBUG) debug("uninstall failed for " + getMAIText(mai) + " in " + repo.getSourceLocation());
646 rc = false;
647 break;
648 }
649 }
650 } else if (interactiveFlag.isEnabled()) {
651 rc = uninstallInteractive(repo, found, msg);
652 }
653 }
654 return rc;
655 }
656
657 String usage() {
658 return "uninstall [-v] [-f | -i] -r repositoryLocation moduleName [moduleVersion] [modulePlatformBinding]\n"
659 + " removes a module from a repository, along with associated files.";
660 }
661
662 /** Uninstall the ModuleArchiveInfo from the Repository. */
663 private boolean uninstall(Repository repo, ModuleArchiveInfo mai, Messenger msg) {
664 boolean rc = false;
665 if (DEBUG) debug("Uninstalling " + getMAIText(mai));
666 try {
667 rc = repo.uninstall(mai);
668 if (verboseFlag.isEnabled()) {
669 if (rc) {
670 msg.println("Uninstalled " + getMAIText(mai));
671 } else {
672 msg.error("Failed to uninstall " + getMAIText(mai));
673 }
674 }
675 } catch (Exception ex) {
676 msg.error("Exception while uninstalling " + getInfo()
677 + ": " + ex.getMessage());
678 }
679 return rc;
680 }
681
682 /** Uninstall one of the ModuleArchiveInfos from the Repository. */
683 private boolean uninstallInteractive(
684 Repository repo,
685 List<ModuleArchiveInfo> found,
686 Messenger msg) {
687 boolean rc = false;
688
689 String spaces = " ";
690 String fmt = "%" + found.size() + "d %s\n";
691
692 StringWriter sw = new StringWriter();
760 if (version != null) {
761 s += " with version " + version;
762 }
763 if (platformBinding != null) {
764 s += " and platform-binding of " + platformBinding;
765 }
766 return s;
767 }
768 }
769
770 /** Prints information about modules found in repositories. */
771 private class ListCommand extends Command {
772 @SuppressWarnings("unchecked")
773 private final Map<Character, Flag> myFlags = (Map<Character, Flag>) commonFlags.clone();
774
775 ListCommand() {
776 super("list");
777 parentFlag.register(myFlags);
778 }
779
780 void reset() {
781 parentFlag.reset();
782 }
783
784 int parseFlags(String[] args, Map<Character, Flag> flags) {
785 return super.parseFlags(args, myFlags);
786 }
787
788 boolean parseArgs(String[] args, Messenger msg){
789 boolean rc = true;
790 if (args.length == 0) {
791 moduleName = null;
792 } else if (args.length == 1) {
793 moduleName = args[0];
794 } else {
795 rc = false;
796 }
797 return rc;
798 }
799
800 boolean run(Repository repo, Messenger msg) {
801 ListRepositoryVisitor visitor = new ListRepositoryVisitor();
802 visitor.run(repo, msg);
803 boolean found = visitor.wasFound();
804 if (verboseFlag.isEnabled() && !found) {
805 if (moduleName != null) {
806 msg.error("Could not find module name starting with '" + moduleName + "'");
807 } else {
808 msg.error("Could not find any modules");
809 }
810 }
811 return found;
812 }
813
814 String usage() {
815 return "list [-v] [-p] [-r repositoryLocation] moduleName\n"
816 + " lists the modules in the repository";
817 }
818
819 class ListRepositoryVisitor extends RepositoryVisitor {
820 private boolean found = false;
821
822 boolean wasFound() { return found; }
823
824 void doit(Repository repo, Messenger msg) {
825 boolean printedHeader = false;
826 for (ModuleArchiveInfo mai : repo.list()) {
827 if (moduleName == null || mai.getName().startsWith(moduleName)) {
828 if (!printedHeader) {
829 msg.println(getRepositoryText(repo));
830 msg.println(verboseFlag.isEnabled() ? maiHeadingVerbose : maiHeading);
831 printedHeader = true;
832 }
833 msg.println(getMAIText(mai));
834 found = true;
835 }
836 }
837 }
838 }
839 }
840 }
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 package sun.module.tools;
27
28 import java.io.BufferedReader;
29 import java.io.File;
30 import java.io.FileNotFoundException;
31 import java.io.IOException;
32 import java.io.InputStreamReader;
33 import java.io.OutputStream;
34 import java.io.PrintStream;
35 import java.io.PrintWriter;
36 import java.io.StringWriter;
37 import java.net.MalformedURLException;
38 import java.net.URI;
39 import java.net.URL;
40 import java.net.URISyntaxException;
41 import java.module.*;
42 import java.module.annotation.ImportPolicyClass;
43 import java.security.AccessController;
44 import java.security.PrivilegedAction;
45 import java.text.DateFormat;
46 import java.util.Arrays;
47 import java.util.ArrayList;
48 import java.util.Collections;
49 import java.util.Date;
50 import java.util.HashMap;
51 import java.util.HashSet;
52 import java.util.LinkedHashMap;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Set;
56 import sun.module.repository.RepositoryConfig;
57 import sun.security.action.GetPropertyAction;
58 import sun.tools.jar.CommandLine;
59
60 /**
61 * Java Modules Repository Management Tool
62 * @since 1.7
63 */
64 public class JRepo {
65 public static final boolean DEBUG;
66
67 static {
68 DEBUG = (AccessController.doPrivileged(new GetPropertyAction("sun.module.tools.debug")) != null);
69 }
70
71 private static void debug(String s) {
72 System.err.println(s);
73 }
74
75 /** For printing user output. */
76 private final Messenger msg;
77
78 /** Optional command line argument used by {@code list()}. */
79 private String moduleName;
80
81 /** Map from this tool's command names to the commands themselves. */
82 private static final Map<String, Command> commands = new LinkedHashMap<String, Command>();
83
84 /** Usage message created from each command's usage. */
85 private static String usage = null;
86
87 /** Format for ModuleArchiveInfo name & version. */
88 private static final String MAIFormat = "%-20s %-20s";
89
90 /** Format for additional/verbose ModuleArchiveInfo details. */
91 private static final String MAIFormatVerbose = " %-9s %-7s %-17s %s";
92
93 /** Format for ModuleDefinition name & version. */
94 private static final String MDFormat = "%s-%s";
95
96 /** Format for additional/verbose ModuleDefinition details. */
97 private static final String MDFormatVerbose = " %s";
98
99 /** String containing column headings for name & version. */
100 private static final String maiHeading;
101
102 /** String containing column headings for additional/verbose module information. */
103 private static final String maiHeadingVerbose;
104
105 /** Indicates that parent repositories should be used by a command. */
106 private static final Flag parentFlag = new Flag('p');
107
108 /** Indicates that command output should be verbose. */
109 private static final Flag verboseFlag = new Flag('v');
110
111 /** Location of repository; if not given uses system repository. */
112 private static final RepositoryFlag repositoryFlag = new RepositoryFlag();
113
114 /** Indicates dependencies command should display info on core modules. */
115 private static final Flag javaseFlag = new Flag('j');
116
117 /** Provides way to specify platform binding fo dependencies command. */
118 private static final BindingFlag bindingFlag = new BindingFlag();
119
120 /** Contains the flags that are common to all commands. */
121 private static final HashMap<Character, Flag> commonFlags = new HashMap<Character, Flag>();
122
123 static {
124 repositoryFlag.register(commonFlags);
125 verboseFlag.register(commonFlags);
126
127 StringWriter sw = new StringWriter();
128 PrintWriter pw = new PrintWriter(sw);
129 pw.printf(MAIFormat, "Name", "Version"); // XXX i18n
130 maiHeading = sw.toString();
131
132 sw = new StringWriter();
133 pw = new PrintWriter(sw);
134 pw.printf(MAIFormat, "Name", "Version"); // XXX i18n
135 pw.printf(MAIFormatVerbose, "Platform", "Arch", "Modified", "Filename"); // XXX i18n
136 maiHeadingVerbose = sw.toString();
137 }
138
139 public JRepo(OutputStream out, OutputStream err, BufferedReader reader) {
140 this(new PrintStream(out), new PrintStream(err), reader);
141 }
142
143 public JRepo(PrintStream out, PrintStream err, BufferedReader reader) {
144 msg = new Messenger("jrepo", out, err, reader);
145 synchronized(commands) {
146 if (commands.isEmpty()) {
147 new ListCommand().register(commands);
148 new InstallCommand().register(commands);
149 new UninstallCommand().register(commands);
150 new DependenciesCommand().register(commands);
151 }
152 }
153 reset();
154 }
155
156 public static void main(String[] args) {
157 JRepo jrepo = new JRepo(
158 System.out, System.err,
159 new BufferedReader(new InputStreamReader(System.in)));
160 System.exit(jrepo.run(args) ? 0 : 1);
161 }
162
163 public synchronized boolean run(String[] args) {
164 reset();
165
166 if (DEBUG) { for (String s : args) debug("arg: '" + s + "'"); }
167 Command cmd = parseArgs(args);
168 if (DEBUG && cmd != null) debug("running " + cmd);
169 if (cmd == null) {
170 return false;
194 Repository rc = null;
195
196 String repositoryLocation = repositoryFlag.getLocation();
197
198 if (repositoryLocation == null) {
199 rc = Repository.getSystemRepository();
200 } else {
201 // If repositoryLocation is a URL use URLRepository else LocalRepository.
202 try {
203 URL u = new URL(repositoryLocation);
204 rc = Modules.newURLRepository(
205 "jrepo", u, null, RepositoryConfig.getSystemRepository());
206 } catch (MalformedURLException ex) {
207 File f = new File(repositoryLocation);
208 if (f.exists() && f.canRead()) {
209 rc = Modules.newLocalRepository(
210 "jrepo",
211 f.getCanonicalFile(), null,
212 RepositoryConfig.getSystemRepository());
213 } else {
214 throw new IOException("Cannot access repository at " // XXX i18n
215 + repositoryLocation);
216 }
217 }
218 }
219 return rc;
220 }
221
222 /** Reset instance state to default values. */
223 private void reset() {
224 moduleName = null;
225 parentFlag.reset();
226 repositoryFlag.reset();
227 verboseFlag.reset();
228 for (Command cmd : commands.values()) {
229 cmd.reset();
230 }
231 }
232
233
234 /** Parse command line arguments.*/
235 private Command parseArgs(String[] args) {
268 try {
269 if (cmd.parseArgs(args, msg)) {
270 return cmd;
271 } else {
272 usageError();
273 return null;
274 }
275 } catch (ArrayIndexOutOfBoundsException e) {
276 usageError();
277 }
278 } catch (IllegalArgumentException ex) {
279 msg.error(ex.getMessage());
280 usageError();
281 return null;
282 }
283 return cmd;
284 }
285
286
287 /** Returns a user-grokkable description of the repository. */
288 private static String getRepositoryText(Repository repo) {
289 String rc;
290 URI u = repo.getSourceLocation();
291 if (u == null) {
292 rc = "Bootstrap repository";
293 } else {
294 try {
295 rc = "Repository " + u.toURL().toExternalForm();
296 } catch (MalformedURLException ex) {
297 rc = "Repository unknown";
298 }
299 }
300 return rc;
301 }
302
303 private static String getMAIText(ModuleArchiveInfo mai) {
304 StringWriter sw = new StringWriter();
305 PrintWriter pw = new PrintWriter(sw);
306 pw.printf(MAIFormat, mai.getName(), mai.getVersion());
307 if (verboseFlag.isEnabled()) {
308 long t = mai.getLastModified();
309 String lastMod = null;
310 if (t != 0) {
311 lastMod = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(new Date(t));
312 }
313 pw.printf(MAIFormatVerbose,
314 mai.getPlatform() == null ? "generic" : mai.getPlatform(),
315 mai.getArch() == null ? "generic" : mai.getArch(),
316 lastMod == null ? "n/a" : lastMod,
317 mai.getFileName() == null ? "n/a" : mai.getFileName()
318 );
319 }
320 return sw.toString();
321 }
322
323 private static String getMText(Module m) {
324 return getMDText(m.getModuleDefinition());
325 }
326
327 private static String getMDText(ModuleDefinition md) {
328 StringWriter sw = new StringWriter();
329 PrintWriter pw = new PrintWriter(sw);
330 pw.printf(MDFormat, md.getName(), md.getVersion());
331 if (verboseFlag.isEnabled()) {
332 pw.printf(MDFormatVerbose,
333 md.getRepository() == Repository.getBootstrapRepository()
334 ? "bootstrap"
335 : md.getRepository().getSourceLocation().toString());
336 }
337 return sw.toString();
338 }
339
340 void usageError() {
341 usageError(msg);
342 }
343
344 static void usageError(Messenger msg) {
345 if (usage == null) {
346 StringBuilder ub = new StringBuilder(
347 "Usage: jrepo <command>\nwhere <command> includes:"); // XXX i18n
348 for (Command c : commands.values()) {
349 String u = c.usage();
350 if (u != null) {
351 ub.append("\n ").append(c.usage());
352 }
353 }
354 usage = ub.toString();
355 }
356 msg.error(usage);
357 }
358
359 /**
360 * Represents a flag given on the command line. Flags always are 2
361 * characters long, and start with a '-'. They can have additional
362 * associated arguments (cf {@link #RepositoryFlag}).
363 */
364 private static class Flag {
365 private final char name;
366
367 private boolean enabled = false;
368
369 private static final Map<Character, Flag> flags = new HashMap<Character, Flag>();
370
371 Flag(char name) {
372 this.name = name;
373 flags.put(new Character(name), this);
374 }
375
376 void register(Map<Character, Flag> registry) {
377 registry.put(new Character(name), this);
378 }
379
380 char getName() {
381 return name;
382 }
383
384 /**
385 * @return the number of arguments consumed by this Flag
386 * @throws IllegalArgumentException if invalid args are given
387 */
388 int set(String[] args, int pos) throws IllegalArgumentException {
389 enabled = true;
390 return 1;
391 }
392
393 void reset() {
394 enabled = false;
395 }
396
397 boolean isEnabled() {
398 return enabled;
399 }
400
401 static Flag get(char c) {
402 return flags.get(new Character(c));
403 }
404 }
405
406 private static class RepositoryFlag extends Flag {
407 String location = null;
408
409 RepositoryFlag() {
410 super('r');
411 }
412
413 @Override
414 int set(String[] args, int pos) throws IllegalArgumentException {
415 int rc = super.set(args, pos);
416 location = args[pos + 1];
417 return rc + 1;
418 }
419
420 String getLocation() {
421 return location;
422 }
423
424 @Override
425 void reset() {
426 super.reset();
427 location = null;
428 }
429 }
430
431 private static class BindingFlag extends Flag {
432 String platform;
433 String arch;
434
435 BindingFlag() {
436 super('b');
437 }
438
439 @Override
440 int set(String[] args, int pos) throws IllegalArgumentException {
441 int rc = super.set(args, pos);
442 String[] binding = args[pos + 1].split("-");
443 if (binding.length != 2) {
444 throw new IllegalArgumentException(
445 "Must 2 and only 2 elements for platform-arch");
446 }
447 platform = binding[0];
448 arch = binding[1];
449 return rc + 1;
450 }
451
452 String getPlatform() {
453 return platform;
454 }
455
456 String getArch() {
457 return arch;
458 }
459
460 @Override
461 void reset() {
462 super.reset();
463 platform = null;
464 arch = null;
465 }
466 }
467
468 /*
469 * Command types: An abstract base class, plus one concrete class for
470 * each Command.
471 */
472
473 /*
474 * Represents a Command.
475 */
476 private static abstract class Command {
477 private final String name;
478
479 Command(String name) {
480 this.name = name;
481 }
482
483 /** Adds this command to the given registry. */
484 void register(Map<String, Command> registry) {
485 registry.put(name, this);
486 }
487
495 /** Parses the arguments particular to this command. */
496 abstract boolean parseArgs(String[] args, Messenger msg);
497
498 /**
499 * Parse the Flags for this command.
500 * @return number of flags found.
501 * @throws IllegalArgumentException if an invalid flag is given.
502 */
503 int parseFlags(String[] args, Map<Character, Flag> flags) {
504 int rc = 0;
505 int i = 0;
506 while (i < args.length) {
507 String s = args[i];
508 if (s.length() == 2 && s.charAt(0) == '-') {
509 Flag f = flags.get(s.charAt(1));
510 if (f != null) {
511 int numConsumed = f.set(args, i);
512 i += numConsumed; // Increases at each iteration.
513 rc += numConsumed; // Increases only when the arg is a flag.
514 } else {
515 throw new IllegalArgumentException("unrecognized flag: " // XXX i18n
516 + args[i]);
517 }
518 } else {
519 i++;
520 }
521 }
522 return rc;
523 }
524
525 /** Represents the actual behavior of the command. */
526 abstract boolean run(Repository repo, Messenger msg);
527
528 /** Returns a usage string describing this command, or null if the
529 * command is a synonym for another command.
530 */
531 abstract String usage();
532
533 @Override
534 public String toString() { return name; }
535
536 /**
537 * RepositoryVisitor types walk a parent chain of repositories, invoking
538 * {@code doit} in each one. The abstract base class provides the recursion;
539 * each concrete subclass provides the per-repository behavior. The
540 * recursion is such that the bootstrap repository is visited first,
541 * and the system repository is last.
542 */
543 abstract class RepositoryVisitor {
544 abstract void doit(Repository repo, Messenger msg);
545
546 void preVisit(Messenger msg) { }
547
548 void postVisit(Messenger msg) { }
549
550 final void run(Repository repo, Messenger msg) {
551 visit(repo, msg);
552 }
553
554 private final void visit(Repository repo, Messenger msg) {
555 Repository parent = parentFlag.isEnabled() ? repo.getParent() : null;
556 if (parent != null) {
557 visit(parent, msg);
558 }
559 preVisit(msg);
560 doit(repo, msg);
561 postVisit(msg);
562 }
563 }
564 }
565
566
567 /** Lists dependencies of a module. */
568 private class DependenciesCommand extends Command {
569 @SuppressWarnings("unchecked")
570 private final Map<Character, Flag> myFlags = (Map<Character, Flag>) commonFlags.clone();
571
572 private String name;
573 private Version version;
574
575 // For printing module name, version.
576 private String moduleString;
577
578 DependenciesCommand() {
579 super("dependencies");
580 javaseFlag.register(myFlags);
581 bindingFlag.register(myFlags);
582 }
583
584 @Override
585 void reset() {
586 javaseFlag.reset();
587 bindingFlag.reset();
588 }
589
590 @Override
591 int parseFlags(String[] args, Map<Character, Flag> flags) {
592 return super.parseFlags(args, myFlags);
593 }
594
595 boolean parseArgs(String[] args, Messenger msg) {
596 if (args.length > 0) {
597 name = args[0];
598 }
599 if (args.length > 1) {
600 try {
601 version = Version.valueOf(args[1]);
602 } catch (IllegalArgumentException ex) {
603 return false;
604 }
605 }
606 if (args.length > 2) {
607 return false;
608 }
609
610 if (DEBUG) debug("name: " + name + " ver: " + version
611 + " plat: " + bindingFlag.getPlatform()
612 + " arch: " + bindingFlag.getArch());
613 return true;
614 }
615
616 boolean run(Repository repo, Messenger msg) {
617 boolean rc = true;
618 if (name != null) {
619 VersionConstraint vc = (version == null
620 ? VersionConstraint.DEFAULT
621 : version.toVersionConstraint());
622 printHeader(name, vc);
623 rc = depend(repo, name, vc,
624 bindingFlag.getPlatform(), bindingFlag.getArch());
625 printTrailer(null);
626 } else {
627 for (ModuleArchiveInfo mai : repo.list()) {
628 reset();
629 String maiName = mai.getName();
630 VersionConstraint vc = mai.getVersion().toVersionConstraint();
631 printHeader(maiName, vc);
632 rc &= depend(repo, maiName, vc,
633 mai.getPlatform(), mai.getArch());
634 printTrailer("\n");
635 }
636 }
637 return rc;
638 }
639
640 boolean depend(Repository repo, String name, VersionConstraint constraint,
641 String platform, String arch) {
642 ImportTraverser traverser = new ImportTraverser();
643 ImportTraverser.Visitor visitor = new ImportVisitor(traverser, msg);
644 try {
645 traverser.traverse(visitor, repo, name, constraint, platform, arch);
646 if (traverser.traversedAny()) {
647 return true;
648 }
649 if (verboseFlag.isEnabled()) {
650 msg.error("Cannot find module " + name + " in " // XXX i18n
651 + getRepositoryText(repo));
652 }
653 return false;
654 } catch (ModuleInitializationException ex) {
655 msg.error("Cannot instantiate module for " + name // XXX i18n
656 + ": " + ex);
657 return false;
658 }
659 }
660
661
662 void printHeader(String name, VersionConstraint vc) {
663 if (verboseFlag.isEnabled()) {
664 msg.println("Dependencies for " // XXXi18n
665 + name + "-" + vc + ":");
666 }
667 }
668
669 void printTrailer(String s) {
670 if (verboseFlag.isEnabled()) {
671 msg.print(s);
672 }
673 }
674
675 // TBD support platform binding. Ideally, want to allow specifying
676 // version, or binding, or both.
677 String usage() {
678 return "dependencies [-v] [-r repositoryLocation] [-b platform-arch]"// XXX i18n
679 + " [moduleName [moduleVersion] ]\n"
680 + " Lists all modules on which identified modules depend.\n"
681 + " If no moduleName is given, lists dependencies of all"
682 + " module archives in the repository\n"
683 + " which are instantiable on the current platform.";
684 }
685 }
686
687
688 private static class ImportVisitor extends ImportTraverser.Visitor {
689 private final Messenger msg;
690
691 private static final String INDENT = " ";
692 private static final int INDENT_LENGTH = INDENT.length();
693
694 private String indent = "";
695
696 ImportVisitor(ImportTraverser traverser, Messenger msg) {
697 super(traverser);
698 this.msg = msg;
699 }
700
701 @Override
702 protected void init(Module m) {
703 printModule(m);
704 }
705
706 @Override
707 protected boolean preVisit(Module m) {
708 if (javaseFlag.isEnabled() == false
709 && m.getModuleDefinition().getName().startsWith("java.se")) {
710 return false;
711 } else {
712 indent += INDENT;
713 return true;
714 }
715 }
716
717 @Override
718 protected void visit(Module m) {
719 printModule(m);
720 }
721
722 @Override
723 protected void postVisit(Module m) {
724 if (javaseFlag.isEnabled() == false
725 && m.getModuleDefinition().getName().startsWith("java.se")) {
726 // empty
727 } else {
728 indent = indent.substring(INDENT_LENGTH);
729 }
730 }
731
732 void printModule(Module m) {
733 msg.println(indent + getMText(m));
734 }
735 }
736
737 /** Installs a JAM into a repository. */
738 private class InstallCommand extends Command {
739 private String jamName;
740
741 InstallCommand() {
742 super("install");
743 }
744
745 boolean parseArgs(String[] args, Messenger msg) {
746 boolean rc = false;
747 if (!parentFlag.isEnabled()
748 && repositoryFlag.getLocation() != null
749 && args.length == 1) {
750 jamName = args[0];
751 rc = true;
752 }
753 return rc;
754 }
755
756 boolean run(Repository repo, Messenger msg) {
774 jamURL = jamName;
775 }
776 try {
777 ModuleArchiveInfo mai = repo.install(new URL(jamURL).toURI());
778 if (verboseFlag.isEnabled()) {
779 msg.println("Installed " + jamName + ": " + getMAIText(mai));
780 }
781 return true;
782 } catch (URISyntaxException ex) {
783 msg.error("Cannot install " + jamName + ": no such file, or malformed URI");
784 } catch (MalformedURLException ex) {
785 msg.error("Cannot install " + jamName + ": no such file, or malformed URL");
786 } catch (IOException ex) {
787 msg.error("Cannot install " + jamName + ": " + ex.getMessage());
788 }
789 }
790 return false;
791 }
792
793 String usage() {
794 return "install [-v] -r repositoryLocation jamFile | jamURL\n" // XXX i18n
795 + " installs a module into a repository";
796 }
797 }
798
799 /** Uninstalls a module from a repository. */
800 private class UninstallCommand extends Command {
801 @SuppressWarnings("unchecked")
802 private final Map<Character, Flag> myFlags = (Map<Character, Flag>) commonFlags.clone();
803
804 private Flag forceFlag = new Flag('f');
805
806 private Flag interactiveFlag = new Flag('i');
807
808 private String moduleName;
809
810 private Version version;
811
812 private String platformBinding;
813
814 UninstallCommand() {
815 super("uninstall");
816 forceFlag.register(myFlags);
817 interactiveFlag.register(myFlags);
818 }
819
820 @Override
821 void reset() {
822 version = null;
823 platformBinding = null;
824 forceFlag.reset();
825 interactiveFlag.reset();
826 }
827
828 @Override
829 int parseFlags(String[] args, Map<Character, Flag> flags) {
830 return super.parseFlags(args, myFlags);
831 }
832
833 boolean parseArgs(String[] args, Messenger msg) {
834 if (repositoryFlag.getLocation() == null) {
835 return false;
836 }
837
838 if (forceFlag.isEnabled() && interactiveFlag.isEnabled()) {
839 // Doesn't make sense for these to both be enabled
840 msg.error("uninstall cannot simultaneously use both -i and -f: choose one or the other"); // XXX i81n
841 return false;
842 }
843
844 if (args.length >= 1) {
845 moduleName = args[0];
846 if (args.length >= 2 && args.length < 4) {
847 try {
848 version = Version.valueOf(args[1]);
895 }
896 }
897 } else if (forceFlag.isEnabled()) {
898 if (DEBUG) debug("forced uninstall of multiple matches");
899 rc = true;
900 for (ModuleArchiveInfo mai : found) {
901 if (!uninstall(repo, mai, msg)) {
902 if (DEBUG) debug("uninstall failed for " + getMAIText(mai) + " in " + repo.getSourceLocation());
903 rc = false;
904 break;
905 }
906 }
907 } else if (interactiveFlag.isEnabled()) {
908 rc = uninstallInteractive(repo, found, msg);
909 }
910 }
911 return rc;
912 }
913
914 String usage() {
915 return "uninstall [-v] [-f | -i] -r repositoryLocation moduleName"// XXX i18n
916 + " [moduleVersion] [modulePlatformBinding]\n"
917 + " removes a module from a repository, along with associated files.";
918 }
919
920 /** Uninstall the ModuleArchiveInfo from the Repository. */
921 private boolean uninstall(Repository repo, ModuleArchiveInfo mai, Messenger msg) {
922 boolean rc = false;
923 if (DEBUG) debug("Uninstalling " + getMAIText(mai));
924 try {
925 rc = repo.uninstall(mai);
926 if (verboseFlag.isEnabled()) {
927 if (rc) {
928 msg.println("Uninstalled " + getMAIText(mai)); // XXX i18n
929 } else {
930 msg.error("Failed to uninstall " + getMAIText(mai)); // XXX i18n
931 }
932 }
933 } catch (Exception ex) {
934 msg.error("Exception while uninstalling " + getInfo()
935 + ": " + ex.getMessage());
936 }
937 return rc;
938 }
939
940 /** Uninstall one of the ModuleArchiveInfos from the Repository. */
941 private boolean uninstallInteractive(
942 Repository repo,
943 List<ModuleArchiveInfo> found,
944 Messenger msg) {
945 boolean rc = false;
946
947 String spaces = " ";
948 String fmt = "%" + found.size() + "d %s\n";
949
950 StringWriter sw = new StringWriter();
1018 if (version != null) {
1019 s += " with version " + version;
1020 }
1021 if (platformBinding != null) {
1022 s += " and platform-binding of " + platformBinding;
1023 }
1024 return s;
1025 }
1026 }
1027
1028 /** Prints information about modules found in repositories. */
1029 private class ListCommand extends Command {
1030 @SuppressWarnings("unchecked")
1031 private final Map<Character, Flag> myFlags = (Map<Character, Flag>) commonFlags.clone();
1032
1033 ListCommand() {
1034 super("list");
1035 parentFlag.register(myFlags);
1036 }
1037
1038 @Override
1039 void reset() {
1040 parentFlag.reset();
1041 }
1042
1043 @Override
1044 int parseFlags(String[] args, Map<Character, Flag> flags) {
1045 return super.parseFlags(args, myFlags);
1046 }
1047
1048 boolean parseArgs(String[] args, Messenger msg){
1049 boolean rc = true;
1050 if (args.length == 0) {
1051 moduleName = null;
1052 } else if (args.length == 1) {
1053 moduleName = args[0];
1054 } else {
1055 rc = false;
1056 }
1057 return rc;
1058 }
1059
1060 boolean run(Repository repo, Messenger msg) {
1061 ListRepositoryVisitor visitor = new ListRepositoryVisitor();
1062 visitor.run(repo, msg);
1063 boolean found = visitor.wasFound();
1064 if (verboseFlag.isEnabled() && !found) {
1065 if (moduleName != null) {
1066 msg.error("Could not find module name starting with '"//
1067 // XXX i18n
1068 + moduleName + "'");
1069 } else {
1070 msg.error("Could not find any modules"); // XXX i18n
1071 }
1072 }
1073 return found;
1074 }
1075
1076 String usage() {
1077 return "list [-v] [-p] [-r repositoryLocation] moduleName\n"//
1078 // XXX i18n
1079 + " lists the modules in the repository";
1080 }
1081
1082 class ListRepositoryVisitor extends RepositoryVisitor {
1083 private boolean found = false;
1084
1085 boolean wasFound() { return found; }
1086
1087 void doit(Repository repo, Messenger msg) {
1088 boolean printedHeader = false;
1089 List<ModuleArchiveInfo> maiList = repo.list();
1090 if (maiList.size() == 0 && verboseFlag.isEnabled()) {
1091 msg.println(getRepositoryText(repo));
1092 msg.println(" empty");
1093 } else {
1094 for (ModuleArchiveInfo mai : repo.list()) {
1095 if (moduleName == null || mai.getName().startsWith(moduleName)) {
1096 if (!printedHeader) {
1097 msg.println(getRepositoryText(repo));
1098 msg.println(verboseFlag.isEnabled() ? maiHeadingVerbose : maiHeading);
1099 printedHeader = true;
1100 }
1101 msg.println(getMAIText(mai));
1102 found = true;
1103 }
1104 }
1105 }
1106 }
1107 }
1108 }
1109 }
|