blob: b2352f0172775f5102fae442c21ee04a195cd596 [file] [log] [blame]
Stuart McCulloch26e7a5a2011-10-17 10:31:43 +00001package aQute.lib.osgi;
2
3import java.io.*;
4import java.net.*;
5import java.util.*;
6import java.util.concurrent.*;
7import java.util.jar.*;
8import java.util.regex.*;
9
10import aQute.bnd.service.*;
11import aQute.lib.io.*;
12import aQute.libg.generics.*;
13import aQute.libg.header.*;
14import aQute.libg.reporter.*;
15
16public class Processor implements Reporter, Registry, Constants, Closeable {
17 static ThreadLocal<Processor> current = new ThreadLocal<Processor>();
18 static ExecutorService executor = Executors.newCachedThreadPool();
19 static Random random = new Random();
20
21 // TODO handle include files out of date
22 // TODO make splitter skip eagerly whitespace so trim is not necessary
23 public static String LIST_SPLITTER = "\\s*,\\s*";
24 final List<String> errors = new ArrayList<String>();
25 final List<String> warnings = new ArrayList<String>();
26 final Set<Object> basicPlugins = new HashSet<Object>();
27 final Set<Closeable> toBeClosed = new HashSet<Closeable>();
28 Set<Object> plugins;
29
30 boolean pedantic;
31 boolean trace;
32 boolean exceptions;
33 boolean fileMustExist = true;
34
35 private File base = new File("").getAbsoluteFile();
36
37 Properties properties;
38 private Macro replacer;
39 private long lastModified;
40 private File propertiesFile;
41 private boolean fixup = true;
42 long modified;
43 Processor parent;
44 Set<File> included;
45 CL pluginLoader;
46 Collection<String> filter;
47 HashSet<String> missingCommand;
48
49 public Processor() {
50 properties = new Properties();
51 }
52
53 public Processor(Properties parent) {
54 properties = new Properties(parent);
55 }
56
57 public Processor(Processor parent) {
58 this(parent.properties);
59 this.parent = parent;
60 }
61
62 public void setParent(Processor processor) {
63 this.parent = processor;
64 Properties ext = new Properties(processor.properties);
65 ext.putAll(this.properties);
66 this.properties = ext;
67 }
68
69 public Processor getParent() {
70 return parent;
71 }
72
73 public Processor getTop() {
74 if (parent == null)
75 return this;
76 else
77 return parent.getTop();
78 }
79
80 public void getInfo(Processor processor, String prefix) {
81 if (isFailOk())
82 addAll(warnings, processor.getErrors(), prefix);
83 else
84 addAll(errors, processor.getErrors(), prefix);
85 addAll(warnings, processor.getWarnings(), prefix);
86
87 processor.errors.clear();
88 processor.warnings.clear();
89 }
90
91 public void getInfo(Processor processor) {
92 getInfo(processor, "");
93 }
94
95 private <T> void addAll(List<String> to, List<? extends T> from, String prefix) {
96 for (T x : from) {
97 to.add(prefix + x);
98 }
99 }
100
101 /**
102 * A processor can mark itself current for a thread.
103 *
104 * @return
105 */
106 private Processor current() {
107 Processor p = current.get();
108 if (p == null)
109 return this;
110 else
111 return p;
112 }
113
114 public void warning(String string, Object... args) {
115 Processor p = current();
116 String s = String.format(string, args);
117 if (!p.warnings.contains(s))
118 p.warnings.add(s);
119 }
120
121 public void error(String string, Object... args) {
122 Processor p = current();
123 if (p.isFailOk())
124 p.warning(string, args);
125 else {
126 String s = String.format(string, args);
127 if (!p.errors.contains(s))
128 p.errors.add(s);
129 }
130 }
131
132 public void error(String string, Throwable t, Object... args) {
133 Processor p = current();
134
135 if (p.isFailOk())
136 p.warning(string + ": " + t, args);
137 else {
138 p.errors.add("Exception: " + t.getMessage());
139 String s = String.format(string, args);
140 if (!p.errors.contains(s))
141 p.errors.add(s);
142 }
143 if (p.exceptions)
144 t.printStackTrace();
145 }
146
147 public List<String> getWarnings() {
148 return warnings;
149 }
150
151 public List<String> getErrors() {
152 return errors;
153 }
154
155 public Map<String, Map<String, String>> parseHeader(String value) {
156 return parseHeader(value, this);
157 }
158
159 /**
160 * Standard OSGi header parser.
161 *
162 * @param value
163 * @return
164 */
165 static public Map<String, Map<String, String>> parseHeader(String value, Processor logger) {
166 return OSGiHeader.parseHeader(value, logger);
167 }
168
169 Map<String, Map<String, String>> getClauses(String header) {
170 return parseHeader(getProperty(header));
171 }
172
173 public void addClose(Closeable jar) {
174 toBeClosed.add(jar);
175 }
176
177 /**
178 * Remove all entries from a map that start with a specific prefix
179 *
180 * @param <T>
181 * @param source
182 * @param prefix
183 * @return
184 */
185 static <T> Map<String, T> removeKeys(Map<String, T> source, String prefix) {
186 Map<String, T> temp = new TreeMap<String, T>(source);
187 for (Iterator<String> p = temp.keySet().iterator(); p.hasNext();) {
188 String pack = (String) p.next();
189 if (pack.startsWith(prefix))
190 p.remove();
191 }
192 return temp;
193 }
194
195 public void progress(String s, Object... args) {
196 trace(s, args);
197 }
198
199 public boolean isPedantic() {
200 return current().pedantic;
201 }
202
203 public void setPedantic(boolean pedantic) {
204 this.pedantic = pedantic;
205 }
206
207 public static File getFile(File base, String file) {
208 return IO.getFile(base, file);
209 }
210
211 public File getFile(String file) {
212 return getFile(base, file);
213 }
214
215 /**
216 * Return a list of plugins that implement the given class.
217 *
218 * @param clazz
219 * Each returned plugin implements this class/interface
220 * @return A list of plugins
221 */
222 public <T> List<T> getPlugins(Class<T> clazz) {
223 List<T> l = new ArrayList<T>();
224 Set<Object> all = getPlugins();
225 for (Object plugin : all) {
226 if (clazz.isInstance(plugin))
227 l.add(clazz.cast(plugin));
228 }
229 return l;
230 }
231
232 /**
233 * Returns the first plugin it can find of the given type.
234 *
235 * @param <T>
236 * @param clazz
237 * @return
238 */
239 public <T> T getPlugin(Class<T> clazz) {
240 Set<Object> all = getPlugins();
241 for (Object plugin : all) {
242 if (clazz.isInstance(plugin))
243 return clazz.cast(plugin);
244 }
245 return null;
246 }
247
248 /**
249 * Return a list of plugins. Plugins are defined with the -plugin command.
250 * They are class names, optionally associated with attributes. Plugins can
251 * implement the Plugin interface to see these attributes.
252 *
253 * Any object can be a plugin.
254 *
255 * @return
256 */
257 protected synchronized Set<Object> getPlugins() {
258 if (this.plugins != null)
259 return this.plugins;
260
261 missingCommand = new HashSet<String>();
262 Set<Object> list = new LinkedHashSet<Object>();
263
264 // The owner of the plugin is always in there.
265 list.add(this);
266 setTypeSpecificPlugins(list);
267
268 if (parent != null)
269 list.addAll(parent.getPlugins());
270
271 // We only use plugins now when they are defined on our level
272 // and not if it is in our parent. We inherit from our parent
273 // through the previous block.
274
275 if (properties.containsKey(PLUGIN)) {
276 String spe = getProperty(PLUGIN);
277 if (spe.equals(NONE))
278 return new LinkedHashSet<Object>();
279
280 loadPlugins(list, spe);
281 }
282
283 return this.plugins = list;
284 }
285
286 /**
287 * @param list
288 * @param spe
289 */
290 protected void loadPlugins(Set<Object> list, String spe) {
291 Map<String, Map<String, String>> plugins = parseHeader(spe);
292 for (Map.Entry<String, Map<String, String>> entry : plugins.entrySet()) {
293 String key = (String) entry.getKey();
294
295 try {
296 CL loader = getLoader();
297 String path = entry.getValue().get(PATH_DIRECTIVE);
298 if (path != null) {
299 String parts[] = path.split("\\s*,\\s*");
300 for (String p : parts) {
301 File f = getFile(p).getAbsoluteFile();
302 loader.add(f.toURI().toURL());
303 }
304 }
305
306 trace("Using plugin %s", key);
307
308 // Plugins could use the same class with different
309 // parameters so we could have duplicate names Remove
310 // the ! added by the parser to make each name unique.
311 key = removeDuplicateMarker(key);
312
313 try {
314 Class<?> c = (Class<?>) loader.loadClass(key);
315 Object plugin = c.newInstance();
316 customize(plugin, entry.getValue());
317 list.add(plugin);
318 } catch (Throwable t) {
319 // We can defer the error if the plugin specifies
320 // a command name. In that case, we'll verify that
321 // a bnd file does not contain any references to a
322 // plugin
323 // command. The reason this feature was added was
324 // to compile plugin classes with the same build.
325 String commands = entry.getValue().get(COMMAND_DIRECTIVE);
326 if (commands == null)
327 error("Problem loading the plugin: %s exception: (%s)", key, t);
328 else {
329 Collection<String> cs = split(commands);
330 missingCommand.addAll(cs);
331 }
332 }
333 } catch (Throwable e) {
334 error("Problem loading the plugin: " + key + " exception: " + e);
335 }
336 }
337 }
338
339 protected void setTypeSpecificPlugins(Set<Object> list) {
340 list.add(executor);
341 list.add(random);
342 list.addAll(basicPlugins);
343 }
344
345 /**
346 * @param plugin
347 * @param entry
348 */
349 protected <T> T customize(T plugin, Map<String, String> map) {
350 if (plugin instanceof Plugin) {
351 if (map != null)
352 ((Plugin) plugin).setProperties(map);
353
354 ((Plugin) plugin).setReporter(this);
355 }
356 if (plugin instanceof RegistryPlugin) {
357 ((RegistryPlugin) plugin).setRegistry(this);
358 }
359 return plugin;
360 }
361
362 public boolean isFailOk() {
363 String v = getProperty(Analyzer.FAIL_OK, null);
364 return v != null && v.equalsIgnoreCase("true");
365 }
366
367 public File getBase() {
368 return base;
369 }
370
371 public void setBase(File base) {
372 this.base = base;
373 }
374
375 public void clear() {
376 errors.clear();
377 warnings.clear();
378 }
379
380 public void trace(String msg, Object... parms) {
381 Processor p = current();
382 if (p.trace) {
383 System.out.printf("# " + msg + "\n", parms);
384 }
385 }
386
387 public <T> List<T> newList() {
388 return new ArrayList<T>();
389 }
390
391 public <T> Set<T> newSet() {
392 return new TreeSet<T>();
393 }
394
395 public static <K, V> Map<K, V> newMap() {
396 return new LinkedHashMap<K, V>();
397 }
398
399 public static <K, V> Map<K, V> newHashMap() {
400 return new HashMap<K, V>();
401 }
402
403 public <T> List<T> newList(Collection<T> t) {
404 return new ArrayList<T>(t);
405 }
406
407 public <T> Set<T> newSet(Collection<T> t) {
408 return new TreeSet<T>(t);
409 }
410
411 public <K, V> Map<K, V> newMap(Map<K, V> t) {
412 return new LinkedHashMap<K, V>(t);
413 }
414
415 public void close() {
416 for (Closeable c : toBeClosed) {
417 try {
418 c.close();
419 } catch (IOException e) {
420 // Who cares?
421 }
422 }
423 toBeClosed.clear();
424 }
425
426 public String _basedir(String args[]) {
427 if (base == null)
428 throw new IllegalArgumentException("No base dir set");
429
430 return base.getAbsolutePath();
431 }
432
433 /**
434 * Property handling ...
435 *
436 * @return
437 */
438
439 public Properties getProperties() {
440 if (fixup) {
441 fixup = false;
442 begin();
443 }
444
445 return properties;
446 }
447
448 public String getProperty(String key) {
449 return getProperty(key, null);
450 }
451
452 public void mergeProperties(File file, boolean override) {
453 if (file.isFile()) {
454 try {
455 Properties properties = loadProperties(file);
456 mergeProperties(properties, override);
457 } catch (Exception e) {
458 error("Error loading properties file: " + file);
459 }
460 } else {
461 if (!file.exists())
462 error("Properties file does not exist: " + file);
463 else
464 error("Properties file must a file, not a directory: " + file);
465 }
466 }
467
468 public void mergeProperties(Properties properties, boolean override) {
469 for (Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();) {
470 String key = (String) e.nextElement();
471 String value = properties.getProperty(key);
472 if (override || !getProperties().containsKey(key))
473 setProperty(key, value);
474 }
475 }
476
477 public void setProperties(Properties properties) {
478 doIncludes(getBase(), properties);
479 this.properties.putAll(properties);
480 }
481
482 public void addProperties(File file) throws Exception {
483 addIncluded(file);
484 Properties p = loadProperties(file);
485 setProperties(p);
486 }
487
488 public synchronized void addIncluded(File file) {
489 if (included == null)
490 included = new HashSet<File>();
491 included.add(file);
492 }
493
494 /**
495 * Inspect the properties and if you find -includes parse the line included
496 * manifest files or properties files. The files are relative from the given
497 * base, this is normally the base for the analyzer.
498 *
499 * @param ubase
500 * @param p
501 * @param done
502 * @throws IOException
503 * @throws IOException
504 */
505
506 private void doIncludes(File ubase, Properties p) {
507 String includes = p.getProperty(INCLUDE);
508 if (includes != null) {
509 includes = getReplacer().process(includes);
510 p.remove(INCLUDE);
511 Collection<String> clauses = parseHeader(includes).keySet();
512
513 for (String value : clauses) {
514 boolean fileMustExist = true;
515 boolean overwrite = true;
516 while (true) {
517 if (value.startsWith("-")) {
518 fileMustExist = false;
519 value = value.substring(1).trim();
520 } else if (value.startsWith("~")) {
521 // Overwrite properties!
522 overwrite = false;
523 value = value.substring(1).trim();
524 } else
525 break;
526 }
527 try {
528 File file = getFile(ubase, value).getAbsoluteFile();
529 if (!file.isFile() && fileMustExist) {
530 error("Included file " + file
531 + (file.exists() ? " does not exist" : " is directory"));
532 } else
533 doIncludeFile(file, overwrite, p);
534 } catch (Exception e) {
535 if (fileMustExist)
536 error("Error in processing included file: " + value, e);
537 }
538 }
539 }
540 }
541
542 /**
543 * @param file
544 * @param parent
545 * @param done
546 * @param overwrite
547 * @throws FileNotFoundException
548 * @throws IOException
549 */
550 public void doIncludeFile(File file, boolean overwrite, Properties target) throws Exception {
551 if (included != null && included.contains(file)) {
552 error("Cyclic or multiple include of " + file);
553 } else {
554 addIncluded(file);
555 updateModified(file.lastModified(), file.toString());
556 InputStream in = new FileInputStream(file);
557 Properties sub;
558 if (file.getName().toLowerCase().endsWith(".mf")) {
559 sub = getManifestAsProperties(in);
560 } else
561 sub = loadProperties(in, file.getAbsolutePath());
562
563 in.close();
564
565 doIncludes(file.getParentFile(), sub);
566 // make sure we do not override properties
567 for (Map.Entry<?, ?> entry : sub.entrySet()) {
568 if (overwrite || !target.containsKey(entry.getKey()))
569 target.setProperty((String) entry.getKey(), (String) entry.getValue());
570 }
571 }
572 }
573
574 public void unsetProperty(String string) {
575 getProperties().remove(string);
576
577 }
578
579 public boolean refresh() {
580 plugins = null; // We always refresh our plugins
581
582 if (propertiesFile == null)
583 return false;
584
585 updateModified(propertiesFile.lastModified(), "properties file");
586 boolean changed = false;
587 if (included != null) {
588 for (File file : included) {
589
590 if (file.exists() == false || file.lastModified() > modified) {
591 updateModified(file.lastModified(), "include file: " + file);
592 changed = true;
593 }
594 }
595 }
596
597 // System.out.println("Modified " + modified + " file: "
598 // + propertiesFile.lastModified() + " diff "
599 // + (modified - propertiesFile.lastModified()));
600
601 // Date last = new Date(propertiesFile.lastModified());
602 // Date current = new Date(modified);
603 changed |= modified < propertiesFile.lastModified();
604 if (changed) {
605 included = null;
606 properties.clear();
607 setProperties(propertiesFile, base);
608 propertiesChanged();
609 return true;
610 }
611 return false;
612 }
613
614 public void propertiesChanged() {
615 }
616
617 /**
618 * Set the properties by file. Setting the properties this way will also set
619 * the base for this analyzer. After reading the properties, this will call
620 * setProperties(Properties) which will handle the includes.
621 *
622 * @param propertiesFile
623 * @throws FileNotFoundException
624 * @throws IOException
625 */
626 public void setProperties(File propertiesFile) throws IOException {
627 propertiesFile = propertiesFile.getAbsoluteFile();
628 setProperties(propertiesFile, propertiesFile.getParentFile());
629 }
630
631 public void setProperties(File propertiesFile, File base) {
632 this.propertiesFile = propertiesFile.getAbsoluteFile();
633 setBase(base);
634 try {
635 if (propertiesFile.isFile()) {
636 // System.out.println("Loading properties " + propertiesFile);
637 long modified = propertiesFile.lastModified();
638 if (modified > System.currentTimeMillis() + 100) {
639 System.out.println("Huh? This is in the future " + propertiesFile);
640 this.modified = System.currentTimeMillis();
641 } else
642 this.modified = modified;
643
644 included = null;
645 Properties p = loadProperties(propertiesFile);
646 setProperties(p);
647 } else {
648 if (fileMustExist) {
649 error("No such properties file: " + propertiesFile);
650 }
651 }
652 } catch (IOException e) {
653 error("Could not load properties " + propertiesFile);
654 }
655 }
656
657 protected void begin() {
658 if (isTrue(getProperty(PEDANTIC)))
659 setPedantic(true);
660 }
661
662 public static boolean isTrue(String value) {
663 if (value == null)
664 return false;
665
666 return !"false".equalsIgnoreCase(value);
667 }
668
669 /**
670 * Get a property with a proper default
671 *
672 * @param headerName
673 * @param deflt
674 * @return
675 */
676 public String getProperty(String key, String deflt) {
677 String value = null;
678 Processor source = this;
679
680 if (filter != null && filter.contains(key)) {
681 value = (String) getProperties().get(key);
682 } else {
683 while (source != null) {
684 value = (String) source.getProperties().get(key);
685 if (value != null)
686 break;
687
688 source = source.getParent();
689 }
690 }
691
692 if (value != null)
693 return getReplacer().process(value, source);
694 else if (deflt != null)
695 return getReplacer().process(deflt, this);
696 else
697 return null;
698 }
699
700 /**
701 * Helper to load a properties file from disk.
702 *
703 * @param file
704 * @return
705 * @throws IOException
706 */
707 public Properties loadProperties(File file) throws IOException {
708 updateModified(file.lastModified(), "Properties file: " + file);
709 InputStream in = new FileInputStream(file);
710 Properties p = loadProperties(in, file.getAbsolutePath());
711 in.close();
712 return p;
713 }
714
715 Properties loadProperties(InputStream in, String name) throws IOException {
716 int n = name.lastIndexOf('/');
717 if (n > 0)
718 name = name.substring(0, n);
719 if (name.length() == 0)
720 name = ".";
721
722 try {
723 Properties p = new Properties();
724 p.load(in);
725 return replaceAll(p, "\\$\\{\\.\\}", name);
726 } catch (Exception e) {
727 error("Error during loading properties file: " + name + ", error:" + e);
728 return new Properties();
729 }
730 }
731
732 /**
733 * Replace a string in all the values of the map. This can be used to
734 * preassign variables that change. I.e. the base directory ${.} for a
735 * loaded properties
736 */
737
738 public static Properties replaceAll(Properties p, String pattern, String replacement) {
739 Properties result = new Properties();
740 for (Iterator<Map.Entry<Object, Object>> i = p.entrySet().iterator(); i.hasNext();) {
741 Map.Entry<Object, Object> entry = i.next();
742 String key = (String) entry.getKey();
743 String value = (String) entry.getValue();
744 value = value.replaceAll(pattern, replacement);
745 result.put(key, value);
746 }
747 return result;
748 }
749
750 /**
751 * Merge the attributes of two maps, where the first map can contain
752 * wildcarded names. The idea is that the first map contains patterns (for
753 * example *) with a set of attributes. These patterns are matched against
754 * the found packages in actual. If they match, the result is set with the
755 * merged set of attributes. It is expected that the instructions are
756 * ordered so that the instructor can define which pattern matches first.
757 * Attributes in the instructions override any attributes from the actual.<br/>
758 *
759 * A pattern is a modified regexp so it looks like globbing. The * becomes a
760 * .* just like the ? becomes a .?. '.' are replaced with \\. Additionally,
761 * if the pattern starts with an exclamation mark, it will remove that
762 * matches for that pattern (- the !) from the working set. So the following
763 * patterns should work:
764 * <ul>
765 * <li>com.foo.bar</li>
766 * <li>com.foo.*</li>
767 * <li>com.foo.???</li>
768 * <li>com.*.[^b][^a][^r]</li>
769 * <li>!com.foo.* (throws away any match for com.foo.*)</li>
770 * </ul>
771 * Enough rope to hang the average developer I would say.
772 *
773 *
774 * @param instructions
775 * the instructions with patterns. A
776 * @param actual
777 * the actual found packages
778 */
779
780 public static Map<String, Map<String, String>> merge(String type,
781 Map<String, Map<String, String>> instructions, Map<String, Map<String, String>> actual,
782 Set<String> superfluous, Map<String, Map<String, String>> ignored) {
783 Map<String, Map<String, String>> toVisit = new HashMap<String, Map<String, String>>(actual); // we
784 // do
785 // not
786 // want
787 // to
788 // ruin
789 // our
790 // original
791 Map<String, Map<String, String>> result = newMap();
792 for (Iterator<String> i = instructions.keySet().iterator(); i.hasNext();) {
793 String instruction = i.next();
794 String originalInstruction = instruction;
795
796 Map<String, String> instructedAttributes = instructions.get(instruction);
797
798 // Check if we have a fixed (starts with '=') or a
799 // duplicate name. A fixed name is added to the output without
800 // checking against the contents. Duplicates are marked
801 // at the end. In that case we do not pick up any contained
802 // information but just add them to the output including the
803 // marker.
804 if (instruction.startsWith("=")) {
805 result.put(instruction.substring(1), instructedAttributes);
806 superfluous.remove(originalInstruction);
807 continue;
808 }
809 if (isDuplicate(instruction)) {
810 result.put(instruction, instructedAttributes);
811 superfluous.remove(originalInstruction);
812 continue;
813 }
814
815 Instruction instr = Instruction.getPattern(instruction);
816
817 for (Iterator<String> p = toVisit.keySet().iterator(); p.hasNext();) {
818 String packageName = p.next();
819
820 if (instr.matches(packageName)) {
821 superfluous.remove(originalInstruction);
822 if (!instr.isNegated()) {
823 Map<String, String> newAttributes = new HashMap<String, String>();
824 newAttributes.putAll(actual.get(packageName));
825 newAttributes.putAll(instructedAttributes);
826 result.put(packageName, newAttributes);
827 } else if (ignored != null) {
828 ignored.put(packageName, new HashMap<String, String>());
829 }
830 p.remove(); // Can never match again for another pattern
831 }
832 }
833
834 }
835 return result;
836 }
837
838 /**
839 * Print a standard Map based OSGi header.
840 *
841 * @param exports
842 * map { name => Map { attribute|directive => value } }
843 * @return the clauses
844 */
845 public static String printClauses(Map<String, Map<String, String>> exports) {
846 return printClauses(exports, false);
847 }
848
849 public static String printClauses(Map<String, Map<String, String>> exports,boolean checkMultipleVersions) {
850 StringBuffer sb = new StringBuffer();
851 String del = "";
852 for (Iterator<String> i = exports.keySet().iterator(); i.hasNext();) {
853 String name = i.next();
854 Map<String, String> clause = exports.get(name);
855
856 // We allow names to be duplicated in the input
857 // by ending them with '~'. This is necessary to use
858 // the package names as keys. However, we remove these
859 // suffixes in the output so that you can set multiple
860 // exports with different attributes.
861 String outname = removeDuplicateMarker(name);
862 sb.append(del);
863 sb.append(outname);
864 printClause(clause, sb);
865 del = ",";
866 }
867 return sb.toString();
868 }
869
870 public static void printClause(Map<String, String> map,
871 StringBuffer sb) {
872
873 for (Iterator<String> j = map.keySet().iterator(); j.hasNext();) {
874 String key = j.next();
875
876 // Skip directives we do not recognize
877 if (key.equals(NO_IMPORT_DIRECTIVE) || key.equals(PROVIDE_DIRECTIVE) || key.equals(SPLIT_PACKAGE_DIRECTIVE) || key.equals(FROM_DIRECTIVE))
878 continue;
879
880 String value = ((String) map.get(key)).trim();
881 sb.append(";");
882 sb.append(key);
883 sb.append("=");
884
885 boolean clean = (value.length() >= 2 && value.charAt(0) == '"' && value.charAt(value
886 .length() - 1) == '"') || Verifier.TOKEN.matcher(value).matches();
887 if (!clean)
888 sb.append("\"");
889 sb.append(value);
890 if (!clean)
891 sb.append("\"");
892 }
893 }
894
895 public Macro getReplacer() {
896 if (replacer == null)
897 return replacer = new Macro(this, getMacroDomains());
898 else
899 return replacer;
900 }
901
902 /**
903 * This should be overridden by subclasses to add extra macro command
904 * domains on the search list.
905 *
906 * @return
907 */
908 protected Object[] getMacroDomains() {
909 return new Object[] {};
910 }
911
912 /**
913 * Return the properties but expand all macros. This always returns a new
914 * Properties object that can be used in any way.
915 *
916 * @return
917 */
918 public Properties getFlattenedProperties() {
919 return getReplacer().getFlattenedProperties();
920
921 }
922
923 public void updateModified(long time, String reason) {
924 if (time > lastModified) {
925 lastModified = time;
926 }
927 }
928
929 public long lastModified() {
930 return lastModified;
931 }
932
933 /**
934 * Add or override a new property.
935 *
936 * @param key
937 * @param value
938 */
939 public void setProperty(String key, String value) {
940 checkheader: for (int i = 0; i < headers.length; i++) {
941 if (headers[i].equalsIgnoreCase(value)) {
942 value = headers[i];
943 break checkheader;
944 }
945 }
946 getProperties().put(key, value);
947 }
948
949 /**
950 * Read a manifest but return a properties object.
951 *
952 * @param in
953 * @return
954 * @throws IOException
955 */
956 public static Properties getManifestAsProperties(InputStream in) throws IOException {
957 Properties p = new Properties();
958 Manifest manifest = new Manifest(in);
959 for (Iterator<Object> it = manifest.getMainAttributes().keySet().iterator(); it.hasNext();) {
960 Attributes.Name key = (Attributes.Name) it.next();
961 String value = manifest.getMainAttributes().getValue(key);
962 p.put(key.toString(), value);
963 }
964 return p;
965 }
966
967 public File getPropertiesFile() {
968 return propertiesFile;
969 }
970
971 public void setFileMustExist(boolean mustexist) {
972 fileMustExist = mustexist;
973 }
974
975 static public String read(InputStream in) throws Exception {
976 InputStreamReader ir = new InputStreamReader(in, "UTF8");
977 StringBuilder sb = new StringBuilder();
978
979 try {
980 char chars[] = new char[1000];
981 int size = ir.read(chars);
982 while (size > 0) {
983 sb.append(chars, 0, size);
984 size = ir.read(chars);
985 }
986 } finally {
987 ir.close();
988 }
989 return sb.toString();
990 }
991
992 /**
993 * Join a list.
994 *
995 * @param args
996 * @return
997 */
998 public static String join(Collection<?> list, String delimeter) {
999 return join(delimeter, list);
1000 }
1001
1002 public static String join(String delimeter, Collection<?>... list) {
1003 StringBuilder sb = new StringBuilder();
1004 String del = "";
1005 for (Collection<?> l : list) {
1006 if (list != null) {
1007 for (Object item : l) {
1008 sb.append(del);
1009 sb.append(item);
1010 del = delimeter;
1011 }
1012 }
1013 }
1014 return sb.toString();
1015 }
1016
1017 public static String join(Object[] list, String delimeter) {
1018 if (list == null)
1019 return "";
1020 StringBuilder sb = new StringBuilder();
1021 String del = "";
1022 for (Object item : list) {
1023 sb.append(del);
1024 sb.append(item);
1025 del = delimeter;
1026 }
1027 return sb.toString();
1028 }
1029
1030 public static String join(Collection<?>... list) {
1031 return join(",", list);
1032 }
1033
1034 public static <T> String join(T list[]) {
1035 return join(list, ",");
1036 }
1037
1038 public static void split(String s, Collection<String> set) {
1039
1040 String elements[] = s.trim().split(LIST_SPLITTER);
1041 for (String element : elements) {
1042 if (element.length() > 0)
1043 set.add(element);
1044 }
1045 }
1046
1047 public static Collection<String> split(String s) {
1048 return split(s, LIST_SPLITTER);
1049 }
1050
1051 public static Collection<String> split(String s, String splitter) {
1052 if (s != null)
1053 s = s.trim();
1054 if (s == null || s.trim().length() == 0)
1055 return Collections.emptyList();
1056
1057 return Arrays.asList(s.split(splitter));
1058 }
1059
1060 public static String merge(String... strings) {
1061 ArrayList<String> result = new ArrayList<String>();
1062 for (String s : strings) {
1063 if (s != null)
1064 split(s, result);
1065 }
1066 return join(result);
1067 }
1068
1069 public boolean isExceptions() {
1070 return exceptions;
1071 }
1072
1073 public void setExceptions(boolean exceptions) {
1074 this.exceptions = exceptions;
1075 }
1076
1077 /**
1078 * Make the file short if it is inside our base directory, otherwise long.
1079 *
1080 * @param f
1081 * @return
1082 */
1083 public String normalize(String f) {
1084 if (f.startsWith(base.getAbsolutePath() + "/"))
1085 return f.substring(base.getAbsolutePath().length() + 1);
1086 else
1087 return f;
1088 }
1089
1090 public String normalize(File f) {
1091 return normalize(f.getAbsolutePath());
1092 }
1093
1094 public static String removeDuplicateMarker(String key) {
1095 int i = key.length() - 1;
1096 while (i >= 0 && key.charAt(i) == DUPLICATE_MARKER)
1097 --i;
1098
1099 return key.substring(0, i + 1);
1100 }
1101
1102 public static boolean isDuplicate(String name) {
1103 return name.length() > 0 && name.charAt(name.length() - 1) == DUPLICATE_MARKER;
1104 }
1105
1106 public void setTrace(boolean x) {
1107 trace = x;
1108 }
1109
1110 static class CL extends URLClassLoader {
1111
1112 CL() {
1113 super(new URL[0], Processor.class.getClassLoader());
1114 }
1115
1116 void add(URL url) {
1117 URL urls[] = getURLs();
1118 for (URL u : urls) {
1119 if (u.equals(url))
1120 return;
1121 }
1122 super.addURL(url);
1123 }
1124
1125 public Class<?> loadClass(String name) throws NoClassDefFoundError {
1126 try {
1127 Class<?> c = super.loadClass(name);
1128 return c;
1129 } catch (Throwable t) {
1130 StringBuilder sb = new StringBuilder();
1131 sb.append(name);
1132 sb.append(" not found, parent: ");
1133 sb.append(getParent());
1134 sb.append(" urls:");
1135 sb.append(Arrays.toString(getURLs()));
1136 sb.append(" exception:");
1137 sb.append(t);
1138 throw new NoClassDefFoundError(sb.toString());
1139 }
1140 }
1141 }
1142
1143 private CL getLoader() {
1144 if (pluginLoader == null) {
1145 pluginLoader = new CL();
1146 }
1147 return pluginLoader;
1148 }
1149
1150 /*
1151 * Check if this is a valid project.
1152 */
1153 public boolean exists() {
1154 return base != null && base.isDirectory() && propertiesFile != null
1155 && propertiesFile.isFile();
1156 }
1157
1158 public boolean isOk() {
1159 return isFailOk() || (getErrors().size() == 0);
1160 }
1161
1162 public boolean isPerfect() {
1163 return getErrors().size() == 0 && getWarnings().size() == 0;
1164 }
1165
1166 public void setForceLocal(Collection<String> local) {
1167 filter = local;
1168 }
1169
1170 /**
1171 * Answer if the name is a missing plugin's command name. If a bnd file
1172 * contains the command name of a plugin, and that plugin is not available,
1173 * then an error is reported during manifest calculation. This allows the
1174 * plugin to fail to load when it is not needed.
1175 *
1176 * We first get the plugins to ensure it is properly initialized.
1177 *
1178 * @param name
1179 * @return
1180 */
1181 public boolean isMissingPlugin(String name) {
1182 getPlugins();
1183 return missingCommand != null && missingCommand.contains(name);
1184 }
1185
1186 /**
1187 * Append two strings to for a path in a ZIP or JAR file. It is guaranteed
1188 * to return a string that does not start, nor ends with a '/', while it is
1189 * properly separated with slashes. Double slashes are properly removed.
1190 *
1191 * <pre>
1192 * &quot;/&quot; + &quot;abc/def/&quot; becomes &quot;abc/def&quot;
1193 *
1194 * &#064;param prefix
1195 * &#064;param suffix
1196 * &#064;return
1197 *
1198 */
1199 public static String appendPath(String... parts) {
1200 StringBuilder sb = new StringBuilder();
1201 boolean lastSlash = true;
1202 for (String part : parts) {
1203 for (int i = 0; i < part.length(); i++) {
1204 char c = part.charAt(i);
1205 if (c == '/') {
1206 if (!lastSlash)
1207 sb.append('/');
1208 lastSlash = true;
1209 } else {
1210 sb.append(c);
1211 lastSlash = false;
1212 }
1213 }
1214 if (!lastSlash & sb.length() > 0) {
1215 sb.append('/');
1216 lastSlash = true;
1217 }
1218 }
1219 if (lastSlash && sb.length() > 0)
1220 sb.deleteCharAt(sb.length() - 1);
1221
1222 return sb.toString();
1223 }
1224
1225 /**
1226 * Parse the a=b strings and return a map of them.
1227 *
1228 * @param attrs
1229 * @param clazz
1230 * @return
1231 */
1232 public static Map<String, String> doAttrbutes(Object[] attrs, Clazz clazz, Macro macro) {
1233 if (attrs == null || attrs.length == 0)
1234 return Collections.emptyMap();
1235
1236 Map<String, String> map = newMap();
1237 for (Object a : attrs) {
1238 String attr = (String) a;
1239 int n = attr.indexOf("=");
1240 if (n > 0) {
1241 map.put(attr.substring(0, n), macro.process(attr.substring(n + 1)));
1242 } else
1243 throw new IllegalArgumentException(String.format(
1244 "Invalid attribute on package-info.java in %s , %s. Must be <key>=<name> ",
1245 clazz, attr));
1246 }
1247 return map;
1248 }
1249
1250 public static String append(String... strings) {
1251 List<String> result = Create.list();
1252 for (String s : strings) {
1253 result.addAll(split(s));
1254 }
1255 return join(result);
1256 }
1257
1258 public synchronized Class<?> getClass(String type, File jar) throws Exception {
1259 CL cl = getLoader();
1260 cl.add(jar.toURI().toURL());
1261 return cl.loadClass(type);
1262 }
1263
1264 public boolean isTrace() {
1265 return current().trace;
1266 }
1267
1268 public static long getDuration(String tm, long dflt) {
1269 if (tm == null)
1270 return dflt;
1271
1272 tm = tm.toUpperCase();
1273 TimeUnit unit = TimeUnit.MILLISECONDS;
1274 Matcher m = Pattern
1275 .compile(
1276 "\\s*(\\d+)\\s*(NANOSECONDS|MICROSECONDS|MILLISECONDS|SECONDS|MINUTES|HOURS|DAYS)?")
1277 .matcher(tm);
1278 if (m.matches()) {
1279 long duration = Long.parseLong(tm);
1280 String u = m.group(2);
1281 if (u != null)
1282 unit = TimeUnit.valueOf(u);
1283 duration = TimeUnit.MILLISECONDS.convert(duration, unit);
1284 return duration;
1285 }
1286 return dflt;
1287 }
1288
1289 /**
1290 * Generate a random string, which is guaranteed to be a valid Java
1291 * identifier (first character is an ASCII letter, subsequent characters are
1292 * ASCII letters or numbers). Takes an optional parameter for the length of
1293 * string to generate; default is 8 characters.
1294 */
1295 public String _random(String[] args) {
1296 int numchars = 8;
1297 if (args.length > 1) {
1298 try {
1299 numchars = Integer.parseInt(args[1]);
1300 } catch (NumberFormatException e) {
1301 throw new IllegalArgumentException(
1302 "Invalid character count parameter in ${random} macro.");
1303 }
1304 }
1305
1306 if (random == null)
1307 random = new Random();
1308
1309 char[] letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
1310 char[] alphanums = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
1311 .toCharArray();
1312
1313 char[] array = new char[numchars];
1314 for (int i = 0; i < numchars; i++) {
1315 char c;
1316 if (i == 0)
1317 c = letters[random.nextInt(letters.length)];
1318 else
1319 c = alphanums[random.nextInt(alphanums.length)];
1320 array[i] = c;
1321 }
1322
1323 return new String(array);
1324 }
1325
1326 /**
1327 * Set the current command thread. This must be balanced with the
1328 * {@link #end(Processor)} method. The method returns the previous command
1329 * owner or null.
1330 *
1331 * The command owner will receive all warnings and error reports.
1332 */
1333
1334 protected Processor beginHandleErrors(String message) {
1335 trace("begin %s", message);
1336 Processor previous = current.get();
1337 current.set(this);
1338 return previous;
1339 }
1340
1341 /**
1342 * End a command. Will restore the previous command owner.
1343 *
1344 * @param previous
1345 */
1346 protected void endHandleErrors(Processor previous) {
1347 trace("end");
1348 current.set(previous);
1349 }
1350
1351 public static Executor getExecutor() {
1352 return executor;
1353 }
1354
1355 /**
1356 * These plugins are added to the total list of plugins. The separation
1357 * is necessary because the list of plugins is refreshed now and then
1358 * so we need to be able to add them at any moment in time.
1359 *
1360 * @param plugin
1361 */
1362 public synchronized void addBasicPlugin(Object plugin) {
1363 basicPlugins.add(plugin);
1364 if (plugins != null)
1365 plugins.add(plugin);
1366 }
1367
1368 public synchronized void removeBasicPlugin(Object plugin) {
1369 basicPlugins.remove(plugin);
1370 if (plugins != null)
1371 plugins.remove(plugin);
1372 }
1373}