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