blob: bfd062180b7429d415b06eaa0b897531d430551c [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
Stuart McCullochcd1ddd72012-07-19 13:11:20 +000010import aQute.bnd.version.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000011import aQute.lib.collections.*;
12import aQute.lib.io.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000013
14/**
15 * Provide a macro processor. This processor can replace variables in strings
16 * based on a properties and a domain. The domain can implement functions that
17 * start with a "_" and take args[], the names of these functions are available
18 * as functions in the macro processor (without the _). Macros can nest to any
Stuart McCulloch4482c702012-06-15 13:27:53 +000019 * depth but may not contain loops. Add POSIX macros: ${#parameter} String
20 * length. ${parameter%word} Remove smallest suffix pattern. ${parameter%%word}
21 * Remove largest suffix pattern. ${parameter#word} Remove smallest prefix
22 * pattern. ${parameter##word} Remove largest prefix pattern.
Stuart McCullochf3173222012-06-07 21:57:32 +000023 */
Stuart McCullochb32291a2012-07-16 14:10:57 +000024public class Macro {
Stuart McCullochf3173222012-06-07 21:57:32 +000025 Processor domain;
26 Object targets[];
27 boolean flattening;
28
29 public Macro(Processor domain, Object... targets) {
30 this.domain = domain;
31 this.targets = targets;
32 if (targets != null) {
33 for (Object o : targets) {
34 assert o != null;
35 }
36 }
37 }
38
39 public String process(String line, Processor source) {
40 return process(line, new Link(source, null, line));
41 }
42
43 String process(String line, Link link) {
44 StringBuilder sb = new StringBuilder();
45 process(line, 0, '\u0000', '\u0000', sb, link);
46 return sb.toString();
47 }
48
49 int process(CharSequence org, int index, char begin, char end, StringBuilder result, Link link) {
50 StringBuilder line = new StringBuilder(org);
51 int nesting = 1;
52
53 StringBuilder variable = new StringBuilder();
54 outer: while (index < line.length()) {
55 char c1 = line.charAt(index++);
56 if (c1 == end) {
57 if (--nesting == 0) {
58 result.append(replace(variable.toString(), link));
59 return index;
60 }
Stuart McCulloch4482c702012-06-15 13:27:53 +000061 } else if (c1 == begin)
62 nesting++;
63 else if (c1 == '\\' && index < line.length() - 1 && line.charAt(index) == '$') {
64 // remove the escape backslash and interpret the dollar
65 // as a
66 // literal
67 index++;
68 variable.append('$');
69 continue outer;
70 } else if (c1 == '$' && index < line.length() - 2) {
71 char c2 = line.charAt(index);
72 char terminator = getTerminator(c2);
73 if (terminator != 0) {
74 index = process(line, index + 1, c2, terminator, variable, link);
75 continue outer;
76 }
77 } else if (c1 == '.' && index < line.length() && line.charAt(index) == '/') {
78 // Found the sequence ./
79 if (index == 1 || Character.isWhitespace(line.charAt(index - 2))) {
80 // make sure it is preceded by whitespace or starts at begin
81 index++;
82 variable.append(domain.getBase().getAbsolutePath());
83 variable.append('/');
84 continue outer;
85 }
Stuart McCullochf3173222012-06-07 21:57:32 +000086 }
Stuart McCullochf3173222012-06-07 21:57:32 +000087 variable.append(c1);
88 }
89 result.append(variable);
90 return index;
91 }
92
93 public static char getTerminator(char c) {
94 switch (c) {
95 case '(' :
96 return ')';
97 case '[' :
98 return ']';
99 case '{' :
100 return '}';
101 case '<' :
102 return '>';
103 case '\u00ab' : // Guillemet double << >>
104 return '\u00bb';
105 case '\u2039' : // Guillemet single
106 return '\u203a';
107 }
108 return 0;
109 }
110
111 protected String replace(String key, Link link) {
112 if (link != null && link.contains(key))
113 return "${infinite:" + link.toString() + "}";
114
115 if (key != null) {
116 key = key.trim();
117 if (key.length() > 0) {
118 Processor source = domain;
119 String value = null;
120
121 if (key.indexOf(';') < 0) {
122 Instruction ins = new Instruction(key);
123 if (!ins.isLiteral()) {
124 SortedList<String> sortedList = SortedList.fromIterator(domain.iterator());
125 StringBuilder sb = new StringBuilder();
126 String del = "";
127 for (String k : sortedList) {
128 if (ins.matches(k)) {
129 String v = replace(k, new Link(source, link, key));
130 if (v != null) {
131 sb.append(del);
132 del = ",";
133 sb.append(v);
134 }
135 }
136 }
137 return sb.toString();
138 }
139 }
140 while (value == null && source != null) {
141 value = source.getProperties().getProperty(key);
142 source = source.getParent();
143 }
144
145 if (value != null)
146 return process(value, new Link(source, link, key));
147
148 value = doCommands(key, link);
149 if (value != null)
150 return process(value, new Link(source, link, key));
151
152 if (key != null && key.trim().length() > 0) {
153 value = System.getProperty(key);
154 if (value != null)
155 return value;
156 }
157 if (!flattening && !key.equals("@"))
158 domain.warning("No translation found for macro: " + key);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000159 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000160 domain.warning("Found empty macro key");
161 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000162 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000163 domain.warning("Found null macro key");
164 }
165 return "${" + key + "}";
166 }
167
168 /**
169 * Parse the key as a command. A command consist of parameters separated by
170 * ':'.
171 *
172 * @param key
173 * @return
174 */
175 static Pattern commands = Pattern.compile("(?<!\\\\);");
176
177 private String doCommands(String key, Link source) {
178 String[] args = commands.split(key);
179 if (args == null || args.length == 0)
180 return null;
181
182 for (int i = 0; i < args.length; i++)
183 if (args[i].indexOf('\\') >= 0)
184 args[i] = args[i].replaceAll("\\\\;", ";");
185
186 if (args[0].startsWith("^")) {
187 String varname = args[0].substring(1).trim();
188
189 Processor parent = source.start.getParent();
190 if (parent != null)
191 return parent.getProperty(varname);
Stuart McCulloch669423b2012-06-26 16:34:24 +0000192 return null;
Stuart McCullochf3173222012-06-07 21:57:32 +0000193 }
194
195 Processor rover = domain;
196 while (rover != null) {
197 String result = doCommand(rover, args[0], args);
198 if (result != null)
199 return result;
200
201 rover = rover.getParent();
202 }
203
204 for (int i = 0; targets != null && i < targets.length; i++) {
205 String result = doCommand(targets[i], args[0], args);
206 if (result != null)
207 return result;
208 }
209
210 return doCommand(this, args[0], args);
211 }
212
213 private String doCommand(Object target, String method, String[] args) {
214 if (target == null)
215 ; // System.err.println("Huh? Target should never be null " +
216 // domain);
217 else {
218 String cname = "_" + method.replaceAll("-", "_");
219 try {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000220 Method m = target.getClass().getMethod(cname, new Class[] {
221 String[].class
222 });
223 return (String) m.invoke(target, new Object[] {
224 args
225 });
Stuart McCullochf3173222012-06-07 21:57:32 +0000226 }
227 catch (NoSuchMethodException e) {
228 // Ignore
229 }
230 catch (InvocationTargetException e) {
231 if (e.getCause() instanceof IllegalArgumentException) {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000232 domain.error("%s, for cmd: %s, arguments; %s", e.getCause().getMessage(), method, Arrays.toString(args));
Stuart McCulloch4482c702012-06-15 13:27:53 +0000233 } else {
Stuart McCullochcd1ddd72012-07-19 13:11:20 +0000234 domain.warning("Exception in replace: %s", e.getCause());
Stuart McCullochf3173222012-06-07 21:57:32 +0000235 e.getCause().printStackTrace();
236 }
237 }
238 catch (Exception e) {
239 domain.warning("Exception in replace: " + e + " method=" + method);
240 e.printStackTrace();
241 }
242 }
243 return null;
244 }
245
246 /**
247 * Return a unique list where the duplicates are removed.
248 *
249 * @param args
250 * @return
251 */
252 static String _uniqHelp = "${uniq;<list> ...}";
253
254 public String _uniq(String args[]) {
255 verifyCommand(args, _uniqHelp, null, 1, Integer.MAX_VALUE);
256 Set<String> set = new LinkedHashSet<String>();
257 for (int i = 1; i < args.length; i++) {
258 Processor.split(args[i], set);
259 }
260 return Processor.join(set, ",");
261 }
262
Stuart McCulloch669423b2012-06-26 16:34:24 +0000263 public String _pathseparator(@SuppressWarnings("unused") String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000264 return File.pathSeparator;
265 }
266
Stuart McCulloch669423b2012-06-26 16:34:24 +0000267 public String _separator(@SuppressWarnings("unused") String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000268 return File.separator;
269 }
270
271 public String _filter(String args[]) {
272 return filter(args, false);
273 }
274
275 public String _filterout(String args[]) {
276 return filter(args, true);
277
278 }
279
280 static String _filterHelp = "${%s;<list>;<regex>}";
281
282 String filter(String[] args, boolean include) {
283 verifyCommand(args, String.format(_filterHelp, args[0]), null, 3, 3);
284
285 Collection<String> list = new ArrayList<String>(Processor.split(args[1]));
286 Pattern pattern = Pattern.compile(args[2]);
287
288 for (Iterator<String> i = list.iterator(); i.hasNext();) {
289 if (pattern.matcher(i.next()).matches() == include)
290 i.remove();
291 }
292 return Processor.join(list);
293 }
294
295 static String _sortHelp = "${sort;<list>...}";
296
297 public String _sort(String args[]) {
298 verifyCommand(args, _sortHelp, null, 2, Integer.MAX_VALUE);
299
300 List<String> result = new ArrayList<String>();
301 for (int i = 1; i < args.length; i++) {
302 Processor.split(args[i], result);
303 }
304 Collections.sort(result);
305 return Processor.join(result);
306 }
307
308 static String _joinHelp = "${join;<list>...}";
309
310 public String _join(String args[]) {
311
312 verifyCommand(args, _joinHelp, null, 1, Integer.MAX_VALUE);
313
314 List<String> result = new ArrayList<String>();
315 for (int i = 1; i < args.length; i++) {
316 Processor.split(args[i], result);
317 }
318 return Processor.join(result);
319 }
320
321 static String _ifHelp = "${if;<condition>;<iftrue> [;<iffalse>] }";
322
323 public String _if(String args[]) {
324 verifyCommand(args, _ifHelp, null, 3, 4);
325 String condition = args[1].trim();
326 if (!condition.equalsIgnoreCase("false"))
327 if (condition.length() != 0)
328 return args[2];
329
330 if (args.length > 3)
331 return args[3];
Stuart McCulloch669423b2012-06-26 16:34:24 +0000332 return "";
Stuart McCullochf3173222012-06-07 21:57:32 +0000333 }
334
Stuart McCulloch669423b2012-06-26 16:34:24 +0000335 public String _now(@SuppressWarnings("unused") String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000336 return new Date().toString();
337 }
338
339 public final static String _fmodifiedHelp = "${fmodified;<list of filenames>...}, return latest modification date";
340
341 public String _fmodified(String args[]) throws Exception {
342 verifyCommand(args, _fmodifiedHelp, null, 2, Integer.MAX_VALUE);
343
344 long time = 0;
345 Collection<String> names = new ArrayList<String>();
346 for (int i = 1; i < args.length; i++) {
347 Processor.split(args[i], names);
348 }
349 for (String name : names) {
350 File f = new File(name);
351 if (f.exists() && f.lastModified() > time)
352 time = f.lastModified();
353 }
354 return "" + time;
355 }
356
357 public String _long2date(String args[]) {
358 try {
359 return new Date(Long.parseLong(args[1])).toString();
360 }
361 catch (Exception e) {
362 e.printStackTrace();
363 }
364 return "not a valid long";
365 }
366
367 public String _literal(String args[]) {
368 if (args.length != 2)
369 throw new RuntimeException("Need a value for the ${literal;<value>} macro");
370 return "${" + args[1] + "}";
371 }
372
373 public String _def(String args[]) {
374 if (args.length != 2)
375 throw new RuntimeException("Need a value for the ${def;<value>} macro");
376
377 return domain.getProperty(args[1], "");
378 }
379
380 /**
Stuart McCullochf3173222012-06-07 21:57:32 +0000381 * replace ; <list> ; regex ; replace
382 *
383 * @param args
384 * @return
385 */
386 public String _replace(String args[]) {
387 if (args.length != 4) {
388 domain.warning("Invalid nr of arguments to replace " + Arrays.asList(args));
389 return null;
390 }
391
392 String list[] = args[1].split("\\s*,\\s*");
393 StringBuilder sb = new StringBuilder();
394 String del = "";
395 for (int i = 0; i < list.length; i++) {
396 String element = list[i].trim();
397 if (!element.equals("")) {
398 sb.append(del);
399 sb.append(element.replaceAll(args[2], args[3]));
400 del = ", ";
401 }
402 }
403
404 return sb.toString();
405 }
406
407 public String _warning(String args[]) {
408 for (int i = 1; i < args.length; i++) {
409 domain.warning(process(args[i]));
410 }
411 return "";
412 }
413
414 public String _error(String args[]) {
415 for (int i = 1; i < args.length; i++) {
416 domain.error(process(args[i]));
417 }
418 return "";
419 }
420
421 /**
422 * toclassname ; <path>.class ( , <path>.class ) *
423 *
424 * @param args
425 * @return
426 */
427 static String _toclassnameHelp = "${classname;<list of class names>}, convert class paths to FQN class names ";
428
429 public String _toclassname(String args[]) {
430 verifyCommand(args, _toclassnameHelp, null, 2, 2);
431 Collection<String> paths = Processor.split(args[1]);
432
433 List<String> names = new ArrayList<String>(paths.size());
434 for (String path : paths) {
435 if (path.endsWith(".class")) {
436 String name = path.substring(0, path.length() - 6).replace('/', '.');
437 names.add(name);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000438 } else if (path.endsWith(".java")) {
439 String name = path.substring(0, path.length() - 5).replace('/', '.');
440 names.add(name);
441 } else {
442 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 +0000443 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000444 }
445 return Processor.join(names, ",");
446 }
447
448 /**
449 * toclassname ; <path>.class ( , <path>.class ) *
450 *
451 * @param args
452 * @return
453 */
454
455 static String _toclasspathHelp = "${toclasspath;<list>[;boolean]}, convert a list of class names to paths";
456
457 public String _toclasspath(String args[]) {
458 verifyCommand(args, _toclasspathHelp, null, 2, 3);
459 boolean cl = true;
460 if (args.length > 2)
461 cl = Boolean.valueOf(args[2]);
462
463 Collection<String> names = Processor.split(args[1]);
464 Collection<String> paths = new ArrayList<String>(names.size());
465 for (String name : names) {
466 String path = name.replace('.', '/') + (cl ? ".class" : "");
467 paths.add(path);
468 }
469 return Processor.join(paths, ",");
470 }
471
472 public String _dir(String args[]) {
473 if (args.length < 2) {
474 domain.warning("Need at least one file name for ${dir;...}");
475 return null;
Stuart McCullochf3173222012-06-07 21:57:32 +0000476 }
Stuart McCulloch669423b2012-06-26 16:34:24 +0000477 String del = "";
478 StringBuilder sb = new StringBuilder();
479 for (int i = 1; i < args.length; i++) {
480 File f = domain.getFile(args[i]);
481 if (f.exists() && f.getParentFile().exists()) {
482 sb.append(del);
483 sb.append(f.getParentFile().getAbsolutePath());
484 del = ",";
485 }
486 }
487 return sb.toString();
Stuart McCullochf3173222012-06-07 21:57:32 +0000488
489 }
490
491 public String _basename(String args[]) {
492 if (args.length < 2) {
493 domain.warning("Need at least one file name for ${basename;...}");
494 return null;
Stuart McCullochf3173222012-06-07 21:57:32 +0000495 }
Stuart McCulloch669423b2012-06-26 16:34:24 +0000496 String del = "";
497 StringBuilder sb = new StringBuilder();
498 for (int i = 1; i < args.length; i++) {
499 File f = domain.getFile(args[i]);
500 if (f.exists() && f.getParentFile().exists()) {
501 sb.append(del);
502 sb.append(f.getName());
503 del = ",";
504 }
505 }
506 return sb.toString();
Stuart McCullochf3173222012-06-07 21:57:32 +0000507
508 }
509
510 public String _isfile(String args[]) {
511 if (args.length < 2) {
512 domain.warning("Need at least one file name for ${isfile;...}");
513 return null;
Stuart McCullochf3173222012-06-07 21:57:32 +0000514 }
Stuart McCulloch669423b2012-06-26 16:34:24 +0000515 boolean isfile = true;
516 for (int i = 1; i < args.length; i++) {
517 File f = new File(args[i]).getAbsoluteFile();
518 isfile &= f.isFile();
519 }
520 return isfile ? "true" : "false";
Stuart McCullochf3173222012-06-07 21:57:32 +0000521
522 }
523
524 public String _isdir(String args[]) {
525 if (args.length < 2) {
526 domain.warning("Need at least one file name for ${isdir;...}");
527 return null;
Stuart McCullochf3173222012-06-07 21:57:32 +0000528 }
Stuart McCulloch669423b2012-06-26 16:34:24 +0000529 boolean isdir = true;
530 for (int i = 1; i < args.length; i++) {
531 File f = new File(args[i]).getAbsoluteFile();
532 isdir &= f.isDirectory();
533 }
534 return isdir ? "true" : "false";
Stuart McCullochf3173222012-06-07 21:57:32 +0000535
536 }
537
538 public String _tstamp(String args[]) {
539 String format = "yyyyMMddHHmm";
540 long now = System.currentTimeMillis();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000541 TimeZone tz = TimeZone.getTimeZone("UTC");
Stuart McCullochf3173222012-06-07 21:57:32 +0000542
543 if (args.length > 1) {
544 format = args[1];
Stuart McCullochf3173222012-06-07 21:57:32 +0000545 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000546 if (args.length > 2) {
547 tz = TimeZone.getTimeZone(args[2]);
548 }
549 if (args.length > 3) {
550 now = Long.parseLong(args[3]);
551 }
552 if (args.length > 4) {
553 domain.warning("Too many arguments for tstamp: " + Arrays.toString(args));
554 }
555
Stuart McCullochf3173222012-06-07 21:57:32 +0000556 SimpleDateFormat sdf = new SimpleDateFormat(format);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000557 sdf.setTimeZone(tz);
558
Stuart McCullochf3173222012-06-07 21:57:32 +0000559 return sdf.format(new Date(now));
560 }
561
562 /**
563 * Wildcard a directory. The lists can contain Instruction that are matched
Stuart McCulloch4482c702012-06-15 13:27:53 +0000564 * against the given directory ${lsr;<dir>;<list>(;<list>)*}
565 * ${lsa;<dir>;<list>(;<list>)*}
Stuart McCullochf3173222012-06-07 21:57:32 +0000566 *
567 * @author aqute
Stuart McCullochf3173222012-06-07 21:57:32 +0000568 */
569
570 public String _lsr(String args[]) {
571 return ls(args, true);
572 }
573
574 public String _lsa(String args[]) {
575 return ls(args, false);
576 }
577
578 String ls(String args[], boolean relative) {
579 if (args.length < 2)
Stuart McCulloch4482c702012-06-15 13:27:53 +0000580 throw new IllegalArgumentException("the ${ls} macro must at least have a directory as parameter");
Stuart McCullochf3173222012-06-07 21:57:32 +0000581
582 File dir = domain.getFile(args[1]);
583 if (!dir.isAbsolute())
Stuart McCulloch4482c702012-06-15 13:27:53 +0000584 throw new IllegalArgumentException("the ${ls} macro directory parameter is not absolute: " + dir);
Stuart McCullochf3173222012-06-07 21:57:32 +0000585
586 if (!dir.exists())
Stuart McCulloch4482c702012-06-15 13:27:53 +0000587 throw new IllegalArgumentException("the ${ls} macro directory parameter does not exist: " + dir);
Stuart McCullochf3173222012-06-07 21:57:32 +0000588
589 if (!dir.isDirectory())
590 throw new IllegalArgumentException(
Stuart McCulloch4482c702012-06-15 13:27:53 +0000591 "the ${ls} macro directory parameter points to a file instead of a directory: " + dir);
Stuart McCullochf3173222012-06-07 21:57:32 +0000592
Stuart McCulloch528a65d2012-07-20 16:50:39 +0000593 Collection<File> files = new ArrayList<File>(new SortedList<File>(dir.listFiles()));
Stuart McCullochf3173222012-06-07 21:57:32 +0000594
595 for (int i = 2; i < args.length; i++) {
596 Instructions filters = new Instructions(args[i]);
Stuart McCulloch528a65d2012-07-20 16:50:39 +0000597 files = filters.select(files, true);
Stuart McCullochf3173222012-06-07 21:57:32 +0000598 }
599
600 List<String> result = new ArrayList<String>();
601 for (File file : files)
602 result.add(relative ? file.getName() : file.getAbsolutePath());
603
604 return Processor.join(result, ",");
605 }
606
Stuart McCulloch669423b2012-06-26 16:34:24 +0000607 public String _currenttime(@SuppressWarnings("unused") String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000608 return Long.toString(System.currentTimeMillis());
609 }
610
611 /**
612 * Modify a version to set a version policy. Thed policy is a mask that is
613 * mapped to a version.
614 *
615 * <pre>
616 * + increment
617 * - decrement
618 * = maintain
619 * &tilde; discard
620 *
621 * ==+ = maintain major, minor, increment micro, discard qualifier
622 * &tilde;&tilde;&tilde;= = just get the qualifier
623 * version=&quot;[${version;==;${@}},${version;=+;${@}})&quot;
624 * </pre>
625 *
Stuart McCullochf3173222012-06-07 21:57:32 +0000626 * @param args
627 * @return
628 */
629 final static String MASK_STRING = "[\\-+=~0123456789]{0,3}[=~]?";
630 final static Pattern MASK = Pattern.compile(MASK_STRING);
631 final static String _versionHelp = "${version;<mask>;<version>}, modify a version\n"
632 + "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
Stuart McCulloch4482c702012-06-15 13:27:53 +0000633 + "M ::= '+' | '-' | MQ\n" + "MQ ::= '~' | '='";
634 final static Pattern _versionPattern[] = new Pattern[] {
635 null, null, MASK, Verifier.VERSION
636 };
Stuart McCullochf3173222012-06-07 21:57:32 +0000637
638 public String _version(String args[]) {
639 verifyCommand(args, _versionHelp, null, 2, 3);
640
641 String mask = args[1];
642
643 Version version = null;
644 if (args.length >= 3)
645 version = new Version(args[2]);
646
647 return version(version, mask);
648 }
649
650 String version(Version version, String mask) {
651 if (version == null) {
652 String v = domain.getProperty("@");
653 if (v == null) {
654 domain.error(
655 "No version specified for ${version} or ${range} and no implicit version ${@} either, mask=%s",
656 mask);
657 v = "0";
658 }
659 version = new Version(v);
660 }
661
662 StringBuilder sb = new StringBuilder();
663 String del = "";
664
665 for (int i = 0; i < mask.length(); i++) {
666 char c = mask.charAt(i);
667 String result = null;
668 if (c != '~') {
669 if (i == 3) {
670 result = version.getQualifier();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000671 } else if (Character.isDigit(c)) {
672 // Handle masks like +00, =+0
673 result = String.valueOf(c);
674 } else {
675 int x = version.get(i);
676 switch (c) {
677 case '+' :
678 x++;
679 break;
680 case '-' :
681 x--;
682 break;
683 case '=' :
684 break;
685 }
686 result = Integer.toString(x);
Stuart McCullochf3173222012-06-07 21:57:32 +0000687 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000688 if (result != null) {
689 sb.append(del);
690 del = ".";
691 sb.append(result);
692 }
693 }
694 }
695 return sb.toString();
696 }
697
698 /**
699 * Schortcut for version policy
700 *
701 * <pre>
702 * -provide-policy : ${policy;[==,=+)}
703 * -consume-policy : ${policy;[==,+)}
704 * </pre>
705 *
706 * @param args
707 * @return
708 */
709
Stuart McCulloch4482c702012-06-15 13:27:53 +0000710 static Pattern RANGE_MASK = Pattern.compile("(\\[|\\()(" + MASK_STRING + "),(" + MASK_STRING + ")(\\]|\\))");
Stuart McCullochf3173222012-06-07 21:57:32 +0000711 static String _rangeHelp = "${range;<mask>[;<version>]}, range for version, if version not specified lookyp ${@}\n"
712 + "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
Stuart McCulloch4482c702012-06-15 13:27:53 +0000713 + "M ::= '+' | '-' | MQ\n"
714 + "MQ ::= '~' | '='";
715 static Pattern _rangePattern[] = new Pattern[] {
716 null, RANGE_MASK
717 };
Stuart McCullochf3173222012-06-07 21:57:32 +0000718
719 public String _range(String args[]) {
720 verifyCommand(args, _rangeHelp, _rangePattern, 2, 3);
721 Version version = null;
722 if (args.length >= 3)
723 version = new Version(args[2]);
724 else {
725 String v = domain.getProperty("@");
726 if (v == null)
727 return null;
728 version = new Version(v);
729 }
730 String spec = args[1];
731
732 Matcher m = RANGE_MASK.matcher(spec);
733 m.matches();
734 String floor = m.group(1);
735 String floorMask = m.group(2);
736 String ceilingMask = m.group(3);
737 String ceiling = m.group(4);
738
739 String left = version(version, floorMask);
740 String right = version(version, ceilingMask);
741 StringBuilder sb = new StringBuilder();
742 sb.append(floor);
743 sb.append(left);
744 sb.append(",");
745 sb.append(right);
746 sb.append(ceiling);
747
748 String s = sb.toString();
749 VersionRange vr = new VersionRange(s);
750 if (!(vr.includes(vr.getHigh()) || vr.includes(vr.getLow()))) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000751 domain.error("${range} macro created an invalid range %s from %s and mask %s", s, version, spec);
Stuart McCullochf3173222012-06-07 21:57:32 +0000752 }
753 return sb.toString();
754 }
755
756 /**
757 * System command. Execute a command and insert the result.
758 *
759 * @param args
760 * @param help
761 * @param patterns
762 * @param low
763 * @param high
764 */
765 public String system_internal(boolean allowFail, String args[]) throws Exception {
766 verifyCommand(args, "${" + (allowFail ? "system-allow-fail" : "system")
767 + ";<command>[;<in>]}, execute a system command", null, 2, 3);
768 String command = args[1];
769 String input = null;
770
771 if (args.length > 2) {
772 input = args[2];
773 }
774
775 Process process = Runtime.getRuntime().exec(command, null, domain.getBase());
776 if (input != null) {
777 process.getOutputStream().write(input.getBytes("UTF-8"));
778 }
779 process.getOutputStream().close();
780
781 String s = IO.collect(process.getInputStream(), "UTF-8");
782 int exitValue = process.waitFor();
783 if (exitValue != 0)
784 return exitValue + "";
785
786 if (!allowFail && (exitValue != 0)) {
787 domain.error("System command " + command + " failed with " + exitValue);
788 }
789 return s.trim();
790 }
791
792 public String _system(String args[]) throws Exception {
793 return system_internal(false, args);
794 }
795
796 public String _system_allow_fail(String args[]) throws Exception {
797 String result = "";
798 try {
799 result = system_internal(true, args);
800 }
801 catch (Throwable t) {
802 /* ignore */
803 }
804 return result;
805 }
806
807 public String _env(String args[]) {
808 verifyCommand(args, "${env;<name>}, get the environmet variable", null, 2, 2);
809
810 try {
811 return System.getenv(args[1]);
812 }
813 catch (Throwable t) {
814 return null;
815 }
816 }
817
818 /**
819 * Get the contents of a file.
820 *
821 * @param in
822 * @return
823 * @throws IOException
824 */
825
826 public String _cat(String args[]) throws IOException {
827 verifyCommand(args, "${cat;<in>}, get the content of a file", null, 2, 2);
828 File f = domain.getFile(args[1]);
829 if (f.isFile()) {
830 return IO.collect(f);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000831 } else if (f.isDirectory()) {
832 return Arrays.toString(f.list());
833 } else {
834 try {
835 URL url = new URL(args[1]);
836 return IO.collect(url, "UTF-8");
837 }
838 catch (MalformedURLException mfue) {
839 // Ignore here
840 }
841 return null;
Stuart McCullochf3173222012-06-07 21:57:32 +0000842 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000843 }
844
Stuart McCulloch669423b2012-06-26 16:34:24 +0000845 public static void verifyCommand(String args[], @SuppressWarnings("unused") String help, Pattern[] patterns, int low, int high) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000846 String message = "";
847 if (args.length > high) {
848 message = "too many arguments";
Stuart McCulloch4482c702012-06-15 13:27:53 +0000849 } else if (args.length < low) {
850 message = "too few arguments";
851 } else {
852 for (int i = 0; patterns != null && i < patterns.length && i < args.length; i++) {
853 if (patterns[i] != null) {
854 Matcher m = patterns[i].matcher(args[i]);
855 if (!m.matches())
Stuart McCulloch54229442012-07-12 22:12:58 +0000856 message += String.format("Argument %s (%s) does not match %s%n", i, args[i],
Stuart McCulloch4482c702012-06-15 13:27:53 +0000857 patterns[i].pattern());
Stuart McCullochf3173222012-06-07 21:57:32 +0000858 }
859 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000860 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000861 if (message.length() != 0) {
862 StringBuilder sb = new StringBuilder();
863 String del = "${";
864 for (String arg : args) {
865 sb.append(del);
866 sb.append(arg);
867 del = ";";
868 }
869 sb.append("}, is not understood. ");
870 sb.append(message);
871 throw new IllegalArgumentException(sb.toString());
872 }
873 }
874
875 // Helper class to track expansion of variables
876 // on the stack.
877 static class Link {
878 Link previous;
879 String key;
880 Processor start;
881
882 public Link(Processor start, Link previous, String key) {
883 this.previous = previous;
884 this.key = key;
885 this.start = start;
886 }
887
888 public boolean contains(String key) {
889 if (this.key.equals(key))
890 return true;
891
892 if (previous == null)
893 return false;
894
895 return previous.contains(key);
896 }
897
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000898 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000899 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}