blob: 3e109d68ce05bff64f7e04428d82fa4c0a773cd4 [file] [log] [blame]
Stuart McCullochf3173222012-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 McCulloch4482c702012-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 McCullochf3173222012-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 McCulloch4482c702012-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 McCullochf3173222012-06-07 21:57:32 +000087 }
Stuart McCullochf3173222012-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 McCulloch4482c702012-06-15 13:27:53 +0000160 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000161 domain.warning("Found empty macro key");
162 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000163 } else {
Stuart McCullochf3173222012-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);
Stuart McCulloch669423b2012-06-26 16:34:24 +0000193 return null;
Stuart McCullochf3173222012-06-07 21:57:32 +0000194 }
195
196 Processor rover = domain;
197 while (rover != null) {
198 String result = doCommand(rover, args[0], args);
199 if (result != null)
200 return result;
201
202 rover = rover.getParent();
203 }
204
205 for (int i = 0; targets != null && i < targets.length; i++) {
206 String result = doCommand(targets[i], args[0], args);
207 if (result != null)
208 return result;
209 }
210
211 return doCommand(this, args[0], args);
212 }
213
214 private String doCommand(Object target, String method, String[] args) {
215 if (target == null)
216 ; // System.err.println("Huh? Target should never be null " +
217 // domain);
218 else {
219 String cname = "_" + method.replaceAll("-", "_");
220 try {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000221 Method m = target.getClass().getMethod(cname, new Class[] {
222 String[].class
223 });
224 return (String) m.invoke(target, new Object[] {
225 args
226 });
Stuart McCullochf3173222012-06-07 21:57:32 +0000227 }
228 catch (NoSuchMethodException e) {
229 // Ignore
230 }
231 catch (InvocationTargetException e) {
232 if (e.getCause() instanceof IllegalArgumentException) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000233 domain.error("%s, for cmd: %s, arguments; %s", e.getMessage(), method, Arrays.toString(args));
234 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000235 domain.warning("Exception in replace: " + e.getCause());
236 e.getCause().printStackTrace();
237 }
238 }
239 catch (Exception e) {
240 domain.warning("Exception in replace: " + e + " method=" + method);
241 e.printStackTrace();
242 }
243 }
244 return null;
245 }
246
247 /**
248 * Return a unique list where the duplicates are removed.
249 *
250 * @param args
251 * @return
252 */
253 static String _uniqHelp = "${uniq;<list> ...}";
254
255 public String _uniq(String args[]) {
256 verifyCommand(args, _uniqHelp, null, 1, Integer.MAX_VALUE);
257 Set<String> set = new LinkedHashSet<String>();
258 for (int i = 1; i < args.length; i++) {
259 Processor.split(args[i], set);
260 }
261 return Processor.join(set, ",");
262 }
263
Stuart McCulloch669423b2012-06-26 16:34:24 +0000264 public String _pathseparator(@SuppressWarnings("unused") String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000265 return File.pathSeparator;
266 }
267
Stuart McCulloch669423b2012-06-26 16:34:24 +0000268 public String _separator(@SuppressWarnings("unused") String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000269 return File.separator;
270 }
271
272 public String _filter(String args[]) {
273 return filter(args, false);
274 }
275
276 public String _filterout(String args[]) {
277 return filter(args, true);
278
279 }
280
281 static String _filterHelp = "${%s;<list>;<regex>}";
282
283 String filter(String[] args, boolean include) {
284 verifyCommand(args, String.format(_filterHelp, args[0]), null, 3, 3);
285
286 Collection<String> list = new ArrayList<String>(Processor.split(args[1]));
287 Pattern pattern = Pattern.compile(args[2]);
288
289 for (Iterator<String> i = list.iterator(); i.hasNext();) {
290 if (pattern.matcher(i.next()).matches() == include)
291 i.remove();
292 }
293 return Processor.join(list);
294 }
295
296 static String _sortHelp = "${sort;<list>...}";
297
298 public String _sort(String args[]) {
299 verifyCommand(args, _sortHelp, null, 2, Integer.MAX_VALUE);
300
301 List<String> result = new ArrayList<String>();
302 for (int i = 1; i < args.length; i++) {
303 Processor.split(args[i], result);
304 }
305 Collections.sort(result);
306 return Processor.join(result);
307 }
308
309 static String _joinHelp = "${join;<list>...}";
310
311 public String _join(String args[]) {
312
313 verifyCommand(args, _joinHelp, null, 1, Integer.MAX_VALUE);
314
315 List<String> result = new ArrayList<String>();
316 for (int i = 1; i < args.length; i++) {
317 Processor.split(args[i], result);
318 }
319 return Processor.join(result);
320 }
321
322 static String _ifHelp = "${if;<condition>;<iftrue> [;<iffalse>] }";
323
324 public String _if(String args[]) {
325 verifyCommand(args, _ifHelp, null, 3, 4);
326 String condition = args[1].trim();
327 if (!condition.equalsIgnoreCase("false"))
328 if (condition.length() != 0)
329 return args[2];
330
331 if (args.length > 3)
332 return args[3];
Stuart McCulloch669423b2012-06-26 16:34:24 +0000333 return "";
Stuart McCullochf3173222012-06-07 21:57:32 +0000334 }
335
Stuart McCulloch669423b2012-06-26 16:34:24 +0000336 public String _now(@SuppressWarnings("unused") String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000337 return new Date().toString();
338 }
339
340 public final static String _fmodifiedHelp = "${fmodified;<list of filenames>...}, return latest modification date";
341
342 public String _fmodified(String args[]) throws Exception {
343 verifyCommand(args, _fmodifiedHelp, null, 2, Integer.MAX_VALUE);
344
345 long time = 0;
346 Collection<String> names = new ArrayList<String>();
347 for (int i = 1; i < args.length; i++) {
348 Processor.split(args[i], names);
349 }
350 for (String name : names) {
351 File f = new File(name);
352 if (f.exists() && f.lastModified() > time)
353 time = f.lastModified();
354 }
355 return "" + time;
356 }
357
358 public String _long2date(String args[]) {
359 try {
360 return new Date(Long.parseLong(args[1])).toString();
361 }
362 catch (Exception e) {
363 e.printStackTrace();
364 }
365 return "not a valid long";
366 }
367
368 public String _literal(String args[]) {
369 if (args.length != 2)
370 throw new RuntimeException("Need a value for the ${literal;<value>} macro");
371 return "${" + args[1] + "}";
372 }
373
374 public String _def(String args[]) {
375 if (args.length != 2)
376 throw new RuntimeException("Need a value for the ${def;<value>} macro");
377
378 return domain.getProperty(args[1], "");
379 }
380
381 /**
Stuart McCullochf3173222012-06-07 21:57:32 +0000382 * replace ; <list> ; regex ; replace
383 *
384 * @param args
385 * @return
386 */
387 public String _replace(String args[]) {
388 if (args.length != 4) {
389 domain.warning("Invalid nr of arguments to replace " + Arrays.asList(args));
390 return null;
391 }
392
393 String list[] = args[1].split("\\s*,\\s*");
394 StringBuilder sb = new StringBuilder();
395 String del = "";
396 for (int i = 0; i < list.length; i++) {
397 String element = list[i].trim();
398 if (!element.equals("")) {
399 sb.append(del);
400 sb.append(element.replaceAll(args[2], args[3]));
401 del = ", ";
402 }
403 }
404
405 return sb.toString();
406 }
407
408 public String _warning(String args[]) {
409 for (int i = 1; i < args.length; i++) {
410 domain.warning(process(args[i]));
411 }
412 return "";
413 }
414
415 public String _error(String args[]) {
416 for (int i = 1; i < args.length; i++) {
417 domain.error(process(args[i]));
418 }
419 return "";
420 }
421
422 /**
423 * toclassname ; <path>.class ( , <path>.class ) *
424 *
425 * @param args
426 * @return
427 */
428 static String _toclassnameHelp = "${classname;<list of class names>}, convert class paths to FQN class names ";
429
430 public String _toclassname(String args[]) {
431 verifyCommand(args, _toclassnameHelp, null, 2, 2);
432 Collection<String> paths = Processor.split(args[1]);
433
434 List<String> names = new ArrayList<String>(paths.size());
435 for (String path : paths) {
436 if (path.endsWith(".class")) {
437 String name = path.substring(0, path.length() - 6).replace('/', '.');
438 names.add(name);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000439 } else if (path.endsWith(".java")) {
440 String name = path.substring(0, path.length() - 5).replace('/', '.');
441 names.add(name);
442 } else {
443 domain.warning("in toclassname, " + args[1] + " is not a class path because it does not end in .class");
Stuart McCullochf3173222012-06-07 21:57:32 +0000444 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000445 }
446 return Processor.join(names, ",");
447 }
448
449 /**
450 * toclassname ; <path>.class ( , <path>.class ) *
451 *
452 * @param args
453 * @return
454 */
455
456 static String _toclasspathHelp = "${toclasspath;<list>[;boolean]}, convert a list of class names to paths";
457
458 public String _toclasspath(String args[]) {
459 verifyCommand(args, _toclasspathHelp, null, 2, 3);
460 boolean cl = true;
461 if (args.length > 2)
462 cl = Boolean.valueOf(args[2]);
463
464 Collection<String> names = Processor.split(args[1]);
465 Collection<String> paths = new ArrayList<String>(names.size());
466 for (String name : names) {
467 String path = name.replace('.', '/') + (cl ? ".class" : "");
468 paths.add(path);
469 }
470 return Processor.join(paths, ",");
471 }
472
473 public String _dir(String args[]) {
474 if (args.length < 2) {
475 domain.warning("Need at least one file name for ${dir;...}");
476 return null;
Stuart McCullochf3173222012-06-07 21:57:32 +0000477 }
Stuart McCulloch669423b2012-06-26 16:34:24 +0000478 String del = "";
479 StringBuilder sb = new StringBuilder();
480 for (int i = 1; i < args.length; i++) {
481 File f = domain.getFile(args[i]);
482 if (f.exists() && f.getParentFile().exists()) {
483 sb.append(del);
484 sb.append(f.getParentFile().getAbsolutePath());
485 del = ",";
486 }
487 }
488 return sb.toString();
Stuart McCullochf3173222012-06-07 21:57:32 +0000489
490 }
491
492 public String _basename(String args[]) {
493 if (args.length < 2) {
494 domain.warning("Need at least one file name for ${basename;...}");
495 return null;
Stuart McCullochf3173222012-06-07 21:57:32 +0000496 }
Stuart McCulloch669423b2012-06-26 16:34:24 +0000497 String del = "";
498 StringBuilder sb = new StringBuilder();
499 for (int i = 1; i < args.length; i++) {
500 File f = domain.getFile(args[i]);
501 if (f.exists() && f.getParentFile().exists()) {
502 sb.append(del);
503 sb.append(f.getName());
504 del = ",";
505 }
506 }
507 return sb.toString();
Stuart McCullochf3173222012-06-07 21:57:32 +0000508
509 }
510
511 public String _isfile(String args[]) {
512 if (args.length < 2) {
513 domain.warning("Need at least one file name for ${isfile;...}");
514 return null;
Stuart McCullochf3173222012-06-07 21:57:32 +0000515 }
Stuart McCulloch669423b2012-06-26 16:34:24 +0000516 boolean isfile = true;
517 for (int i = 1; i < args.length; i++) {
518 File f = new File(args[i]).getAbsoluteFile();
519 isfile &= f.isFile();
520 }
521 return isfile ? "true" : "false";
Stuart McCullochf3173222012-06-07 21:57:32 +0000522
523 }
524
525 public String _isdir(String args[]) {
526 if (args.length < 2) {
527 domain.warning("Need at least one file name for ${isdir;...}");
528 return null;
Stuart McCullochf3173222012-06-07 21:57:32 +0000529 }
Stuart McCulloch669423b2012-06-26 16:34:24 +0000530 boolean isdir = true;
531 for (int i = 1; i < args.length; i++) {
532 File f = new File(args[i]).getAbsoluteFile();
533 isdir &= f.isDirectory();
534 }
535 return isdir ? "true" : "false";
Stuart McCullochf3173222012-06-07 21:57:32 +0000536
537 }
538
539 public String _tstamp(String args[]) {
540 String format = "yyyyMMddHHmm";
541 long now = System.currentTimeMillis();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000542 TimeZone tz = TimeZone.getTimeZone("UTC");
Stuart McCullochf3173222012-06-07 21:57:32 +0000543
544 if (args.length > 1) {
545 format = args[1];
Stuart McCullochf3173222012-06-07 21:57:32 +0000546 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000547 if (args.length > 2) {
548 tz = TimeZone.getTimeZone(args[2]);
549 }
550 if (args.length > 3) {
551 now = Long.parseLong(args[3]);
552 }
553 if (args.length > 4) {
554 domain.warning("Too many arguments for tstamp: " + Arrays.toString(args));
555 }
556
Stuart McCullochf3173222012-06-07 21:57:32 +0000557 SimpleDateFormat sdf = new SimpleDateFormat(format);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000558 sdf.setTimeZone(tz);
559
Stuart McCullochf3173222012-06-07 21:57:32 +0000560 return sdf.format(new Date(now));
561 }
562
563 /**
564 * Wildcard a directory. The lists can contain Instruction that are matched
Stuart McCulloch4482c702012-06-15 13:27:53 +0000565 * against the given directory ${lsr;<dir>;<list>(;<list>)*}
566 * ${lsa;<dir>;<list>(;<list>)*}
Stuart McCullochf3173222012-06-07 21:57:32 +0000567 *
568 * @author aqute
Stuart McCullochf3173222012-06-07 21:57:32 +0000569 */
570
571 public String _lsr(String args[]) {
572 return ls(args, true);
573 }
574
575 public String _lsa(String args[]) {
576 return ls(args, false);
577 }
578
579 String ls(String args[], boolean relative) {
580 if (args.length < 2)
Stuart McCulloch4482c702012-06-15 13:27:53 +0000581 throw new IllegalArgumentException("the ${ls} macro must at least have a directory as parameter");
Stuart McCullochf3173222012-06-07 21:57:32 +0000582
583 File dir = domain.getFile(args[1]);
584 if (!dir.isAbsolute())
Stuart McCulloch4482c702012-06-15 13:27:53 +0000585 throw new IllegalArgumentException("the ${ls} macro directory parameter is not absolute: " + dir);
Stuart McCullochf3173222012-06-07 21:57:32 +0000586
587 if (!dir.exists())
Stuart McCulloch4482c702012-06-15 13:27:53 +0000588 throw new IllegalArgumentException("the ${ls} macro directory parameter does not exist: " + dir);
Stuart McCullochf3173222012-06-07 21:57:32 +0000589
590 if (!dir.isDirectory())
591 throw new IllegalArgumentException(
Stuart McCulloch4482c702012-06-15 13:27:53 +0000592 "the ${ls} macro directory parameter points to a file instead of a directory: " + dir);
Stuart McCullochf3173222012-06-07 21:57:32 +0000593
594 List<File> files = new ArrayList<File>(new SortedList<File>(dir.listFiles()));
595
596 for (int i = 2; i < args.length; i++) {
597 Instructions filters = new Instructions(args[i]);
598 filters.select(files, true);
599 }
600
601 List<String> result = new ArrayList<String>();
602 for (File file : files)
603 result.add(relative ? file.getName() : file.getAbsolutePath());
604
605 return Processor.join(result, ",");
606 }
607
Stuart McCulloch669423b2012-06-26 16:34:24 +0000608 public String _currenttime(@SuppressWarnings("unused") String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000609 return Long.toString(System.currentTimeMillis());
610 }
611
612 /**
613 * Modify a version to set a version policy. Thed policy is a mask that is
614 * mapped to a version.
615 *
616 * <pre>
617 * + increment
618 * - decrement
619 * = maintain
620 * &tilde; discard
621 *
622 * ==+ = maintain major, minor, increment micro, discard qualifier
623 * &tilde;&tilde;&tilde;= = just get the qualifier
624 * version=&quot;[${version;==;${@}},${version;=+;${@}})&quot;
625 * </pre>
626 *
Stuart McCullochf3173222012-06-07 21:57:32 +0000627 * @param args
628 * @return
629 */
630 final static String MASK_STRING = "[\\-+=~0123456789]{0,3}[=~]?";
631 final static Pattern MASK = Pattern.compile(MASK_STRING);
632 final static String _versionHelp = "${version;<mask>;<version>}, modify a version\n"
633 + "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
Stuart McCulloch4482c702012-06-15 13:27:53 +0000634 + "M ::= '+' | '-' | MQ\n" + "MQ ::= '~' | '='";
635 final static Pattern _versionPattern[] = new Pattern[] {
636 null, null, MASK, Verifier.VERSION
637 };
Stuart McCullochf3173222012-06-07 21:57:32 +0000638
639 public String _version(String args[]) {
640 verifyCommand(args, _versionHelp, null, 2, 3);
641
642 String mask = args[1];
643
644 Version version = null;
645 if (args.length >= 3)
646 version = new Version(args[2]);
647
648 return version(version, mask);
649 }
650
651 String version(Version version, String mask) {
652 if (version == null) {
653 String v = domain.getProperty("@");
654 if (v == null) {
655 domain.error(
656 "No version specified for ${version} or ${range} and no implicit version ${@} either, mask=%s",
657 mask);
658 v = "0";
659 }
660 version = new Version(v);
661 }
662
663 StringBuilder sb = new StringBuilder();
664 String del = "";
665
666 for (int i = 0; i < mask.length(); i++) {
667 char c = mask.charAt(i);
668 String result = null;
669 if (c != '~') {
670 if (i == 3) {
671 result = version.getQualifier();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000672 } else if (Character.isDigit(c)) {
673 // Handle masks like +00, =+0
674 result = String.valueOf(c);
675 } else {
676 int x = version.get(i);
677 switch (c) {
678 case '+' :
679 x++;
680 break;
681 case '-' :
682 x--;
683 break;
684 case '=' :
685 break;
686 }
687 result = Integer.toString(x);
Stuart McCullochf3173222012-06-07 21:57:32 +0000688 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000689 if (result != null) {
690 sb.append(del);
691 del = ".";
692 sb.append(result);
693 }
694 }
695 }
696 return sb.toString();
697 }
698
699 /**
700 * Schortcut for version policy
701 *
702 * <pre>
703 * -provide-policy : ${policy;[==,=+)}
704 * -consume-policy : ${policy;[==,+)}
705 * </pre>
706 *
707 * @param args
708 * @return
709 */
710
Stuart McCulloch4482c702012-06-15 13:27:53 +0000711 static Pattern RANGE_MASK = Pattern.compile("(\\[|\\()(" + MASK_STRING + "),(" + MASK_STRING + ")(\\]|\\))");
Stuart McCullochf3173222012-06-07 21:57:32 +0000712 static String _rangeHelp = "${range;<mask>[;<version>]}, range for version, if version not specified lookyp ${@}\n"
713 + "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
Stuart McCulloch4482c702012-06-15 13:27:53 +0000714 + "M ::= '+' | '-' | MQ\n"
715 + "MQ ::= '~' | '='";
716 static Pattern _rangePattern[] = new Pattern[] {
717 null, RANGE_MASK
718 };
Stuart McCullochf3173222012-06-07 21:57:32 +0000719
720 public String _range(String args[]) {
721 verifyCommand(args, _rangeHelp, _rangePattern, 2, 3);
722 Version version = null;
723 if (args.length >= 3)
724 version = new Version(args[2]);
725 else {
726 String v = domain.getProperty("@");
727 if (v == null)
728 return null;
729 version = new Version(v);
730 }
731 String spec = args[1];
732
733 Matcher m = RANGE_MASK.matcher(spec);
734 m.matches();
735 String floor = m.group(1);
736 String floorMask = m.group(2);
737 String ceilingMask = m.group(3);
738 String ceiling = m.group(4);
739
740 String left = version(version, floorMask);
741 String right = version(version, ceilingMask);
742 StringBuilder sb = new StringBuilder();
743 sb.append(floor);
744 sb.append(left);
745 sb.append(",");
746 sb.append(right);
747 sb.append(ceiling);
748
749 String s = sb.toString();
750 VersionRange vr = new VersionRange(s);
751 if (!(vr.includes(vr.getHigh()) || vr.includes(vr.getLow()))) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000752 domain.error("${range} macro created an invalid range %s from %s and mask %s", s, version, spec);
Stuart McCullochf3173222012-06-07 21:57:32 +0000753 }
754 return sb.toString();
755 }
756
757 /**
758 * System command. Execute a command and insert the result.
759 *
760 * @param args
761 * @param help
762 * @param patterns
763 * @param low
764 * @param high
765 */
766 public String system_internal(boolean allowFail, String args[]) throws Exception {
767 verifyCommand(args, "${" + (allowFail ? "system-allow-fail" : "system")
768 + ";<command>[;<in>]}, execute a system command", null, 2, 3);
769 String command = args[1];
770 String input = null;
771
772 if (args.length > 2) {
773 input = args[2];
774 }
775
776 Process process = Runtime.getRuntime().exec(command, null, domain.getBase());
777 if (input != null) {
778 process.getOutputStream().write(input.getBytes("UTF-8"));
779 }
780 process.getOutputStream().close();
781
782 String s = IO.collect(process.getInputStream(), "UTF-8");
783 int exitValue = process.waitFor();
784 if (exitValue != 0)
785 return exitValue + "";
786
787 if (!allowFail && (exitValue != 0)) {
788 domain.error("System command " + command + " failed with " + exitValue);
789 }
790 return s.trim();
791 }
792
793 public String _system(String args[]) throws Exception {
794 return system_internal(false, args);
795 }
796
797 public String _system_allow_fail(String args[]) throws Exception {
798 String result = "";
799 try {
800 result = system_internal(true, args);
801 }
802 catch (Throwable t) {
803 /* ignore */
804 }
805 return result;
806 }
807
808 public String _env(String args[]) {
809 verifyCommand(args, "${env;<name>}, get the environmet variable", null, 2, 2);
810
811 try {
812 return System.getenv(args[1]);
813 }
814 catch (Throwable t) {
815 return null;
816 }
817 }
818
819 /**
820 * Get the contents of a file.
821 *
822 * @param in
823 * @return
824 * @throws IOException
825 */
826
827 public String _cat(String args[]) throws IOException {
828 verifyCommand(args, "${cat;<in>}, get the content of a file", null, 2, 2);
829 File f = domain.getFile(args[1]);
830 if (f.isFile()) {
831 return IO.collect(f);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000832 } else if (f.isDirectory()) {
833 return Arrays.toString(f.list());
834 } else {
835 try {
836 URL url = new URL(args[1]);
837 return IO.collect(url, "UTF-8");
838 }
839 catch (MalformedURLException mfue) {
840 // Ignore here
841 }
842 return null;
Stuart McCullochf3173222012-06-07 21:57:32 +0000843 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000844 }
845
Stuart McCulloch669423b2012-06-26 16:34:24 +0000846 public static void verifyCommand(String args[], @SuppressWarnings("unused") String help, Pattern[] patterns, int low, int high) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000847 String message = "";
848 if (args.length > high) {
849 message = "too many arguments";
Stuart McCulloch4482c702012-06-15 13:27:53 +0000850 } else if (args.length < low) {
851 message = "too few arguments";
852 } else {
853 for (int i = 0; patterns != null && i < patterns.length && i < args.length; i++) {
854 if (patterns[i] != null) {
855 Matcher m = patterns[i].matcher(args[i]);
856 if (!m.matches())
857 message += String.format("Argument %s (%s) does not match %s\n", i, args[i],
858 patterns[i].pattern());
Stuart McCullochf3173222012-06-07 21:57:32 +0000859 }
860 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000861 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000862 if (message.length() != 0) {
863 StringBuilder sb = new StringBuilder();
864 String del = "${";
865 for (String arg : args) {
866 sb.append(del);
867 sb.append(arg);
868 del = ";";
869 }
870 sb.append("}, is not understood. ");
871 sb.append(message);
872 throw new IllegalArgumentException(sb.toString());
873 }
874 }
875
876 // Helper class to track expansion of variables
877 // on the stack.
878 static class Link {
879 Link previous;
880 String key;
881 Processor start;
882
883 public Link(Processor start, Link previous, String key) {
884 this.previous = previous;
885 this.key = key;
886 this.start = start;
887 }
888
889 public boolean contains(String key) {
890 if (this.key.equals(key))
891 return true;
892
893 if (previous == null)
894 return false;
895
896 return previous.contains(key);
897 }
898
899 public String toString() {
900 StringBuilder sb = new StringBuilder();
901 String del = "[";
902 for (Link r = this; r != null; r = r.previous) {
903 sb.append(del);
904 sb.append(r.key);
905 del = ",";
906 }
907 sb.append("]");
908 return sb.toString();
909 }
910 }
911
912 /**
913 * Take all the properties and translate them to actual values. This method
914 * takes the set properties and traverse them over all entries, including
915 * the default properties for that properties. The values no longer contain
916 * macros.
917 *
918 * @return A new Properties with the flattened values
919 */
920 public Properties getFlattenedProperties() {
921 // Some macros only work in a lower processor, so we
922 // do not report unknown macros while flattening
923 flattening = true;
924 try {
925 Properties flattened = new Properties();
926 Properties source = domain.getProperties();
927 for (Enumeration< ? > e = source.propertyNames(); e.hasMoreElements();) {
928 String key = (String) e.nextElement();
929 if (!key.startsWith("_"))
930 if (key.startsWith("-"))
931 flattened.put(key, source.getProperty(key));
932 else
933 flattened.put(key, process(source.getProperty(key)));
934 }
935 return flattened;
936 }
937 finally {
938 flattening = false;
939 }
940 }
941
942 public final static String _fileHelp = "${file;<base>;<paths>...}, create correct OS dependent path";
943
944 public String _osfile(String args[]) {
945 verifyCommand(args, _fileHelp, null, 3, 3);
946 File base = new File(args[1]);
947 File f = Processor.getFile(base, args[2]);
948 return f.getAbsolutePath();
949 }
950
951 public String _path(String args[]) {
952 List<String> list = new ArrayList<String>();
953 for (int i = 1; i < args.length; i++) {
954 list.addAll(Processor.split(args[i]));
955 }
956 return Processor.join(list, File.pathSeparator);
957 }
958
959 public static Properties getParent(Properties p) {
960 try {
961 Field f = Properties.class.getDeclaredField("defaults");
962 f.setAccessible(true);
963 return (Properties) f.get(p);
964 }
965 catch (Exception e) {
966 Field[] fields = Properties.class.getFields();
967 System.err.println(Arrays.toString(fields));
968 return null;
969 }
970 }
971
972 public String process(String line) {
973 return process(line, domain);
974 }
975
976}