FELIX-1262: add local Bnd source to apply temporary patches
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@793527 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/Make.java b/bundleplugin/src/main/java/aQute/bnd/make/Make.java
new file mode 100644
index 0000000..463556c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/Make.java
@@ -0,0 +1,101 @@
+package aQute.bnd.make;
+
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+
+public class Make {
+ Builder builder;
+ Map<Instruction, Map<String, String>> make;
+
+ public Make(Builder builder) {
+ this.builder = builder;
+ // builder.getPlugins().add(new MakeBnd());
+ // builder.getPlugins().add(new MakeCopy());
+ }
+
+ public Resource process(String source) {
+ Map<Instruction, Map<String, String>> make = getMakeHeader();
+ builder.trace("make " + source);
+
+ for (Map.Entry<Instruction, Map<String, String>> entry : make
+ .entrySet()) {
+ Instruction instr = (Instruction) entry.getKey();
+ Matcher m = instr.getMatcher(source);
+ if (m.matches() || instr.isNegated()) {
+ Map<String, String> arguments = replace(m, entry.getValue());
+ List<MakePlugin> plugins = builder.getPlugins(MakePlugin.class);
+ for (MakePlugin plugin : plugins) {
+ try {
+ Resource resource = plugin.make(builder,
+ source, arguments);
+ if (resource != null) {
+ builder.trace("Made " + source + " from args "
+ + arguments + " with " + plugin);
+ return resource;
+ }
+ } catch (Exception e) {
+ builder.error("Plugin " + plugin
+ + " generates error when use in making "
+ + source + " with args " + arguments, e);
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private Map<String, String> replace(Matcher m, Map<String, String> value) {
+ Map<String, String> newArgs = Processor.newMap();
+ for (Map.Entry<String, String> entry : value.entrySet()) {
+ String s = entry.getValue();
+ s = replace(m, s);
+ newArgs.put(entry.getKey(), s);
+ }
+ return newArgs;
+ }
+
+ String replace(Matcher m, CharSequence s) {
+ StringBuffer sb = new StringBuffer();
+ int max = '0' + m.groupCount() + 1;
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (c == '$' && i < s.length() - 1) {
+ c = s.charAt(++i);
+ if (c >= '0' && c <= max) {
+ int index = c - '0';
+ String replacement = m.group(index);
+ if (replacement != null)
+ sb.append(replacement);
+ } else {
+ if (c == '$')
+ i++;
+ sb.append(c);
+ }
+ } else
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
+ Map<Instruction, Map<String, String>> getMakeHeader() {
+ if (make != null)
+ return make;
+ make = Processor.newMap();
+
+ String s = builder.getProperty(Builder.MAKE);
+ Map<String, Map<String, String>> make = builder.parseHeader(s);
+ for (Iterator<Map.Entry<String, Map<String, String>>> e = make
+ .entrySet().iterator(); e.hasNext();) {
+ Map.Entry<String, Map<String, String>> entry = e.next();
+ String pattern = Processor.removeDuplicateMarker(entry.getKey());
+
+ Instruction instr = Instruction.getPattern(pattern);
+ this.make.put(instr, entry.getValue());
+ }
+
+ return this.make;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/MakeBnd.java b/bundleplugin/src/main/java/aQute/bnd/make/MakeBnd.java
new file mode 100644
index 0000000..d7ae8ed
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/MakeBnd.java
@@ -0,0 +1,64 @@
+package aQute.bnd.make;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.bnd.build.*;
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+
+public class MakeBnd implements MakePlugin, Constants {
+ final static Pattern JARFILE = Pattern.compile("(.+)\\.(jar|ipa)");
+
+ public Resource make(Builder builder, String destination,
+ Map<String, String> argumentsOnMake) throws Exception {
+ String type = (String) argumentsOnMake.get("type");
+ if (!"bnd".equals(type))
+ return null;
+
+ String recipe = (String) argumentsOnMake.get("recipe");
+ if (recipe == null) {
+ builder.error("No recipe specified on a make instruction for "
+ + destination);
+ return null;
+ }
+ File bndfile = builder.getFile(recipe);
+ if (bndfile.isFile()) {
+ // We do not use a parent because then we would
+ // build ourselves again. So we can not blindly
+ // inherit the properties.
+ Builder bchild = builder.getSubBuilder();
+ bchild.removeBundleSpecificHeaders();
+
+ // We must make sure that we do not include ourselves again!
+ bchild.setProperty(Analyzer.INCLUDE_RESOURCE, "");
+ bchild.setProperties(bndfile, builder.getBase());
+
+ Jar jar = bchild.build();
+ Jar dot = builder.getTarget();
+
+ if (builder.hasSources()) {
+ for (String key : jar.getResources().keySet()) {
+ if (key.startsWith("OSGI-OPT/src"))
+ dot.putResource(key, (Resource) jar.getResource(key));
+ }
+ }
+ builder.getInfo(bchild, bndfile.getName() +": ");
+ String debug = bchild.getProperty(DEBUG);
+ if (Processor.isTrue(debug)) {
+ if ( builder instanceof ProjectBuilder ) {
+ ProjectBuilder pb = (ProjectBuilder) builder;
+ File target = pb.getProject().getTarget();
+ target.mkdirs();
+ String bsn = bchild.getBsn();
+ File output = new File(target, bsn+".jar");
+ jar.write(output);
+ }
+ }
+ return new JarResource(jar);
+ } else
+ return null;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/MakeCopy.java b/bundleplugin/src/main/java/aQute/bnd/make/MakeCopy.java
new file mode 100644
index 0000000..3d5e4c8
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/MakeCopy.java
@@ -0,0 +1,45 @@
+package aQute.bnd.make;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+
+public class MakeCopy implements MakePlugin {
+
+ public Resource make(Builder builder, String destination,
+ Map<String, String> argumentsOnMake) throws Exception {
+ String type = argumentsOnMake.get("type");
+ if (!type.equals("copy"))
+ return null;
+
+ String from = argumentsOnMake.get("from");
+ if (from == null) {
+ String content = argumentsOnMake.get("content");
+ if (content == null)
+ throw new IllegalArgumentException(
+ "No 'from' or 'content' field in copy "
+ + argumentsOnMake);
+ return new EmbeddedResource(content.getBytes("UTF-8"),0);
+ } else {
+
+ File f = builder.getFile(from);
+ if (f.isFile())
+ return new FileResource(f);
+ else {
+ try {
+ URL url = new URL(from);
+ return new URLResource(url);
+ } catch(MalformedURLException mfue) {
+ // We ignore this
+ }
+ throw new IllegalArgumentException(
+ "Copy source does not exist " + from
+ + " for destination " + destination);
+ }
+ }
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/ServiceComponent.java b/bundleplugin/src/main/java/aQute/bnd/make/ServiceComponent.java
new file mode 100644
index 0000000..a641c0b
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/ServiceComponent.java
@@ -0,0 +1,408 @@
+package aQute.bnd.make;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.filter.*;
+import aQute.lib.osgi.*;
+import aQute.libg.version.*;
+
+/**
+ * This class is an analyzer plugin. It looks at the properties and tries to
+ * find out if the Service-Component header contains the bnd shortut syntax. If
+ * not, the header is copied to the output, if it does, an XML file is created
+ * and added to the JAR and the header is modified appropriately.
+ */
+public class ServiceComponent implements AnalyzerPlugin {
+ public final static String NAMESPACE_STEM = "http://www.osgi.org/xmlns/scr";
+ public final static String JIDENTIFIER = "<<identifier>>";
+ public final static String COMPONENT_FACTORY = "factory:";
+ public final static String COMPONENT_SERVICEFACTORY = "servicefactory:";
+ public final static String COMPONENT_IMMEDIATE = "immediate:";
+ public final static String COMPONENT_ENABLED = "enabled:";
+ public final static String COMPONENT_DYNAMIC = "dynamic:";
+ public final static String COMPONENT_MULTIPLE = "multiple:";
+ public final static String COMPONENT_PROVIDE = "provide:";
+ public final static String COMPONENT_OPTIONAL = "optional:";
+ public final static String COMPONENT_PROPERTIES = "properties:";
+ public final static String COMPONENT_IMPLEMENTATION = "implementation:";
+
+ // v1.1.0
+ public final static String COMPONENT_VERSION = "version:";
+ public final static String COMPONENT_CONFIGURATION_POLICY = "configuration-policy:";
+ public final static String COMPONENT_MODIFIED = "modified:";
+ public final static String COMPONENT_ACTIVATE = "activate:";
+ public final static String COMPONENT_DEACTIVATE = "deactivate:";
+
+ public final static String[] componentDirectives = new String[] {
+ COMPONENT_FACTORY, COMPONENT_IMMEDIATE, COMPONENT_ENABLED,
+ COMPONENT_DYNAMIC, COMPONENT_MULTIPLE, COMPONENT_PROVIDE,
+ COMPONENT_OPTIONAL, COMPONENT_PROPERTIES, COMPONENT_IMPLEMENTATION,
+ COMPONENT_SERVICEFACTORY, COMPONENT_VERSION,
+ COMPONENT_CONFIGURATION_POLICY, COMPONENT_MODIFIED,
+ COMPONENT_ACTIVATE, COMPONENT_DEACTIVATE };
+
+ public final static Set<String> SET_COMPONENT_DIRECTIVES = new HashSet<String>(
+ Arrays
+ .asList(componentDirectives));
+
+ public final static Set<String> SET_COMPONENT_DIRECTIVES_1_1 = //
+ new HashSet<String>(
+ Arrays
+ .asList(
+ COMPONENT_VERSION,
+ COMPONENT_CONFIGURATION_POLICY,
+ COMPONENT_MODIFIED,
+ COMPONENT_ACTIVATE,
+ COMPONENT_DEACTIVATE));
+
+ public boolean analyzeJar(Analyzer analyzer) throws Exception {
+
+ ComponentMaker m = new ComponentMaker(analyzer);
+
+ Map<String, Map<String, String>> l = m.doServiceComponent();
+
+ if (!l.isEmpty())
+ analyzer.setProperty(Constants.SERVICE_COMPONENT, Processor
+ .printClauses(l, ""));
+
+ analyzer.getInfo(m, "Service Component");
+ m.close();
+ return false;
+ }
+
+ private static class ComponentMaker extends Processor {
+ Analyzer analyzer;
+
+ ComponentMaker(Analyzer analyzer) {
+ super(analyzer);
+ this.analyzer = analyzer;
+ }
+
+ Map<String, Map<String, String>> doServiceComponent() throws Exception {
+ String header = getProperty(SERVICE_COMPONENT);
+ return doServiceComponent(header);
+ }
+
+ /**
+ * Check if a service component header is actually referring to a class.
+ * If so, replace the reference with an XML file reference. This makes
+ * it easier to create and use components.
+ *
+ * @throws UnsupportedEncodingException
+ *
+ */
+ public Map<String, Map<String, String>> doServiceComponent(
+ String serviceComponent) throws IOException {
+ Map<String, Map<String, String>> list = newMap();
+ Map<String, Map<String, String>> sc = parseHeader(serviceComponent);
+ Map<String, String> empty = Collections.emptyMap();
+
+ for (Iterator<Map.Entry<String, Map<String, String>>> i = sc
+ .entrySet().iterator(); i.hasNext();) {
+ Map.Entry<String, Map<String, String>> entry = i.next();
+ String name = entry.getKey();
+ Map<String, String> info = entry.getValue();
+ if (name == null) {
+ error("No name in Service-Component header: " + info);
+ continue;
+ }
+ if (name.indexOf("*") >= 0 || analyzer.getJar().exists(name)) {
+ // Normal service component, we do not process them
+ list.put(name, info);
+ } else {
+ String impl = name;
+
+ if (info.containsKey(COMPONENT_IMPLEMENTATION))
+ impl = info.get(COMPONENT_IMPLEMENTATION);
+
+ if (!analyzer.checkClass(impl)) {
+ error("Not found Service-Component header: " + name);
+ } else {
+ // We have a definition, so make an XML resources
+ Resource resource = createComponentResource(name, info);
+ analyzer.getJar().putResource(
+ "OSGI-INF/" + name + ".xml", resource);
+ list.put("OSGI-INF/" + name + ".xml", empty);
+ }
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Create the resource for a DS component.
+ *
+ * @param list
+ * @param name
+ * @param info
+ * @throws UnsupportedEncodingException
+ */
+ Resource createComponentResource(String name, Map<String, String> info)
+ throws IOException {
+ String namespace = getNamespace(info);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PrintWriter pw = new PrintWriter(new OutputStreamWriter(out,
+ "UTF-8"));
+ pw.println("<?xml version='1.0' encoding='utf-8'?>");
+ pw.print("<component name='" + name + "'");
+ if (namespace != null) {
+ pw.print(" xmlns='" + namespace + "'");
+ }
+
+ doAttribute(pw, info.get(COMPONENT_FACTORY), "factory");
+ doAttribute(pw, info.get(COMPONENT_IMMEDIATE), "immediate",
+ "false", "true");
+ doAttribute(pw, info.get(COMPONENT_ENABLED), "enabled", "true",
+ "false");
+ doAttribute(pw, info.get(COMPONENT_CONFIGURATION_POLICY),
+ "configuration-policy", "optional", "require", "ignore");
+ doAttribute(pw, info.get(COMPONENT_ACTIVATE), "activate",
+ JIDENTIFIER);
+ doAttribute(pw, info.get(COMPONENT_DEACTIVATE), "deactivate",
+ JIDENTIFIER);
+ doAttribute(pw, info.get(COMPONENT_MODIFIED), "modified",
+ JIDENTIFIER);
+
+ pw.println(">");
+
+ // Allow override of the implementation when people
+ // want to choose their own name
+ String impl = (String) info.get(COMPONENT_IMPLEMENTATION);
+ pw.println(" <implementation class='"
+ + (impl == null ? name : impl) + "'/>");
+
+ String provides = info.get(COMPONENT_PROVIDE);
+ boolean servicefactory = Boolean.getBoolean(info
+ .get(COMPONENT_SERVICEFACTORY)
+ + "");
+ provides(pw, provides, servicefactory);
+ properties(pw, info);
+ reference(info, pw);
+ pw.println("</component>");
+ pw.close();
+ byte[] data = out.toByteArray();
+ out.close();
+ return new EmbeddedResource(data, 0);
+ }
+
+ private void doAttribute(PrintWriter pw, String value, String name,
+ String... matches) {
+ if (value != null) {
+ if (matches.length != 0) {
+ if (matches.length == 1 && matches[0].equals(JIDENTIFIER)) {
+ if (!Verifier.isIdentifier(value))
+ error(
+ "Component attribute %s has value %s but is not a Java identifier",
+ name, value);
+ } else {
+
+ if (!Verifier.isMember(value, matches))
+ error(
+ "Component attribute %s has value %s but is not a member of %s",
+ name, value, Arrays.toString(matches));
+ }
+ }
+ pw.print(" ");
+ pw.print(name);
+ pw.print("='");
+ pw.print(value);
+ pw.print("'");
+ }
+ }
+
+ /**
+ * Check if we need to use the v1.1 namespace (or later).
+ *
+ * @param info
+ * @return
+ */
+ private String getNamespace(Map<String, String> info) {
+ String version = info.get(COMPONENT_VERSION);
+ if (version != null) {
+ try {
+ Version v = new Version(version);
+ return NAMESPACE_STEM + "/v" + v;
+ } catch (Exception e) {
+ error("version: specified on component header but not a valid version: "
+ + version);
+ return null;
+ }
+ }
+ for (String key : info.keySet()) {
+ if (SET_COMPONENT_DIRECTIVES_1_1.contains(key)) {
+ return NAMESPACE_STEM + "/v1.1.0";
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Print the Service-Component properties element
+ *
+ * @param pw
+ * @param info
+ */
+ void properties(PrintWriter pw, Map<String, String> info) {
+ Collection<String> properties = split(info
+ .get(COMPONENT_PROPERTIES));
+ for (Iterator<String> p = properties.iterator(); p.hasNext();) {
+ String clause = p.next();
+ int n = clause.indexOf('=');
+ if (n <= 0) {
+ error("Not a valid property in service component: "
+ + clause);
+ } else {
+ String type = null;
+ String name = clause.substring(0, n);
+ if (name.indexOf('@') >= 0) {
+ String parts[] = name.split("@");
+ name = parts[1];
+ type = parts[0];
+ }
+ String value = clause.substring(n + 1).trim();
+ // TODO verify validity of name and value.
+ pw.print("<property name='");
+ pw.print(name);
+ pw.print("'");
+
+ if (type != null) {
+ if (VALID_PROPERTY_TYPES.matcher(type).matches()) {
+ pw.print(" type='");
+ pw.print(type);
+ pw.print("'");
+ } else {
+ warning("Invalid property type '" + type
+ + "' for property " + name);
+ }
+ }
+
+ String parts[] = value.split("\\s*(\\||\\n)\\s*");
+ if (parts.length > 1) {
+ pw.println(">");
+ for (String part : parts) {
+ pw.println(part);
+ }
+ pw.println("</property>");
+ } else {
+ pw.print(" value='");
+ pw.print(parts[0]);
+ pw.print("'/>");
+ }
+ }
+ }
+ }
+
+ /**
+ * @param pw
+ * @param provides
+ */
+ void provides(PrintWriter pw, String provides, boolean servicefactory) {
+ if (provides != null) {
+ if (!servicefactory)
+ pw.println(" <service>");
+ else
+ pw.println(" <service servicefactory='true'>");
+
+ StringTokenizer st = new StringTokenizer(provides, ",");
+ while (st.hasMoreTokens()) {
+ String interfaceName = st.nextToken();
+ pw.println(" <provide interface='" + interfaceName
+ + "'/>");
+ if (!analyzer.checkClass(interfaceName))
+ error("Component definition provides a class that is neither imported nor contained: "
+ + interfaceName);
+ }
+ pw.println(" </service>");
+ }
+ }
+
+ public final static Pattern REFERENCE = Pattern.compile("([^(]+)(\\(.+\\))?");
+
+ /**
+ * @param info
+ * @param pw
+ */
+
+ void reference(Map<String, String> info, PrintWriter pw) {
+ Collection<String> dynamic = new ArrayList<String>(split(info.get(COMPONENT_DYNAMIC)));
+ Collection<String> optional = new ArrayList<String>(split(info.get(COMPONENT_OPTIONAL)));
+ Collection<String> multiple = new ArrayList<String>(split(info.get(COMPONENT_MULTIPLE)));
+
+ for (Iterator<Map.Entry<String, String>> r = info.entrySet()
+ .iterator(); r.hasNext();) {
+ Map.Entry<String, String> ref = r.next();
+ String referenceName = (String) ref.getKey();
+ String target = null;
+ String interfaceName = (String) ref.getValue();
+ if (interfaceName == null || interfaceName.length() == 0) {
+ error("Invalid Interface Name for references in Service Component: "
+ + referenceName + "=" + interfaceName);
+ }
+ char c = interfaceName.charAt(interfaceName.length() - 1);
+ if ("?+*~".indexOf(c) >= 0) {
+ if (c == '?' || c == '*' || c == '~')
+ optional.add(referenceName);
+ if (c == '+' || c == '*')
+ multiple.add(referenceName);
+ if (c == '+' || c == '*' || c == '?')
+ dynamic.add(referenceName);
+ interfaceName = interfaceName.substring(0, interfaceName
+ .length() - 1);
+ }
+
+ if (referenceName.endsWith(":")) {
+ if (!SET_COMPONENT_DIRECTIVES.contains(referenceName))
+ error("Unrecognized directive in Service-Component header: "
+ + referenceName);
+ continue;
+ }
+
+ Matcher m = REFERENCE.matcher(interfaceName);
+ if (m.matches()) {
+ interfaceName = m.group(1);
+ target = m.group(2);
+ }
+
+ if (!analyzer.checkClass(interfaceName))
+ error("Component definition refers to a class that is neither imported nor contained: "
+ + interfaceName);
+
+ pw.print(" <reference name='" + referenceName
+ + "' interface='" + interfaceName + "'");
+
+ String cardinality = optional.contains(referenceName) ? "0"
+ : "1";
+ cardinality += "..";
+ cardinality += multiple.contains(referenceName) ? "n" : "1";
+ if (!cardinality.equals("1..1"))
+ pw.print(" cardinality='" + cardinality + "'");
+
+ if (Character.isLowerCase(referenceName.charAt(0))) {
+ String z = referenceName.substring(0, 1).toUpperCase()
+ + referenceName.substring(1);
+ pw.print(" bind='set" + z + "'");
+ pw.print(" unbind='unset" + z + "'");
+ // TODO Verify that the methods exist
+ }
+
+ if (dynamic.contains(referenceName)) {
+ pw.print(" policy='dynamic'");
+ }
+
+ if (target != null) {
+ Filter filter = new Filter(target);
+ if (filter.verify() == null)
+ pw.print(" target='" + filter.toString() + "'");
+ else
+ error("Target for " + referenceName
+ + " is not a correct filter: " + target + " "
+ + filter.verify());
+ }
+ pw.println("/>");
+ }
+ }
+ }
+}