diff --git a/biz.aQute.bndlib.tests/test/test/ProcessorTest.java b/biz.aQute.bndlib.tests/test/test/ProcessorTest.java index f9f760bb72..02b2adac2a 100644 --- a/biz.aQute.bndlib.tests/test/test/ProcessorTest.java +++ b/biz.aQute.bndlib.tests/test/test/ProcessorTest.java @@ -9,6 +9,7 @@ import java.io.File; import java.io.IOException; +import java.io.StringReader; import java.net.URI; import java.util.ArrayList; import java.util.Collection; @@ -23,6 +24,7 @@ import aQute.bnd.osgi.Constants; import aQute.bnd.osgi.OSInformation; import aQute.bnd.osgi.Processor; +import aQute.bnd.osgi.Processor.MacroReference; import aQute.bnd.osgi.Processor.PropertyKey; import aQute.bnd.osgi.resource.RequirementBuilder; import aQute.bnd.osgi.resource.ResourceBuilder; @@ -36,6 +38,86 @@ public class ProcessorTest { + @Test + void testMacroReferences() throws IOException { + testMacroReference(""" + c + """, """ + a ${b} ${c} ${now} + """, MacroReference.ALL, "b", "c", "now"); + testMacroReference(""" + c + """, """ + a ${b} ${c} ${now} + """, MacroReference.UNKNOWN, "b"); + testMacroReference(""" + c + """, """ + a ${b} ${c} ${now} + """, MacroReference.COMMAND, "now"); + testMacroReference(""" + c + """, """ + a ${b} ${c} ${now} + """, MacroReference.EXISTS, "c"); + } + + @Test + void testMacroReferencesWithArgs() throws IOException { + testMacroReference(""" + c + """, """ + a ${foo;${bar;${baz}}} + """, MacroReference.UNKNOWN, "foo", "bar", "baz"); + } + + @Test + void testMacroReferencesCommandWithArgs() throws IOException { + testMacroReference(""" + c + """, """ + a ${uniq;${bar};${baz};x} + """, MacroReference.UNKNOWN, "bar", "baz"); + testMacroReference(""" + c + """, """ + a ${uniq;${bar};${baz};x,${c}} + """, MacroReference.EXISTS, "c"); + testMacroReference(""" + c + """, """ + a ${uniq;${bar};${baz};x,${c}} + """, MacroReference.COMMAND, "uniq"); + } + + @Test + void testMacroReferencesDef() throws IOException { + testMacroReference(""" + c + """, """ + a ${def;foo;bar} + """, MacroReference.UNKNOWN, "foo"); + testMacroReference(""" + c + """, """ + a ${def;c;bar} + """, MacroReference.UNKNOWN); + testMacroReference(""" + c + """, """ + a ${template;foo;bar} + """, MacroReference.UNKNOWN, "foo"); + } + + private void testMacroReference(String parentSource, String childSource, Processor.MacroReference macro, + String... references) throws IOException { + try (Processor parent = new Processor(); Processor child = new Processor(parent)) { + parent.setProperties(new StringReader(parentSource)); + child.setProperties(new StringReader(childSource)); + assertThat(child.getMacroReferences(macro)).containsExactly(references); + } + } + @Test public void testFixup() throws IOException { try (Processor p = new Processor()) { diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/Macro.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/Macro.java index d506b1d170..e23959f806 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/Macro.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/Macro.java @@ -90,7 +90,7 @@ */ public class Macro { private final static String NULLVALUE = "c29e43048791e250dfd5723e7b8aa048df802c9262cfa8fbc4475b2e392a8ad2"; - private final static String LITERALVALUE = "017a3ddbfc0fcd27bcdb2590cdb713a379ae59ef"; + protected final static String LITERALVALUE = "017a3ddbfc0fcd27bcdb2590cdb713a379ae59ef"; private final static Pattern NUMERIC_P = Pattern .compile("[-+]?(\\d*\\.?\\d+|\\d+\\.)(e[-+]?[0-9]+)?"); @@ -342,7 +342,7 @@ public String replace(String key, Link link) { return replace(key, null, link, '{', '}'); } - private String replace(String key, List args, Link link, char begin, char end) { + protected String replace(String key, List args, Link link, char begin, char end) { String value = getMacro(key, args, link, begin, end); if (value != LITERALVALUE) { if (value != null) @@ -405,6 +405,26 @@ private String doCommands(String[] args, Link source) { return doCommand(this, args[0], args); } + protected BiFunction getFunction(String method) { + Processor rover = domain; + while (rover != null) { + BiFunction function = getFunction(rover, method); + if (function != null) + return function; + + rover = rover.getParent(); + } + + for (int i = 0; targets != null && i < targets.length; i++) { + BiFunction function = getFunction(targets[i], method); + if (function != null) + return function; + } + + BiFunction function = getFunction(this, method); + return function; + } + private String doCommand(Object target, String method, String[] args) { if (target == null) ; // System.err.println("Huh? Target should never be null " + @@ -422,43 +442,7 @@ private String doCommand(Object target, String method, String[] args) { } } - Map> macros = macrosByClass.computeIfAbsent(target.getClass(), - c -> Arrays.stream(c.getMethods()) - .filter(m -> (m.getName() - .charAt(0) == '_') && (m.getParameterCount() == 1) - && (m.getParameterTypes()[0] == String[].class)) - .collect(toMap(m -> m.getName() - .substring(1), m -> { - Memoize mh = Memoize.supplier(() -> { - try { - return publicLookup().unreflect(m); - } catch (Exception e) { - throw Exceptions.duck(e); - } - }); - if (Modifier.isStatic(m.getModifiers())) { - return (Object t, String[] a) -> { - try { - return mh.get() - .invoke(a); - } catch (Throwable e) { - throw Exceptions.duck(e); - } - }; - } else { - return (Object t, String[] a) -> { - try { - return mh.get() - .invoke(t, a); - } catch (Throwable e) { - throw Exceptions.duck(e); - } - }; - } - }))); - - String macro = method.replace('-', '_'); - BiFunction invoker = macros.get(macro); + BiFunction invoker = getFunction(target, method); if (invoker == null) { return null; } @@ -481,6 +465,46 @@ private String doCommand(Object target, String method, String[] args) { return null; } + BiFunction getFunction(Object target, String method) { + Map> macros = macrosByClass.computeIfAbsent(target.getClass(), + c -> Arrays.stream(c.getMethods()) + .filter(m -> (m.getName() + .charAt(0) == '_') && (m.getParameterCount() == 1) && (m.getParameterTypes()[0] == String[].class)) + .collect(toMap(m -> m.getName() + .substring(1), m -> { + Memoize mh = Memoize.supplier(() -> { + try { + return publicLookup().unreflect(m); + } catch (Exception e) { + throw Exceptions.duck(e); + } + }); + if (Modifier.isStatic(m.getModifiers())) { + return (Object t, String[] a) -> { + try { + return mh.get() + .invoke(a); + } catch (Throwable e) { + throw Exceptions.duck(e); + } + }; + } else { + return (Object t, String[] a) -> { + try { + return mh.get() + .invoke(t, a); + } catch (Throwable e) { + throw Exceptions.duck(e); + } + }; + } + }))); + + String macro = method.replace('-', '_'); + BiFunction invoker = macros.get(macro); + return invoker; + } + /** * Return a unique list where the duplicates are removed. */ @@ -1458,7 +1482,7 @@ public Properties getFlattenedProperties(boolean ignoreInstructions) { } } - static final String _osfileHelp = "${osfile;;}, create correct OS dependent path"; + static final String _osfileHelp = "${osfile;;}, create correct OS dependent path"; public String _osfile(String[] args) { verifyCommand(args, _osfileHelp, null, 3, 3); diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/Processor.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/Processor.java index aae0564898..ef511f5369 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/Processor.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/Processor.java @@ -29,6 +29,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -46,6 +47,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.Supplier; @@ -1018,8 +1020,7 @@ private String getProperty(String key, String deflt, String separator, boolean i * floor indicates where the property is defined relative to its parents. * Zero is in the current processor, 1, is its parents, and so on. */ - public record PropertyKey(Processor processor, String key, int floor) - implements Comparable { + public record PropertyKey(Processor processor, String key, int floor) implements Comparable { /** * Check if this PropertyKey belongs to the given processor @@ -1053,7 +1054,7 @@ public String getRawValue() { @Override public int compareTo(PropertyKey o) { int n = key.compareTo(o.key); - if ( n != 0) + if (n != 0) return n; return Integer.compare(floor, o.floor); } @@ -1095,7 +1096,7 @@ public List getPropertyKeys(Predicate predicate) { List keys = new ArrayList<>(); Processor rover = this; int level = 0; - while( rover != null) { + while (rover != null) { Processor localRover = rover; int localLevel = level; rover.stream(false) // local only @@ -2750,4 +2751,107 @@ public boolean isPedantic() { public void setPedantic(boolean pedantic) { this.pedantic = pedantic; } + + /** + * Enum used in getMacroReferences() to filter the properties by reason. + */ + public enum MacroReference { + /** + * Property is neither a COMMAND nor EXISTS. + */ + UNKNOWN, + /** + * Exists as a property + */ + EXISTS, + /** + * Is a built in command + */ + COMMAND, + + /** + * return all property keys + */ + ALL + } + + /** + * Find all the macro references in the properties defined in this processor + * or its ancestors. A reference can exist as property, be a command, or + * unknown. If no {@link MacroReference}'s are given, all references are + * returned. + * + * @param what specifies requested reference type + * @return the set of property keys that match what + */ + public Set getMacroReferences(MacroReference... what) { + + Set propertyKeys = getPropertyKeys(true); + Set result = new LinkedHashSet<>(); + class EMacro extends Macro { + boolean exists = false; + boolean unknown = false; + boolean command = false; + boolean all = false; + + public EMacro() { + super(Processor.this, getMacroDomains()); + for (MacroReference w : what) { + switch (w) { + case UNKNOWN -> unknown = true; + case EXISTS -> exists = true; + case COMMAND -> command = true; + default -> all = true; + } + } + all |= exists == unknown && unknown == command; + } + + @Override + protected String replace(String invocation, List args, Link link, char begin, char end) { + if (args != null && !args.isEmpty()) { + String key = args.remove(0); + reference(key, args); + for (String arg : args) { + process(arg, link); + } + } + return ""; + } + + private void reference(String key, List args) { + if (all) { + result.add(key); + } else { + boolean x = propertyKeys.contains(key); + if (x) { + if (exists) + result.add(key); + } else { + BiFunction function = getFunction(key); + if (function == null) { + if (unknown) + result.add(key); + } else { + switch (key) { + case "def", "template", "foreach" -> { + if (args.size() > 1) { + reference(args.get(0), Collections.emptyList()); + } + } + } + if (command) + result.add(key); + } + } + } + } + } + EMacro macro = new EMacro(); + for (String key : propertyKeys) { + String unexpandedProperty = getUnexpandedProperty(key); + macro.process(unexpandedProperty); + } + return result; + } }