blob: 36c16ddd542140c25e67bab7c9c2600a5f81de6a [file] [log] [blame]
Stuart McCullochbb014372012-06-07 21:57:32 +00001package aQute.lib.osgi;
2
3import java.io.*;
4import java.lang.reflect.*;
5import java.net.*;
6import java.text.*;
7import java.util.*;
8import java.util.regex.*;
9
10import aQute.lib.collections.*;
11import aQute.lib.io.*;
12import aQute.libg.sed.*;
13import aQute.libg.version.*;
14
15/**
16 * Provide a macro processor. This processor can replace variables in strings
17 * based on a properties and a domain. The domain can implement functions that
18 * start with a "_" and take args[], the names of these functions are available
19 * as functions in the macro processor (without the _). Macros can nest to any
Stuart McCulloch2286f232012-06-15 13:27:53 +000020 * depth but may not contain loops. Add POSIX macros: ${#parameter} String
21 * length. ${parameter%word} Remove smallest suffix pattern. ${parameter%%word}
22 * Remove largest suffix pattern. ${parameter#word} Remove smallest prefix
23 * pattern. ${parameter##word} Remove largest prefix pattern.
Stuart McCullochbb014372012-06-07 21:57:32 +000024 */
25public class Macro implements Replacer {
26 Processor domain;
27 Object targets[];
28 boolean flattening;
29
30 public Macro(Processor domain, Object... targets) {
31 this.domain = domain;
32 this.targets = targets;
33 if (targets != null) {
34 for (Object o : targets) {
35 assert o != null;
36 }
37 }
38 }
39
40 public String process(String line, Processor source) {
41 return process(line, new Link(source, null, line));
42 }
43
44 String process(String line, Link link) {
45 StringBuilder sb = new StringBuilder();
46 process(line, 0, '\u0000', '\u0000', sb, link);
47 return sb.toString();
48 }
49
50 int process(CharSequence org, int index, char begin, char end, StringBuilder result, Link link) {
51 StringBuilder line = new StringBuilder(org);
52 int nesting = 1;
53
54 StringBuilder variable = new StringBuilder();
55 outer: while (index < line.length()) {
56 char c1 = line.charAt(index++);
57 if (c1 == end) {
58 if (--nesting == 0) {
59 result.append(replace(variable.toString(), link));
60 return index;
61 }
Stuart McCulloch2286f232012-06-15 13:27:53 +000062 } else if (c1 == begin)
63 nesting++;
64 else if (c1 == '\\' && index < line.length() - 1 && line.charAt(index) == '$') {
65 // remove the escape backslash and interpret the dollar
66 // as a
67 // literal
68 index++;
69 variable.append('$');
70 continue outer;
71 } else if (c1 == '$' && index < line.length() - 2) {
72 char c2 = line.charAt(index);
73 char terminator = getTerminator(c2);
74 if (terminator != 0) {
75 index = process(line, index + 1, c2, terminator, variable, link);
76 continue outer;
77 }
78 } else if (c1 == '.' && index < line.length() && line.charAt(index) == '/') {
79 // Found the sequence ./
80 if (index == 1 || Character.isWhitespace(line.charAt(index - 2))) {
81 // make sure it is preceded by whitespace or starts at begin
82 index++;
83 variable.append(domain.getBase().getAbsolutePath());
84 variable.append('/');
85 continue outer;
86 }
Stuart McCullochbb014372012-06-07 21:57:32 +000087 }
Stuart McCullochbb014372012-06-07 21:57:32 +000088 variable.append(c1);
89 }
90 result.append(variable);
91 return index;
92 }
93
94 public static char getTerminator(char c) {
95 switch (c) {
96 case '(' :
97 return ')';
98 case '[' :
99 return ']';
100 case '{' :
101 return '}';
102 case '<' :
103 return '>';
104 case '\u00ab' : // Guillemet double << >>
105 return '\u00bb';
106 case '\u2039' : // Guillemet single
107 return '\u203a';
108 }
109 return 0;
110 }
111
112 protected String replace(String key, Link link) {
113 if (link != null && link.contains(key))
114 return "${infinite:" + link.toString() + "}";
115
116 if (key != null) {
117 key = key.trim();
118 if (key.length() > 0) {
119 Processor source = domain;
120 String value = null;
121
122 if (key.indexOf(';') < 0) {
123 Instruction ins = new Instruction(key);
124 if (!ins.isLiteral()) {
125 SortedList<String> sortedList = SortedList.fromIterator(domain.iterator());
126 StringBuilder sb = new StringBuilder();
127 String del = "";
128 for (String k : sortedList) {
129 if (ins.matches(k)) {
130 String v = replace(k, new Link(source, link, key));
131 if (v != null) {
132 sb.append(del);
133 del = ",";
134 sb.append(v);
135 }
136 }
137 }
138 return sb.toString();
139 }
140 }
141 while (value == null && source != null) {
142 value = source.getProperties().getProperty(key);
143 source = source.getParent();
144 }
145
146 if (value != null)
147 return process(value, new Link(source, link, key));
148
149 value = doCommands(key, link);
150 if (value != null)
151 return process(value, new Link(source, link, key));
152
153 if (key != null && key.trim().length() > 0) {
154 value = System.getProperty(key);
155 if (value != null)
156 return value;
157 }
158 if (!flattening && !key.equals("@"))
159 domain.warning("No translation found for macro: " + key);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000160 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000161 domain.warning("Found empty macro key");
162 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000163 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000164 domain.warning("Found null macro key");
165 }
166 return "${" + key + "}";
167 }
168
169 /**
170 * Parse the key as a command. A command consist of parameters separated by
171 * ':'.
172 *
173 * @param key
174 * @return
175 */
176 static Pattern commands = Pattern.compile("(?<!\\\\);");
177
178 private String doCommands(String key, Link source) {
179 String[] args = commands.split(key);
180 if (args == null || args.length == 0)
181 return null;
182
183 for (int i = 0; i < args.length; i++)
184 if (args[i].indexOf('\\') >= 0)
185 args[i] = args[i].replaceAll("\\\\;", ";");
186
187 if (args[0].startsWith("^")) {
188 String varname = args[0].substring(1).trim();
189
190 Processor parent = source.start.getParent();
191 if (parent != null)
192 return parent.getProperty(varname);
193 else
194 return null;
195 }
196
197 Processor rover = domain;
198 while (rover != null) {
199 String result = doCommand(rover, args[0], args);
200 if (result != null)
201 return result;
202
203 rover = rover.getParent();
204 }
205
206 for (int i = 0; targets != null && i < targets.length; i++) {
207 String result = doCommand(targets[i], args[0], args);
208 if (result != null)
209 return result;
210 }
211
212 return doCommand(this, args[0], args);
213 }
214
215 private String doCommand(Object target, String method, String[] args) {
216 if (target == null)
217 ; // System.err.println("Huh? Target should never be null " +
218 // domain);
219 else {
220 String cname = "_" + method.replaceAll("-", "_");
221 try {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000222 Method m = target.getClass().getMethod(cname, new Class[] {
223 String[].class
224 });
225 return (String) m.invoke(target, new Object[] {
226 args
227 });
Stuart McCullochbb014372012-06-07 21:57:32 +0000228 }
229 catch (NoSuchMethodException e) {
230 // Ignore
231 }
232 catch (InvocationTargetException e) {
233 if (e.getCause() instanceof IllegalArgumentException) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000234 domain.error("%s, for cmd: %s, arguments; %s", e.getMessage(), method, Arrays.toString(args));
235 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000236 domain.warning("Exception in replace: " + e.getCause());
237 e.getCause().printStackTrace();
238 }
239 }
240 catch (Exception e) {
241 domain.warning("Exception in replace: " + e + " method=" + method);
242 e.printStackTrace();
243 }
244 }
245 return null;
246 }
247
248 /**
249 * Return a unique list where the duplicates are removed.
250 *
251 * @param args
252 * @return
253 */
254 static String _uniqHelp = "${uniq;<list> ...}";
255
256 public String _uniq(String args[]) {
257 verifyCommand(args, _uniqHelp, null, 1, Integer.MAX_VALUE);
258 Set<String> set = new LinkedHashSet<String>();
259 for (int i = 1; i < args.length; i++) {
260 Processor.split(args[i], set);
261 }
262 return Processor.join(set, ",");
263 }
264
265 public String _pathseparator(String args[]) {
266 return File.pathSeparator;
267 }
268
269 public String _separator(String args[]) {
270 return File.separator;
271 }
272
273 public String _filter(String args[]) {
274 return filter(args, false);
275 }
276
277 public String _filterout(String args[]) {
278 return filter(args, true);
279
280 }
281
282 static String _filterHelp = "${%s;<list>;<regex>}";
283
284 String filter(String[] args, boolean include) {
285 verifyCommand(args, String.format(_filterHelp, args[0]), null, 3, 3);
286
287 Collection<String> list = new ArrayList<String>(Processor.split(args[1]));
288 Pattern pattern = Pattern.compile(args[2]);
289
290 for (Iterator<String> i = list.iterator(); i.hasNext();) {
291 if (pattern.matcher(i.next()).matches() == include)
292 i.remove();
293 }
294 return Processor.join(list);
295 }
296
297 static String _sortHelp = "${sort;<list>...}";
298
299 public String _sort(String args[]) {
300 verifyCommand(args, _sortHelp, null, 2, Integer.MAX_VALUE);
301
302 List<String> result = new ArrayList<String>();
303 for (int i = 1; i < args.length; i++) {
304 Processor.split(args[i], result);
305 }
306 Collections.sort(result);
307 return Processor.join(result);
308 }
309
310 static String _joinHelp = "${join;<list>...}";
311
312 public String _join(String args[]) {
313
314 verifyCommand(args, _joinHelp, null, 1, Integer.MAX_VALUE);
315
316 List<String> result = new ArrayList<String>();
317 for (int i = 1; i < args.length; i++) {
318 Processor.split(args[i], result);
319 }
320 return Processor.join(result);
321 }
322
323 static String _ifHelp = "${if;<condition>;<iftrue> [;<iffalse>] }";
324
325 public String _if(String args[]) {
326 verifyCommand(args, _ifHelp, null, 3, 4);
327 String condition = args[1].trim();
328 if (!condition.equalsIgnoreCase("false"))
329 if (condition.length() != 0)
330 return args[2];
331
332 if (args.length > 3)
333 return args[3];
334 else
335 return "";
336 }
337
338 public String _now(String args[]) {
339 return new Date().toString();
340 }
341
342 public final static String _fmodifiedHelp = "${fmodified;<list of filenames>...}, return latest modification date";
343
344 public String _fmodified(String args[]) throws Exception {
345 verifyCommand(args, _fmodifiedHelp, null, 2, Integer.MAX_VALUE);
346
347 long time = 0;
348 Collection<String> names = new ArrayList<String>();
349 for (int i = 1; i < args.length; i++) {
350 Processor.split(args[i], names);
351 }
352 for (String name : names) {
353 File f = new File(name);
354 if (f.exists() && f.lastModified() > time)
355 time = f.lastModified();
356 }
357 return "" + time;
358 }
359
360 public String _long2date(String args[]) {
361 try {
362 return new Date(Long.parseLong(args[1])).toString();
363 }
364 catch (Exception e) {
365 e.printStackTrace();
366 }
367 return "not a valid long";
368 }
369
370 public String _literal(String args[]) {
371 if (args.length != 2)
372 throw new RuntimeException("Need a value for the ${literal;<value>} macro");
373 return "${" + args[1] + "}";
374 }
375
376 public String _def(String args[]) {
377 if (args.length != 2)
378 throw new RuntimeException("Need a value for the ${def;<value>} macro");
379
380 return domain.getProperty(args[1], "");
381 }
382
383 /**
Stuart McCullochbb014372012-06-07 21:57:32 +0000384 * replace ; <list> ; regex ; replace
385 *
386 * @param args
387 * @return
388 */
389 public String _replace(String args[]) {
390 if (args.length != 4) {
391 domain.warning("Invalid nr of arguments to replace " + Arrays.asList(args));
392 return null;
393 }
394
395 String list[] = args[1].split("\\s*,\\s*");
396 StringBuilder sb = new StringBuilder();
397 String del = "";
398 for (int i = 0; i < list.length; i++) {
399 String element = list[i].trim();
400 if (!element.equals("")) {
401 sb.append(del);
402 sb.append(element.replaceAll(args[2], args[3]));
403 del = ", ";
404 }
405 }
406
407 return sb.toString();
408 }
409
410 public String _warning(String args[]) {
411 for (int i = 1; i < args.length; i++) {
412 domain.warning(process(args[i]));
413 }
414 return "";
415 }
416
417 public String _error(String args[]) {
418 for (int i = 1; i < args.length; i++) {
419 domain.error(process(args[i]));
420 }
421 return "";
422 }
423
424 /**
425 * toclassname ; <path>.class ( , <path>.class ) *
426 *
427 * @param args
428 * @return
429 */
430 static String _toclassnameHelp = "${classname;<list of class names>}, convert class paths to FQN class names ";
431
432 public String _toclassname(String args[]) {
433 verifyCommand(args, _toclassnameHelp, null, 2, 2);
434 Collection<String> paths = Processor.split(args[1]);
435
436 List<String> names = new ArrayList<String>(paths.size());
437 for (String path : paths) {
438 if (path.endsWith(".class")) {
439 String name = path.substring(0, path.length() - 6).replace('/', '.');
440 names.add(name);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000441 } else if (path.endsWith(".java")) {
442 String name = path.substring(0, path.length() - 5).replace('/', '.');
443 names.add(name);
444 } else {
445 domain.warning("in toclassname, " + args[1] + " is not a class path because it does not end in .class");
Stuart McCullochbb014372012-06-07 21:57:32 +0000446 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000447 }
448 return Processor.join(names, ",");
449 }
450
451 /**
452 * toclassname ; <path>.class ( , <path>.class ) *
453 *
454 * @param args
455 * @return
456 */
457
458 static String _toclasspathHelp = "${toclasspath;<list>[;boolean]}, convert a list of class names to paths";
459
460 public String _toclasspath(String args[]) {
461 verifyCommand(args, _toclasspathHelp, null, 2, 3);
462 boolean cl = true;
463 if (args.length > 2)
464 cl = Boolean.valueOf(args[2]);
465
466 Collection<String> names = Processor.split(args[1]);
467 Collection<String> paths = new ArrayList<String>(names.size());
468 for (String name : names) {
469 String path = name.replace('.', '/') + (cl ? ".class" : "");
470 paths.add(path);
471 }
472 return Processor.join(paths, ",");
473 }
474
475 public String _dir(String args[]) {
476 if (args.length < 2) {
477 domain.warning("Need at least one file name for ${dir;...}");
478 return null;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000479 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000480 String del = "";
481 StringBuilder sb = new StringBuilder();
482 for (int i = 1; i < args.length; i++) {
483 File f = domain.getFile(args[i]);
484 if (f.exists() && f.getParentFile().exists()) {
485 sb.append(del);
486 sb.append(f.getParentFile().getAbsolutePath());
487 del = ",";
488 }
489 }
490 return sb.toString();
491 }
492
493 }
494
495 public String _basename(String args[]) {
496 if (args.length < 2) {
497 domain.warning("Need at least one file name for ${basename;...}");
498 return null;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000499 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000500 String del = "";
501 StringBuilder sb = new StringBuilder();
502 for (int i = 1; i < args.length; i++) {
503 File f = domain.getFile(args[i]);
504 if (f.exists() && f.getParentFile().exists()) {
505 sb.append(del);
506 sb.append(f.getName());
507 del = ",";
508 }
509 }
510 return sb.toString();
511 }
512
513 }
514
515 public String _isfile(String args[]) {
516 if (args.length < 2) {
517 domain.warning("Need at least one file name for ${isfile;...}");
518 return null;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000519 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000520 boolean isfile = true;
521 for (int i = 1; i < args.length; i++) {
522 File f = new File(args[i]).getAbsoluteFile();
523 isfile &= f.isFile();
524 }
525 return isfile ? "true" : "false";
526 }
527
528 }
529
530 public String _isdir(String args[]) {
531 if (args.length < 2) {
532 domain.warning("Need at least one file name for ${isdir;...}");
533 return null;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000534 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000535 boolean isdir = true;
536 for (int i = 1; i < args.length; i++) {
537 File f = new File(args[i]).getAbsoluteFile();
538 isdir &= f.isDirectory();
539 }
540 return isdir ? "true" : "false";
541 }
542
543 }
544
545 public String _tstamp(String args[]) {
546 String format = "yyyyMMddHHmm";
547 long now = System.currentTimeMillis();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000548 TimeZone tz = TimeZone.getTimeZone("UTC");
Stuart McCullochbb014372012-06-07 21:57:32 +0000549
550 if (args.length > 1) {
551 format = args[1];
Stuart McCullochbb014372012-06-07 21:57:32 +0000552 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000553 if (args.length > 2) {
554 tz = TimeZone.getTimeZone(args[2]);
555 }
556 if (args.length > 3) {
557 now = Long.parseLong(args[3]);
558 }
559 if (args.length > 4) {
560 domain.warning("Too many arguments for tstamp: " + Arrays.toString(args));
561 }
562
Stuart McCullochbb014372012-06-07 21:57:32 +0000563 SimpleDateFormat sdf = new SimpleDateFormat(format);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000564 sdf.setTimeZone(tz);
565
Stuart McCullochbb014372012-06-07 21:57:32 +0000566 return sdf.format(new Date(now));
567 }
568
569 /**
570 * Wildcard a directory. The lists can contain Instruction that are matched
Stuart McCulloch2286f232012-06-15 13:27:53 +0000571 * against the given directory ${lsr;<dir>;<list>(;<list>)*}
572 * ${lsa;<dir>;<list>(;<list>)*}
Stuart McCullochbb014372012-06-07 21:57:32 +0000573 *
574 * @author aqute
Stuart McCullochbb014372012-06-07 21:57:32 +0000575 */
576
577 public String _lsr(String args[]) {
578 return ls(args, true);
579 }
580
581 public String _lsa(String args[]) {
582 return ls(args, false);
583 }
584
585 String ls(String args[], boolean relative) {
586 if (args.length < 2)
Stuart McCulloch2286f232012-06-15 13:27:53 +0000587 throw new IllegalArgumentException("the ${ls} macro must at least have a directory as parameter");
Stuart McCullochbb014372012-06-07 21:57:32 +0000588
589 File dir = domain.getFile(args[1]);
590 if (!dir.isAbsolute())
Stuart McCulloch2286f232012-06-15 13:27:53 +0000591 throw new IllegalArgumentException("the ${ls} macro directory parameter is not absolute: " + dir);
Stuart McCullochbb014372012-06-07 21:57:32 +0000592
593 if (!dir.exists())
Stuart McCulloch2286f232012-06-15 13:27:53 +0000594 throw new IllegalArgumentException("the ${ls} macro directory parameter does not exist: " + dir);
Stuart McCullochbb014372012-06-07 21:57:32 +0000595
596 if (!dir.isDirectory())
597 throw new IllegalArgumentException(
Stuart McCulloch2286f232012-06-15 13:27:53 +0000598 "the ${ls} macro directory parameter points to a file instead of a directory: " + dir);
Stuart McCullochbb014372012-06-07 21:57:32 +0000599
600 List<File> files = new ArrayList<File>(new SortedList<File>(dir.listFiles()));
601
602 for (int i = 2; i < args.length; i++) {
603 Instructions filters = new Instructions(args[i]);
604 filters.select(files, true);
605 }
606
607 List<String> result = new ArrayList<String>();
608 for (File file : files)
609 result.add(relative ? file.getName() : file.getAbsolutePath());
610
611 return Processor.join(result, ",");
612 }
613
614 public String _currenttime(String args[]) {
615 return Long.toString(System.currentTimeMillis());
616 }
617
618 /**
619 * Modify a version to set a version policy. Thed policy is a mask that is
620 * mapped to a version.
621 *
622 * <pre>
623 * + increment
624 * - decrement
625 * = maintain
626 * &tilde; discard
627 *
628 * ==+ = maintain major, minor, increment micro, discard qualifier
629 * &tilde;&tilde;&tilde;= = just get the qualifier
630 * version=&quot;[${version;==;${@}},${version;=+;${@}})&quot;
631 * </pre>
632 *
Stuart McCullochbb014372012-06-07 21:57:32 +0000633 * @param args
634 * @return
635 */
636 final static String MASK_STRING = "[\\-+=~0123456789]{0,3}[=~]?";
637 final static Pattern MASK = Pattern.compile(MASK_STRING);
638 final static String _versionHelp = "${version;<mask>;<version>}, modify a version\n"
639 + "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
Stuart McCulloch2286f232012-06-15 13:27:53 +0000640 + "M ::= '+' | '-' | MQ\n" + "MQ ::= '~' | '='";
641 final static Pattern _versionPattern[] = new Pattern[] {
642 null, null, MASK, Verifier.VERSION
643 };
Stuart McCullochbb014372012-06-07 21:57:32 +0000644
645 public String _version(String args[]) {
646 verifyCommand(args, _versionHelp, null, 2, 3);
647
648 String mask = args[1];
649
650 Version version = null;
651 if (args.length >= 3)
652 version = new Version(args[2]);
653
654 return version(version, mask);
655 }
656
657 String version(Version version, String mask) {
658 if (version == null) {
659 String v = domain.getProperty("@");
660 if (v == null) {
661 domain.error(
662 "No version specified for ${version} or ${range} and no implicit version ${@} either, mask=%s",
663 mask);
664 v = "0";
665 }
666 version = new Version(v);
667 }
668
669 StringBuilder sb = new StringBuilder();
670 String del = "";
671
672 for (int i = 0; i < mask.length(); i++) {
673 char c = mask.charAt(i);
674 String result = null;
675 if (c != '~') {
676 if (i == 3) {
677 result = version.getQualifier();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000678 } else if (Character.isDigit(c)) {
679 // Handle masks like +00, =+0
680 result = String.valueOf(c);
681 } else {
682 int x = version.get(i);
683 switch (c) {
684 case '+' :
685 x++;
686 break;
687 case '-' :
688 x--;
689 break;
690 case '=' :
691 break;
692 }
693 result = Integer.toString(x);
Stuart McCullochbb014372012-06-07 21:57:32 +0000694 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000695 if (result != null) {
696 sb.append(del);
697 del = ".";
698 sb.append(result);
699 }
700 }
701 }
702 return sb.toString();
703 }
704
705 /**
706 * Schortcut for version policy
707 *
708 * <pre>
709 * -provide-policy : ${policy;[==,=+)}
710 * -consume-policy : ${policy;[==,+)}
711 * </pre>
712 *
713 * @param args
714 * @return
715 */
716
Stuart McCulloch2286f232012-06-15 13:27:53 +0000717 static Pattern RANGE_MASK = Pattern.compile("(\\[|\\()(" + MASK_STRING + "),(" + MASK_STRING + ")(\\]|\\))");
Stuart McCullochbb014372012-06-07 21:57:32 +0000718 static String _rangeHelp = "${range;<mask>[;<version>]}, range for version, if version not specified lookyp ${@}\n"
719 + "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
Stuart McCulloch2286f232012-06-15 13:27:53 +0000720 + "M ::= '+' | '-' | MQ\n"
721 + "MQ ::= '~' | '='";
722 static Pattern _rangePattern[] = new Pattern[] {
723 null, RANGE_MASK
724 };
Stuart McCullochbb014372012-06-07 21:57:32 +0000725
726 public String _range(String args[]) {
727 verifyCommand(args, _rangeHelp, _rangePattern, 2, 3);
728 Version version = null;
729 if (args.length >= 3)
730 version = new Version(args[2]);
731 else {
732 String v = domain.getProperty("@");
733 if (v == null)
734 return null;
735 version = new Version(v);
736 }
737 String spec = args[1];
738
739 Matcher m = RANGE_MASK.matcher(spec);
740 m.matches();
741 String floor = m.group(1);
742 String floorMask = m.group(2);
743 String ceilingMask = m.group(3);
744 String ceiling = m.group(4);
745
746 String left = version(version, floorMask);
747 String right = version(version, ceilingMask);
748 StringBuilder sb = new StringBuilder();
749 sb.append(floor);
750 sb.append(left);
751 sb.append(",");
752 sb.append(right);
753 sb.append(ceiling);
754
755 String s = sb.toString();
756 VersionRange vr = new VersionRange(s);
757 if (!(vr.includes(vr.getHigh()) || vr.includes(vr.getLow()))) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000758 domain.error("${range} macro created an invalid range %s from %s and mask %s", s, version, spec);
Stuart McCullochbb014372012-06-07 21:57:32 +0000759 }
760 return sb.toString();
761 }
762
763 /**
764 * System command. Execute a command and insert the result.
765 *
766 * @param args
767 * @param help
768 * @param patterns
769 * @param low
770 * @param high
771 */
772 public String system_internal(boolean allowFail, String args[]) throws Exception {
773 verifyCommand(args, "${" + (allowFail ? "system-allow-fail" : "system")
774 + ";<command>[;<in>]}, execute a system command", null, 2, 3);
775 String command = args[1];
776 String input = null;
777
778 if (args.length > 2) {
779 input = args[2];
780 }
781
782 Process process = Runtime.getRuntime().exec(command, null, domain.getBase());
783 if (input != null) {
784 process.getOutputStream().write(input.getBytes("UTF-8"));
785 }
786 process.getOutputStream().close();
787
788 String s = IO.collect(process.getInputStream(), "UTF-8");
789 int exitValue = process.waitFor();
790 if (exitValue != 0)
791 return exitValue + "";
792
793 if (!allowFail && (exitValue != 0)) {
794 domain.error("System command " + command + " failed with " + exitValue);
795 }
796 return s.trim();
797 }
798
799 public String _system(String args[]) throws Exception {
800 return system_internal(false, args);
801 }
802
803 public String _system_allow_fail(String args[]) throws Exception {
804 String result = "";
805 try {
806 result = system_internal(true, args);
807 }
808 catch (Throwable t) {
809 /* ignore */
810 }
811 return result;
812 }
813
814 public String _env(String args[]) {
815 verifyCommand(args, "${env;<name>}, get the environmet variable", null, 2, 2);
816
817 try {
818 return System.getenv(args[1]);
819 }
820 catch (Throwable t) {
821 return null;
822 }
823 }
824
825 /**
826 * Get the contents of a file.
827 *
828 * @param in
829 * @return
830 * @throws IOException
831 */
832
833 public String _cat(String args[]) throws IOException {
834 verifyCommand(args, "${cat;<in>}, get the content of a file", null, 2, 2);
835 File f = domain.getFile(args[1]);
836 if (f.isFile()) {
837 return IO.collect(f);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000838 } else if (f.isDirectory()) {
839 return Arrays.toString(f.list());
840 } else {
841 try {
842 URL url = new URL(args[1]);
843 return IO.collect(url, "UTF-8");
844 }
845 catch (MalformedURLException mfue) {
846 // Ignore here
847 }
848 return null;
Stuart McCullochbb014372012-06-07 21:57:32 +0000849 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000850 }
851
Stuart McCulloch2286f232012-06-15 13:27:53 +0000852 public static void verifyCommand(String args[], String help, Pattern[] patterns, int low, int high) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000853 String message = "";
854 if (args.length > high) {
855 message = "too many arguments";
Stuart McCulloch2286f232012-06-15 13:27:53 +0000856 } else if (args.length < low) {
857 message = "too few arguments";
858 } else {
859 for (int i = 0; patterns != null && i < patterns.length && i < args.length; i++) {
860 if (patterns[i] != null) {
861 Matcher m = patterns[i].matcher(args[i]);
862 if (!m.matches())
863 message += String.format("Argument %s (%s) does not match %s\n", i, args[i],
864 patterns[i].pattern());
Stuart McCullochbb014372012-06-07 21:57:32 +0000865 }
866 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000867 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000868 if (message.length() != 0) {
869 StringBuilder sb = new StringBuilder();
870 String del = "${";
871 for (String arg : args) {
872 sb.append(del);
873 sb.append(arg);
874 del = ";";
875 }
876 sb.append("}, is not understood. ");
877 sb.append(message);
878 throw new IllegalArgumentException(sb.toString());
879 }
880 }
881
882 // Helper class to track expansion of variables
883 // on the stack.
884 static class Link {
885 Link previous;
886 String key;
887 Processor start;
888
889 public Link(Processor start, Link previous, String key) {
890 this.previous = previous;
891 this.key = key;
892 this.start = start;
893 }
894
895 public boolean contains(String key) {
896 if (this.key.equals(key))
897 return true;
898
899 if (previous == null)
900 return false;
901
902 return previous.contains(key);
903 }
904
905 public String toString() {
906 StringBuilder sb = new StringBuilder();
907 String del = "[";
908 for (Link r = this; r != null; r = r.previous) {
909 sb.append(del);
910 sb.append(r.key);
911 del = ",";
912 }
913 sb.append("]");
914 return sb.toString();
915 }
916 }
917
918 /**
919 * Take all the properties and translate them to actual values. This method
920 * takes the set properties and traverse them over all entries, including
921 * the default properties for that properties. The values no longer contain
922 * macros.
923 *
924 * @return A new Properties with the flattened values
925 */
926 public Properties getFlattenedProperties() {
927 // Some macros only work in a lower processor, so we
928 // do not report unknown macros while flattening
929 flattening = true;
930 try {
931 Properties flattened = new Properties();
932 Properties source = domain.getProperties();
933 for (Enumeration< ? > e = source.propertyNames(); e.hasMoreElements();) {
934 String key = (String) e.nextElement();
935 if (!key.startsWith("_"))
936 if (key.startsWith("-"))
937 flattened.put(key, source.getProperty(key));
938 else
939 flattened.put(key, process(source.getProperty(key)));
940 }
941 return flattened;
942 }
943 finally {
944 flattening = false;
945 }
946 }
947
948 public final static String _fileHelp = "${file;<base>;<paths>...}, create correct OS dependent path";
949
950 public String _osfile(String args[]) {
951 verifyCommand(args, _fileHelp, null, 3, 3);
952 File base = new File(args[1]);
953 File f = Processor.getFile(base, args[2]);
954 return f.getAbsolutePath();
955 }
956
957 public String _path(String args[]) {
958 List<String> list = new ArrayList<String>();
959 for (int i = 1; i < args.length; i++) {
960 list.addAll(Processor.split(args[i]));
961 }
962 return Processor.join(list, File.pathSeparator);
963 }
964
965 public static Properties getParent(Properties p) {
966 try {
967 Field f = Properties.class.getDeclaredField("defaults");
968 f.setAccessible(true);
969 return (Properties) f.get(p);
970 }
971 catch (Exception e) {
972 Field[] fields = Properties.class.getFields();
973 System.err.println(Arrays.toString(fields));
974 return null;
975 }
976 }
977
978 public String process(String line) {
979 return process(line, domain);
980 }
981
982}