1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.river.tool;
20
21 import java.io.BufferedReader;
22 import java.io.BufferedWriter;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStreamReader;
28 import java.io.OutputStreamWriter;
29 import java.io.Writer;
30 import java.net.URI;
31 import java.net.URISyntaxException;
32 import java.security.MessageDigest;
33 import java.security.NoSuchAlgorithmException;
34 import java.text.MessageFormat;
35 import java.util.ArrayList;
36 import java.util.Collections;
37 import java.util.Enumeration;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.Iterator;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.MissingResourceException;
44 import java.util.ResourceBundle;
45 import java.util.Set;
46 import java.util.StringTokenizer;
47 import java.util.jar.Attributes;
48 import java.util.jar.Attributes.Name;
49 import java.util.jar.JarEntry;
50 import java.util.jar.JarFile;
51 import java.util.jar.JarOutputStream;
52 import java.util.jar.Manifest;
53 import java.util.logging.ConsoleHandler;
54 import java.util.logging.Handler;
55 import java.util.logging.Level;
56 import java.util.logging.Logger;
57 import java.util.regex.Matcher;
58 import java.util.regex.Pattern;
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178 public class JarWrapper {
179
180 private static ResourceBundle resources;
181 private static final Object resourcesLock = new Object();
182 private static final Logger logger =
183 Logger.getLogger(JarWrapper.class.getName());
184
185 private final File destJar;
186
187
188
189
190 private final File baseDir;
191 private final SourceJarURL[] srcJars;
192 private final Manifest manifest;
193 private final MessageDigest digest;
194 private final JarIndexWriter indexWriter;
195 private final PreferredListWriter prefWriter;
196 private final StringBuffer classPath = new StringBuffer();
197 private String mainClass = null;
198 private final Set seenJars = new HashSet();
199
200 static private final String DEFAULT_HTTPMD_ALGORITHM = "SHA-1";
201
202
203
204
205 private JarWrapper(String destJar,
206 String baseDir,
207 String[] srcJars,
208 String httpmdAlg,
209 boolean index,
210 Manifest mf,
211 List apiClasses)
212 {
213 this.destJar = new File(destJar);
214 if (this.destJar.exists()) {
215 throw new LocalizedIllegalArgumentException(
216 "jarwrapper.fileexists", destJar);
217 }
218 if (baseDir != null) {
219 this.baseDir = new File(baseDir);
220 if (!this.baseDir.isDirectory()) {
221 throw new LocalizedIllegalArgumentException(
222 "jarwrapper.invalidbasedir", baseDir);
223 }
224 }
225 else {
226 this.baseDir = null;
227 }
228 this.srcJars = new SourceJarURL[srcJars.length];
229 for (int i = 0; i < srcJars.length; i++) {
230 try {
231 SourceJarURL url;
232 if (baseDir == null) {
233 File file = new File(srcJars[i]);
234 url = new SourceJarURL(file.getName(),
235 file.getParentFile());
236 }
237 else {
238 url = new SourceJarURL(srcJars[i]);
239 }
240 if (url.algorithm != null) {
241 throw new LocalizedIllegalArgumentException(
242 "jarwrapper.urlhasdigest", url);
243 }
244 this.srcJars[i] = url;
245 } catch (LocalizedIOException e) {
246 throw new LocalizedIllegalArgumentException(e);
247 } catch (IOException e) {
248 throw (IllegalArgumentException)
249 new IllegalArgumentException(e.getMessage()).initCause(e);
250 }
251 }
252 if (httpmdAlg != null) {
253 try {
254 digest = MessageDigest.getInstance(httpmdAlg);
255 } catch (NoSuchAlgorithmException e) {
256 throw (IllegalArgumentException)
257 new LocalizedIllegalArgumentException(
258 "jarwrapper.invalidhttpmdalg", httpmdAlg).initCause(e);
259 }
260 } else {
261 digest = null;
262 }
263 manifest = mf != null ? new Manifest(mf) : new Manifest();
264 indexWriter = index ? new JarIndexWriter() : null;
265 List classes = new ArrayList();
266 if (apiClasses != null) {
267 for (Iterator classNames = apiClasses.iterator();
268 classNames.hasNext();) {
269 String className = (String) classNames.next();
270 if (className != null) {
271 classes.add(className.replace('.', '/') + ".class");
272 }
273 }
274 }
275 prefWriter = new PreferredListWriter(classes);
276 }
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360 public static void main(String[] args) {
361 String destJar;
362 String baseDir;
363 String[] srcJars;
364 String httpmdAlg = null;
365 boolean index = true;
366 Manifest mf = null;
367
368 int i = 0;
369 while (i < args.length && args[i].startsWith("-")) {
370 String s = args[i++];
371 if (s.equals("-help")) {
372 System.err.println(localize("jarwrapper.usage"));
373 System.exit(0);
374 } else if (s.equals("-verbose")) {
375 setLoggingLevel(Level.FINER);
376 } else if (s.equals("-debug")) {
377 setLoggingLevel(Level.ALL);
378 } else if (s.equals("-httpmd") || s.startsWith("-httpmd=")) {
379 if (httpmdAlg != null) {
380 System.err.println(localize("jarwrapper.multiplehttpmd"));
381 System.err.println(localize("jarwrapper.usage"));
382 System.exit(1);
383 }
384 int split = s.indexOf('=');
385 httpmdAlg = (split != -1) ?
386 s.substring(split + 1) : DEFAULT_HTTPMD_ALGORITHM;
387 } else if (s.startsWith("-manifest=")) {
388 int split = s.indexOf('=');
389 String fileName = s.substring(split + 1);
390 try {
391 mf = retrieveManifest(fileName);
392 } catch (IOException ioe) {
393 System.err.println(localize("jarwrapper.badmanifest", s));
394 System.exit(1);
395 }
396 } else if (s.equals("-noindex")) {
397 index = false;
398 } else {
399 System.err.println(localize("jarwrapper.badoption", s));
400 System.err.println(localize("jarwrapper.usage"));
401 System.exit(1);
402 }
403 }
404 if (args.length - i < 3) {
405 System.err.println(localize("jarwrapper.insufficientargs"));
406 System.err.println(localize("jarwrapper.usage"));
407 System.exit(1);
408 }
409 destJar = args[i++];
410 baseDir = args[i++];
411 srcJars = new String[args.length - i];
412 System.arraycopy(args, i, srcJars, 0, srcJars.length);
413
414 try {
415 wrap(destJar, baseDir, srcJars, httpmdAlg, index, mf);
416 } catch (Throwable t) {
417 if (t instanceof LocalizedIllegalArgumentException ||
418 t instanceof LocalizedIOException)
419 {
420 System.err.println(t.getMessage());
421 } else {
422 System.err.println(localize("jarwrapper.fatalexception"));
423 t.printStackTrace();
424 }
425 System.exit(1);
426 }
427 }
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448 public static void wrap(String destJar,
449 String baseDir,
450 String[] srcJars,
451 String httpmdAlg,
452 boolean index)
453 throws IOException
454 {
455 wrap(destJar, baseDir, srcJars, httpmdAlg, index, null);
456 }
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484 public static void wrap(String destJar,
485 String baseDir,
486 String[] srcJars,
487 String httpmdAlg,
488 boolean index,
489 Manifest mf)
490 throws IOException
491 {
492 wrap(destJar, baseDir, srcJars, httpmdAlg, index, mf, null);
493 }
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524 public static void wrap(String destJar,
525 String baseDir,
526 String[] srcJars,
527 String httpmdAlg,
528 boolean index,
529 Manifest mf,
530 List apiClasses)
531 throws IOException
532 {
533 new JarWrapper(destJar, baseDir, srcJars, httpmdAlg, index, mf,
534 apiClasses).wrap();
535 }
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569 public static void wrap(String destJar,
570 String[] srcJars,
571 String httpmdAlg,
572 boolean index,
573 Manifest mf,
574 List apiClasses)
575 throws IOException
576 {
577 new JarWrapper(destJar, null, srcJars, httpmdAlg, index, mf,
578 apiClasses).wrap();
579 }
580
581
582
583
584 private void wrap() throws IOException {
585 for (int i = 0; i < srcJars.length; i++) {
586 process(srcJars[i], null);
587 }
588 outputWrapperJar();
589 }
590
591
592
593
594
595
596
597
598 private void process(SourceJarURL url, PreferredListReader prefReader)
599 throws IOException
600 {
601 File file = baseDir == null ? url.toFile() : url.toFile(baseDir);
602 boolean seen = seenJars.contains(file);
603 boolean checkMainClass = mainClass == null && prefReader == null;
604 if (seen && !checkMainClass) {
605 return;
606 }
607
608 if (logger.isLoggable(Level.FINE)) {
609 logger.log(Level.FINE, "processing {0}", new Object[]{ file });
610 }
611 if (!file.exists()) {
612 throw new LocalizedIOException("jarwrapper.filenotfound", file);
613 }
614 JarFile jar = new JarFile(file, false);
615
616 if (checkMainClass) {
617 mainClass = getMainClass(jar);
618 }
619 if (!seen) {
620 seenJars.add(file);
621
622 if (digest != null) {
623 url = new SourceJarURL(
624 url.path,
625 digest.getAlgorithm(),
626 getDigestString(digest, file),
627 null);
628 }
629 if (classPath.length() > 0) {
630 classPath.append(' ');
631 }
632 classPath.append(url);
633
634 if (indexWriter != null) {
635 indexWriter.addEntries(jar, url);
636 }
637 if (prefReader == null) {
638 prefReader = new PreferredListReader(jar);
639 }
640 prefWriter.addEntries(jar, prefReader);
641
642 List l = new ArrayList();
643 l.addAll(new JarIndexReader(jar).getJars());
644 l.addAll(getClassPath(jar));
645 for (Iterator i = l.iterator(); i.hasNext(); ) {
646 SourceJarURL u = (SourceJarURL) i.next();
647 u = url.resolve(new SourceJarURL(u.path, null, null, null));
648 process(u, prefReader);
649 }
650 }
651 }
652
653
654
655
656
657 private List getClassPath(JarFile jar) throws IOException {
658 Manifest mf = jar.getManifest();
659 if (mf == null) {
660 return Collections.EMPTY_LIST;
661 }
662 Attributes atts = mf.getMainAttributes();
663 String cp = atts.getValue(Name.CLASS_PATH);
664 if (cp == null) {
665 return Collections.EMPTY_LIST;
666 }
667 if (logger.isLoggable(Level.FINER)) {
668 logger.log(Level.FINER, "Class-Path: {0}", new Object[]{ cp });
669 }
670 List l = new ArrayList();
671 for (StringTokenizer tok = new StringTokenizer(cp, " ");
672 tok.hasMoreTokens(); )
673 {
674 SourceJarURL url = new SourceJarURL(tok.nextToken());
675 if (digest != null && url.algorithm == null) {
676 throw new LocalizedIOException("jarwrapper.nonhttpmdurl", url);
677 }
678 l.add(url);
679 }
680 return l;
681 }
682
683
684
685
686
687 private void outputWrapperJar() throws IOException {
688 if (logger.isLoggable(Level.FINE)) {
689 logger.log(Level.FINE, "writing {0}", new Object[]{ destJar });
690 }
691 Attributes atts = manifest.getMainAttributes();
692 if (atts.get(Name.MANIFEST_VERSION) == null)
693 atts.put(Name.MANIFEST_VERSION, "1.0");
694 Name creatorName = new Name("Created-By");
695 if (atts.get(creatorName) == null )
696 atts.put(creatorName, JarWrapper.class.getName());
697 if (atts.get(Name.CLASS_PATH) == null)
698 atts.put(Name.CLASS_PATH, classPath.toString());
699 if ((atts.get(Name.MAIN_CLASS) == null) && (mainClass != null)) {
700 atts.put(Name.MAIN_CLASS, mainClass);
701 }
702
703 boolean completed = false;
704 try {
705 JarOutputStream jout =
706 new JarOutputStream(new FileOutputStream(destJar), manifest);
707 if (indexWriter != null) {
708 indexWriter.write(jout);
709 }
710 prefWriter.write(jout);
711 jout.close();
712 completed = true;
713 } finally {
714 if (!completed) {
715 deleteWrapperJar();
716 }
717 }
718 }
719
720
721
722
723 private void deleteWrapperJar() {
724 try {
725 if (!destJar.delete() && logger.isLoggable(Level.WARNING)) {
726 logger.log(
727 Level.WARNING,
728 "failed to delete {0}",
729 new Object[]{ destJar });
730 }
731 } catch (Throwable t) {
732 logger.log(
733 Level.WARNING, "exception deleting wrapper JAR file", t);
734 }
735 }
736
737
738
739
740
741 private static String getMainClass(JarFile jar) throws IOException {
742 Manifest mf = jar.getManifest();
743 if (mf == null) {
744 return null;
745 }
746 Attributes atts = mf.getMainAttributes();
747 String mc = atts.getValue(Name.MAIN_CLASS);
748 if (mc != null && logger.isLoggable(Level.FINER)) {
749 logger.log(Level.FINER, "Main-Class: {0}", new Object[]{ mc });
750 }
751 return mc;
752 }
753
754
755
756
757 private static String getDigestString(MessageDigest digest, File file)
758 throws IOException
759 {
760 FileInputStream fin = new FileInputStream(file);
761 byte[] buf = new byte[2048];
762 int n;
763 while ((n = fin.read(buf)) >= 0) {
764 digest.update(buf, 0, n);
765 }
766 buf = digest.digest();
767 fin.close();
768
769 StringBuffer sb = new StringBuffer(buf.length * 2);
770 for (int i = 0; i < buf.length; i++) {
771 byte b = buf[i];
772 sb.append(Character.forDigit((b >> 4) & 0xf, 16));
773 sb.append(Character.forDigit(b & 0xf, 16));
774 }
775 return sb.toString();
776 }
777
778
779
780
781 private static void setLoggingLevel(Level level) {
782 logger.setLevel(level);
783 for (Logger l = logger; l != null; l = l.getParent()) {
784 Handler[] handlers = l.getHandlers();
785 for (int i = 0; i < handlers.length; i++) {
786 if (handlers[i] instanceof ConsoleHandler) {
787 handlers[i].setLevel(level);
788 }
789 }
790 if (!l.getUseParentHandlers()) {
791 break;
792 }
793 }
794 }
795
796
797
798
799 static String localize(String key) {
800 return localize(key, new Object[0]);
801 }
802
803
804
805
806
807 static String localize(String key, Object val) {
808 return localize(key, new Object[] { val });
809 }
810
811
812
813
814
815 static String localize(String key, Object[] vals) {
816 String fmt = getResourceString(key);
817 if (fmt == null) {
818 return "error: no text found in resource bundle for key: " + key;
819 }
820 return MessageFormat.format(fmt, vals);
821 }
822
823
824
825
826
827
828 private static String getResourceString(String key) {
829 synchronized (resourcesLock) {
830 if (resources == null) {
831 resources = ResourceBundle.getBundle(
832 "org.apache.river.tool.resources.jarwrapper");
833 }
834 }
835 try {
836 return resources.getString(key);
837 } catch (MissingResourceException e) {
838 return null;
839 }
840 }
841
842
843
844
845
846 private static Manifest retrieveManifest(String fileName)
847 throws IOException
848 {
849 FileInputStream fis = new FileInputStream(fileName);
850 Manifest mf = new Manifest(fis);
851 fis.close();
852 return mf;
853 }
854
855
856
857
858 private static class LocalizedIllegalArgumentException
859 extends IllegalArgumentException
860 {
861 private static final long serialVersionUID = 0L;
862
863
864
865
866
867
868 LocalizedIllegalArgumentException(String key, Object val) {
869 super(localize(key, val));
870 }
871
872
873
874
875
876 LocalizedIllegalArgumentException(LocalizedIOException cause) {
877 super(cause.getMessage());
878 initCause(cause);
879 }
880 }
881
882
883
884
885 private static class LocalizedIOException extends IOException {
886
887 private static final long serialVersionUID = 0L;
888
889
890
891
892
893
894 LocalizedIOException(String key, Object val) {
895 super(localize(key, val));
896 }
897
898
899
900
901
902
903 LocalizedIOException(String key, Object[] vals) {
904 super(localize(key, vals));
905 }
906 }
907
908
909
910
911
912 private static class SourceJarURL {
913
914 private static final Pattern httpmdPattern =
915 Pattern.compile("(.*);(.+?)=(.+?)(?:,(.*))?$");
916
917
918 final String raw;
919
920 final String path;
921
922 final String algorithm;
923
924 final String digest;
925
926 final String comment;
927
928
929
930
931 private File baseDir;
932
933
934
935
936 SourceJarURL(String raw) throws IOException {
937 try {
938 this.raw = raw;
939 Matcher m = httpmdPattern.matcher(raw);
940 if (m.matches()) {
941 path = m.group(1);
942 algorithm = m.group(2);
943 digest = m.group(3);
944 comment = m.group(4);
945 } else {
946 path = raw;
947 algorithm = null;
948 digest = null;
949 comment = null;
950 }
951
952 URI uri = new URI(path);
953 if (uri.getScheme() != null) {
954 throw new LocalizedIOException(
955 "jarwrapper.urlhasscheme", raw);
956 } else if (uri.getAuthority() != null) {
957 throw new LocalizedIOException(
958 "jarwrapper.urlhasauthority", raw);
959 }
960 String p = uri.getPath();
961 if (p == null || p.length() == 0) {
962 throw new LocalizedIOException(
963 "jarwrapper.urlemptypath", raw);
964 } else if (p.startsWith("/")) {
965 throw new LocalizedIOException(
966 "jarwrapper.urlabsolute", raw);
967 }
968 } catch (URISyntaxException e) {
969 throw (IOException) new LocalizedIOException(
970 "jarwrapper.invalidurlsyntax", raw).initCause(e);
971 }
972 }
973
974
975
976
977
978 SourceJarURL(String raw, File baseDir) throws IOException {
979 this(raw);
980
981 this.baseDir = baseDir;
982 }
983
984
985
986
987 SourceJarURL(String path,
988 String algorithm,
989 String digest,
990 String comment)
991 {
992 if (algorithm != null) {
993 raw = path + ';' + algorithm + '=' + digest +
994 ((comment != null) ? ',' + comment : "");
995 } else {
996 raw = path;
997 }
998 this.path = path;
999 this.algorithm = algorithm;
1000 this.digest = digest;
1001 this.comment = comment;
1002 }
1003
1004
1005
1006
1007 SourceJarURL resolve(SourceJarURL other) {
1008 try {
1009
1010 URI uri = new URI('/' + path);
1011 String p = uri.resolve(other.path).getPath().substring(1);
1012 return new SourceJarURL(
1013 p, other.algorithm, other.digest, other.comment);
1014 } catch (URISyntaxException e) {
1015 throw new AssertionError(e);
1016 }
1017 }
1018
1019
1020
1021
1022 File toFile() {
1023 return toFile(baseDir);
1024 }
1025
1026
1027
1028
1029 File toFile(File base) {
1030 try {
1031 String p = new URI(path).getPath();
1032 return new File(base, p.replace('/', File.separatorChar));
1033 } catch (URISyntaxException e) {
1034 throw new InternalError(e);
1035 }
1036 }
1037
1038 public boolean equals(Object obj) {
1039 return obj instanceof SourceJarURL &&
1040 raw.equals(((SourceJarURL) obj).raw);
1041 }
1042
1043 public int hashCode() {
1044 return raw.hashCode();
1045 }
1046
1047 public String toString() {
1048 return raw;
1049 }
1050 }
1051
1052
1053
1054
1055 private static class JarIndexReader {
1056
1057 private static final Pattern headerPattern =
1058 Pattern.compile("^JarIndex-Version:\\s*(.*?)$");
1059 private static final Pattern versionPattern =
1060 Pattern.compile("^1(\\.\\d+)*$");
1061
1062 private final List jars;
1063
1064
1065
1066
1067 JarIndexReader(JarFile jar) throws IOException {
1068 List l = new ArrayList();
1069 jars = Collections.unmodifiableList(l);
1070 JarEntry ent = jar.getJarEntry("META-INF/INDEX.LIST");
1071 if (ent == null) {
1072 return;
1073 }
1074 logger.finer("reading JAR index");
1075 BufferedReader r = new BufferedReader(
1076 new InputStreamReader(jar.getInputStream(ent), "UTF8"));
1077
1078 String s = r.readLine();
1079 if (s == null) {
1080 throw new IOException("missing JAR index header");
1081 }
1082 s = s.trim();
1083 Matcher m = headerPattern.matcher(s);
1084 if (!m.matches()) {
1085 throw new IOException("illegal JAR index header: " + s);
1086 }
1087 s = m.group(1);
1088 if (!versionPattern.matcher(s).matches()) {
1089 throw new IOException("unsupported JAR index version: " + s);
1090 }
1091
1092 s = r.readLine();
1093 if (s == null) {
1094 throw new IOException("truncated JAR index");
1095 }
1096 s = s.trim();
1097 if (s.length() > 0) {
1098 throw new IOException(
1099 "non-empty line after JAR index header: " + s);
1100 }
1101
1102 while ((s = r.readLine()) != null) {
1103 SourceJarURL url = new SourceJarURL(s.trim());
1104 if (logger.isLoggable(Level.FINEST)) {
1105 logger.log(
1106 Level.FINEST,
1107 "JAR index references {0}",
1108 new Object[]{ url });
1109 }
1110 l.add(url);
1111 do {
1112 s = r.readLine();
1113 } while (s != null && s.trim().length() > 0);
1114 }
1115 if (l.isEmpty()) {
1116 throw new IOException("empty JAR index");
1117 }
1118 }
1119
1120
1121
1122
1123
1124 List getJars() {
1125 return jars;
1126 }
1127 }
1128
1129
1130
1131
1132 private static class JarIndexWriter {
1133
1134 private final List urls = new ArrayList();
1135 private final Map contentMap = new HashMap();
1136
1137 JarIndexWriter() {
1138 }
1139
1140
1141
1142
1143
1144 void addEntries(JarFile jar, SourceJarURL url) {
1145 Set contents = new HashSet();
1146 for (Enumeration e = jar.entries(); e.hasMoreElements();) {
1147 String name = ((JarEntry) e.nextElement()).getName();
1148 if (!(name.startsWith("META-INF") || name.endsWith("/"))) {
1149 int pos = name.lastIndexOf("/");
1150 contents.add((pos != -1) ? name.substring(0, pos) : name);
1151 }
1152 }
1153 if (!contents.isEmpty()) {
1154 urls.add(url);
1155 contentMap.put(url, contents);
1156 }
1157 }
1158
1159
1160
1161
1162 void write(JarOutputStream jout) throws IOException {
1163 if (contentMap.isEmpty()) {
1164 logger.finer("omitting empty JAR index");
1165 return;
1166 }
1167 logger.finer("writing JAR index");
1168 jout.putNextEntry(new JarEntry("META-INF/INDEX.LIST"));
1169 Writer w =
1170 new BufferedWriter(new OutputStreamWriter(jout, "UTF8"));
1171 w.write("JarIndex-Version: 1.0\n\n");
1172
1173
1174 for (Iterator i = urls.iterator(); i.hasNext();) {
1175 SourceJarURL url = (SourceJarURL) i.next();
1176 Set contents = (Set) contentMap.get(url);
1177
1178 if (!url.raw.endsWith(".jar")) {
1179 if (url.algorithm != null) {
1180 url = new SourceJarURL(
1181 url.path, url.algorithm, url.digest, ".jar");
1182 } else if (logger.isLoggable(Level.WARNING)) {
1183 logger.log(
1184 Level.WARNING,
1185 "JAR index entry {0} does not end in .jar",
1186 new Object[]{ url });
1187 }
1188 }
1189 if (logger.isLoggable(Level.FINEST)) {
1190 logger.log(
1191 Level.FINEST,
1192 "writing JAR index entry {0}: {1}",
1193 new Object[]{ url, contents });
1194 }
1195 w.write(url + "\n");
1196 for (Iterator j = contents.iterator(); j.hasNext(); ) {
1197 w.write(j.next() + "\n");
1198 }
1199 w.write("\n");
1200 }
1201 w.flush();
1202 jout.closeEntry();
1203 }
1204 }
1205
1206
1207
1208
1209 private static class PreferredListReader {
1210
1211 private static final Pattern headerPattern =
1212 Pattern.compile("^PreferredResources-Version:\\s*(.*?)$");
1213 private static final Pattern versionPattern =
1214 Pattern.compile("^1\\.\\d+$");
1215 private static final Pattern namePattern =
1216 Pattern.compile("^Name:\\s*(.*)$");
1217 private static final Pattern preferredPattern =
1218 Pattern.compile("^Preferred:\\s*(.*)$");
1219
1220 private final boolean defaultPref;
1221 private final Map namePrefs = new HashMap();
1222 private final Map packagePrefs = new HashMap();
1223 private final Map subtreePrefs = new HashMap();
1224
1225
1226
1227
1228 PreferredListReader(JarFile jar) throws IOException {
1229 JarEntry ent = jar.getJarEntry("META-INF/PREFERRED.LIST");
1230 if (ent == null) {
1231 defaultPref = false;
1232 return;
1233 }
1234 logger.finer("reading preferred list");
1235 BufferedReader r = new BufferedReader(
1236 new InputStreamReader(jar.getInputStream(ent), "UTF8"));
1237
1238 String s = r.readLine();
1239 if (s == null) {
1240 throw new IOException("missing preferred list header");
1241 }
1242 s = s.trim();
1243 Matcher m = headerPattern.matcher(s);
1244 if (!m.matches()) {
1245 throw new IOException("illegal preferred list header: " + s);
1246 }
1247 s = m.group(1);
1248 if (!versionPattern.matcher(s).matches()) {
1249 throw new IOException(
1250 "unsupported preferred list version: " + s);
1251 }
1252
1253 s = nextNonBlankLine(r);
1254 if (s == null) {
1255 throw new IOException("empty preferred list");
1256 }
1257 if ((m = preferredPattern.matcher(s)).matches()) {
1258 defaultPref = Boolean.valueOf(m.group(1)).booleanValue();
1259 s = nextNonBlankLine(r);
1260 } else {
1261 defaultPref = false;
1262 }
1263
1264 while (s != null) {
1265 if (!(m = namePattern.matcher(s)).matches()) {
1266 throw new IOException(
1267 "expected preferred entry name: " + s);
1268 }
1269 String name = m.group(1);
1270
1271 s = nextNonBlankLine(r);
1272 if (s == null) {
1273 throw new IOException("EOF before preferred entry");
1274 }
1275 if (!(m = preferredPattern.matcher(s)).matches()) {
1276 throw new IOException("expected preferred entry: " + s);
1277 }
1278 Boolean pref = Boolean.valueOf(m.group(1));
1279
1280 String key;
1281 Map map;
1282 if (name.endsWith("/*")) {
1283 key = name.substring(0, name.length() - 2);
1284 map = packagePrefs;
1285 } else if (name.endsWith("/")) {
1286 key = name.substring(0, name.length() - 1);
1287 map = packagePrefs;
1288 } else if (name.endsWith("/-")) {
1289 key = name.substring(0, name.length() - 2);
1290 map = subtreePrefs;
1291 } else {
1292 key = name;
1293 map = namePrefs;
1294 }
1295 if (key.length() == 0) {
1296 throw new IOException(
1297 "invalid preferred entry name: " + name);
1298 }
1299 map.put(key, pref);
1300 if (logger.isLoggable(Level.FINEST)) {
1301 logger.log(
1302 Level.FINEST,
1303 "read preferred list entry {0}: {1}",
1304 new Object[]{ name, pref });
1305 }
1306
1307 s = nextNonBlankLine(r);
1308 }
1309 }
1310
1311
1312
1313
1314 boolean isPreferred(String entry) {
1315 Boolean b = (Boolean) namePrefs.get(entry);
1316 if (b != null) {
1317 return b.booleanValue();
1318 }
1319
1320 if (entry.endsWith(".class")) {
1321 int i = entry.lastIndexOf('$');
1322 while (i >= 0) {
1323 String outer = entry.substring(0, i) + ".class";
1324 if ((b = (Boolean) namePrefs.get(outer)) != null) {
1325 return b.booleanValue();
1326 }
1327 i = entry.lastIndexOf('$', i - 1);
1328 }
1329 }
1330
1331 int i = entry.lastIndexOf('/');
1332 if (i >= 0) {
1333 String base = entry.substring(0, i);
1334 if ((b = (Boolean) packagePrefs.get(base)) != null) {
1335 return b.booleanValue();
1336 }
1337
1338 for (;;) {
1339 if ((b = (Boolean) subtreePrefs.get(base)) != null) {
1340 return b.booleanValue();
1341 }
1342 if ((i = base.lastIndexOf('/')) < 0) {
1343 break;
1344 }
1345 base = base.substring(0, i);
1346 }
1347 }
1348
1349 return defaultPref;
1350 }
1351
1352
1353
1354
1355
1356 private static String nextNonBlankLine(BufferedReader reader)
1357 throws IOException
1358 {
1359 String s;
1360 while ((s = reader.readLine()) != null) {
1361 s = s.trim();
1362 if (s.length() > 0 && s.charAt(0) != '#') {
1363 return s;
1364 }
1365 }
1366 return null;
1367 }
1368 }
1369
1370
1371
1372
1373 private static class PreferredListWriter {
1374
1375 private static final int NAME_LEN = "Name: ".length();
1376 private static final int PREFERRED_LEN = "Preferred: ".length();
1377 private static final int TRUE_LEN = "true".length();
1378 private static final int FALSE_LEN = "false".length();
1379 private static final int NEWLINE_LEN = "\n".length();
1380
1381 private final HashMap pathMap = new HashMap();
1382 private final DirNode rootNode = new DirNode("");
1383 private int numPrefs = 0;
1384 private final List apiClasses;
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394 PreferredListWriter(List apiClasses) {
1395 this.apiClasses = apiClasses;
1396 pathMap.put("", rootNode);
1397 }
1398
1399
1400
1401
1402
1403 void addEntries(JarFile jar, PreferredListReader prefReader)
1404 throws IOException
1405 {
1406 for (Enumeration e = jar.entries(); e.hasMoreElements(); ) {
1407 String path = ((JarEntry) e.nextElement()).getName();
1408 if (!(path.startsWith("META-INF") || path.endsWith("/"))) {
1409 boolean pref = prefReader.isPreferred(path);
1410 if (logger.isLoggable(Level.FINEST)) {
1411 logger.log(
1412 Level.FINEST,
1413 pref ? "preferred: {0}" : "not preferred: {0}",
1414 new Object[]{ path });
1415 }
1416 addFile(path, jar.getName(), pref);
1417 }
1418 }
1419 }
1420
1421
1422
1423
1424 void write(JarOutputStream jout) throws IOException {
1425 if (numPrefs == 0) {
1426 logger.finer("omitting empty preferred list");
1427 return;
1428 }
1429 logger.finer("writing preferred list");
1430
1431 jout.putNextEntry(new JarEntry("META-INF/PREFERRED.LIST"));
1432 Writer w =
1433 new BufferedWriter(new OutputStreamWriter(jout, "UTF8"));
1434 w.write("PreferredResources-Version: 1.0\n");
1435
1436 rootNode.compileList();
1437 rootNode.writeList(w);
1438
1439 w.flush();
1440 jout.closeEntry();
1441 }
1442
1443
1444
1445
1446 private void addFile(String path, String jarFileName, boolean preferred)
1447 throws IOException
1448 {
1449 FileNode fn = (FileNode) pathMap.get(path);
1450 if (fn != null) {
1451 if (fn.preferred != preferred) {
1452
1453
1454
1455 if (apiClasses.contains(path)) {
1456 if (fn.preferred) {
1457 fn.preferred = false;
1458 numPrefs--;
1459 }
1460 }
1461 else {
1462 throw new LocalizedIOException(
1463 "jarwrapper.prefconflict",
1464 new Object[] { path, jarFileName, fn.jarFileName });
1465 }
1466 }
1467 return;
1468 }
1469
1470 fn = new FileNode(path, jarFileName, preferred);
1471 pathMap.put(path, fn);
1472 if (preferred) {
1473 numPrefs++;
1474 }
1475
1476 path = parentPath(path);
1477 DirNode dn = (DirNode) pathMap.get(path);
1478 if (dn != null) {
1479 dn.files.add(fn);
1480 return;
1481 }
1482 dn = new DirNode(path);
1483 pathMap.put(path, dn);
1484 dn.files.add(fn);
1485
1486 for (path = parentPath(path); ; path = parentPath(path)) {
1487 DirNode pn = (DirNode) pathMap.get(path);
1488 if (pn != null) {
1489 pn.subdirs.add(dn);
1490 return;
1491 }
1492 pn = new DirNode(path);
1493 pathMap.put(path, pn);
1494 pn.subdirs.add(dn);
1495 dn = pn;
1496 }
1497 }
1498
1499
1500
1501
1502 private static String parentPath(String path) {
1503 if (path.endsWith("/")) {
1504 path = path.substring(0, path.length() - 1);
1505 }
1506 int i = path.lastIndexOf('/');
1507 return (i >= 0) ? path.substring(0, i + 1) : "";
1508 }
1509
1510 static int min(int i1, int i2, int i3) {
1511 return Math.min(i1, Math.min(i2, i3));
1512 }
1513
1514
1515
1516
1517
1518
1519
1520 static int calcEntryLength(String name, boolean pref) {
1521 int len = NEWLINE_LEN;
1522 if (name != null) {
1523 len += NAME_LEN + name.length() + NEWLINE_LEN;
1524 }
1525 len += PREFERRED_LEN + (pref ? TRUE_LEN : FALSE_LEN) + NEWLINE_LEN;
1526 return len;
1527 }
1528
1529
1530
1531
1532
1533
1534 static void writeEntry(Writer w, String name, boolean pref)
1535 throws IOException
1536 {
1537 if (logger.isLoggable(Level.FINEST)) {
1538 logger.log(
1539 Level.FINEST,
1540 "writing preferred list entry {0}: {1}",
1541 new Object[]{
1542 (name != null) ? name : "<default>",
1543 Boolean.valueOf(pref) });
1544 }
1545 w.write("\n");
1546 if (name != null) {
1547 w.write("Name: " + name + "\n");
1548 }
1549 w.write("Preferred: " + pref + "\n");
1550 }
1551
1552
1553
1554
1555 private static class FileNode {
1556
1557
1558 static final int NONE = 0;
1559 static final int SKIP = 1;
1560 static final int INCLUDE = 2;
1561
1562 final String path;
1563 final String jarFileName;
1564 boolean preferred;
1565 int action;
1566
1567 FileNode(String path, String jarFileName, boolean preferred) {
1568 this.path = path;
1569 this.preferred = preferred;
1570 this.jarFileName = jarFileName;
1571 }
1572 }
1573
1574
1575
1576
1577 private class DirNode {
1578
1579 final String path;
1580 final List subdirs = new ArrayList();
1581 final List files = new ArrayList();
1582
1583
1584
1585
1586
1587
1588 int prefSubtreeLen;
1589
1590
1591
1592
1593
1594
1595 int prefPackageLen;
1596
1597
1598
1599
1600
1601 int unprefSubtreeLen;
1602
1603
1604
1605
1606
1607
1608 int unprefPackageLen;
1609
1610 DirNode(String path) {
1611 this.path = path;
1612 }
1613
1614
1615
1616
1617 void compileList() {
1618 int prefLen = 0, unprefLen = 0;
1619 for (Iterator i = files.iterator(); i.hasNext(); ) {
1620 FileNode fn = (FileNode) i.next();
1621
1622 for (int j = fn.path.lastIndexOf('$');
1623 j != -1;
1624 j = fn.path.lastIndexOf('$', j - 1))
1625 {
1626 FileNode fn2 = (FileNode) pathMap.get(
1627 fn.path.substring(0, j) + ".class");
1628 if (fn2 != null) {
1629 fn.action = (fn.preferred == fn2.preferred) ?
1630 FileNode.SKIP : FileNode.INCLUDE;
1631 break;
1632 }
1633 }
1634
1635 int entryLen = calcEntryLength(fn.path, fn.preferred);
1636 if (fn.action == FileNode.SKIP) {
1637
1638 } else if (fn.action == FileNode.INCLUDE) {
1639 prefLen += entryLen;
1640 unprefLen += entryLen;
1641 } else if (fn.preferred) {
1642 unprefLen += entryLen;
1643 } else {
1644 prefLen += entryLen;
1645 }
1646 }
1647 prefSubtreeLen = prefLen;
1648 prefPackageLen = prefLen;
1649 unprefSubtreeLen = unprefLen;
1650 unprefPackageLen = unprefLen;
1651
1652 for (Iterator i = subdirs.iterator(); i.hasNext();) {
1653 DirNode dn = (DirNode) i.next();
1654 dn.compileList();
1655 String subtreePath = dn.path + "-";
1656
1657 prefSubtreeLen += min(
1658 dn.prefSubtreeLen,
1659 dn.unprefSubtreeLen +
1660 calcEntryLength(subtreePath, false),
1661 dn.unprefPackageLen + calcEntryLength(dn.path, false));
1662 prefPackageLen += min(
1663 dn.prefSubtreeLen + calcEntryLength(subtreePath, true),
1664 dn.prefPackageLen + calcEntryLength(dn.path, true),
1665 dn.unprefSubtreeLen);
1666 unprefSubtreeLen += min(
1667 dn.prefSubtreeLen + calcEntryLength(subtreePath, true),
1668 dn.prefPackageLen + calcEntryLength(dn.path, true),
1669 dn.unprefSubtreeLen);
1670 unprefPackageLen += min(
1671 dn.prefSubtreeLen,
1672 dn.unprefSubtreeLen +
1673 calcEntryLength(subtreePath, false),
1674 dn.unprefPackageLen + calcEntryLength(dn.path, false));
1675 }
1676 }
1677
1678
1679
1680
1681
1682 void writeList(Writer w) throws IOException {
1683 int totalPrefSubtreeLen =
1684 prefSubtreeLen + calcEntryLength(null, true);
1685 boolean defaultPref = totalPrefSubtreeLen < unprefSubtreeLen;
1686 if (defaultPref) {
1687 writeEntry(w, null, true);
1688 }
1689 writeFiles(w, defaultPref);
1690 for (Iterator i = subdirs.iterator(); i.hasNext();) {
1691 ((DirNode) i.next()).writeDir(w, defaultPref);
1692 }
1693 }
1694
1695
1696
1697
1698
1699 void writeDir(Writer w, boolean contextPref) throws IOException {
1700 boolean dirPref;
1701 boolean subdirPref;
1702 String subtreePath = path + "-";
1703 if (contextPref) {
1704 int totalUnprefPackageLen =
1705 unprefPackageLen + calcEntryLength(path, false);
1706 int totalUnprefSubtreeLen =
1707 unprefSubtreeLen + calcEntryLength(subtreePath, false);
1708 int best = min(
1709 prefSubtreeLen,
1710 totalUnprefPackageLen,
1711 totalUnprefSubtreeLen);
1712 if (best == prefSubtreeLen) {
1713 dirPref = true;
1714 subdirPref = true;
1715 } else if (best == totalUnprefPackageLen) {
1716 writeEntry(w, path, false);
1717 dirPref = false;
1718 subdirPref = true;
1719 } else {
1720 writeEntry(w, subtreePath, false);
1721 dirPref = false;
1722 subdirPref = false;
1723 }
1724 } else {
1725 int totalPrefPackageLen =
1726 prefPackageLen + calcEntryLength(path, true);
1727 int totalPrefSubtreeLen =
1728 prefSubtreeLen + calcEntryLength(subtreePath, true);
1729 int best = min(
1730 unprefSubtreeLen,
1731 totalPrefPackageLen,
1732 totalPrefSubtreeLen);
1733 if (best == unprefSubtreeLen) {
1734 dirPref = false;
1735 subdirPref = false;
1736 } else if (best == totalPrefPackageLen) {
1737 writeEntry(w, path, true);
1738 dirPref = true;
1739 subdirPref = false;
1740 } else {
1741 writeEntry(w, subtreePath, true);
1742 dirPref = true;
1743 subdirPref = true;
1744 }
1745 }
1746 writeFiles(w, dirPref);
1747 for (Iterator i = subdirs.iterator(); i.hasNext(); ) {
1748 ((DirNode) i.next()).writeDir(w, subdirPref);
1749 }
1750 }
1751
1752
1753
1754
1755
1756 void writeFiles(Writer w, boolean contextPref) throws IOException {
1757 for (Iterator i = files.iterator(); i.hasNext(); ) {
1758 FileNode fn = (FileNode) i.next();
1759 if (fn.action != FileNode.SKIP &&
1760 (fn.action == FileNode.INCLUDE ||
1761 fn.preferred != contextPref))
1762 {
1763 writeEntry(w, fn.path, fn.preferred);
1764 }
1765 }
1766 }
1767 }
1768 }
1769 }