Initial commit of Sigil contribution. (FELIX-1142)
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@793581 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/bnd/BundleBuilder.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/bnd/BundleBuilder.java
new file mode 100644
index 0000000..6f6a33e
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/bnd/BundleBuilder.java
@@ -0,0 +1,800 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.bnd;
+
+import static java.lang.String.format;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.jar.Attributes;
+
+import org.cauldron.bld.config.BldAttr;
+import org.cauldron.bld.config.IBldProject;
+import org.cauldron.bld.config.IBldProject.IBldBundle;
+import org.cauldron.bld.core.internal.model.osgi.PackageImport;
+import org.cauldron.bld.core.repository.SystemRepositoryProvider;
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.osgi.framework.Version;
+
+import aQute.lib.osgi.Builder;
+import aQute.lib.osgi.Constants;
+import aQute.lib.osgi.Jar;
+import aQute.lib.osgi.Processor;
+
+public class BundleBuilder {
+ public static final String COMPONENT_ACTIVATOR_PKG = "XXX-FIXME-XXX";
+ public static final String COMPONENT_ACTIVATOR = COMPONENT_ACTIVATOR_PKG + ".Activator";
+ public static final String[] COMPONENT_ACTIVATOR_DEPS = { "XXX-FIXME-XXX",
+ "org.osgi.framework", "org.osgi.util.tracker" };
+ public static final String COMPONENT_DIR = "META-INF/XXX-FIXME-XXX";
+ public static final String COMPONENT_FLAG = "Installable-Component";
+ public static final String COMPONENT_LIST = "Installable-Component-Templates";
+
+ private IBldProject project;
+ private File[] classpath;
+ private String destPattern;
+ private Properties env;
+ private List<String> errors = new ArrayList<String>();
+ private List<String> warnings = new ArrayList<String>();
+
+ private Set<String> unused = new HashSet<String>();
+ private String lastBundle = null;
+
+ private boolean addMissingImports;
+ private boolean omitUnusedImports;
+ private String defaultPubtype;
+ private String codebaseFormat;
+ private Set<String> systemPkgs;
+
+ public interface Log {
+ void warn(String msg);
+
+ void verbose(String msg);
+ }
+
+ /**
+ * creates a BundleBuilder.
+ *
+ * @param classpath
+ * @param destPattern
+ * ivy-like pattern: PATH/[id].[ext] [id] is replaced with the
+ * bundle id. [name] is replaced with the Bundle-SymbolicName
+ * [ext] is replaced with "jar".
+ * @param hashtable
+ */
+ public BundleBuilder(IBldProject project, File[] classpath, String destPattern, Properties env) {
+ this.project = project;
+ this.classpath = classpath;
+ this.destPattern = destPattern;
+ this.env = env;
+
+ Properties options = project.getOptions();
+
+ addMissingImports = options.containsKey(BldAttr.OPTION_ADD_IMPORTS)
+ && Boolean.parseBoolean(options.getProperty(BldAttr.OPTION_ADD_IMPORTS));
+ omitUnusedImports = options.containsKey(BldAttr.OPTION_OMIT_IMPORTS)
+ && Boolean.parseBoolean(options.getProperty(BldAttr.OPTION_OMIT_IMPORTS));
+
+ defaultPubtype = options.getProperty(BldAttr.PUBTYPE_ATTRIBUTE, "rmi.codebase");
+
+ codebaseFormat = options.getProperty("codebaseFormat",
+ "cds://%1$s?bundle.symbolic.name=%2$s&type=%3$s");
+
+ for (IBldBundle b : project.getBundles()) {
+ lastBundle = b.getId();
+ for (IPackageImport import1 : b.getImports()) {
+ if (import1.getOSGiImport().equals(IPackageImport.OSGiImport.AUTO)) {
+ unused.add(import1.getPackageName());
+ }
+ }
+ }
+
+ try {
+ systemPkgs = new HashSet<String>();
+ Properties profile = SystemRepositoryProvider.readProfile(null);
+ String pkgs = profile.getProperty("org.osgi.framework.system.packages");
+ for (String pkg : pkgs.split(",\\s*")) {
+ systemPkgs.add(pkg);
+ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ public List<String> errors() {
+ return errors;
+ }
+
+ public List<String> warnings() {
+ return warnings;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void convertErrors(String prefix, List messages) {
+ // TODO: make error mapping more generic
+ final String jarEmpty = "The JAR is empty";
+
+ for (Object omsg : messages) {
+ if (jarEmpty.equals(omsg))
+ warnings.add(prefix + omsg);
+ else
+ errors.add(prefix + omsg);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void convertWarnings(String prefix, List messages) {
+ for (Object omsg : messages) {
+ warnings.add(prefix + omsg);
+ }
+ }
+
+ public boolean createBundle(IBldBundle bundle, boolean force, Log log) throws Exception {
+ int bracket = destPattern.indexOf('[');
+ if (bracket < 0) {
+ throw new Exception("destPattern MUST contain [id] or [name].");
+ }
+
+ String dest = destPattern.replaceFirst("\\[id\\]", bundle.getId());
+ dest = dest.replaceFirst("\\[name\\]", bundle.getSymbolicName());
+ dest = dest.replaceFirst("\\[ext\\]", "jar");
+
+ bracket = dest.indexOf('[');
+ if (bracket >= 0) {
+ String token = dest.substring(bracket);
+ throw new Exception("destPattern: expected [id] or [name]: " + token);
+ }
+
+ errors.clear();
+ warnings.clear();
+
+ if (!bundle.getDownloadContents().isEmpty()) {
+ // create dljar
+ Properties dlspec = new Properties();
+ StringBuilder sb = new StringBuilder();
+
+ for (String pkg : bundle.getDownloadContents()) {
+ if (sb.length() > 0)
+ sb.append(",");
+ sb.append(pkg);
+ }
+
+ dlspec.setProperty(Constants.PRIVATE_PACKAGE, sb.toString());
+ dlspec.setProperty(Constants.BUNDLE_NAME, "Newton download jar");
+ dlspec.setProperty(Constants.NOEXTRAHEADERS, "true");
+ // stop it being a bundle, so cds doesn't scan it
+ dlspec.setProperty(Constants.REMOVE_HEADERS, Constants.BUNDLE_SYMBOLICNAME);
+
+ Builder builder = new Builder();
+ builder.setProperties(dlspec);
+ builder.setClasspath(classpath);
+
+ Jar dljar = builder.build();
+ convertErrors("BND (dljar): ", builder.getErrors());
+ convertWarnings("BND (dljar): ", builder.getWarnings());
+
+ String dldest = dest.replaceFirst("\\.jar$", "-dl.jar");
+ File dloutput = new File(dldest);
+ if (!dloutput.exists() || dloutput.lastModified() <= dljar.lastModified() || force) {
+ // jar.write(dldest) catches and ignores IOException
+ OutputStream out = new FileOutputStream(dldest);
+ dljar.write(out);
+ out.close();
+ dljar.close();
+ // XXX deleting dljar causes it to be rebuilt each time
+ // XXX but leaving it mean it may be installed where it's not
+ // wanted/needed.
+ dloutput.deleteOnExit();
+ }
+ builder.close();
+ }
+
+ Properties spec = getBndSpec(bundle, dest);
+
+ if (log != null) {
+ log.verbose("BND instructions: " + spec.toString());
+ }
+
+ Builder builder = new Builder();
+ builder.setPedantic(true);
+ builder.setProperties(spec);
+ builder.mergeProperties(env, false);
+
+ builder.setClasspath(classpath);
+ // builder.setSourcepath(sourcepath);
+
+ Jar jar = builder.build();
+
+ convertErrors("BND: ", builder.getErrors());
+ convertWarnings("BND: ", builder.getWarnings());
+
+ augmentImports(builder, jar, bundle);
+
+ if (log != null) {
+ for (String warn : warnings) {
+ log.warn(warn);
+ }
+ }
+
+ if (!errors.isEmpty()) {
+ throw new Exception(errors.toString());
+ }
+
+ boolean modified = false;
+ File output = new File(dest);
+
+ if (!output.exists() || force || (output.lastModified() <= jar.lastModified())
+ || (output.lastModified() <= project.getLastModified())) {
+ modified = true;
+ // jar.write(dest) catches and ignores IOException
+ OutputStream out = new FileOutputStream(dest);
+ jar.write(out);
+ out.close();
+ jar.close();
+ }
+
+ builder.close();
+
+ return modified;
+ }
+
+ private void augmentImports(Builder builder, Jar jar, IBldBundle bundle) throws IOException {
+ Attributes main = jar.getManifest().getMainAttributes();
+ String impHeader = main.getValue(Constants.IMPORT_PACKAGE);
+ Map<String, Map<String, String>> bndImports = Processor.parseHeader(impHeader, builder);
+
+ if (bndImports.isEmpty())
+ return;
+
+ ArrayList<String> self = new ArrayList<String>();
+ ArrayList<String> missing = new ArrayList<String>();
+ ArrayList<String> modified = new ArrayList<String>();
+ ArrayList<String> unversioned = new ArrayList<String>();
+
+ String expHeader = main.getValue(Constants.EXPORT_PACKAGE);
+ Set<String> bndExports = Processor.parseHeader(expHeader, builder).keySet();
+
+ HashMap<String, IPackageImport> imports = new HashMap<String, IPackageImport>();
+ for (IPackageImport pi : getImports(bundle)) {
+ switch (pi.getOSGiImport()) {
+ case NEVER:
+ break;
+ case ALWAYS:
+ String pkg = pi.getPackageName();
+ if (!bndImports.containsKey(pkg)) {
+ // Bnd doesn't think this import is needed - but we know
+ // better
+ HashMap<String, String> attrs = new HashMap<String, String>();
+ attrs.put(BldAttr.VERSION_ATTRIBUTE, pi.getVersions().toString());
+ bndImports.put(pkg, attrs);
+ modified.add(pkg + ";resolve=runtime");
+ }
+ // fall thru */
+ case AUTO:
+ imports.put(pi.getPackageName(), pi);
+ break;
+ }
+ }
+
+ boolean importDot = false;
+
+ for (String pkg : bndImports.keySet()) {
+ unused.remove(pkg);
+ Map<String, String> attrs = bndImports.get(pkg);
+ String currentVersion = (String) attrs.get(BldAttr.VERSION_ATTRIBUTE);
+ IPackageImport pi = imports.get(pkg);
+
+ if (pi != null) {
+ VersionRange range = pi.getVersions();
+ String version = range.toString();
+
+ if (!version.equals(currentVersion) && !range.equals(VersionRange.ANY_VERSION)) {
+ attrs.put(BldAttr.VERSION_ATTRIBUTE, version);
+ if (pi.isOptional())
+ attrs.put(BldAttr.RESOLUTION_ATTRIBUTE, BldAttr.RESOLUTION_OPTIONAL);
+ modified
+ .add(pkg + ";version=" + version + (pi.isOptional() ? ";optional" : ""));
+ } else if ((currentVersion == null) && !systemPkgs.contains(pkg)) {
+ unversioned.add(pkg);
+ }
+ } else {
+ // bnd added the import ...
+ if (currentVersion == null) {
+ String defaultVersion = project.getDefaultPackageVersion(pkg);
+ if (defaultVersion != null) {
+ attrs.put(BldAttr.VERSION_ATTRIBUTE, defaultVersion);
+ currentVersion = defaultVersion;
+ }
+ }
+
+ String imp = pkg + (currentVersion == null ? "" : ";version=" + currentVersion);
+ if (bndExports.contains(pkg)) {
+ self.add(imp);
+ } else {
+ if (pkg.equals(".")) {
+ warnings.add("Bnd wants to import '.' (ignored)");
+ importDot = true;
+ } else {
+ missing.add(imp);
+ }
+ }
+ }
+ }
+
+ if (!modified.isEmpty() || importDot) {
+ if (importDot)
+ bndImports.remove(".");
+ // warnings.add("INFO: sigil modified imports: " + modified);
+ main.putValue(Constants.IMPORT_PACKAGE, Processor.printClauses(bndImports,
+ "resolution:"));
+ }
+
+ if (!self.isEmpty()) {
+ // warnings.add("INFO: added self imports: " + self);
+ }
+
+ if (!missing.isEmpty()) {
+ warnings.add("missing imports (added): " + missing);
+ }
+
+ if (!unversioned.isEmpty()) {
+ warnings.add("unversioned imports: " + unversioned);
+ }
+
+ if (bundle.getId().equals(lastBundle)) {
+ if (!unused.isEmpty()) {
+ warnings.add("unused imports (omitted): " + unused);
+ }
+ }
+ }
+
+ public Properties getBndSpec(IBldBundle bundle, String dest) throws IOException {
+ Properties spec = new Properties();
+
+ String junkHeaders = Constants.INCLUDE_RESOURCE; // shows local build
+ // paths; can be
+ // verbose
+ junkHeaders += "," + Constants.PRIVATE_PACKAGE; // less useful, as we
+ // use it for exported
+ // content too.
+
+ spec.setProperty(Constants.REMOVE_HEADERS, junkHeaders);
+ spec.setProperty(Constants.NOEXTRAHEADERS, "true"); // Created-By,
+ // Bnd-LastModified
+ // and Tool
+ spec.setProperty(Constants.CREATED_BY, "sigil.codecauldron.org");
+
+ Properties headers = bundle.getHeaders();
+ // XXX: catch attempts to set headers that conflict with Bnd
+ // instructions we generate?
+ spec.putAll(headers);
+
+ spec.setProperty(Constants.BUNDLE_SYMBOLICNAME, bundle.getSymbolicName());
+ spec.setProperty(Constants.BUNDLE_VERSION, bundle.getVersion());
+
+ String activator = bundle.getActivator();
+ if (activator != null)
+ spec.setProperty(Constants.BUNDLE_ACTIVATOR, activator);
+
+ addRequirements(bundle, spec);
+
+ List<String> exports = addExports(bundle, spec);
+
+ String composites = addResources(bundle, spec);
+
+ ArrayList<String> contents = new ArrayList<String>();
+ contents.addAll(bundle.getContents());
+
+ if (contents.isEmpty()) {
+ if (!project.getSourcePkgs().isEmpty()) {
+ contents.addAll(project.getSourcePkgs());
+ } else {
+ contents.addAll(exports);
+ }
+ }
+
+ if (composites.length() > 0) {
+ if (spec.containsKey(Constants.BUNDLE_ACTIVATOR))
+ warnings.add("-activator ignored when -composites specified.");
+ spec.setProperty(Constants.BUNDLE_ACTIVATOR, COMPONENT_ACTIVATOR);
+ spec.setProperty(COMPONENT_FLAG, "true");
+ spec.setProperty(COMPONENT_LIST, composites);
+ // add activator pkg directly, to avoid needing to add jar to
+ // Bundle-ClassPath.
+ // split-package directive needed to stop Bnd whinging when using
+ // other bundles containing the component-activator.
+ contents.add(COMPONENT_ACTIVATOR_PKG + ";-split-package:=merge-first");
+ }
+
+ List<String> srcPkgs = addLibs(bundle, dest, spec);
+
+ contents.addAll(srcPkgs);
+ addContents(contents, spec);
+
+ IRequiredBundle fh = bundle.getFragmentHost();
+ if (fh != null) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(fh.getSymbolicName());
+ addVersions(fh.getVersions(), sb);
+ spec.setProperty(Constants.FRAGMENT_HOST, sb.toString());
+ }
+
+ return spec;
+ }
+
+ private void addContents(List<String> contents, Properties spec) {
+ // add contents
+ StringBuilder sb = new StringBuilder();
+ for (String pkg : contents) {
+ if (sb.length() > 0)
+ sb.append(",");
+ sb.append(pkg);
+ }
+
+ if (sb.length() > 0)
+ spec.setProperty(Constants.PRIVATE_PACKAGE, sb.toString());
+ }
+
+ private void appendProperty(String key, String value, Properties p) {
+ String list = p.getProperty(key);
+
+ if (list == null) {
+ list = value;
+ } else {
+ list = list + "," + value;
+ }
+
+ p.setProperty(key, list);
+ }
+
+ private List<String> addLibs(IBldBundle bundle, String dest, Properties spec)
+ throws IOException {
+ // final String cleanVersion =
+ // Builder.cleanupVersion(bundle.getVersion());
+ final String name = bundle.getSymbolicName();
+
+ ArrayList<String> srcPkgs = new ArrayList<String>();
+ Map<String, Map<String, String>> libs = bundle.getLibs();
+
+ if (!bundle.getDownloadContents().isEmpty()) {
+ // implicitly add dljar
+ File fdest = new File(dest);
+ String dlname = fdest.getName().replaceFirst("\\.jar$", "-dl.jar");
+
+ HashMap<String, String> attr = new HashMap<String, String>();
+ attr.put(BldAttr.KIND_ATTRIBUTE, "codebase");
+ attr.put(BldAttr.PUBLISH_ATTRIBUTE, dlname);
+
+ HashMap<String, Map<String, String>> lib2 = new HashMap<String, Map<String, String>>();
+ lib2.putAll(libs);
+ lib2.put(dlname, attr);
+ libs = lib2;
+ }
+
+ StringBuilder items = new StringBuilder();
+
+ for (String jarpath : libs.keySet()) {
+ Map<String, String> attr = libs.get(jarpath);
+ String kind = attr.get(BldAttr.KIND_ATTRIBUTE);
+ String publish = attr.get(BldAttr.PUBLISH_ATTRIBUTE);
+
+ // first find the lib ..
+ String path = attr.get(BldAttr.PATH_ATTRIBUTE);
+ if (path == null)
+ path = jarpath;
+
+ File fsPath = bundle.resolve(path);
+
+ if (!fsPath.exists()) {
+ // try destDir
+ File destDir = new File(dest).getParentFile();
+ File file = new File(destDir, fsPath.getName());
+
+ if (!file.exists()) {
+ // try searching classpath
+ file = findInClasspathDir(fsPath.getName());
+ }
+
+ if (file != null && file.exists())
+ fsPath = file;
+ }
+
+ if (!fsPath.exists()) {
+ // XXX: find external bundle using name and version range?
+ // For now just let BND fail when it can't find resource.
+ }
+
+ appendProperty(Constants.INCLUDE_RESOURCE, jarpath + "=" + fsPath, spec);
+
+ if ("classpath".equals(kind)) {
+ String bcp = spec.getProperty(Constants.BUNDLE_CLASSPATH);
+ if (bcp == null || bcp.length() == 0)
+ spec.setProperty(Constants.BUNDLE_CLASSPATH, ".");
+ appendProperty(Constants.BUNDLE_CLASSPATH, jarpath, spec);
+ }
+
+ if (publish != null) {
+ String pubtype = attr.get(BldAttr.PUBTYPE_ATTRIBUTE);
+ if (pubtype == null)
+ pubtype = defaultPubtype;
+
+ if ("codebase".equals(kind)) {
+ String codebase = format(codebaseFormat, publish, name, pubtype);
+ String zone = attr.get(BldAttr.ZONE_ATTRIBUTE);
+ if (zone != null)
+ codebase += "&zone=" + zone;
+ appendProperty("RMI-Codebase", codebase, spec);
+ }
+
+ // add item to publish xml
+ items.append(format("<item name=\"%s\" path=\"%s\">\n", publish, jarpath));
+ items.append(format("<attribute name=\"type\" value=\"%s\"/>\n", pubtype));
+ items.append("</item>\n");
+ }
+ }
+
+ if (items.length() > 0) {
+ File publishFile = new File(dest.replaceFirst("\\.jar$", "-publish.xml"));
+ publishFile.deleteOnExit();
+ PrintWriter writer = new PrintWriter(new FileWriter(publishFile));
+
+ writer.println("<publish>");
+ writer.println(format("<attribute name=\"bundle.symbolic.name\" value=\"%s\"/>", name));
+ writer.print(items.toString());
+ writer.println("</publish>");
+ writer.close();
+
+ appendProperty(Constants.INCLUDE_RESOURCE, "publish.xml=" + publishFile, spec);
+ }
+
+ return srcPkgs;
+ }
+
+ private String addResources(IBldBundle bundle, Properties spec) {
+ Map<String, String> resources = bundle.getResources();
+ StringBuilder composites = new StringBuilder();
+
+ for (String composite : bundle.getComposites()) {
+ File path = bundle.resolve(composite);
+ String name = path.getName();
+
+ String bPath = COMPONENT_DIR + "/" + name;
+ resources.put(bPath, path.getPath());
+
+ if (composites.length() > 0)
+ composites.append(",");
+ composites.append(bPath);
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ for (String bPath : resources.keySet()) {
+ if (bPath.startsWith("@")) { // Bnd in-line jar
+ if (sb.length() > 0)
+ sb.append(",");
+ sb.append('@');
+ sb.append(bundle.resolve(bPath.substring(1)));
+ continue;
+ }
+
+ String fsPath = resources.get(bPath);
+ if ("".equals(fsPath))
+ fsPath = bPath;
+
+ File resolved = bundle.resolve(fsPath);
+
+ // fsPath may contain Bnd variable, making path appear to not exist
+
+ if (!resolved.exists()) {
+ // Bnd already looks for classpath jars
+ File found = findInClasspathDir(fsPath);
+ if (found != null) {
+ fsPath = found.getPath();
+ } else {
+ fsPath = resolved.getAbsolutePath();
+ }
+ } else {
+ fsPath = resolved.getAbsolutePath();
+ }
+
+ if (sb.length() > 0)
+ sb.append(",");
+ sb.append(bPath);
+ sb.append('=');
+ sb.append(fsPath);
+ }
+
+ if (sb.length() > 0)
+ spec.setProperty(Constants.INCLUDE_RESOURCE, sb.toString());
+
+ return composites.toString();
+ }
+
+ private List<IPackageImport> getImports(IBldBundle bundle) {
+ List<IPackageImport> imports = bundle.getImports();
+ Set<String> pkgs = new HashSet<String>();
+
+ for (IPackageImport pi : imports) {
+ pkgs.add(pi.getPackageName());
+ }
+
+ // add component activator imports
+ if (!bundle.getComposites().isEmpty()) {
+ for (String pkg : BundleBuilder.COMPONENT_ACTIVATOR_DEPS) {
+ if (pkgs.contains(pkg))
+ continue;
+ PackageImport pi = new PackageImport();
+ pi.setPackageName(pkg);
+ String versions = project.getDefaultPackageVersion(pkg);
+ if (versions != null)
+ pi.setVersions(VersionRange.parseVersionRange(versions));
+ imports.add(pi);
+ }
+ }
+
+ return imports;
+ }
+
+ private void addRequirements(IBldBundle bundle, Properties spec) {
+ StringBuilder sb = new StringBuilder();
+
+ // option;addMissingImports=true
+ // Lets Bnd calculate imports (i.e. specify *),
+ // which are then examined by augmentImports();
+
+ // option;omitUnusedImports=true (implies addMissingImports=true)
+ // When project contains multiple bundles which don't all use all
+ // imports,
+ // avoids warnings like:
+ // "Importing packages that are never referred to by any class on the Bundle-ClassPath"
+
+ if (omitUnusedImports && !addMissingImports) {
+ warnings.add("omitUnusedImports ignored as addMissingImports=false.");
+ omitUnusedImports = false;
+ }
+
+ List<IPackageImport> imports = getImports(bundle);
+
+ sb.setLength(0);
+
+ // allow existing header;Package-Import to specify ignored packages
+ sb.append(spec.getProperty(Constants.IMPORT_PACKAGE, ""));
+
+ for (IPackageImport pi : imports) {
+ switch (pi.getOSGiImport()) {
+ case AUTO:
+ if (omitUnusedImports)
+ continue; // added by Import-Package: * and fixed by
+ // augmentImports()
+ break;
+ case NEVER:
+ if (pi.isDependency())
+ continue; // resolve=compile
+ break;
+ case ALWAYS:
+ // Bnd will probably whinge that this import is not used.
+ // we omit it here and replace it in augmentImports,
+ // but only if addMissingImports is true;
+ // otherwise, if the import is used, Bnd will fail.
+ if (addMissingImports)
+ continue;
+ break;
+ }
+
+ if (sb.length() > 0)
+ sb.append(",");
+
+ if (pi.getOSGiImport().equals(IPackageImport.OSGiImport.NEVER)) {
+ sb.append("!");
+ sb.append(pi.getPackageName());
+ } else {
+ sb.append(pi.getPackageName());
+ addVersions(pi.getVersions(), sb);
+
+ if (pi.isOptional()) {
+ sb.append(";resolution:=optional");
+ }
+ }
+ }
+
+ if (addMissingImports) {
+ if (sb.length() > 0)
+ sb.append(",");
+ sb.append("*");
+ }
+
+ spec.setProperty(Constants.IMPORT_PACKAGE, sb.toString());
+
+ sb.setLength(0);
+ for (IRequiredBundle rb : bundle.getRequires()) {
+ if (sb.length() > 0)
+ sb.append(",");
+ sb.append(rb.getSymbolicName());
+ addVersions(rb.getVersions(), sb);
+ }
+
+ if (sb.length() > 0) {
+ spec.setProperty(Constants.REQUIRE_BUNDLE, sb.toString());
+ }
+ }
+
+ private List<String> addExports(IBldBundle bundle, Properties spec) {
+ List<IPackageExport> exports = bundle.getExports();
+ ArrayList<String> list = new ArrayList<String>();
+ StringBuilder sb = new StringBuilder();
+
+ for (IPackageExport export : exports) {
+ if (sb.length() > 0)
+ sb.append(",");
+ sb.append(export.getPackageName());
+ if (!export.getVersion().equals(Version.emptyVersion)) {
+ sb.append(";version=\"");
+ sb.append(export.getVersion());
+ sb.append("\"");
+ }
+ list.add(export.getPackageName());
+ }
+
+ if (sb.length() > 0) {
+ // EXPORT_CONTENTS just sets the Export-Package manifest header;
+ // it doesn't add contents like EXPORT_PACKAGE does.
+ spec.setProperty(Constants.EXPORT_CONTENTS, sb.toString());
+ }
+
+ return list;
+ }
+
+ private void addVersions(VersionRange range, StringBuilder sb) {
+ if (!range.equals(VersionRange.ANY_VERSION)) {
+ sb.append(";version=\"");
+ sb.append(range);
+ sb.append("\"");
+ }
+ }
+
+ private File findInClasspathDir(String file) {
+ for (File cp : classpath) {
+ if (cp.isDirectory()) {
+ File path = new File(cp, file);
+ if (path.exists()) {
+ return path;
+ }
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldAttr.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldAttr.java
new file mode 100644
index 0000000..7496121
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldAttr.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.config;
+
+public class BldAttr {
+ // Sigil attributes
+
+ public static final String KIND_ATTRIBUTE = "kind";
+
+ public static final String RESOLVE_ATTRIBUTE = "resolve";
+ public static final String RESOLVE_AUTO = "auto";
+ public static final String RESOLVE_COMPILE = "compile";
+ public static final String RESOLVE_RUNTIME = "runtime";
+ public static final String RESOLVE_IGNORE = "ignore";
+
+ public static final String PUBLISH_ATTRIBUTE = "publish";
+ public static final String PUBTYPE_ATTRIBUTE = "type";
+ public static final String PATH_ATTRIBUTE = "path";
+ public static final Object ZONE_ATTRIBUTE = "zone";
+
+ // Sigil options
+
+ public static final String OPTION_ADD_IMPORTS = "addMissingImports";
+ public static final String OPTION_OMIT_IMPORTS = "omitUnusedImports";
+
+ // OSGi attributes
+
+ public static final String RESOLUTION_ATTRIBUTE = "resolution";
+ public static final String RESOLUTION_OPTIONAL = "optional";
+
+ public static final String VERSION_ATTRIBUTE = "version";
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldConfig.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldConfig.java
new file mode 100644
index 0000000..225406a
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldConfig.java
@@ -0,0 +1,445 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.config;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+
+import org.cauldron.bld.core.util.QuoteUtil;
+
+public class BldConfig {
+
+ // control properties
+ public static final String C_BUNDLES = "-bundles";
+ public static final String C_REPOSITORIES = "-repositories";
+
+ // string properties
+ public static final String S_ACTIVATOR = "-activator";
+ public static final String S_DEFAULTS = "-defaults";
+ public static final String S_ID = "id";
+ public static final String S_SYM_NAME = "name";
+ public static final String S_VERSION = "version";
+ public static final String[] STRING_KEYS = { S_ACTIVATOR, S_DEFAULTS, S_ID, S_SYM_NAME, S_VERSION };
+
+ // list properties
+ public static final String L_COMPOSITES = "-composites";
+ public static final String L_CONTENTS = "-contents";
+ public static final String L_DL_CONTENTS = "-downloads";
+ public static final String L_SRC_CONTENTS = "-sourcedirs";
+ public static final String L_RESOURCES = "-resources";
+ public static final String[] LIST_KEYS = {
+ L_COMPOSITES, L_CONTENTS, L_DL_CONTENTS, L_SRC_CONTENTS, L_RESOURCES };
+
+ // map properties
+ public static final String M_EXPORTS = "-exports";
+ public static final String M_IMPORTS = "-imports";
+ public static final String M_REQUIRES = "-requires";
+ public static final String M_FRAGMENT = "-fragment";
+ public static final String M_LIBS = "-libs";
+ public static final String[] MAP_KEYS = { M_EXPORTS, M_IMPORTS, M_REQUIRES, M_FRAGMENT, M_LIBS };
+
+ // property properties
+ public static final String P_HEADER = "header";
+ public static final String P_OPTION = "option";
+ public static final String P_PACKAGE_VERSION = "package";
+ public static final String P_BUNDLE_VERSION = "bundle";
+ public static final String[] PROP_KEYS = { P_HEADER, P_OPTION, P_PACKAGE_VERSION, P_BUNDLE_VERSION };
+
+ // private constants
+ private static final String LIST_REGEX = ",\\s*";
+ private static final String MAPATTR_REGEX = ";\\s*";
+ private static final String MAPATTR_SEP = ";";
+ private static final String SUBKEY_SEP = ";";
+
+ // configuration is stored in typed maps
+ private Map<String, String> string = new TreeMap<String, String>();
+ private Map<String, List<String>> list = new TreeMap<String, List<String>>();
+ private Map<String, Map<String, Map<String, String>>> map = new TreeMap<String, Map<String,Map<String,String>>>();
+ private Map<String, BldConfig> config = new TreeMap<String, BldConfig>();
+ private Map<String, Properties> property = new TreeMap<String, Properties>();
+
+ // default config - not modified or saved
+ private BldConfig dflt;
+
+ private Properties unknown = new Properties();
+ private String comment = "";
+
+ public BldConfig() {
+ }
+
+ public BldConfig(Properties p) throws IOException {
+ merge(p);
+ }
+
+ public void setDefault(BldConfig dflt) {
+ this.dflt = dflt;
+ }
+
+ public void setComment(String comment) {
+ this.comment = comment;
+ }
+
+ public Properties getUnknown() {
+ return unknown;
+ }
+
+ public String getString(String id, String key) {
+ if (id != null && config.containsKey(id)) {
+ String value = config.get(id).getString(null, key);
+ if (value != null)
+ return value;
+ }
+ return string.containsKey(key) ? string.get(key) : (dflt != null ? dflt.getString(id, key) : null);
+ }
+
+ public List<String> getList(String id, String key) {
+ if (id != null && config.containsKey(id)) {
+ List<String> value = config.get(id).getList(null, key);
+ if (value != null)
+ return value;
+ }
+ return list.containsKey(key) ? list.get(key) : (dflt != null ? dflt.getList(id, key) : Collections.<String>emptyList());
+ }
+
+ public Map<String, Map<String,String>> getMap(String id, String key) {
+ if (id != null && config.containsKey(id)) {
+ Map<String, Map<String,String>> value = config.get(id).getMap(null, key);
+ if (value != null)
+ return value;
+ }
+ return map.containsKey(key) ? map.get(key)
+ : (dflt != null ? dflt.getMap(id, key) : Collections.<String, Map<String,String>>emptyMap());
+ }
+
+ public void setString(String id, String key, String value) {
+ if (!value.equals(getString(id, key))) {
+ if (id != null) {
+ if (!config.containsKey(id))
+ config.put(id, new BldConfig());
+ config.get(id).setString(null, key, value);
+ } else {
+ String dval = (dflt == null ? dflt.getString(null, key) : null);
+ if (value.equals("") && (dval == null || dval.equals(""))) {
+ string.remove(key);
+ } else {
+ string.put(key, value);
+ }
+ }
+ }
+ }
+
+ public void setList(String id, String key, List<String> value) {
+ if (!value.equals(getList(id, key))) {
+ if (id != null) {
+ if (!config.containsKey(id))
+ config.put(id, new BldConfig());
+ config.get(id).setList(null, key, value);
+ } else if (value.isEmpty() && (dflt == null || dflt.getList(null, key).isEmpty())) {
+ list.remove(key);
+ } else {
+ list.put(key, value);
+ }
+ }
+ }
+
+ public void setMap(String id, String key, Map<String, Map<String,String>> value) {
+ if (!value.equals(getMap(id, key))) {
+ if (id != null) {
+ if (!config.containsKey(id))
+ config.put(id, new BldConfig());
+ config.get(id).setMap(null, key, value);
+ } else if (value.isEmpty() && (dflt == null || dflt.getMap(null, key).isEmpty())) {
+ map.remove(key);
+ } else {
+ map.put(key, value);
+ }
+ }
+ }
+
+ public Properties getProps(String id, String key) {
+ // merge main and sub properties
+ Properties props = new Properties();
+
+ if (dflt != null)
+ props.putAll(dflt.getProps(id, key));
+
+ if (property.containsKey(key))
+ props.putAll(property.get(key));
+
+ if (id != null && config.containsKey(id)) {
+ Properties p2 = config.get(id).getProps(null, key);
+ if (p2 != null)
+ props.putAll(p2);
+ }
+
+ return props;
+ }
+
+ // only sets one property at a time
+ public void setProp(String id, String key, String k2, String v2) {
+ if (v2 == null)
+ return;
+ Properties props = getProps(id, key);
+ if (!v2.equals(props.getProperty(key))) {
+ if (id != null) {
+ if (!config.containsKey(id))
+ config.put(id, new BldConfig());
+ config.get(id).setProp(null, key, k2, v2);
+ } else {
+ if (property.containsKey(key)) {
+ property.get(key).put(k2, v2);
+ } else {
+ Properties value = new Properties();
+ value.put(k2, v2);
+ property.put(key, value);
+ }
+ }
+ }
+ }
+
+ /**
+ * write config in Property file format.
+ * This allows us to make it prettier than Properties.store().
+ */
+ public void write(final PrintWriter out) {
+ out.println(comment);
+
+ // Note: don't add date stamp, or file will differ each time it's saved.
+ out.println("# sigil project file, saved by plugin.\n");
+
+ dump("", new Properties() {
+ private static final long serialVersionUID = 1L; //appease eclipse
+ @Override
+ public Object put(Object key, Object value) {
+ if (value instanceof String) {
+ out.println(key + ": " + value);
+ out.println("");
+ } else if (value instanceof List) {
+ out.println(key + ": \\");
+ for (Object k : (List<?>) value) {
+ out.println("\t" + k + ", \\");
+ }
+ out.println("");
+ }
+ else if (value instanceof Map) {
+ out.println(key + ": \\");
+ StringBuilder b = new StringBuilder();
+ for (Map.Entry<?, ?> e : ((Map<?,?>) value).entrySet()) {
+ b.append("\t");
+ b.append(e.getKey());
+ Map<?, ?> v = (Map<?, ?>) e.getValue();
+ if (!v.isEmpty()) {
+ for (Map.Entry<?, ?> e2 : v.entrySet()) {
+ b.append(MAPATTR_SEP);
+ b.append(e2.getKey());
+ b.append("=");
+ String v2 = e2.getValue().toString();
+ if (v2.contains(",")) {
+ b.append("\"");
+ b.append(v2);
+ b.append("\"");
+ }
+ else {
+ b.append(v2);
+ }
+ }
+ }
+ b.append (", \\\n");
+ }
+ out.println(b.toString());
+ }
+ return null;
+ }
+ });
+ out.println("# end");
+ }
+
+ /**
+ * dump config in pseudo Properties format.
+ * Note: some values are not Strings (they're List<String>).
+ */
+ private void dump(String prefix, Properties p) {
+ for (String key : string.keySet()) {
+ p.put(prefix + key, string.get(key));
+ }
+
+ for (String key : list.keySet()) {
+ List<String> list2 = list.get(key);
+ p.put(prefix + key, list2);
+ }
+
+ for (String key : map.keySet()) {
+ Map<String, Map<String,String>> map2 = map.get(key);
+ p.put(prefix + key, map2);
+ }
+
+ for (String key : property.keySet()) {
+ Properties props = property.get(key);
+ for (Object k2 : props.keySet()) {
+ p.put(prefix + key + SUBKEY_SEP + k2, props.get(k2));
+ }
+ }
+
+ for (String key : config.keySet()) {
+ BldConfig config2 = config.get(key);
+ config2.dump(key + SUBKEY_SEP + prefix, p);
+ }
+
+ for (Object key : unknown.keySet()) {
+ String value = unknown.getProperty((String)key);
+ if (value.length() > 0)
+ p.put(prefix + key, value);
+ }
+ }
+
+ /**
+ * merges properties into current configuration.
+ * @param p
+ * @throws IOException
+ */
+ public void merge(Properties p) throws IOException {
+ if (p.isEmpty())
+ return;
+
+ final List<String> strings = Arrays.asList(STRING_KEYS);
+ final List<String> lists = Arrays.asList(LIST_KEYS);
+ final List<String> maps = Arrays.asList(MAP_KEYS);
+
+ List<String> bundleKeys = new ArrayList<String>();
+ List<String> repoKeys = new ArrayList<String>();
+
+ String bundles = p.getProperty(C_BUNDLES);
+ if (bundles != null) {
+ bundleKeys.addAll(Arrays.asList(bundles.split(LIST_REGEX)));
+ list.put(C_BUNDLES, bundleKeys);
+ }
+
+ String repos = p.getProperty(C_REPOSITORIES);
+ if (repos != null) {
+ for ( String s : repos.split(LIST_REGEX) ) {
+ repoKeys.add(s.trim());
+ }
+ list.put(C_REPOSITORIES, repoKeys);
+ }
+
+ List<String> subKeys = new ArrayList<String>();
+ subKeys.addAll(Arrays.asList(PROP_KEYS));
+ subKeys.addAll(bundleKeys);
+ subKeys.addAll(repoKeys);
+
+ Map<String, Properties> sub = new TreeMap<String, Properties>();
+
+ for (Object k : p.keySet()) {
+ String key = (String) k;
+ if (key.equals(C_BUNDLES) || key.equals(C_REPOSITORIES))
+ continue;
+
+ String value = p.getProperty(key);
+ String[] keys = key.split(SUBKEY_SEP, 2);
+
+ if (keys.length > 1) {
+ Properties p2 = sub.get(keys[0]);
+ if (p2 == null) {
+ p2 = new Properties();
+ sub.put(keys[0], p2);
+ if (!subKeys.contains(keys[0])) {
+ unknown.setProperty(keys[0] + SUBKEY_SEP, "");
+ }
+ }
+ p2.setProperty(keys[1], value);
+ } else if (strings.contains(key)) {
+ if (!string.containsKey(key))
+ string.put(key, value);
+ } else if (lists.contains(key)) {
+ if (!list.containsKey(key)) {
+ ArrayList<String> list2 = new ArrayList<String>();
+ for (String s : value.split(LIST_REGEX)) {
+ if ( s.trim().length() > 0 ) {
+ list2.add(s.trim());
+ }
+ }
+ if ( !list2.isEmpty() ) {
+ list.put(key, list2);
+ }
+ }
+ } else if (maps.contains(key)) {
+ if (!map.containsKey(key)) {
+ Map<String, Map<String,String>> map2 = new TreeMap<String, Map<String,String>>();
+
+ for (String subValue : QuoteUtil.split(value)) {
+ if (subValue.trim().length() > 0) {
+ String[] split = subValue.split(MAPATTR_REGEX);
+ Map<String,String> map3 = new TreeMap<String,String>();
+ for (int i = 1; i < split.length; ++i){
+ String[] keyVal = split[i].split(":?=", 2);
+ if (keyVal.length != 2) {
+ throw new IOException("attribute missing '=':" + subValue);
+ }
+ map3.put(keyVal[0], keyVal[1]);
+ }
+ map2.put(split[0], map3);
+ }
+ }
+
+ map.put(key, map2);
+ }
+ } else {
+ unknown.setProperty(key, value);
+ }
+ }
+
+ for (String subKey : sub.keySet()) {
+ Properties props = sub.get(subKey);
+ if (!props.isEmpty()) {
+ if (bundleKeys.contains(subKey)) {
+ BldConfig config2 = new BldConfig(props);
+ Properties unkProps = config2.getUnknown();
+
+ if (config2.map.containsKey(M_IMPORTS))
+ unkProps.setProperty(M_IMPORTS, "");
+
+ if (config2.map.containsKey(M_REQUIRES))
+ unkProps.setProperty(M_REQUIRES, "");
+
+ for (Object unk : unkProps.keySet()) {
+ unknown.setProperty(subKey + SUBKEY_SEP + unk, "");
+ }
+ config.put(subKey, config2);
+ } else {
+ property.put(subKey, props);
+ }
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "string: " + string + " list:" + list + " map: " + map + " prop: " + property + " config:" + config;
+ }
+
+}
+
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldConverter.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldConverter.java
new file mode 100644
index 0000000..0f8a332
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldConverter.java
@@ -0,0 +1,464 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.config;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.cauldron.bld.config.IBldProject.IBldBundle;
+import org.cauldron.bld.core.BldCore;
+import org.cauldron.bld.core.internal.model.eclipse.SigilBundle;
+import org.cauldron.bld.core.internal.model.osgi.BundleModelElement;
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.model.eclipse.ISCAComposite;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IBundleModelElement;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.osgi.framework.Version;
+
+import aQute.lib.osgi.Constants;
+
+public class BldConverter {
+ private static final String classpathFormat = "<classpathentry kind=\"%s\" path=\"%s\"/>";
+ private BldConfig config;
+ private Properties packageDefaults;
+ private TreeSet<String> packageWildDefaults;
+
+ public BldConverter(BldConfig config) {
+ this.config = config;
+ }
+
+ /**
+ * converts to an ISigilBundle.
+ * @param id
+ * @param bundle
+ * @return
+ */
+ public ISigilBundle getBundle(String id, IBldBundle bundle) {
+
+ ISigilBundle sigilBundle = new SigilBundle();
+ IBundleModelElement info = new BundleModelElement();
+ sigilBundle.setBundleInfo(info);
+
+ // exports
+ // FIXME: UI doesn't understand export wildcard packages
+ for (IPackageExport export : bundle.getExports()) {
+ IPackageExport clone = (IPackageExport) export.clone();
+ clone.setParent(null);
+ info.addExport(clone);
+ }
+
+ // imports
+ for (IPackageImport import1 : bundle.getImports()) {
+ IPackageImport clone = (IPackageImport) import1.clone();
+ clone.setParent(null);
+ info.addImport(clone);
+ }
+
+ // requires
+ for (IRequiredBundle require : bundle.getRequires()) {
+ IRequiredBundle clone = (IRequiredBundle) require.clone();
+ clone.setParent(null);
+ info.addRequiredBundle(clone);
+ }
+
+ // fragment
+ IRequiredBundle fragment = bundle.getFragmentHost();
+ if (fragment != null) {
+ info.setFragmentHost(fragment);
+ }
+
+ // contents
+ for (String pkg : bundle.getContents()) {
+ sigilBundle.addPackage(pkg);
+ }
+
+ // downloads
+ for (String pkg : bundle.getDownloadContents()) {
+ sigilBundle.addDownloadPackage(pkg);
+ }
+
+ // sources
+ for (String source : config.getList(null, BldConfig.L_SRC_CONTENTS) ) {
+ sigilBundle.addClasspathEntry(String.format(classpathFormat, "src", source));
+ }
+
+ // libs
+ Map<String, Map<String, String>> libs = bundle.getLibs();
+
+ for (String path : libs.keySet()) {
+ Map<String, String> attr = libs.get(path);
+ String kind = attr.get(BldAttr.KIND_ATTRIBUTE);
+ String publish = attr.get(BldAttr.PUBLISH_ATTRIBUTE);
+
+ if (publish != null) {
+ // FIXME: UI doesn't understand publish=name
+ BldCore.error("Can't convert -libs publish=" + publish);
+ continue;
+ }
+
+ if ("classpath".equals(kind)) {
+ sigilBundle.addClasspathEntry(String.format(classpathFormat, "lib", path));
+ } else {
+ BldCore.error("Can't convert -libs kind=" + kind);
+ }
+ }
+
+ // resources
+ // FIXME: UI doesn't support -resources: path1=path2
+ Map<String, String> resources = bundle.getResources();
+ for (String resource : resources.keySet()) {
+ String fsPath = resources.get(resource);
+ if (!"".equals(fsPath)) {
+ BldCore.error("FIXME: can't convert resource: " + resource + "=" + fsPath);
+ }
+ sigilBundle.addSourcePath(new Path(resource));
+ }
+
+ ////////////////////
+ // simple headers
+
+ info.setSymbolicName(bundle.getSymbolicName());
+
+ info.setVersion(Version.parseVersion(bundle.getVersion()));
+
+ String activator = bundle.getActivator();
+ if (activator != null)
+ info.setActivator(activator);
+
+ Properties headers = config.getProps(id, BldConfig.P_HEADER);
+ String header;
+
+ header = headers.getProperty("CATEGORY");
+ if (header != null)
+ info.setCategory(header);
+
+ header = headers.getProperty(Constants.BUNDLE_CONTACTADDRESS);
+ if (header != null)
+ info.setContactAddress(header);
+
+ header = headers.getProperty(Constants.BUNDLE_COPYRIGHT);
+ if (header != null)
+ info.setCopyright(header);
+
+ header = headers.getProperty(Constants.BUNDLE_DESCRIPTION);
+ if (header != null)
+ info.setDescription(header);
+
+ header = headers.getProperty(Constants.BUNDLE_VENDOR);
+ if (header != null)
+ info.setVendor(header);
+
+ header = headers.getProperty(Constants.BUNDLE_NAME);
+ if (header != null)
+ info.setName(header);
+
+ header = headers.getProperty(Constants.BUNDLE_DOCURL);
+ if (header != null)
+ info.setDocURI(URI.create(header));
+
+ header = headers.getProperty(Constants.BUNDLE_LICENSE);
+ if (header != null)
+ info.setDocURI(URI.create(header));
+
+ return sigilBundle;
+ }
+
+ private VersionRange defaultVersion(VersionRange current, String defaultRange) {
+ if (current.equals(VersionRange.ANY_VERSION) ||
+ current.equals(VersionRange.parseVersionRange(defaultRange))) {
+ return null;
+ }
+ return current;
+ }
+
+ // FIXME - copied from BldProject
+ private String getDefaultPackageVersion(String name) {
+ if (packageDefaults == null) {
+ packageDefaults = config.getProps(null, BldConfig.P_PACKAGE_VERSION);
+ packageWildDefaults = new TreeSet<String>();
+
+ for (Object key : packageDefaults.keySet()) {
+ String pkg = (String)key;
+ if (pkg.endsWith("*")) {
+ packageWildDefaults.add(pkg.substring(0, pkg.length() - 1));
+ }
+ }
+ }
+
+ String version = packageDefaults.getProperty(name);
+
+ if (version == null) {
+ for (String pkg : packageWildDefaults) {
+ if (name.startsWith(pkg)) {
+ version = packageDefaults.getProperty(pkg + "*");
+ // break; -- don't break, as we want the longest match
+ }
+ }
+ }
+
+ return version;
+ }
+
+
+ /**
+ * converts from an ISigilBundle.
+ *
+ * @param id
+ * @param bundle
+ */
+ public void setBundle(String id, ISigilBundle bundle) {
+ IBundleModelElement info = bundle.getBundleInfo();
+ String bundleVersion = config.getString(id, BldConfig.S_VERSION);
+
+ // exports
+ Map<String, Map<String, String>> exports = new TreeMap<String, Map<String,String>>();
+ for (IPackageExport export : info.getExports()) {
+ Map<String, String> map2 = new TreeMap<String, String>();
+ String version = export.getVersion().toString();
+ if (!version.equals(bundleVersion))
+ map2.put(BldAttr.VERSION_ATTRIBUTE, version);
+ exports.put(export.getPackageName(), map2);
+ }
+
+ if (!exports.isEmpty() || !config.getMap(id, BldConfig.M_EXPORTS).isEmpty()) {
+ config.setMap(id, BldConfig.M_EXPORTS, exports);
+ }
+
+ // imports
+ Map<String, Map<String, String>> imports = new TreeMap<String, Map<String,String>>();
+
+ // FIXME: default version logic is wrong here
+ // if the version to be saved is the same as the default version,
+ // then we should _remove_ the version from the value being saved,
+ // since config.getMap() does not apply default versions.
+ for (IPackageImport import1 : info.getImports()) {
+ Map<String, String> map2 = new TreeMap<String, String>();
+ String name = import1.getPackageName();
+ VersionRange versions = defaultVersion(import1.getVersions(), getDefaultPackageVersion(name));
+
+ boolean isDependency = import1.isDependency();
+ Map<String, String> selfImport = exports.get(name);
+
+ if (selfImport != null) {
+ // avoid saving self-import attributes, e.g.
+ // org.cauldron.newton.example.fractal.engine;resolve=auto;version=1.0.0
+ isDependency = true;
+
+ if (versions != null) {
+ String exportVersion = selfImport.get(BldAttr.VERSION_ATTRIBUTE);
+ if (exportVersion == null)
+ exportVersion = bundleVersion;
+
+ if (exportVersion.equals(versions.toString())) {
+ versions = null;
+ }
+ }
+ }
+
+ if (versions != null) {
+ map2.put(BldAttr.VERSION_ATTRIBUTE, versions.toString());
+ }
+
+ if (import1.isOptional()) {
+ map2.put(BldAttr.RESOLUTION_ATTRIBUTE, BldAttr.RESOLUTION_OPTIONAL);
+ }
+
+ String resolve = BldProject.getResolve(import1, isDependency);
+ if (resolve != null)
+ map2.put(BldAttr.RESOLVE_ATTRIBUTE, resolve);
+
+ imports.put(name, map2);
+ }
+ if (!imports.isEmpty() || !config.getMap(id, BldConfig.M_IMPORTS).isEmpty()) {
+ config.setMap(id, BldConfig.M_IMPORTS, imports);
+ }
+
+ // requires
+ Properties defaultBundles = config.getProps(null, BldConfig.P_BUNDLE_VERSION);
+ Map<String, Map<String, String>> requires = new TreeMap<String, Map<String,String>>();
+
+ for (IRequiredBundle require : info.getRequiredBundles()) {
+ Map<String, String> map2 = new TreeMap<String, String>();
+ String name = require.getSymbolicName();
+ VersionRange versions = defaultVersion(require.getVersions(), defaultBundles.getProperty(name));
+ if (versions != null)
+ map2.put(BldAttr.VERSION_ATTRIBUTE, versions.toString());
+ requires.put(name, map2);
+ }
+ if (!requires.isEmpty() || !config.getMap(id, BldConfig.M_REQUIRES).isEmpty()) {
+ config.setMap(id, BldConfig.M_REQUIRES, requires);
+ }
+
+ // fragment
+ Map<String, Map<String, String>> fragments = new TreeMap<String, Map<String,String>>();
+ IRequiredBundle fragment = info.getFragmentHost();
+ if (fragment != null) {
+ Map<String, String> map2 = new TreeMap<String, String>();
+ String name = fragment.getSymbolicName();
+ VersionRange versions = defaultVersion(fragment.getVersions(), defaultBundles.getProperty(name));
+ if (versions != null)
+ map2.put(BldAttr.VERSION_ATTRIBUTE, versions.toString());
+ fragments.put(name, map2);
+ }
+ if (!fragments.isEmpty() || !config.getMap(id, BldConfig.M_FRAGMENT).isEmpty()) {
+ config.setMap(id, BldConfig.M_FRAGMENT, fragments);
+ }
+
+ // contents
+ List<String> contents = new ArrayList<String>();
+ for (String pkg : bundle.getPackages()) {
+ contents.add(pkg);
+ }
+ if (!contents.isEmpty() || !config.getList(id, BldConfig.L_CONTENTS).isEmpty()) {
+ config.setList(id, BldConfig.L_CONTENTS, contents);
+ }
+
+ // dl contents
+ List<String> dlcontents = new ArrayList<String>();
+ for (String pkg : bundle.getDownloadPackages()) {
+ dlcontents.add(pkg);
+ }
+ if (!dlcontents.isEmpty() || !config.getList(id, BldConfig.L_DL_CONTENTS).isEmpty()) {
+ config.setList(id, BldConfig.L_DL_CONTENTS, dlcontents);
+ }
+
+ // libs
+ Map<String, Map<String, String>> libs = new TreeMap<String, Map<String,String>>();
+ List<String> sources = new ArrayList<String>();
+
+ // classpathEntries map to -libs or -sources
+ for (String entry : bundle.getClasspathEntrys()) {
+ // <classpathentry kind="lib" path="lib/dependee.jar"/>
+ // <classpathentry kind="src" path="src"/>
+ final String regex = ".* kind=\"([^\"]+)\" path=\"([^\"]+)\".*";
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(entry);
+ if (matcher.matches()) {
+ String kind = matcher.group(1);
+ String path = matcher.group(2);
+ if (kind.equals("lib")) {
+ Map<String, String> map2 = new TreeMap<String, String>();
+ map2.put(BldAttr.KIND_ATTRIBUTE, "classpath");
+ libs.put(path, map2);
+ } else if (kind.equals("src")) {
+ sources.add(path);
+ } else {
+ BldCore.error("unknown classpathentry kind=" + kind);
+ }
+ } else {
+ BldCore.error("can't match classpathEntry in: " + entry);
+ }
+ }
+
+ if (!libs.isEmpty() || !config.getMap(id, BldConfig.M_LIBS).isEmpty()) {
+ config.setMap(id, BldConfig.M_LIBS, libs);
+ }
+
+ if (!sources.isEmpty() || !config.getList(id, BldConfig.L_SRC_CONTENTS).isEmpty()) {
+ config.setList(id, BldConfig.L_SRC_CONTENTS, sources);
+ }
+
+ // composites
+ ArrayList<String> composites = new ArrayList<String>();
+ for (ISCAComposite composite : bundle.getComposites()) {
+ String path = composite.getLocation().toString();
+ // TODO relativize
+ composites.add(path);
+ }
+
+ if (!composites.isEmpty() || !config.getList(id, BldConfig.L_COMPOSITES).isEmpty()) {
+ Collections.sort(composites);
+ config.setList(id, BldConfig.L_COMPOSITES, composites);
+ }
+
+ // resources
+ ArrayList<String> resources = new ArrayList<String>();
+ for (IPath ipath : bundle.getSourcePaths()) {
+ resources.add(ipath.toString());
+ }
+
+ if (!resources.isEmpty() || !config.getList(id, BldConfig.L_RESOURCES).isEmpty()) {
+ Collections.sort(resources);
+ config.setList(id, BldConfig.L_RESOURCES, resources);
+ }
+
+ if (info.getSourceLocation() != null) {
+ BldCore.error("SourceLocation conversion not yet implemented.");
+ }
+
+ if (!info.getLibraryImports().isEmpty()) {
+ BldCore.error("LibraryImports conversion not yet implemented.");
+ }
+
+ ////////////////////
+ // simple headers
+
+ List<String> ids = config.getList(null, BldConfig.C_BUNDLES);
+ String idBsn = id != null ? id : ids.get(0);
+ String oldBsn = config.getString(id, BldConfig.S_SYM_NAME);
+ String bsn = info.getSymbolicName();
+
+ if (!bsn.equals(idBsn) || oldBsn != null)
+ config.setString(id, BldConfig.S_SYM_NAME, bsn);
+
+ String version = info.getVersion().toString();
+ if (version != null)
+ config.setString(id, BldConfig.S_VERSION, version);
+
+ String activator = info.getActivator();
+ if (activator != null)
+ config.setString(id, BldConfig.S_ACTIVATOR, activator);
+
+ Properties headers = config.getProps(null, BldConfig.P_HEADER);
+
+ setHeader(headers, id, "CATEGORY", info.getCategory());
+ setHeader(headers, id, Constants.BUNDLE_CONTACTADDRESS, info.getContactAddress());
+ setHeader(headers, id, Constants.BUNDLE_COPYRIGHT, info.getCopyright());
+ setHeader(headers, id, Constants.BUNDLE_DESCRIPTION, info.getDescription());
+ setHeader(headers, id, Constants.BUNDLE_VENDOR, info.getVendor());
+ setHeader(headers, id, Constants.BUNDLE_NAME, info.getName());
+
+ if (info.getDocURI() != null)
+ config.setProp(id, BldConfig.P_HEADER, Constants.BUNDLE_DOCURL, info.getDocURI().toString());
+
+ if (info.getLicenseURI() != null)
+ config.setProp(id, BldConfig.P_HEADER, Constants.BUNDLE_LICENSE, info.getLicenseURI().toString());
+ }
+
+ private void setHeader(Properties headers, String id, String key, String value) {
+ if (value == null)
+ value = "";
+ if (!value.equals(headers.getProperty(key, "")))
+ config.setProp(id, BldConfig.P_HEADER, key, value);
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldFactory.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldFactory.java
new file mode 100644
index 0000000..a02adccc
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldFactory.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.config;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+public class BldFactory {
+ private static Map<URI, BldProject> projects = new HashMap<URI, BldProject>();
+
+ public static IBldProject getProject(URI uri) throws IOException {
+ return getProject(uri, false);
+ }
+
+ public static IBldProject getProject(URI uri, boolean ignoreCache) throws IOException {
+ return load(uri, ignoreCache);
+ }
+
+ public static IRepositoryConfig getConfig(URI uri) throws IOException {
+ return load(uri, false);
+ }
+
+ /**
+ * creates a new project file, initialised with defaults.
+ * @param uri where the file will be saved - used to resolve relative paths.
+ * @param defaults relative path to defaults file - default ../sigil.properties.
+ * @return
+ * @throws IOException
+ */
+ public static IBldProject newProject(URI uri, String defaults) throws IOException {
+ BldProject project = new BldProject(uri);
+ Properties p = new Properties();
+ if (defaults != null)
+ p.setProperty(BldConfig.S_DEFAULTS, defaults);
+ project.loadDefaults(p);
+ return project;
+ }
+
+ private static BldProject load(URI uri, boolean ignoreCache) throws IOException {
+ BldProject p = null;
+ if (!ignoreCache) {
+ p = projects.get(uri);
+ }
+
+ if (p == null) {
+ p = new BldProject(uri);
+ p.load();
+ projects.put(uri, p);
+
+ if (Boolean.getBoolean("org.cauldron.bld.config.test")) {
+ File path = new File(uri.getPath() + ".tmp");
+ System.out.println("XXX: config.test writing: " + path);
+ p.saveAs(path);
+ }
+ }
+ return p;
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldProject.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldProject.java
new file mode 100644
index 0000000..585a029
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldProject.java
@@ -0,0 +1,823 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.config;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeSet;
+
+import org.cauldron.bld.bnd.BundleBuilder;
+import org.cauldron.bld.core.internal.model.osgi.BundleModelElement;
+import org.cauldron.bld.core.internal.model.osgi.PackageExport;
+import org.cauldron.bld.core.internal.model.osgi.PackageImport;
+import org.cauldron.bld.core.internal.model.osgi.RequiredBundle;
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IBundleModelElement;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.cauldron.sigil.model.osgi.IPackageImport.OSGiImport;
+import org.osgi.framework.Version;
+
+public class BldProject implements IBldProject, IRepositoryConfig {
+ private static final String OVERRIDE_PREFIX = "sigil.";
+ private static final int MAX_HEADER = 10240;
+ // cache to avoid loading the same default config for each project
+ private static Map<URL, BldConfig> defaultsCache = new HashMap<URL, BldConfig>();
+ private static Properties overrides;
+
+ private List<String> sourcePkgs;
+ private BldConfig config;
+ private BldConverter convert;
+ private BundleModelElement requirements;
+ private File baseDir;
+ private URI loc;
+ private Properties packageDefaults;
+ private TreeSet<String> packageWildDefaults;
+ private long lastModified;
+
+ /* package */BldProject(URI relLoc) {
+ config = new BldConfig();
+ convert = new BldConverter(config);
+ loc = new File(".").toURI().resolve(relLoc).normalize();
+ File f = new File(loc);
+ lastModified = f.lastModified();
+ baseDir = f.getParentFile();
+ }
+
+ /* package */void load() throws IOException {
+ // allow System property overrides, e.g.
+ // ANT_OPTS='-Dsigil.option\;addMissingImports=false' ant
+ config.merge(getOverrides());
+
+ InputStream in = null;
+ try {
+ in = loc.toURL().openStream();
+ BufferedInputStream bis = new BufferedInputStream(in);
+ bis.mark(MAX_HEADER);
+ readHeader(bis);
+ bis.reset();
+
+ Properties p = new Properties();
+ p.load(bis);
+ config.merge(p);
+
+ Properties unknown = config.getUnknown();
+ if (!unknown.isEmpty())
+ System.err.println("WARN: unknown keys " + unknown.keySet() + " in " + loc);
+
+ loadDefaults(p);
+ requirements = parseRequirements();
+ }
+ finally {
+ if ( in != null ) {
+ in.close();
+ }
+ }
+ }
+
+ /* package */void loadDefaults(Properties p) throws IOException {
+ BldConfig c = loadDefaults(p, baseDir, null);
+ config.setDefault(c);
+
+ Properties options = config.getProps(null, BldConfig.P_OPTION);
+
+ if (!options.containsKey(BldAttr.OPTION_ADD_IMPORTS))
+ c.setProp(null, BldConfig.P_OPTION, BldAttr.OPTION_ADD_IMPORTS, "true");
+
+ // default omitUnusedImports option depends on number of bundles...
+ // we set it here to avoid it being written by save(),
+ // but as this may alter cached defaults, once set we have to reset it
+ // for each project.
+
+ boolean omitSet = options.containsKey("__omit_set__");
+ boolean multiple = getBundleIds().size() > 1;
+
+ if (multiple || omitSet) {
+ if (!options.containsKey(BldAttr.OPTION_OMIT_IMPORTS) || omitSet) {
+ c.setProp(null, BldConfig.P_OPTION, BldAttr.OPTION_OMIT_IMPORTS, multiple + "");
+ c.setProp(null, BldConfig.P_OPTION, "__omit_set__", "true");
+ }
+ }
+ }
+
+ private synchronized BldConfig loadDefaults(Properties props, File base, BldConfig dflt)
+ throws IOException {
+ boolean cached = false;
+ String defaults = props.getProperty(BldConfig.S_DEFAULTS, "-"
+ + IBldProject.PROJECT_DEFAULTS);
+
+ if (base != null && defaults.length() > 0) {
+ boolean ignore = defaults.startsWith("-");
+
+ if (ignore)
+ defaults = defaults.substring(1);
+
+ try {
+ File file = new File(base, defaults).getCanonicalFile();
+ URL url = file.toURL();
+
+ if (dflt == null) {
+ dflt = defaultsCache.get(url);
+ if (dflt != null)
+ return dflt;
+
+ dflt = new BldConfig();
+ defaultsCache.put(url, dflt);
+ cached = true;
+ }
+
+ Properties p = new Properties();
+ p.load(url.openStream());
+ dflt.merge(p);
+
+ ignore = false;
+ loadDefaults(p, file.getParentFile(), dflt);
+ } catch (IOException e) {
+ if (!ignore)
+ throw e;
+ }
+ }
+
+ if (dflt == null)
+ return new BldConfig();
+
+ if (cached) {
+ Properties unknown = dflt.getUnknown();
+ if (!unknown.isEmpty())
+ System.err.println("WARN: unknown keys " + unknown.keySet() + " in defaults for "
+ + loc);
+ }
+
+ return dflt;
+ }
+
+ private static Properties getOverrides() {
+ if (overrides == null) {
+ overrides = new Properties();
+ Properties sysProps = System.getProperties();
+
+ for (Object okey : sysProps.keySet()) {
+ String key = (String) okey;
+ if (key.startsWith(OVERRIDE_PREFIX)) {
+ overrides.setProperty(key.substring(OVERRIDE_PREFIX.length()), sysProps
+ .getProperty(key));
+ }
+ }
+ }
+
+ return overrides;
+ }
+
+ private void readHeader(InputStream in) throws IOException {
+ BufferedReader r = new BufferedReader(new InputStreamReader(in));
+ StringBuffer header = new StringBuffer();
+ String line;
+ while ((line = r.readLine()) != null) {
+ if (line.startsWith("#")) {
+ header.append(line);
+ header.append("\n");
+ } else {
+ config.setComment(header.toString());
+ break;
+ }
+ }
+ }
+
+ public File resolve(String path) {
+ File file = new File(path);
+ if (!file.isAbsolute()) {
+ // can't use loc.resolve(value), as value may not be valid URI.
+ file = new File(baseDir, path);
+ }
+ return file;
+ }
+
+ public String getVersion() {
+ String version = config.getString(null, BldConfig.S_VERSION);
+ return version == null ? "0" : version;
+ }
+
+ public IBundleModelElement getDependencies() {
+ IBundleModelElement dependencies = new BundleModelElement();
+
+ for (IModelElement element : getRequirements().children()) {
+ if (element instanceof IPackageImport) {
+ IPackageImport import1 = (IPackageImport) element;
+ if (!import1.isDependency())
+ continue;
+
+ IPackageImport pi = (IPackageImport) (element.clone());
+ pi.setParent(null);
+ dependencies.addImport(pi);
+ } else {
+ IRequiredBundle rb = (IRequiredBundle) (element.clone());
+ rb.setParent(null);
+ dependencies.addRequiredBundle(rb);
+ }
+ }
+
+ boolean containsComposite = false;
+
+ for (IBldBundle bundle : getBundles()) {
+ if (!bundle.getComposites().isEmpty()) {
+ containsComposite = true;
+ break;
+ }
+ }
+
+ // add dependency on component activator
+ if (containsComposite) {
+ PackageImport pi = new PackageImport();
+ pi.setPackageName(BundleBuilder.COMPONENT_ACTIVATOR_PKG);
+ pi.setOSGiImport(OSGiImport.NEVER);
+ dependencies.addImport(pi);
+ }
+
+ return dependencies;
+ }
+
+ private IBundleModelElement getRequirements() {
+ return requirements;
+ }
+
+ /*
+ * private boolean globMatch(String pkg, Set<String> set) { // exact match
+ * if (set.contains(pkg)) return true;
+ *
+ * // org.foo.bar matches org.foo. for (String glob : set) { if
+ * (glob.matches(pkg)) { return true; } }
+ *
+ * return false; }
+ */
+
+ /**
+ * set internal OSGiImport and isDependency flags, based on external
+ * resolution= attribute.
+ *
+ * OSGiImport: AUTO ALWAYS NEVER dependency: default - compile !dependency:
+ * auto runtime ignore
+ *
+ */
+ private void setResolve(IPackageImport pi, String resolve) throws IOException {
+ if (pi.isOptional())
+ pi.setDependency(false);
+
+ if (BldAttr.RESOLVE_COMPILE.equals(resolve)) {
+ if (pi.isOptional())
+ pi.setDependency(true);
+ else
+ pi.setOSGiImport(OSGiImport.NEVER);
+ } else if (BldAttr.RESOLVE_RUNTIME.equals(resolve)) {
+ pi.setDependency(false);
+ pi.setOSGiImport(OSGiImport.ALWAYS);
+ } else if (BldAttr.RESOLVE_AUTO.equals(resolve)) {
+ pi.setDependency(false);
+ } else if (BldAttr.RESOLVE_IGNORE.equals(resolve)) {
+ pi.setDependency(false);
+ pi.setOSGiImport(OSGiImport.NEVER);
+ } else if (resolve != null) {
+ throw new IOException("Bad attribute value: " + BldAttr.RESOLVE_ATTRIBUTE + "="
+ + resolve);
+ }
+ }
+
+ /**
+ * get external resolve= attribute from internal PackageImport flags. This
+ * is called from BldConverter.setBundle().
+ */
+ public static String getResolve(IPackageImport pi, boolean isDependency) {
+ OSGiImport osgiImport = pi.getOSGiImport();
+ String resolve = null;
+
+ if (isDependency) {
+ if (osgiImport.equals(OSGiImport.NEVER) || pi.isOptional())
+ resolve = BldAttr.RESOLVE_COMPILE;
+ } else {
+ switch (osgiImport) {
+ case ALWAYS:
+ resolve = BldAttr.RESOLVE_RUNTIME;
+ break;
+ case AUTO:
+ resolve = BldAttr.RESOLVE_AUTO;
+ break;
+ case NEVER:
+ resolve = BldAttr.RESOLVE_IGNORE;
+ break;
+ }
+ }
+ return resolve;
+ }
+
+ public String getDefaultPackageVersion(String name) {
+ if (packageDefaults == null) {
+ packageDefaults = config.getProps(null, BldConfig.P_PACKAGE_VERSION);
+ packageWildDefaults = new TreeSet<String>();
+
+ for (Object key : packageDefaults.keySet()) {
+ String pkg = (String) key;
+ if (pkg.endsWith("*")) {
+ packageWildDefaults.add(pkg.substring(0, pkg.length() - 1));
+ }
+ }
+ }
+
+ String version = packageDefaults.getProperty(name);
+
+ if (version == null) {
+ for (String pkg : packageWildDefaults) {
+ if (name.startsWith(pkg)) {
+ version = packageDefaults.getProperty(pkg + "*");
+ // break; -- don't break, as we want the longest match
+ }
+ }
+ }
+
+ return version;
+ }
+
+ private synchronized BundleModelElement parseRequirements() throws IOException {
+ BundleModelElement reqs = new BundleModelElement();
+
+ List<String> sourceContents = getSourcePkgs();
+ HashSet<String> exports = new HashSet<String>();
+
+ for (IBldBundle bundle : getBundles()) {
+ for (IPackageExport export : bundle.getExports()) {
+ exports.add(export.getPackageName());
+ }
+ }
+
+ Map<String, Map<String, String>> imports = config.getMap(null, BldConfig.M_IMPORTS);
+
+ for (String name : imports.keySet()) {
+ Map<String, String> attr = imports.get(name);
+
+ String resolve = attr.get(BldAttr.RESOLVE_ATTRIBUTE);
+ String resolution = attr.get(BldAttr.RESOLUTION_ATTRIBUTE);
+ String versions = attr.containsKey(BldAttr.VERSION_ATTRIBUTE) ? attr
+ .get(BldAttr.VERSION_ATTRIBUTE) : getDefaultPackageVersion(name);
+
+ PackageImport pi = new PackageImport();
+ pi.setPackageName(name);
+
+ // avoid dependency on self-exports
+ // XXX: BldConverter.setBundle contains similar logic
+ if (exports.contains(name)
+ && (sourceContents.contains(name) || sourceContents.isEmpty())) {
+ pi.setDependency(false);
+ if (versions == null)
+ versions = getVersion();
+ }
+
+ if (!checkVersionRange(versions)) {
+ throw new IOException("Failed to parse version range for " + resolve
+ + " missing \"'s around version range?");
+ }
+
+ pi.setVersions(VersionRange.parseVersionRange(versions));
+
+ if (BldAttr.RESOLUTION_OPTIONAL.equals(resolution)) {
+ pi.setOptional(true);
+ } else if (resolution != null) {
+ throw new IOException("Bad attribute value: " + BldAttr.RESOLUTION_ATTRIBUTE + "="
+ + resolution);
+ }
+
+ setResolve(pi, resolve);
+
+ reqs.addImport(pi);
+ }
+
+ Map<String, Map<String, String>> requires = config.getMap(null, BldConfig.M_REQUIRES);
+ Properties bundleDefaults = config.getProps(null, BldConfig.P_BUNDLE_VERSION);
+
+ if (requires != null) {
+ for (String name : requires.keySet()) {
+ Map<String, String> attr = requires.get(name);
+ String versions = attr.containsKey(BldAttr.VERSION_ATTRIBUTE) ? attr
+ .get(BldAttr.VERSION_ATTRIBUTE) : bundleDefaults.getProperty(name);
+
+ RequiredBundle rb = new RequiredBundle();
+ rb.setSymbolicName(name);
+ rb.setVersions(VersionRange.parseVersionRange(versions));
+
+ reqs.addRequiredBundle(rb);
+ }
+ }
+
+ for (IBldBundle bundle : getBundles()) {
+ IRequiredBundle fh = bundle.getFragmentHost();
+ if (fh != null)
+ reqs.addRequiredBundle(fh);
+ }
+
+ return reqs;
+ }
+
+ private boolean checkVersionRange(String versions) {
+ if (versions == null || versions.length() == 0) {
+ return true;
+ } else {
+ switch (versions.charAt(0)) {
+ case '(':
+ case '[':
+ switch (versions.charAt(versions.length() - 1)) {
+ case ')':
+ case ']':
+ return true;
+ default:
+ return false;
+ }
+ default:
+ return true;
+ }
+ }
+ }
+
+ public List<String> getBundleIds() {
+ List<String> ids = config.getList(null, BldConfig.C_BUNDLES);
+ if (ids == null)
+ return Collections.emptyList();
+ return ids;
+ }
+
+ public List<IBldBundle> getBundles() {
+ ArrayList<IBldBundle> list = new ArrayList<IBldBundle>();
+
+ for (String id : getBundleIds()) {
+ list.add(new BldBundle(id));
+ }
+
+ return list;
+ }
+
+ // Implement IBldConfig: getRepositoryConfig
+
+ public Map<String, Properties> getRepositoryConfig() {
+ HashMap<String, Properties> map = new HashMap<String, Properties>();
+
+ final Map<String, String> env = System.getenv();
+ final Properties props = new Properties();
+ try {
+ // supports ${.} and ${..} expansions
+ props.setProperty(".", resolve(".").getCanonicalPath());
+ props.setProperty("..", resolve("..").getCanonicalPath());
+ } catch (IOException e) {
+ }
+
+ for (String name : config.getList(null, BldConfig.C_REPOSITORIES)) {
+ Properties repo = config.getProps(null, name);
+
+ for (Object k : repo.keySet()) {
+ String key = (String) k;
+ String value = repo.getProperty(key);
+
+ String expand = BldUtil.expand(value, new Properties() {
+ public String getProperty(String name) {
+ return props.getProperty(name, env.get(name));
+ }
+ });
+
+ if (!value.equals(expand)) {
+ value = expand;
+ repo.setProperty(key, value);
+ }
+
+ // backwards compatible support before ${.} and ${..} was added
+ if (value.startsWith("./") || value.startsWith("../")) {
+ try {
+ // need canonical path, to normalise
+ value = resolve(value).getCanonicalPath();
+ } catch (IOException e) {
+ }
+ repo.setProperty(key, value);
+ }
+ }
+
+ map.put(name, repo);
+ }
+ return map;
+ }
+
+ public Properties getOptions() {
+ return config.getProps(null, BldConfig.P_OPTION);
+ }
+
+ public Properties getDefaultPackageVersions() {
+ return config.getProps(null, BldConfig.P_PACKAGE_VERSION);
+ }
+
+ public ISigilBundle getDefaultBundle() {
+ List<String> bundles = getBundleIds();
+ if (bundles.isEmpty())
+ return null;
+
+ String id = bundles.get(0);
+ return getSigilBundle(id);
+ }
+
+ public ISigilBundle getSigilBundle(String id) {
+ BldBundle bundle = new BldBundle(id);
+ return convert.getBundle(id, bundle);
+ }
+
+ public void setDefaultBundle(ISigilBundle bundle) {
+ setSigilBundle(null, bundle);
+ }
+
+ public void setSigilBundle(String id, ISigilBundle bundle) {
+ List<String> ids = getBundleIds();
+
+ if (ids.isEmpty()) {
+ ArrayList<String> list = new ArrayList<String>();
+ list.add(id == null ? bundle.getBundleInfo().getSymbolicName() : id);
+ config.setList(null, BldConfig.C_BUNDLES, list);
+ } else if (id == null) {
+ id = ids.get(0);
+ } else if (!ids.contains(id)) {
+ List<String> list = config.getList(null, BldConfig.C_BUNDLES);
+ list.add(id);
+ config.setList(null, BldConfig.C_BUNDLES, list);
+ }
+
+ if (ids.size() == 1)
+ id = null; // don't prefix default bundle with id
+
+ convert.setBundle(id, bundle);
+ }
+
+ public void save() throws IOException {
+ saveAs(new File(loc));
+ }
+
+ public void saveAs(File path) throws IOException {
+ File part = new File(path.getPath() + ".part");
+ saveTo(new FileOutputStream((part)));
+
+ path.delete();
+ if (!part.renameTo(path))
+ throw new IOException("failed to rename " + part + " to " + path);
+ }
+
+ public void saveTo(OutputStream out) {
+ PrintWriter writer = new PrintWriter(new OutputStreamWriter(out));
+ config.write(writer);
+ writer.close();
+ }
+
+ public List<String> getSourceDirs() {
+ List<String> list = config.getList(null, BldConfig.L_SRC_CONTENTS);
+ return list != null ? list : Collections.<String> emptyList();
+ }
+
+ public List<String> getSourcePkgs() {
+ if (sourcePkgs == null) {
+ sourcePkgs = new ArrayList<String>();
+ for (String src : getSourceDirs()) {
+ File dir = resolve(src);
+ if (!dir.isDirectory()) {
+ System.err.println("WARN: sourcedir does not exist: " + dir);
+ continue;
+ // throw new RuntimeException("sourcedir: " + dir +
+ // " : is not a directory.");
+ }
+ findSrcPkgs(dir, null, sourcePkgs);
+ }
+ }
+
+ return sourcePkgs;
+ }
+
+ private void findSrcPkgs(File dir, String pkg, List<String> result) {
+ ArrayList<File> dirs = new ArrayList<File>();
+ boolean found = false;
+
+ for (String name : dir.list()) {
+ if (name.endsWith(".java")) {
+ found = true;
+ } else if (!name.equals(".svn")) {
+ File d = new File(dir, name);
+ if (d.isDirectory())
+ dirs.add(d);
+ }
+ }
+
+ if (pkg == null) {
+ pkg = "";
+ } else if (pkg.equals("")) {
+ pkg = dir.getName();
+ } else {
+ pkg = pkg + "." + dir.getName();
+ }
+
+ if (found)
+ result.add(pkg);
+
+ for (File d : dirs)
+ findSrcPkgs(d, pkg, result);
+ }
+
+ /**
+ * BldBundle
+ *
+ */
+ class BldBundle implements IBldBundle {
+ private String id;
+
+ public BldBundle(String id) {
+ this.id = id;
+ }
+
+ public File resolve(String path) {
+ return BldProject.this.resolve(path);
+ }
+
+ private String getString(String key) {
+ return config.getString(id, key);
+ }
+
+ private List<String> getList(String key) {
+ List<String> list = config.getList(id, key);
+ return list != null ? list : Collections.<String> emptyList();
+ }
+
+ private Map<String, Map<String, String>> getMap(String key) {
+ Map<String, Map<String, String>> map = config.getMap(id, key);
+ return map != null ? map : Collections.<String, Map<String, String>> emptyMap();
+ }
+
+ public String getActivator() {
+ return getString(BldConfig.S_ACTIVATOR);
+ }
+
+ public String getId() {
+ String name = getString("id");
+ return name != null ? name : id;
+ }
+
+ public String getVersion() {
+ String ver = getString(BldConfig.S_VERSION);
+ if (ver == null) {
+ ver = BldProject.this.getVersion();
+ }
+ return ver;
+ }
+
+ public String getSymbolicName() {
+ String name = getString(BldConfig.S_SYM_NAME);
+ return name != null ? name : getId();
+ }
+
+ public List<IPackageExport> getExports() {
+ ArrayList<IPackageExport> list = new ArrayList<IPackageExport>();
+ Map<String, Map<String, String>> exports = getMap(BldConfig.M_EXPORTS);
+
+ if (exports != null) {
+ for (String name : exports.keySet()) {
+ Map<String, String> attrs = exports.get(name);
+ PackageExport pkgExport = new PackageExport();
+ pkgExport.setPackageName(name);
+
+ String version = attrs.get(BldAttr.VERSION_ATTRIBUTE);
+ // only default export version from local packages
+ if (version == null
+ && (getSourcePkgs().isEmpty() || getSourcePkgs().contains(name))) {
+ version = getVersion();
+ }
+
+ if (version != null)
+ pkgExport.setVersion(new Version(version));
+
+ list.add(pkgExport);
+ }
+ }
+
+ return list;
+ }
+
+ public List<IPackageImport> getImports() {
+ ArrayList<IPackageImport> list = new ArrayList<IPackageImport>();
+
+ for (IPackageImport import1 : getRequirements().childrenOfType(IPackageImport.class)) {
+ list.add(import1);
+ }
+
+ return list;
+ }
+
+ public List<IRequiredBundle> getRequires() {
+ ArrayList<IRequiredBundle> list = new ArrayList<IRequiredBundle>();
+ list.addAll(Arrays.asList(getRequirements().childrenOfType(IRequiredBundle.class)));
+
+ for (IBldBundle bundle : getBundles()) {
+ IRequiredBundle fh = bundle.getFragmentHost();
+ if (fh != null)
+ list.remove(fh);
+ }
+
+ return list;
+ }
+
+ public IRequiredBundle getFragmentHost() {
+ IRequiredBundle fragment = null;
+ Map<String, Map<String, String>> fragments = getMap(BldConfig.M_FRAGMENT);
+ if (fragments != null) {
+ for (String name : fragments.keySet()) {
+ Map<String, String> attr = fragments.get(name);
+ String versions = attr.isEmpty() ? null : attr.get(BldAttr.VERSION_ATTRIBUTE);
+ fragment = new RequiredBundle();
+ fragment.setSymbolicName(name);
+ fragment.setVersions(VersionRange.parseVersionRange(versions));
+ break;
+ }
+ }
+
+ return fragment;
+ }
+
+ public Map<String, Map<String, String>> getLibs() {
+ Map<String, Map<String, String>> libs = getMap(BldConfig.M_LIBS);
+ return (libs != null) ? libs : Collections.<String, Map<String, String>> emptyMap();
+ }
+
+ public List<String> getContents() {
+ return getList(BldConfig.L_CONTENTS);
+ }
+
+ public List<String> getDownloadContents() {
+ return getList(BldConfig.L_DL_CONTENTS);
+ }
+
+ public List<String> getComposites() {
+ ArrayList<String> list = new ArrayList<String>();
+ for (String composite : getList(BldConfig.L_COMPOSITES)) {
+ list.add(composite);
+ }
+
+ return list;
+ }
+
+ public Map<String, String> getResources() {
+ HashMap<String, String> map = new HashMap<String, String>();
+ List<String> resources = getList(BldConfig.L_RESOURCES);
+
+ if (resources != null) {
+ for (String resource : resources) {
+ String[] paths = resource.split("=", 2);
+ String fsPath = (paths.length > 1 ? paths[1] : "");
+ map.put(paths[0], fsPath);
+ }
+ }
+ return map;
+ }
+
+ public Properties getHeaders() {
+ Properties headers = config.getProps(id, BldConfig.P_HEADER);
+ return headers;
+ }
+
+ }
+
+ public long getLastModified() {
+ return lastModified;
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldUtil.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldUtil.java
new file mode 100644
index 0000000..ad85627
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/BldUtil.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.config;
+
+import java.util.Map;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+// taken from Newton Launcher
+
+public class BldUtil {
+ /**
+ * expands property references embedded in strings. Each occurrence of ${name} is replaced with the value of
+ * p.getProperty("name"); If the property is not set, then the original reference, is returned as follows "?<name>".
+ *
+ * Strings to be expanded should not contain $ or }, except to indicate expansions.
+ *
+ * Value is expanded recursively and so can contain further ${name} references. Also supports shell-expansions
+ * ${name:-value}, ${name:=value} and ${name:+value}.
+ *
+ * <pre>
+ * ${parameter}
+ * The value of parameter is substituted.
+ * ${parameter:-word}
+ * Use Default Values. If parameter is null, the expansion of word
+ * is substituted. Otherwise, the value of parameter is substituted.
+ * ${parameter:=word}
+ * Assign Default Values. If parameter is null, the expansion of
+ * word is assigned to parameter. The value of parameter is then
+ * substituted.
+ * ${parameter:+word}
+ * Use Alternate Value. If parameter is null, nothing is
+ * substituted, otherwise the expansion of word is substituted.
+ * ${parameter:?word}
+ * Raise Error. If parameter is null, a RuntimeException is thown,
+ * with word as the message.
+ * </pre>
+ */
+ public static String expand(String s, Properties p) {
+ // regex to match property references e.g. ${name}
+ // TODO this is very simplistic, so strings to be expanded should not
+ // contain $ or }, except where substitution is expected.
+ // Update: propRef regex now allows substitutions to contain $,
+ // e.g. where a Windows ${user.name} is $Admin or similar.
+ final Pattern propRef = Pattern.compile("\\$\\{(((\\$[^\\{\\}])|[^\\$\\}])+\\$?)\\}");
+ final Pattern backslash = Pattern.compile("\\\\");
+ final Pattern dollar = Pattern.compile("\\$");
+
+ if (s == null) {
+ return null;
+ }
+
+ if (s.indexOf("${") == -1) { // shortcut if no expansions
+ return s;
+ }
+
+ for (int i = 0; i < 20; i++) { // avoids self-referencing expansions
+ // System.out.println("XXX expand[" + i + "] = [" + s + "]");
+ Matcher matcher = propRef.matcher(s);
+
+ if (!matcher.find()) {
+ // replace unmatched items
+ s = s.replaceAll("\\Q??[\\E", "\\${");
+ s = s.replaceAll("\\Q??]\\E", "}");
+ // debug("expanded: " + s);
+ if (s.indexOf("${") != -1) {
+ throw new RuntimeException("Can't expand: " + s);
+ }
+ return s;
+ }
+
+ String key = matcher.group(1);
+ String[] keydef = key.split(":[=+-?@]", 2);
+ String replace;
+
+ if (keydef.length != 2) {
+ replace = key.length() == 0 ? null : p.getProperty(key);
+ }
+ else {
+ replace = keydef[0].length() == 0 ? null : p.getProperty(keydef[0]);
+
+ if (replace != null && (replace.length() == 0 || replace.indexOf("${") != -1)) {
+ // don't want unexpanded replacement, as it may stop ${...:-default}
+ replace = null;
+ }
+
+ if (key.indexOf(":+") != -1) {
+ replace = ((replace == null) ? "" : keydef[1]);
+ }
+ else if (replace == null) {
+ replace = keydef[1];
+
+ if (key.indexOf(":?") != -1) {
+ String msg = "${" + keydef[0] + ":?" + keydef[1] + "} property not set";
+ throw new RuntimeException(msg);
+ }
+
+ if (key.indexOf(":=") != -1) {
+ p.setProperty(keydef[0], keydef[1]);
+ }
+ }
+ }
+
+ if (replace == null) {
+ // TODO: this is a hack to avoid looping on unmatched references
+ // should really leave unchanged and process rest of string.
+ // We use "]" as delimiter to avoid non-matched "}"
+ // terminating potential _propRef match
+ replace = "??[" + key + "??]";
+ }
+
+ // Excerpt from replaceAll() javadoc:
+ //
+ // Note that backslashes (\) and dollar signs ($) in the replacement
+ // string may cause the results to be different than if it were
+ // being
+ // treated as a literal replacement string. Dollar signs may be
+ // treated
+ // as references to captured subsequences, and backslashes are used
+ // to
+ // escape literal characters in the replacement string.
+ // escape any \ or $ in replacement string
+ replace = backslash.matcher(replace).replaceAll("\\\\\\\\");
+ replace = dollar.matcher(replace).replaceAll("\\\\\\$");
+
+ s = s.replaceAll("\\Q${" + key + "}\\E", replace);
+ }
+
+ throw new RuntimeException("expand: loop expanding: " + s);
+ }
+
+ public static String expand(String s) {
+ final Map<String, String> env = System.getenv();
+
+ return expand(s, new Properties() {
+ public String getProperty(String name) {
+ return System.getProperty(name, env.get(name));
+ }
+ });
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/IBldProject.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/IBldProject.java
new file mode 100644
index 0000000..b273b7b
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/IBldProject.java
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.config;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IBundleModelElement;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+
+public interface IBldProject {
+
+ static final String PROJECT_FILE = "sigil.properties";
+ static final String PROJECT_DEFAULTS = "../sigil-defaults.properties";
+
+ void save() throws IOException;
+
+ void saveAs(File path) throws IOException;
+
+ void saveTo(OutputStream out) throws IOException;
+
+ /**
+ * gets default package version ranges.
+ */
+ Properties getDefaultPackageVersions();
+
+ /**
+ * gets default package version range for named package.
+ * Also handles wildcards in defaults.
+ */
+ String getDefaultPackageVersion(String name);
+
+ /**
+ * get project options.
+ */
+ Properties getOptions();
+
+ /**
+ * get project version.
+ */
+ String getVersion();
+
+ /**
+ * gets dependencies (Package-Import and Require-Bundle) needed to compile.
+ */
+ IBundleModelElement getDependencies();
+
+ /**
+ * gets project source directories.
+ * This is a convenient way to specify bundle contents
+ * when the project doesn't contains multiple bundles.
+ */
+ List<String> getSourceDirs();
+
+ /**
+ * gets the list of packages represented by getSourceDirs().
+ * @throws IOException
+ */
+ List<String> getSourcePkgs();
+
+ /**
+ * gets bundle ids.
+ */
+ List<String> getBundleIds();
+
+ /**
+ * gets bundles.
+ */
+ List<IBldBundle> getBundles();
+
+ /**
+ * convert specified bundle to SigilBundle.
+ */
+ ISigilBundle getSigilBundle(String id);
+
+ /**
+ * convert SigilBundle to specified bundle.
+ */
+ void setSigilBundle(String id, ISigilBundle sb);
+
+ /**
+ * converts default bundle to SigilBundle.
+ */
+ ISigilBundle getDefaultBundle();
+
+ /**
+ * converts SigilBundle to default bundle.
+ */
+ void setDefaultBundle(ISigilBundle sb);
+
+ /**
+ * resolves a relative path against the project file location.
+ */
+ File resolve(String path);
+
+ /**
+ * gets the last modification date of the project file.
+ */
+ long getLastModified();
+
+ interface IBldBundle {
+ /**
+ * gets bundle activator
+ */
+ String getActivator();
+
+ /**
+ * gets bundle id within project.
+ */
+ String getId();
+
+ /**
+ * gets bundle version.
+ */
+ String getVersion();
+
+ /**
+ * gets the Bundle-SymbolicName.
+ */
+ String getSymbolicName();
+
+ /**
+ * gets bundles export-packages.
+ */
+ List<IPackageExport> getExports();
+
+ /**
+ * gets project import-packages.
+ */
+ List<IPackageImport> getImports();
+
+ /**
+ * gets project require-bundles.
+ */
+ List<IRequiredBundle> getRequires();
+
+ /**
+ * get bundle fragment-host.
+ */
+ IRequiredBundle getFragmentHost();
+
+ /**
+ * gets bundle libs.
+ */
+ Map<String, Map<String, String>> getLibs();
+
+ /**
+ * gets the bundle contents
+ * @return list of package patterns.
+ */
+ List<String> getContents();
+
+ /**
+ * gets the bundle's associated dljar contents.
+ * This is a convenience which avoids having to define another bundle
+ * just for the dljar, which is then added to the parent bundle.
+ * @return list of package patterns.
+ */
+ List<String> getDownloadContents();
+
+ /**
+ * gets SCA composites.
+ */
+ List<String> getComposites();
+
+ /**
+ * gets the additional resources.
+ * @return map with key as path in bundle, value as path in file system.
+ * Paths are resolved relative to location of project file and also from classpath.
+ */
+ Map<String,String> getResources();
+
+ /**
+ * gets additional bundle headers.
+ */
+ Properties getHeaders();
+
+ /**
+ * resolves a relative path against the project file location.
+ */
+ File resolve(String path);
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/IRepositoryConfig.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/IRepositoryConfig.java
new file mode 100644
index 0000000..edcc1d0
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/config/IRepositoryConfig.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.config;
+
+import java.util.Map;
+import java.util.Properties;
+
+public interface IRepositoryConfig {
+ static final String REPOSITORY_PROVIDER = "provider";
+ static final String REPOSITORY_LEVEL = "level";
+
+ /**
+ * get properties with which to instantiate repositories.
+ * The key REPOSITORY_PROVIDER will be set to the fully qualified class name of the IRepositoryProvider.
+ * The key REPOSITORY_LEVEL indicates repository search order.
+ * @return
+ */
+ Map<String,Properties> getRepositoryConfig();
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/BldCore.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/BldCore.java
new file mode 100644
index 0000000..c39e8ab
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/BldCore.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.cauldron.bld.core.internal.license.LicenseManager;
+import org.cauldron.bld.core.internal.model.eclipse.DownloadJar;
+import org.cauldron.bld.core.internal.model.eclipse.Library;
+import org.cauldron.bld.core.internal.model.eclipse.LibraryImport;
+import org.cauldron.bld.core.internal.model.eclipse.SigilBundle;
+import org.cauldron.bld.core.internal.model.osgi.BundleModelElement;
+import org.cauldron.bld.core.internal.model.osgi.PackageExport;
+import org.cauldron.bld.core.internal.model.osgi.PackageImport;
+import org.cauldron.bld.core.internal.model.osgi.RequiredBundle;
+import org.cauldron.bld.core.licence.ILicenseManager;
+import org.cauldron.sigil.model.ModelElementFactory;
+import org.cauldron.sigil.model.eclipse.IDownloadJar;
+import org.cauldron.sigil.model.eclipse.ILibrary;
+import org.cauldron.sigil.model.eclipse.ILibraryImport;
+import org.cauldron.sigil.model.eclipse.INewtonSystem;
+import org.cauldron.sigil.model.eclipse.ISCAComposite;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IBundleModelElement;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class BldCore implements BundleActivator {
+ private static LicenseManager licenceManager = new LicenseManager();
+
+ private static final Logger log = Logger.getLogger(BldCore.class.getName());
+
+ public static void error(String string, Throwable e) {
+ // TODO
+ log.log( Level.WARNING, string, e );
+ }
+
+ public static void error(String string) {
+ log.log( Level.WARNING, string );
+ }
+
+ public static ILicenseManager getLicenseManager() {
+ return licenceManager;
+ }
+
+ public void start(BundleContext context) throws Exception {
+ init();
+ }
+
+ public static void init() throws Exception {
+ String uri = "http://sigil.codecauldron.org/xml/sigil-namespace";
+ ModelElementFactory.getInstance().register(ISigilBundle.class,
+ SigilBundle.class, "bundle", "sigil", uri);
+ ModelElementFactory.getInstance().register(IDownloadJar.class,
+ DownloadJar.class, "download", "sigil", uri);
+ ModelElementFactory.getInstance().register(ILibrary.class,
+ Library.class, "library", "sigil", uri);
+ ModelElementFactory.getInstance().register(ILibraryImport.class,
+ LibraryImport.class, "library-import", "sigil", uri);
+
+ // osgi elements
+ ModelElementFactory.getInstance().register(IBundleModelElement.class,
+ BundleModelElement.class, "bundle", null, null);
+ ModelElementFactory.getInstance().register(IPackageExport.class,
+ PackageExport.class, "package.export", null, null);
+ ModelElementFactory.getInstance().register(IPackageImport.class,
+ PackageImport.class, "package.import", null, null);
+ ModelElementFactory.getInstance().register(IRequiredBundle.class,
+ RequiredBundle.class, "required.bundle", null, null);
+ }
+
+ public void stop(BundleContext context) throws Exception {
+ // TODO Auto-generated method stub
+
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/license/LicenseManager.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/license/LicenseManager.java
new file mode 100644
index 0000000..456cfc9
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/license/LicenseManager.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.internal.license;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.cauldron.bld.core.licence.ILicenseManager;
+import org.cauldron.bld.core.licence.ILicensePolicy;
+//import org.cauldron.sigil.model.project.ISigilProjectModel;
+
+public class LicenseManager implements ILicenseManager {
+
+ private HashMap<String, Pattern> licenses = new HashMap<String, Pattern>();
+ private HashMap<String, LicensePolicy> policies = new HashMap<String, LicensePolicy>();
+ private LicensePolicy defaultPolicy = new LicensePolicy(this);
+
+ public void addLicense(String name, Pattern pattern) {
+ licenses.put( name, pattern );
+ }
+
+ public void removeLicense(String name) {
+ licenses.remove(name);
+ }
+
+ public Set<String> getLicenseNames() {
+ return Collections.unmodifiableSet(licenses.keySet());
+ }
+
+ public Pattern getLicensePattern(String name) {
+ return licenses.get( name );
+ }
+
+ public ILicensePolicy getDefaultPolicy() {
+ return defaultPolicy;
+ }
+
+ //public ILicensePolicy getPolicy(ISigilProjectModel project) {
+ // synchronized( policies ) {
+ // LicensePolicy p = policies.get(project.getName());
+ //
+ // if ( p == null ) {
+ // p = new LicensePolicy(this, project);
+ // policies.put( project.getName(), p );
+ // }
+ //
+ // return p;
+ // }
+ //}
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/license/LicensePolicy.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/license/LicensePolicy.java
new file mode 100644
index 0000000..4a00b00
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/license/LicensePolicy.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.internal.license;
+
+import org.cauldron.bld.core.licence.ILicensePolicy;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+public class LicensePolicy implements ILicensePolicy {
+
+ private LicenseManager licenseManager;
+
+ public LicensePolicy(LicenseManager licenseManager) {
+ this.licenseManager = licenseManager;
+ }
+
+ public boolean accept(ISigilBundle bundle) {
+ return true;
+ }
+
+ public void addAllowed(String licenseName) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void removeAllowed(String licenseName) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void save(IProgressMonitor monitor) {
+ // TODO Auto-generated method stub
+
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/eclipse/DownloadJar.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/eclipse/DownloadJar.java
new file mode 100644
index 0000000..f8bc392
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/eclipse/DownloadJar.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.internal.model.eclipse;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.cauldron.sigil.model.AbstractCompoundModelElement;
+import org.cauldron.sigil.model.eclipse.IDownloadJar;
+import org.eclipse.core.runtime.IPath;
+
+public class DownloadJar extends AbstractCompoundModelElement implements IDownloadJar {
+
+ private static final long serialVersionUID = 1L;
+
+ private Set<IPath> entries = new HashSet<IPath>();
+
+ public DownloadJar() {
+ super("RMI Classpath Download Jar");
+ }
+
+ public void addEntry(IPath entry) {
+ entries.add( entry );
+ }
+
+ public void removeEntry(IPath entry) {
+ entries.remove( entry );
+ }
+
+ public Set<IPath> getEntrys() {
+ return entries;
+ }
+
+ public void clearEntries() {
+ entries.clear();
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/eclipse/Library.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/eclipse/Library.java
new file mode 100644
index 0000000..9ce6be8
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/eclipse/Library.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.internal.model.eclipse;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.cauldron.sigil.model.AbstractCompoundModelElement;
+import org.cauldron.sigil.model.eclipse.ILibrary;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.osgi.framework.Version;
+
+public class Library extends AbstractCompoundModelElement implements ILibrary {
+
+ private static final long serialVersionUID = 1L;
+
+ private String name;
+ private Version version;
+ private Set<IRequiredBundle> bundles;
+ private Set<IPackageImport> imports;
+
+ public Library() {
+ super("Library");
+ bundles = new HashSet<IRequiredBundle>();
+ imports = new HashSet<IPackageImport>();
+ }
+
+ public void addBundle(IRequiredBundle bundle) {
+ bundles.add(bundle);
+ }
+
+ public void addImport(IPackageImport pi) {
+ imports.add(pi);
+ }
+
+ public Collection<IRequiredBundle> getBundles() {
+ return bundles;
+ }
+
+ public Collection<IPackageImport> getImports() {
+ return imports;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Version getVersion() {
+ return version;
+ }
+
+ public void removeBundle(IRequiredBundle bundle) {
+ bundles.remove(bundle);
+ }
+
+ public void removeImport(IPackageImport pi) {
+ imports.remove(pi);
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setVersion(Version version) {
+ this.version = version;
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/eclipse/LibraryImport.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/eclipse/LibraryImport.java
new file mode 100644
index 0000000..2c86944
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/eclipse/LibraryImport.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.internal.model.eclipse;
+
+import org.cauldron.sigil.model.AbstractModelElement;
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.model.eclipse.ILibraryImport;
+
+public class LibraryImport extends AbstractModelElement implements ILibraryImport {
+
+ private static final long serialVersionUID = 1L;
+
+ public LibraryImport() {
+ super("Library Import");
+ }
+
+ private String libraryName;
+ private VersionRange range = VersionRange.ANY_VERSION;
+
+ public String getLibraryName() {
+ return libraryName;
+ }
+
+ public VersionRange getVersions() {
+ return range;
+ }
+
+ public void setLibraryName(String name) {
+ this.libraryName = name;
+ }
+
+ public void setVersions(VersionRange range) {
+ this.range = range;
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/eclipse/SigilBundle.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/eclipse/SigilBundle.java
new file mode 100644
index 0000000..1e09fcc
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/eclipse/SigilBundle.java
@@ -0,0 +1,356 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.internal.model.eclipse;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.cauldron.bld.core.BldCore;
+import org.cauldron.sigil.model.AbstractCompoundModelElement;
+import org.cauldron.sigil.model.eclipse.IDownloadJar;
+import org.cauldron.sigil.model.eclipse.ISCAComposite;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IBundleModelElement;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.SubMonitor;
+import org.osgi.framework.Version;
+
+/**
+ * @author dave
+ *
+ */
+public class SigilBundle extends AbstractCompoundModelElement implements ISigilBundle {
+
+ private static final long serialVersionUID = 1L;
+
+ private IBundleModelElement bundle;
+ private IDownloadJar download;
+ private Set<IPath> sourcePaths;
+ private Set<IPath> libraryPaths;
+ private Set<ISCAComposite> composites;
+ private Set<String> classpath;
+ private Set<String> packages;
+ private Set<String> dlPackages;
+ private IPath location;
+
+ private IPath sourcePathLocation;
+ private IPath licencePathLocation;
+ private IPath sourceRootPath;
+ private String bundleHost;
+
+ public SigilBundle() {
+ super( "Sigil Bundle" );
+ sourcePaths = new HashSet<IPath>();
+ libraryPaths = new HashSet<IPath>();
+ composites = new HashSet<ISCAComposite>();
+ classpath = new HashSet<String>();
+ packages = new HashSet<String>();
+ dlPackages = new HashSet<String>();
+ }
+
+ public void synchronize(IProgressMonitor monitor) throws IOException {
+ SubMonitor progress = SubMonitor.convert(monitor, 100);
+ progress.subTask("Synchronizing " + bundle.getSymbolicName() + " binary" );
+ sync(location, bundle.getUpdateLocation(), progress.newChild(45));
+
+ try {
+ progress.subTask("Synchronizing " + bundle.getSymbolicName() + " source" );
+ sync(sourcePathLocation, bundle.getSourceLocation(), progress.newChild(45));
+ } catch (IOException e) {
+ BldCore.error( "Failed to download source for " + bundle.getSymbolicName() + " " + bundle.getVersion(), e.getCause() );
+ }
+
+ try {
+ progress.subTask("Synchronizing " + bundle.getSymbolicName() + " licence" );
+ sync(licencePathLocation, bundle.getLicenseURI(), progress.newChild(10));
+ } catch (IOException e) {
+ BldCore.error( "Failed to download licence for " + bundle.getSymbolicName() + " " + bundle.getVersion(), e.getCause() );
+ }
+ }
+
+ public boolean isSynchronized() {
+ return location == null || location.toFile().exists();
+ }
+
+ private static void sync(IPath local, URI remote, IProgressMonitor monitor) throws IOException {
+ try {
+ if ( local != null && !local.toFile().exists() ) {
+ if ( remote != null ) {
+ URL url = remote.toURL();
+ URLConnection connection = url.openConnection();
+ int contentLength = connection.getContentLength();
+
+ monitor.beginTask("Downloading from " + url.getHost(), contentLength);
+
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ URLConnection conn = url.openConnection();
+ if ( conn instanceof HttpURLConnection ) {
+ HttpURLConnection http = (HttpURLConnection) conn;
+ http.setConnectTimeout(10000);
+ http.setReadTimeout(5000);
+ }
+ in = conn.getInputStream();
+ File f = local.toFile();
+ f.getParentFile().mkdirs();
+ out = new FileOutputStream( f );
+ stream( in, out, monitor );
+ }
+ finally {
+ if ( in != null ) {
+ in.close();
+ }
+ if ( out != null ) {
+ out.close();
+ }
+ monitor.done();
+ }
+ }
+ }
+ }
+ catch (IOException e) {
+ local.toFile().delete();
+ throw e;
+ }
+ }
+
+ private static void stream(InputStream in, OutputStream out, IProgressMonitor monitor) throws IOException {
+ byte[] b = new byte[1024];
+ for ( ;; ) {
+ if ( monitor.isCanceled() ) {
+ throw new InterruptedIOException( "User canceled download" );
+ }
+ int r = in.read( b );
+ if ( r == -1 ) break;
+ out.write(b, 0, r);
+ monitor.worked(r);
+ }
+
+ out.flush();
+ }
+
+ public IBundleModelElement getBundleInfo() {
+ return bundle;
+ }
+
+ public void setBundleInfo(IBundleModelElement bundle) {
+ if ( bundle == null ) {
+ if (this.bundle != null) {
+ this.bundle.setParent(null);
+ }
+ }
+ else {
+ bundle.setParent(this);
+ }
+ this.bundle = bundle;
+ }
+
+ public IDownloadJar getDownloadJar() {
+ return download;
+ }
+
+ public void setDownloadJar(IDownloadJar download) {
+ this.download = download;
+ }
+
+ public void addLibraryPath( IPath path ) {
+ libraryPaths.add( path );
+ }
+
+ public void removeLibraryPath( IPath path ) {
+ libraryPaths.remove( path );
+ }
+
+ public Set<IPath> getLibraryPaths() {
+ return libraryPaths;
+ }
+
+ public void addSourcePath( IPath path ) {
+ sourcePaths.add( path );
+ }
+
+ public void removeSourcePath( IPath path ) {
+ sourcePaths.remove( path );
+ }
+
+ public Set<IPath> getSourcePaths() {
+ return sourcePaths;
+ }
+
+ public void clearSourcePaths() {
+ sourcePaths.clear();
+ }
+
+ public void addComposite(ISCAComposite composite) {
+ composites.add( composite );
+ composite.setParent(this);
+ }
+
+ public Set<ISCAComposite> getComposites() {
+ return composites;
+ }
+
+ public void removeComposite(ISCAComposite composite) {
+ if ( composites.remove( composite ) ) {
+ composite.setParent(null);
+ }
+ }
+ public void addClasspathEntry(String encodedClasspath) {
+ classpath.add( encodedClasspath.trim() );
+ }
+
+ public Set<String> getClasspathEntrys() {
+ return classpath;
+ }
+
+ public void removeClasspathEntry(String encodedClasspath) {
+ classpath.remove(encodedClasspath.trim());
+ }
+
+ public IPath getLocation() {
+ return location;
+ }
+
+ public void setLocation(IPath location) {
+ this.location = location;
+ }
+
+ public IPath getSourcePathLocation() {
+ return sourcePathLocation;
+ }
+ public IPath getSourceRootPath() {
+ return sourceRootPath;
+ }
+ public void setSourcePathLocation(IPath location) {
+ this.sourcePathLocation = location;
+ }
+ public void setSourceRootPath(IPath location) {
+ this.sourceRootPath = location;
+ }
+
+ @Override
+ public String toString() {
+ return "SigilBundle[" + (getBundleInfo() == null ? null : (getBundleInfo().getSymbolicName() + ":" + getBundleInfo().getVersion())) + "]";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if ( obj == null ) return false;
+ if ( obj == this ) return true;
+
+ if ( obj instanceof SigilBundle) {
+ return obj.toString().equals( toString() );
+ }
+
+ return false;
+ }
+ @Override
+ public int hashCode() {
+ return 31 * toString().hashCode();
+ }
+
+ public IPath getLicencePathLocation() {
+ return licencePathLocation;
+ }
+
+ public void setLicencePathLocation(IPath licencePathLocation) {
+ this.licencePathLocation = licencePathLocation;
+ }
+
+ public String getFragmentHost() {
+ return bundleHost;
+ }
+
+ public void setFragmentHost(String symbolicName) {
+ this.bundleHost = symbolicName;
+ }
+
+ public String getElementName() {
+ return bundle.getSymbolicName();
+ }
+
+ public Version getVersion() {
+ return bundle.getVersion();
+ }
+
+ public void setVersion(Version version) {
+ this.bundle.setVersion(version);
+ }
+
+ public String getSymbolicName() {
+ return bundle.getSymbolicName();
+ }
+
+ public Set<String> getPackages() {
+ return packages;
+ }
+
+ public void addPackage(String pkg) {
+ packages.add(pkg);
+ }
+
+ public boolean removePackage(String pkg) {
+ return packages.remove(pkg);
+ }
+
+ public Set<String> getDownloadPackages() {
+ return dlPackages;
+ }
+
+ public void addDownloadPackage(String pkg) {
+ dlPackages.add(pkg);
+ }
+
+ public boolean removeDownloadPackage(String pkg) {
+ return dlPackages.remove(pkg);
+ }
+
+ public IPackageExport findExport(String packageName) {
+ for ( IPackageExport e : bundle.getExports() ) {
+ if ( packageName.equals( e.getPackageName() ) ) {
+ return e;
+ }
+ }
+ return null;
+ }
+
+ public IPackageImport findImport(String packageName) {
+ for ( IPackageImport i : bundle.getImports() ) {
+ if ( packageName.equals( i.getPackageName() ) ) {
+ return i;
+ }
+ }
+ return null;
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/osgi/BundleModelElement.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/osgi/BundleModelElement.java
new file mode 100644
index 0000000..f4024ef
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/osgi/BundleModelElement.java
@@ -0,0 +1,382 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.internal.model.osgi;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.cauldron.sigil.model.AbstractCompoundModelElement;
+import org.cauldron.sigil.model.InvalidModelException;
+import org.cauldron.sigil.model.eclipse.ILibraryImport;
+import org.cauldron.sigil.model.osgi.IBundleModelElement;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.osgi.framework.Version;
+
+public class BundleModelElement extends AbstractCompoundModelElement implements IBundleModelElement {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+ // required obr values
+ private URI updateLocation;
+ private String symbolicName;
+ private Version version = Version.emptyVersion;
+ private Set<IPackageImport> imports;
+ private Set<IPackageExport> exports;
+ private Set<IRequiredBundle> requires;
+ private URI sourceLocation;
+ private Set<String> classpathElements;
+ private IRequiredBundle fragmentHost;
+
+ // human readable values
+ private String name;
+ private String description;
+ private String category;
+ private URI licenseURI;
+ private URI docURI;
+ private String vendor;
+ private String contactAddress;
+ private String copyright;
+
+ // internal values
+ private String activator;
+ private Set<ILibraryImport> libraries;
+
+ public BundleModelElement() {
+ super( "OSGi Bundle" );
+ this.imports = new HashSet<IPackageImport>();
+ this.exports = new HashSet<IPackageExport>();
+ this.requires = new HashSet<IRequiredBundle>();
+ this.classpathElements = new HashSet<String>();
+ this.libraries = new HashSet<ILibraryImport>();
+ }
+
+ public String getActivator() {
+ return activator;
+ }
+
+ public void setActivator(String activator) {
+ this.activator = activator;
+ }
+
+ public void addLibraryImport(ILibraryImport library) {
+ libraries.add(library);
+ }
+
+ public Set<ILibraryImport> getLibraryImports() {
+ return libraries;
+ }
+
+ public void removeLibraryImport(ILibraryImport library) {
+ libraries.remove(library);
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#getCategory()
+ */
+ public String getCategory() {
+ return category;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#setCategory(java.lang.String)
+ */
+ public void setCategory(String category) {
+ this.category = category;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#getContactAddress()
+ */
+ public String getContactAddress() {
+ return contactAddress;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#setContactAddress(java.lang.String)
+ */
+ public void setContactAddress(String contactAddress) {
+ this.contactAddress = contactAddress;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#getCopyright()
+ */
+ public String getCopyright() {
+ return copyright;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#setCopyright(java.lang.String)
+ */
+ public void setCopyright(String copyright) {
+ this.copyright = copyright;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#getDocURI()
+ */
+ public URI getDocURI() {
+ return docURI;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#setDocURI(java.net.URI)
+ */
+ public void setDocURI(URI docURI) {
+ this.docURI = docURI;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#getExports()
+ */
+ public Set<IPackageExport> getExports() {
+ return exports;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#addExport(org.cauldron.sigil.model.osgi.PackageExport)
+ */
+ public void addExport(IPackageExport packageExport) {
+ exports.add(packageExport);
+ packageExport.setParent(this);
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#removeExport(org.cauldron.sigil.model.osgi.PackageExport)
+ */
+ public void removeExport(IPackageExport packageExport) {
+ if ( exports.remove(packageExport) ) {
+ packageExport.setParent(null);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#getImports()
+ */
+ public Set<IPackageImport> getImports() {
+ return imports;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#addImport(org.cauldron.sigil.model.osgi.PackageImport)
+ */
+ public void addImport(IPackageImport packageImport) {
+ imports.add(packageImport);
+ packageImport.setParent(this);
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#removeImport(org.cauldron.sigil.model.osgi.PackageImport)
+ */
+ public void removeImport(IPackageImport packageImport) {
+ if ( imports.remove( packageImport ) ) {
+ packageImport.setParent(null);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#getRequiredBundles()
+ */
+ public Set<IRequiredBundle> getRequiredBundles() {
+ return requires;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#addRequiredBundle(org.cauldron.sigil.model.osgi.RequiresBundle)
+ */
+ public void addRequiredBundle(IRequiredBundle bundle) {
+ requires.add( bundle );
+ bundle.setParent(this);
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#removeRequiredBundle(org.cauldron.sigil.model.osgi.RequiresBundle)
+ */
+ public void removeRequiredBundle(IRequiredBundle bundle) {
+ if ( requires.remove(bundle) ) {
+ bundle.setParent(null);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#getLicenseURI()
+ */
+ public URI getLicenseURI() {
+ return licenseURI;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#setLicenseURI(java.net.URI)
+ */
+ public void setLicenseURI(URI licenseURI) {
+ this.licenseURI = licenseURI;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#getSourceLocation()
+ */
+ public URI getSourceLocation() {
+ return sourceLocation;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#setSourceLocation(java.net.URI)
+ */
+ public void setSourceLocation(URI sourceLocation) {
+ this.sourceLocation = sourceLocation;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#getSymbolicName()
+ */
+ public String getSymbolicName() {
+ return symbolicName;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#setSymbolicName(java.lang.String)
+ */
+ public void setSymbolicName(String symbolicName) {
+ this.symbolicName = symbolicName == null ? null : symbolicName.intern();
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#getUpdateLocation()
+ */
+ public URI getUpdateLocation() {
+ return updateLocation;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#setUpdateLocation(java.net.URI)
+ */
+ public void setUpdateLocation(URI updateLocation) {
+ this.updateLocation = updateLocation;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#getVendor()
+ */
+ public String getVendor() {
+ return vendor;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#setVendor(java.lang.String)
+ */
+ public void setVendor(String vendor) {
+ this.vendor = vendor;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#getVersion()
+ */
+ public Version getVersion() {
+ return version;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.model.osgi.IBundleModelElement#setVersion(java.lang.String)
+ */
+ public void setVersion(Version version) {
+ this.version = version == null ? Version.emptyVersion : version;
+ }
+
+ public void checkValid() throws InvalidModelException {
+ if (symbolicName == null)
+ throw new InvalidModelException(this, "Bundle symbolic name not set");
+ }
+
+ public BundleModelElement clone() {
+ BundleModelElement bd = (BundleModelElement) super.clone();
+
+ bd.imports = new HashSet<IPackageImport>();
+ bd.exports = new HashSet<IPackageExport>();
+ bd.requires = new HashSet<IRequiredBundle>();
+
+ for (IPackageImport pi : imports ) {
+ bd.imports.add((IPackageImport) pi.clone());
+ }
+
+ for (IPackageExport pe : exports ) {
+ bd.exports.add((IPackageExport) pe.clone());
+ }
+
+ for ( IRequiredBundle rb : requires ) {
+ bd.requires.add((IRequiredBundle) rb.clone());
+ }
+
+ return bd;
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+
+ buf.append("BundleModelElement[");
+ buf.append(symbolicName);
+ buf.append(", ");
+ buf.append(version);
+ buf.append("]");
+
+ return buf.toString();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public void addClasspath(String path) {
+ classpathElements.add( path );
+ }
+
+ public Collection<String> getClasspaths() {
+ return classpathElements.isEmpty() ? Collections.singleton( "." ) : classpathElements;
+ }
+
+ public void removeClasspath(String path) {
+ classpathElements.remove( path );
+ }
+
+ public IRequiredBundle getFragmentHost() {
+ return fragmentHost;
+ }
+
+ public void setFragmentHost(IRequiredBundle fragmentHost) {
+ this.fragmentHost = fragmentHost;
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/osgi/PackageExport.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/osgi/PackageExport.java
new file mode 100644
index 0000000..536dbaf
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/osgi/PackageExport.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.internal.model.osgi;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import org.cauldron.sigil.model.AbstractModelElement;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.osgi.framework.Version;
+
+public class PackageExport extends AbstractModelElement implements IPackageExport {
+
+ private static final long serialVersionUID = 1L;
+
+ private String name;
+ private Version version;
+ private HashSet<String> uses = new HashSet<String>();
+
+ public PackageExport() {
+ super("OSGi Package Export");
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.model.osgi.IPackageExport#getPackageName()
+ */
+ public String getPackageName() {
+ return name;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.model.osgi.IPackageExport#setPackageName(java.lang.String)
+ */
+ public void setPackageName(String packageName) {
+ this.name = packageName;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.model.osgi.IPackageExport#getVersion()
+ */
+ public Version getVersion() {
+ Version result;
+ if(version != null) {
+ result = version;
+ } else {
+ ISigilBundle owningBundle = getAncestor(ISigilBundle.class);
+ if(owningBundle == null) {
+ result = Version.emptyVersion;
+ } else {
+ result = owningBundle.getVersion();
+ }
+ }
+ return result;
+ }
+
+ public Version getRawVersion() {
+ return version;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.model.osgi.IPackageExport#setVersion(java.lang.String)
+ */
+ public void setVersion(Version version) {
+ this.version = version; // == null ? Version.emptyVersion : version;
+ }
+
+ public void addUse(String use) {
+ uses.add(use);
+ }
+
+ public Collection<String> getUses() {
+ return uses;
+ }
+
+ public void removeUse(String use) {
+ uses.remove(use);
+ }
+
+ @Override
+ public String toString() {
+ return "PackageExport[" + name + ":" + version + ":uses=" + uses + "]";
+ }
+
+ public void setUses(Collection<String> uses) {
+ this.uses.clear();
+ this.uses.addAll(uses);
+ }
+
+ public int compareTo(IPackageExport o) {
+ int i = name.compareTo(o.getPackageName());
+
+ if ( i == 0 ) {
+ i = compareVersion(o.getVersion());
+ }
+
+ return i;
+ }
+
+ private int compareVersion(Version other) {
+ if ( version == null ) {
+ if ( other == null ) {
+ return 0;
+ }
+ else {
+ return 1;
+ }
+ }
+ else {
+ if ( other == null ) {
+ return -1;
+ }
+ else {
+ return version.compareTo(other);
+ }
+ }
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/osgi/PackageImport.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/osgi/PackageImport.java
new file mode 100644
index 0000000..a9319f3
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/osgi/PackageImport.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.internal.model.osgi;
+
+import org.cauldron.sigil.model.AbstractModelElement;
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.InvalidModelException;
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+
+public class PackageImport extends AbstractModelElement implements IPackageImport {
+
+ private static final long serialVersionUID = 1L;
+
+ private String name;
+ private VersionRange versions = VersionRange.ANY_VERSION;
+
+ // resolution directive
+ private boolean optional;
+ private boolean dependency = true;
+ private OSGiImport osgiImport = OSGiImport.AUTO;
+
+ public PackageImport() {
+ super( "OSGi Package Import" );
+ }
+
+ @Override
+ public void checkValid() throws InvalidModelException {
+ if ( name == null ) {
+ throw new InvalidModelException( this, "Package name must be set" );
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.model.osgi.IPackageImport#isOptional()
+ */
+ public boolean isOptional() {
+ return optional;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.model.osgi.IPackageImport#setOptional(boolean)
+ */
+ public void setOptional(boolean optional) {
+ this.optional = optional;
+ }
+
+ public boolean isDependency() {
+ return dependency;
+ }
+
+ public void setDependency(boolean dependency) {
+ this.dependency = dependency;
+ }
+
+ public OSGiImport getOSGiImport() {
+ return osgiImport;
+ }
+
+ public void setOSGiImport(OSGiImport osgiHeader) {
+ this.osgiImport = osgiHeader;
+ }
+
+ public String getPackageName() {
+ return name;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.model.osgi.IPackageImport#setName(java.lang.String)
+ */
+ public void setPackageName(String name) {
+ this.name = name;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.model.osgi.IPackageImport#getVersion()
+ */
+ public VersionRange getVersions() {
+ return versions;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.model.osgi.IPackageImport#setVersion(java.lang.String)
+ */
+ public void setVersions(VersionRange versions) {
+ this.versions = versions == null ? VersionRange.ANY_VERSION : versions;
+ }
+
+ @Override
+ public String toString() {
+ return "Package-Import[" + name + ":" + versions + ":" + (optional ? "optional" : "mandatory") + "]";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+
+ if ( obj instanceof PackageImport ) {
+ PackageImport pi = (PackageImport) obj;
+ return name.equals( pi.name ) && versions.equals( pi.versions ) && optional == pi.optional;
+ }
+ else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int hc = name.hashCode() * versions.hashCode();
+
+ if ( optional ) {
+ hc *= -1;
+ }
+
+ return hc;
+ }
+
+ public boolean accepts(IModelElement provider) {
+ if ( provider instanceof IPackageExport ) {
+ IPackageExport pe = (IPackageExport) provider;
+ return pe.getPackageName().equals( name ) && versions.contains( pe.getVersion() );
+ }
+ else {
+ return false;
+ }
+ }
+
+ public int compareTo(IPackageImport o) {
+ int i = name.compareTo(o.getPackageName());
+
+ if ( i == 0 ) {
+ i = compareVersion(o.getVersions());
+ }
+
+ return i;
+ }
+
+ private int compareVersion(VersionRange range) {
+ if ( versions == null ) {
+ if ( range == null ) {
+ return 0;
+ }
+ else {
+ return 1;
+ }
+ }
+ else {
+ if ( range == null ) {
+ return -1;
+ }
+ else {
+ return versions.getCeiling().compareTo(range.getCeiling());
+ }
+ }
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/osgi/RequiredBundle.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/osgi/RequiredBundle.java
new file mode 100644
index 0000000..35c3f7b
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/internal/model/osgi/RequiredBundle.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.internal.model.osgi;
+
+import org.cauldron.sigil.model.AbstractModelElement;
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.model.osgi.IBundleModelElement;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+
+public class RequiredBundle extends AbstractModelElement implements IRequiredBundle {
+ private static final long serialVersionUID = 1L;
+
+ private String symbolicName;
+ private VersionRange versions = VersionRange.ANY_VERSION;
+ private boolean optional;
+
+ public RequiredBundle() {
+ super("OSGi Bundle Requirement");
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.model.osgi.IRequiresBundle#getSymbolicName()
+ */
+ public String getSymbolicName() {
+ return symbolicName;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.model.osgi.IRequiresBundle#setSymbolicName(java.lang.String)
+ */
+ public void setSymbolicName(String symbolicName) {
+ this.symbolicName = symbolicName == null ? null : symbolicName.intern();
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.model.osgi.IRequiresBundle#getVersion()
+ */
+ public VersionRange getVersions() {
+ return versions;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.model.osgi.IRequiresBundle#setVersion(java.lang.String)
+ */
+ public void setVersions(VersionRange versions) {
+ this.versions = versions == null ? VersionRange.ANY_VERSION : versions;
+ }
+
+ public boolean isOptional() {
+ return optional;
+ }
+
+ public void setOptional(boolean optional) {
+ this.optional = optional;
+ }
+
+ @Override
+ public String toString() {
+ return "RequiredBundle[" + symbolicName + ":" + versions + ":" + (optional ? "optional" : "mandatory") + "]";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+
+ if ( obj instanceof RequiredBundle ) {
+ RequiredBundle rb = (RequiredBundle) obj;
+ return symbolicName.equals( rb.symbolicName ) && versions.equals( rb.versions ) && optional == rb.optional;
+ }
+ else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int hc = symbolicName.hashCode() * versions.hashCode();
+
+ if ( optional ) {
+ hc *= -1;
+ }
+
+ return hc;
+ }
+
+ public boolean accepts(IModelElement provider) {
+ if ( provider instanceof IBundleModelElement ) {
+ IBundleModelElement bndl = (IBundleModelElement) provider;
+ return symbolicName.equals( bndl.getSymbolicName() ) && versions.contains( bndl.getVersion() );
+ }
+ else {
+ return false;
+ }
+ }
+
+ public int compareTo(IRequiredBundle o) {
+ int i = symbolicName.compareTo(o.getSymbolicName());
+
+ if ( i == 0 ) {
+ i = compareVersion(o.getVersions());
+ }
+
+ return i;
+ }
+
+ private int compareVersion(VersionRange range) {
+ if ( versions == null ) {
+ if ( range == null ) {
+ return 0;
+ }
+ else {
+ return 1;
+ }
+ }
+ else {
+ if ( range == null ) {
+ return -1;
+ }
+ else {
+ return versions.getCeiling().compareTo(range.getCeiling());
+ }
+ }
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/licence/ILicenseManager.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/licence/ILicenseManager.java
new file mode 100644
index 0000000..9157288
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/licence/ILicenseManager.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.licence;
+
+import java.util.Set;
+import java.util.regex.Pattern;
+
+//import org.cauldron.sigil.model.project.ISigilProjectModel;
+
+public interface ILicenseManager {
+ void addLicense(String name, Pattern pattern);
+ void removeLicense(String name);
+ Set<String> getLicenseNames();
+ Pattern getLicensePattern(String name);
+ ILicensePolicy getDefaultPolicy();
+ //ILicensePolicy getPolicy(ISigilProjectModel project);
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/licence/ILicensePolicy.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/licence/ILicensePolicy.java
new file mode 100644
index 0000000..a5bcdac
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/licence/ILicensePolicy.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.licence;
+
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+public interface ILicensePolicy {
+ void addAllowed(String licenseName);
+ void removeAllowed(String licenseName);
+ boolean accept(ISigilBundle bundle);
+ void save(IProgressMonitor monitor);
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/BundleResolver.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/BundleResolver.java
new file mode 100644
index 0000000..57d859a
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/BundleResolver.java
@@ -0,0 +1,444 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.repository;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.cauldron.bld.core.BldCore;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.SubMonitor;
+import org.cauldron.sigil.model.ICompoundModelElement;
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.eclipse.ILibrary;
+import org.cauldron.sigil.model.eclipse.ILibraryImport;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.cauldron.sigil.repository.IBundleRepository;
+import org.cauldron.sigil.repository.IBundleResolver;
+import org.cauldron.sigil.repository.IRepositoryManager;
+import org.cauldron.sigil.repository.IResolution;
+import org.cauldron.sigil.repository.IResolutionMonitor;
+import org.cauldron.sigil.repository.ResolutionConfig;
+import org.cauldron.sigil.repository.ResolutionException;
+import org.osgi.framework.Version;
+
+public class BundleResolver implements IBundleResolver {
+
+ private class BundleOrderComparator implements Comparator<ISigilBundle> {
+ private IModelElement requirement;
+
+ public BundleOrderComparator(IModelElement requirement) {
+ this.requirement = requirement;
+ }
+
+ public int compare(ISigilBundle o1, ISigilBundle o2) {
+ int c = compareVersions(o1, o2);
+
+ if ( c == 0 ) {
+ c = compareImports(o1, o2);
+ }
+
+ return c;
+ }
+
+ private int compareImports(ISigilBundle o1, ISigilBundle o2) {
+ int c1 = o1.getBundleInfo().getImports().size();
+ int c2 = o2.getBundleInfo().getImports().size();
+
+ if ( c1 < c2 ) {
+ return -1;
+ }
+ else if ( c2 > c1 ) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ private int compareVersions(ISigilBundle o1, ISigilBundle o2) {
+ Version v1 = null;
+ Version v2 = null;
+ if ( requirement instanceof IPackageImport ) {
+ v1 = findExportVersion( (IPackageImport) requirement, o1 );
+ v2 = findExportVersion( (IPackageImport) requirement, o2 );
+ }
+ else if ( requirement instanceof IRequiredBundle ) {
+ v1 = o1.getBundleInfo().getVersion();
+ v2 = o1.getBundleInfo().getVersion();
+ }
+
+ if ( v1 == null ) {
+ if ( v2 == null ) {
+ return 0;
+ }
+ else {
+ return 1;
+ }
+ }
+ else {
+ if ( v2 == null ) {
+ return -1;
+ }
+ else {
+ return v2.compareTo(v1);
+ }
+ }
+ }
+
+ private Version findExportVersion(IPackageImport pi, ISigilBundle o1) {
+ for ( IPackageExport pe : o1.getBundleInfo().getExports() ) {
+ if ( pi.getPackageName().equals( pi.getPackageName() ) ) {
+ return pe.getVersion();
+ }
+ }
+
+ return null;
+ }
+
+ }
+
+ private class ResolutionContext {
+ private final IModelElement root;
+ private final ResolutionConfig config;
+ private final IResolutionMonitor monitor;
+
+ private final Resolution resolution = new Resolution();
+ private final Set<IModelElement> parsed = new HashSet<IModelElement>();
+ private final LinkedList<IModelElement> requirements = new LinkedList<IModelElement>();
+
+ public ResolutionContext(IModelElement root, ResolutionConfig config, IResolutionMonitor monitor) {
+ this.root = root;
+ this.config = config;
+ this.monitor = monitor;
+ }
+
+ public void enterModelElement(IModelElement element) {
+ parsed.add(element);
+ }
+
+ public void exitModelElement(IModelElement element) {
+ parsed.remove(element);
+ }
+
+ public boolean isNewModelElement(IModelElement element) {
+ return !parsed.contains(element);
+ }
+
+ public boolean isValid() {
+ return resolution.isSuccess();
+ }
+
+ public void setValid(boolean valid) {
+ resolution.setSuccess(valid);
+ }
+
+ public ResolutionException newResolutionException() {
+ return new ResolutionException(root, requirements.toArray( new IModelElement[requirements.size()]) );
+ }
+
+ public void startRequirement(IModelElement element) {
+ requirements.add(element);
+ monitor.startResolution(element);
+ }
+
+ public void endRequirement(IModelElement element) {
+ ISigilBundle provider = resolution.getProvider(element);
+
+ setValid( provider != null || isOptional(element) || config.isIgnoreErrors() );
+
+ if ( isValid() ) {
+ // only clear stack if valid
+ // else use it as an aid to trace errors
+ requirements.remove(element);
+ }
+
+ monitor.endResolution( element, provider );
+ }
+ }
+
+ private class Resolution implements IResolution {
+ private Map<ISigilBundle, List<IModelElement>> providees = new HashMap<ISigilBundle, List<IModelElement>>();
+ private Map<IModelElement, ISigilBundle> providers = new HashMap<IModelElement, ISigilBundle>();
+ private boolean success = true; // assume success
+
+ boolean addProvider(IModelElement element, ISigilBundle provider) {
+ providers.put( element, provider );
+
+ List<IModelElement> requirements = providees.get( provider );
+
+ boolean isNewProvider = requirements == null;
+
+ if ( isNewProvider ) {
+ requirements = new ArrayList<IModelElement>();
+ providees.put( provider, requirements );
+ }
+
+ requirements.add( element );
+
+ return isNewProvider;
+ }
+
+ void removeProvider(IModelElement element, ISigilBundle provider) {
+ providers.remove(element);
+ List<IModelElement> e = providees.get(provider);
+ e.remove(element);
+ if ( e.isEmpty() ) {
+ providees.remove(provider);
+ }
+ }
+
+ void setSuccess(boolean success) {
+ this.success = success;
+ }
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public ISigilBundle getProvider(IModelElement requirement) {
+ return providers.get(requirement);
+ }
+
+ public Set<ISigilBundle> getBundles() {
+ return providees.keySet();
+ }
+
+ public List<IModelElement> getMatchedRequirements(ISigilBundle bundle) {
+ return providees.get(bundle);
+ }
+
+ public boolean isSynchronized() {
+ for ( ISigilBundle b : getBundles() ) {
+ if ( !b.isSynchronized() ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public void synchronize(IProgressMonitor monitor) {
+ Set<ISigilBundle> bundles = getBundles();
+ SubMonitor progress = SubMonitor.convert(monitor, bundles.size());
+
+ for ( ISigilBundle b : bundles ) {
+ if ( monitor.isCanceled() ) {
+ break;
+ }
+
+ try {
+ b.synchronize(progress.newChild(1));
+ } catch (IOException e) {
+ BldCore.error( "Failed to synchronize " + b, e );
+ }
+ }
+ }
+ }
+
+ private static final IResolutionMonitor NULL_MONITOR = new IResolutionMonitor() {
+ public void endResolution(IModelElement requirement,
+ ISigilBundle sigilBundle) {
+ }
+
+ public boolean isCanceled() {
+ return false;
+ }
+
+ public void startResolution(IModelElement requirement) {
+ }
+ };
+
+ private IRepositoryManager repositoryManager;
+
+ public BundleResolver(IRepositoryManager repositoryManager) {
+ this.repositoryManager = repositoryManager;
+ }
+
+ public IResolution resolve(IModelElement element, ResolutionConfig config, IResolutionMonitor monitor) throws ResolutionException {
+ if ( monitor == null ) {
+ monitor = NULL_MONITOR;
+ }
+ ResolutionContext ctx = new ResolutionContext(element, config, monitor);
+
+ resolveElement(element, ctx);
+
+ if ( !ctx.isValid() ) {
+ throw ctx.newResolutionException();
+ }
+
+ return ctx.resolution;
+ }
+
+ private void resolveElement(IModelElement element, ResolutionContext ctx) throws ResolutionException {
+ if ( isRequirement(element) ) {
+ resolveRequirement(element, ctx);
+ }
+
+ if ( ctx.isValid() && element instanceof ICompoundModelElement ) {
+ resolveCompound((ICompoundModelElement) element, ctx);
+ }
+ }
+
+ private void resolveCompound(ICompoundModelElement compound, ResolutionContext ctx) throws ResolutionException {
+ for ( IModelElement element : compound.children() ) {
+ if ( ctx.isNewModelElement(element) ) {
+ if ( isRequirement(element) ) {
+ resolveRequirement(element, ctx);
+ }
+ else if ( element instanceof ICompoundModelElement ) {
+ if ( !ctx.monitor.isCanceled() ) {
+ ctx.enterModelElement( element );
+ resolveElement((ICompoundModelElement) element, ctx);
+ ctx.exitModelElement(element);
+ }
+ }
+
+ if ( !ctx.isValid() ) {
+ break;
+ }
+ }
+ }
+ }
+
+ private void resolveRequirement(IModelElement requirement, ResolutionContext ctx) throws ResolutionException {
+ if ( ctx.config.isOptional() || !isOptional(requirement) ) {
+ ctx.startRequirement(requirement );
+
+ try {
+ int[] priorities = repositoryManager.getPriorityLevels();
+
+ outer: for ( int i = 0; i< priorities.length; i++ ) {
+ List<ISigilBundle> providers = findProvidersAtPriority(priorities[i], requirement, ctx);
+
+ if ( !providers.isEmpty() && !ctx.monitor.isCanceled() ) {
+ if ( providers.size() > 1 ) {
+ Collections.sort(providers, new BundleOrderComparator(requirement));
+ }
+
+ for ( ISigilBundle provider : providers ) {
+ // reset validity - if there's another provider it can still be solved
+ ctx.setValid(true);
+ if ( ctx.resolution.addProvider(requirement, provider) ) {
+ if ( ctx.config.isDependents() ) {
+ resolveElement(provider, ctx);
+ }
+
+ if ( ctx.isValid() ) {
+ break outer;
+ }
+ else {
+ ctx.resolution.removeProvider(requirement, provider);
+ }
+ }
+ else {
+ break outer;
+ }
+ }
+ }
+ }
+ }
+ finally {
+ ctx.endRequirement(requirement);
+ }
+ }
+ }
+
+ private List<ISigilBundle> findProvidersAtPriority(int i, IModelElement requirement, ResolutionContext ctx) throws ResolutionException {
+ ArrayList<ISigilBundle> providers = new ArrayList<ISigilBundle>();
+
+ for (IBundleRepository rep : repositoryManager.getRepositories(i)) {
+ if ( ctx.monitor.isCanceled() ) {
+ break;
+ }
+ providers.addAll( findProviders( requirement, ctx.config, rep ) );
+ }
+
+ return providers;
+ }
+
+ private Collection<ISigilBundle> findProviders(IModelElement requirement, ResolutionConfig config, IBundleRepository rep) throws ResolutionException {
+ ArrayList<ISigilBundle> found = new ArrayList<ISigilBundle>();
+
+ if ( requirement instanceof IPackageImport ) {
+ IPackageImport pi = (IPackageImport) requirement;
+ found.addAll( rep.findAllProviders( pi, config.getOptions() ) );
+ }
+ else if ( requirement instanceof IRequiredBundle ) {
+ IRequiredBundle rb = (IRequiredBundle) requirement;
+ found.addAll( rep.findAllProviders( rb, config.getOptions() ) );
+ }
+ else if ( requirement instanceof ILibraryImport ) {
+ ILibrary lib = repositoryManager.resolveLibrary((ILibraryImport) requirement);
+ if (lib != null) {
+ found.addAll( rep.findProviders(lib, config.getOptions()) );
+ }
+ }
+ else {
+ // shouldn't get here - developer error if do
+ // use isRequirement before getting anywhere near this logic...
+ throw new IllegalStateException( "Invalid requirement type " + requirement );
+ }
+
+ return found;
+ }
+
+
+ private boolean isOptional(IModelElement element) {
+ if ( element instanceof IPackageImport ) {
+ return ((IPackageImport) element).isOptional();
+ }
+ else if ( element instanceof IRequiredBundle ) {
+ return ((IRequiredBundle) element).isOptional();
+ }
+ else if ( element instanceof ILibraryImport ) {
+ ILibrary lib = repositoryManager.resolveLibrary((ILibraryImport) element);
+ for ( IPackageImport pi : lib.getImports() ) {
+ if ( !isOptional(pi) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ else {
+ // should never get this due to isRequirement test prior to calling this
+ // developer error if found
+ throw new IllegalStateException( "Invalid optional element test for " + element);
+ }
+ }
+
+ private boolean isRequirement(IModelElement element) {
+ return element instanceof IPackageImport || element instanceof IRequiredBundle || element instanceof ILibraryImport;
+ }
+
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/DirectoryHelper.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/DirectoryHelper.java
new file mode 100644
index 0000000..0bb50d5
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/DirectoryHelper.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.repository;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+import org.cauldron.bld.core.BldCore;
+import org.cauldron.sigil.model.ModelElementFactory;
+import org.cauldron.sigil.model.ModelElementFactoryException;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IBundleModelElement;
+import org.cauldron.sigil.repository.AbstractBundleRepository;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+
+public class DirectoryHelper {
+ public static void scanBundles(AbstractBundleRepository repository, List<ISigilBundle> bundles, IPath path, IPath source, boolean recursive) {
+ File dir = path.toFile();
+
+ if ( dir.exists() ) {
+ for ( File f : dir.listFiles() ){
+ if ( f.isDirectory() ) {
+ if ( recursive ) {
+ scanBundles( repository, bundles, new Path( f.getAbsolutePath() ), source, recursive );
+ }
+ }
+ else if ( f.isFile() && f.getName().endsWith( ".jar" )){
+ JarFile jar = null;
+ try {
+ jar = new JarFile(f);
+ ISigilBundle bundle = buildBundle(repository, jar.getManifest(), f );
+ if ( bundle != null ) {
+ bundle.setSourcePathLocation( source );
+ bundle.setSourceRootPath( new Path( "src" ) );
+ bundles.add( bundle );
+ }
+ } catch (IOException e) {
+ BldCore.error( "Failed to read jar file " + f, e );
+ } catch (ModelElementFactoryException e) {
+ BldCore.error( "Failed to build bundle " + f , e );
+ } catch (RuntimeException e) {
+ BldCore.error( "Failed to build bundle " + f , e );
+ }
+ finally {
+ if ( jar != null ) {
+ try {
+ jar.close();
+ } catch (IOException e) {
+ BldCore.error( "Failed to close jar file", e );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static ISigilBundle buildBundle(
+ AbstractBundleRepository repository, Manifest manifest, File f) {
+ IBundleModelElement info = repository.buildBundleModelElement( manifest );
+
+ ISigilBundle bundle = null;
+
+ if ( info != null ) {
+ bundle = ModelElementFactory.getInstance().newModelElement( ISigilBundle.class );
+ bundle.addChild(info);
+ bundle.setLocation( new Path( f.getAbsolutePath() ) );
+ }
+
+ return bundle;
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/FileSystemRepository.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/FileSystemRepository.java
new file mode 100644
index 0000000..3525a4e
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/FileSystemRepository.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.repository;
+
+import java.util.ArrayList;
+
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.repository.AbstractBundleRepository;
+import org.cauldron.sigil.repository.IRepositoryVisitor;
+import org.eclipse.core.runtime.IPath;
+
+public class FileSystemRepository extends AbstractBundleRepository {
+
+ private ArrayList<ISigilBundle> bundles;
+ private IPath path;
+ private boolean recurse;
+
+ public FileSystemRepository(String id, IPath path, boolean recurse) {
+ super(id);
+ this.path = path;
+ this.recurse = recurse;
+ }
+
+ @Override
+ public void accept(IRepositoryVisitor visitor, int options) {
+ synchronized( this ) {
+ if ( bundles == null ) {
+ bundles = new ArrayList<ISigilBundle>();
+ DirectoryHelper.scanBundles(this, bundles, path, null, recurse);
+ }
+ }
+
+ for ( ISigilBundle b : bundles ) {
+ if ( !visitor.visit(b) ) {
+ break;
+ }
+ }
+ }
+
+ public void refresh() {
+ synchronized( this ) {
+ bundles = null;
+ }
+
+ notifyChange();
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/FileSystemRepositoryProvider.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/FileSystemRepositoryProvider.java
new file mode 100644
index 0000000..093d06b
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/FileSystemRepositoryProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.repository;
+
+import java.io.File;
+import java.util.Properties;
+
+import org.cauldron.sigil.repository.IBundleRepository;
+import org.cauldron.sigil.repository.IRepositoryProvider;
+import org.cauldron.sigil.repository.RepositoryException;
+import org.eclipse.core.runtime.Path;
+
+public class FileSystemRepositoryProvider implements IRepositoryProvider {
+
+ public IBundleRepository createRepository(String id, Properties preferences)
+ throws RepositoryException {
+ String dir = preferences.getProperty("dir");
+ if (!new File(dir).isDirectory()) {
+ throw new RepositoryException("directory '" + dir +"' does not exist.");
+ }
+ boolean recurse = Boolean.valueOf(preferences.getProperty("recurse"));
+ return new FileSystemRepository(id, new Path(dir), recurse);
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/ProgressWrapper.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/ProgressWrapper.java
new file mode 100644
index 0000000..5ca4ec3
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/ProgressWrapper.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.repository;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.cauldron.sigil.repository.IResolutionMonitor;
+
+public class ProgressWrapper implements IProgressMonitor {
+
+ private IResolutionMonitor monitor;
+
+ public ProgressWrapper(IResolutionMonitor monitor) {
+ this.monitor = monitor;
+ }
+
+ public boolean isCanceled() {
+ return monitor.isCanceled();
+ }
+
+ public void beginTask(String name, int totalWork) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void done() {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void internalWorked(double work) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void setCanceled(boolean value) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void setTaskName(String name) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void subTask(String name) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void worked(int work) {
+ // TODO Auto-generated method stub
+
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/SystemRepository.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/SystemRepository.java
new file mode 100644
index 0000000..b23b64b
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/SystemRepository.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.repository;
+
+import java.io.IOException;
+import java.util.jar.JarFile;
+
+import org.cauldron.bld.core.BldCore;
+import org.cauldron.sigil.model.ModelElementFactory;
+import org.cauldron.sigil.model.ModelElementFactoryException;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IBundleModelElement;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.repository.AbstractBundleRepository;
+import org.cauldron.sigil.repository.IRepositoryVisitor;
+import org.eclipse.core.runtime.IPath;
+
+public class SystemRepository extends AbstractBundleRepository {
+
+ private final String packages;
+ private final IPath frameworkPath;
+
+ public SystemRepository(String id, IPath frameworkPath, String packages) {
+ super(id);
+ this.frameworkPath = frameworkPath;
+ this.packages = packages;
+ }
+
+ private static ISigilBundle systemBundle;
+
+ @Override
+ public void accept(IRepositoryVisitor visitor, int options) {
+ ISigilBundle bundle = loadSystemBundle();
+
+ if ( bundle != null ) {
+ visitor.visit(bundle);
+ }
+ }
+
+ private synchronized ISigilBundle loadSystemBundle() {
+ if (systemBundle == null) {
+ systemBundle = ModelElementFactory.getInstance().newModelElement(ISigilBundle.class);
+
+ JarFile jar = null;
+
+ try {
+ final IBundleModelElement info;
+ if (frameworkPath != null) {
+ systemBundle.setLocation(frameworkPath);
+ jar = new JarFile(frameworkPath.toFile());
+ info = buildBundleModelElement(jar.getManifest());
+ } else {
+ info = ModelElementFactory.getInstance().newModelElement(IBundleModelElement.class);
+ }
+
+ applyProfile(info);
+ systemBundle.addChild(info);
+ } catch (IOException e) {
+ BldCore.error( "Failed to read jar file " + frameworkPath, e );
+ } catch (ModelElementFactoryException e) {
+ BldCore.error( "Failed to build bundle " + frameworkPath , e );
+ } catch (RuntimeException e) {
+ BldCore.error( "Failed to build bundle " + frameworkPath , e );
+ }
+ finally {
+ if (jar != null) {
+ try {
+ jar.close();
+ } catch (IOException e) {
+ BldCore.error( "Failed to close jar file", e );
+ }
+ }
+ }
+ }
+
+ return systemBundle;
+ }
+
+ private void applyProfile(IBundleModelElement info) {
+ if (packages != null) {
+ for (String name : packages.split(",\\s*")) {
+ IPackageExport pe = ModelElementFactory.getInstance().newModelElement(IPackageExport.class);
+ pe.setPackageName(name);
+ info.addExport(pe);
+ }
+ }
+ }
+
+ public synchronized void refresh() {
+ systemBundle = null;
+ notifyChange();
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/SystemRepositoryProvider.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/SystemRepositoryProvider.java
new file mode 100644
index 0000000..c13e278
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/repository/SystemRepositoryProvider.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.repository;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+import org.cauldron.sigil.repository.IBundleRepository;
+import org.cauldron.sigil.repository.IRepositoryProvider;
+import org.cauldron.sigil.repository.RepositoryException;
+import org.eclipse.core.runtime.Path;
+
+public class SystemRepositoryProvider implements IRepositoryProvider {
+
+ public IBundleRepository createRepository(String id, Properties properties)
+ throws RepositoryException {
+ String fw = properties.getProperty("framework");
+ Path frameworkPath = fw == null ? null : new Path(fw);
+ String extraPkgs = properties.getProperty("packages");
+ String profile = properties.getProperty("profile");
+
+ try {
+ Properties p = readProfile(profile);
+ String pkgs = p.getProperty("org.osgi.framework.system.packages") + "," + extraPkgs;
+ return new SystemRepository(id, frameworkPath, pkgs);
+ } catch (IOException e) {
+ throw new RepositoryException("Failed to load profile", e);
+ }
+ }
+
+ public static Properties readProfile(String name) throws IOException {
+ if (name == null) {
+ String version = System.getProperty("java.specification.version");
+ String[] split = version.split("\\.");
+ String prefix = ("6".compareTo(split[1]) <= 0) ? "JavaSE-" : "J2SE-";
+ name = prefix + version;
+ }
+
+ String profilePath = "profiles/" + name + ".profile";
+ InputStream in = SystemRepositoryProvider.class.getClassLoader().getResourceAsStream(profilePath);
+
+ if (in == null)
+ throw new IOException("No such profile: " + profilePath);
+
+ Properties p = new Properties();
+ p.load(in);
+
+ return p;
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/util/QuoteUtil.java b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/util/QuoteUtil.java
new file mode 100644
index 0000000..29c22d1
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/bld/core/util/QuoteUtil.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.bld.core.util;
+
+import java.util.ArrayList;
+
+public class QuoteUtil {
+ public static String[] split(String str) {
+ ArrayList<String> split = new ArrayList<String>();
+ boolean quote = false;
+ StringBuffer buf = new StringBuffer(str.length());
+
+ for ( int i = 0; i < str.length(); i++ ) {
+ char c = str.charAt(i);
+ switch ( c ) {
+ case '"':
+ quote = !quote;
+ break;
+ case ',':
+ if ( !quote ) {
+ split.add( buf.toString().trim() );
+ buf.setLength(0);
+ break;
+ }
+ // else fall through on purpose
+ default:
+ buf.append( c );
+ }
+ }
+
+ if ( buf.length() > 0 ) {
+ split.add( buf.toString().trim() );
+ }
+ return split.toArray( new String[split.size()] );
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/AbstractCompoundModelElement.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/AbstractCompoundModelElement.java
new file mode 100644
index 0000000..98b1a22
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/AbstractCompoundModelElement.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+
+public abstract class AbstractCompoundModelElement extends AbstractModelElement implements ICompoundModelElement {
+
+ private static final long serialVersionUID = 1L;
+
+ public AbstractCompoundModelElement(String description) {
+ super(description);
+ }
+
+ public boolean addChild(IModelElement child) throws InvalidModelException {
+ return support.addChild(child);
+ }
+
+ public boolean removeChild(IModelElement child) {
+ return support.removeChild(child);
+ }
+
+ public IModelElement[] children() {
+ return support.children();
+ }
+
+ private static final ThreadLocal<Map<IModelWalker,Set<IModelElement>>> walkedLocal = new ThreadLocal<Map<IModelWalker,Set<IModelElement>>>();
+
+ public void visit(IModelWalker walker) {
+ if ( walker.visit( this ) ) {
+ Map<IModelWalker,Set<IModelElement>> walked = walkedLocal.get();
+ boolean delete = false;
+
+ if ( walked == null ) {
+ walked = new HashMap<IModelWalker, Set<IModelElement>>();
+ walkedLocal.set(walked);
+ }
+
+ Set<IModelElement> check = walked.get(walker);
+
+ if ( check == null ) {
+ delete = true;
+ check = new HashSet<IModelElement>();
+ }
+
+ check.add( this );
+
+ try {
+ for ( IModelElement e : children() ) {
+ if ( !check.contains( e ) && walker.visit( e ) ) {
+ check.add( e );
+ if ( e instanceof ICompoundModelElement ) {
+ ICompoundModelElement c = (ICompoundModelElement) e;
+ c.visit(walker);
+ }
+ }
+ }
+ }
+ finally {
+ if ( delete ) {
+ walked.remove(walker);
+
+ if ( walked.isEmpty() ) {
+ walkedLocal.set( null );
+ }
+ }
+ }
+ }
+ }
+
+ public Set<Class<? extends IModelElement>> getOptionalChildren() {
+ return support.getChildrenTypes(false);
+ }
+
+ public Set<Class<? extends IModelElement>> getRequiredChildren() {
+ return support.getChildrenTypes(true);
+ }
+
+ public <T extends IModelElement> T[] childrenOfType(Class<T> type) {
+ return support.childrenOfType( type );
+ }
+
+
+ @Override
+ public void checkValid() throws InvalidModelException {
+ super.checkValid();
+
+ for ( IModelElement e : support.children() ) {
+ e.checkValid();
+ }
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/AbstractModelElement.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/AbstractModelElement.java
new file mode 100644
index 0000000..e65b3ea
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/AbstractModelElement.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+
+public abstract class AbstractModelElement implements IModelElement {
+
+ private static final long serialVersionUID = 1L;
+
+ private IModelElement parent;
+
+ private String description;
+ private transient Map<Object, Object> meta;
+ private Map<Serializable, Serializable> serializedMeta;
+ private OverrideOptions override;
+
+ protected final ModelElementSupport support;
+
+ public AbstractModelElement(String description) {
+ support = new ModelElementSupport(this);
+ this.description = description.intern();
+ this.meta = new HashMap<Object, Object>();
+ this.serializedMeta = new HashMap<Serializable, Serializable>();
+ }
+
+ public String getElementDescription() {
+ return description;
+ }
+
+ public Map<Object, Object> getMeta() {
+ return meta;
+ }
+
+ public void setMeta(Map<Object, Object> meta) {
+ this.meta = meta;
+ }
+
+ @Override
+ public AbstractModelElement clone() {
+ try {
+ AbstractModelElement clone = (AbstractModelElement) super.clone();
+
+ clone.meta = new HashMap<Object, Object>(meta);
+
+ return clone;
+ } catch (CloneNotSupportedException e) {
+ // can't happen but make compiler happy
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T extends IModelElement> T getAncestor(Class<T> type) {
+ IModelElement parent = this.parent;
+
+ while ( parent != null ) {
+ if ( type.isInstance( parent ) ) {
+ return (T) parent;
+ }
+ parent = parent.getParent();
+ }
+
+ return null;
+ }
+
+ public IModelElement getParent() {
+ return parent;
+ }
+
+ public void setParent( IModelElement parent ) {
+ if ( parent != null ) {
+ if ( this.parent != null && this.parent != parent ) {
+ throw new IllegalStateException( "Parent already installed");
+ }
+ }
+
+ this.parent = parent;
+ }
+
+ public void checkValid() throws InvalidModelException {
+ for ( String req : getRequiredProperties() ) {
+ try {
+ if ( getProperty( req ) == null ) {
+ throw new InvalidModelException(this, "Missing property " + req );
+ }
+ } catch (NoSuchMethodException e) {
+ throw new InvalidModelException( this, "No such property " + req );
+ }
+ }
+ }
+
+ public Object getProperty(String name) throws NoSuchMethodException {
+ return support.getProperty(name);
+ }
+
+ public void setProperty(String name, Object value)
+ throws NoSuchMethodException {
+ support.setProperty(name, value);
+ }
+
+ public void addProperty(String name, Object value)
+ throws NoSuchMethodException {
+ support.addProperty(name, value);
+ }
+
+ public void removeProperty(String name, Object value)
+ throws NoSuchMethodException {
+ support.removeProperty(name, value);
+ }
+
+ public Object getDefaultPropertyValue(String name) {
+ return support.getDefaultPropertyValue( name );
+ }
+
+ public Set<String> getPropertyNames() {
+ return support.getPropertyNames();
+ }
+
+ public Set<String> getRequiredProperties() {
+ return Collections.emptySet();
+ }
+
+ protected Object writeReplace() {
+ AbstractModelElement clone = clone();
+
+ for (Map.Entry<Object, Object> e : clone.meta.entrySet()) {
+ if (e.getKey() instanceof Serializable && e.getValue() instanceof Serializable) {
+ serializedMeta.put((Serializable) e.getKey(), (Serializable) e.getValue());
+ }
+ }
+
+ clone.meta.clear();
+
+ return clone;
+ }
+
+ public Class<?> getPropertyType(String name) throws NoSuchMethodException {
+ return support.getPropertyType(name);
+ }
+
+ protected Object readResolve() {
+ this.meta = new HashMap<Object, Object>(serializedMeta);
+ serializedMeta.clear();
+ return this;
+ }
+
+ public OverrideOptions getOverride() {
+ return override;
+ }
+
+ public void setOverride(OverrideOptions override) {
+ this.override = override;
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/ICompoundModelElement.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/ICompoundModelElement.java
new file mode 100644
index 0000000..8df2b11
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/ICompoundModelElement.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model;
+
+import java.util.Set;
+
+public interface ICompoundModelElement extends IModelElement {
+ boolean addChild(IModelElement children) throws InvalidModelException;
+
+ boolean removeChild(IModelElement children);
+
+ IModelElement[] children();
+
+ void visit(IModelWalker walker);
+
+ <T extends IModelElement> T[] childrenOfType( Class<T> type );
+
+ Set<Class<? extends IModelElement>> getRequiredChildren();
+
+ Set<Class<? extends IModelElement>> getOptionalChildren();
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IDependency.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IDependency.java
new file mode 100644
index 0000000..0047bb9
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IDependency.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model;
+
+public interface IDependency extends IModelElement {
+ IDependent getDependent();
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IDependent.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IDependent.java
new file mode 100644
index 0000000..d97722e
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IDependent.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model;
+
+public interface IDependent extends IModelElement {
+ IDependency getDepender();
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IDependentModelElement.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IDependentModelElement.java
new file mode 100644
index 0000000..4b3c53e
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IDependentModelElement.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model;
+
+public interface IDependentModelElement extends IModelElement {
+ /**
+ * @return
+ */
+ IDependency[] getDependencies();
+
+ /**
+ * @param dependency
+ */
+ void addDependency(IModelElement dependency);
+
+ /**
+ * @param dependency
+ */
+ void removeDependency(IModelElement dependency);
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IModelElement.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IModelElement.java
new file mode 100644
index 0000000..4d230f0
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IModelElement.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Descriptors represent static information about component, composite or system. They allow other services to decide
+ * how to deal with the given entity without the need to directly interact with the entity.
+ *
+ * @author dave
+ *
+ */
+public interface IModelElement extends Cloneable {
+ /**
+ * A brief human readable description of the component, composite or system.
+ *
+ * @return
+ */
+ String getElementDescription();
+
+ /**
+ * A set of key value pairs designed for use by a machine to classify a particular component, composite or system.
+ *
+ * @return
+ */
+ Map<Object, Object> getMeta();
+
+ /**
+ * Set meta data on this descriptor. Meta data is designed for use by a machine to classify or further enhance a
+ * particular component, composite or system.
+ *
+ * @param meta
+ */
+ void setMeta(Map<Object, Object> meta);
+
+ /**
+ * Check to see if this descriptor defines a complete set of properties. The definition of what constitutes a
+ * complete set is up to the implementing class.
+ *
+ * @throws InvalidModelException
+ */
+ void checkValid() throws InvalidModelException;
+
+ /**
+ * Find the parent element of this model element or null if no parent exists.
+ * @return
+ */
+ IModelElement getParent();
+
+ void setParent( IModelElement parent );
+
+ /**
+ * Finds the first ancestor up the hierarch of parents which is an instance of the specified
+ * type.
+ *
+ * @param type
+ * @return
+ */
+ <T extends IModelElement> T getAncestor(Class<T> type);
+
+ IModelElement clone();
+
+ Set<String> getPropertyNames();
+
+ void setProperty(String name, Object value) throws NoSuchMethodException;
+
+ void addProperty(String name, Object value) throws NoSuchMethodException;
+
+ void removeProperty(String name, Object value) throws NoSuchMethodException;
+
+ Object getProperty(String name) throws NoSuchMethodException;
+
+ Object getDefaultPropertyValue(String name);
+
+ Set<String> getRequiredProperties();
+
+ Class<?> getPropertyType(String name) throws NoSuchMethodException;
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IModelInfo.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IModelInfo.java
new file mode 100644
index 0000000..b4525f8
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IModelInfo.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model;
+
+public interface IModelInfo {
+ String getGroupName();
+ String getGroupURI();
+ String getName();
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IModelWalker.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IModelWalker.java
new file mode 100644
index 0000000..a42c468
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IModelWalker.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model;
+
+
+public interface IModelWalker {
+ boolean visit( IModelElement element );
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/INamedModelElement.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/INamedModelElement.java
new file mode 100644
index 0000000..5a280db
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/INamedModelElement.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model;
+
+public interface INamedModelElement extends IModelElement {
+ void setName(String name);
+ String getName();
+ OverrideOptions getOverride();
+ void setOverride(OverrideOptions override);
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IRequirementModelElement.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IRequirementModelElement.java
new file mode 100644
index 0000000..9e9594c
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/IRequirementModelElement.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model;
+
+public interface IRequirementModelElement {
+ boolean accepts(IModelElement provider);
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/InvalidModelException.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/InvalidModelException.java
new file mode 100644
index 0000000..d4751c0
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/InvalidModelException.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model;
+
+/**
+ * @author dave
+ *
+ */
+public class InvalidModelException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ private IModelElement target;
+
+ public InvalidModelException(IModelElement target, String msg) {
+ super(msg);
+ this.target = target;
+ }
+
+ public InvalidModelException(IModelElement target, String msg, Throwable t) {
+ super(msg, t);
+ this.target = target;
+ }
+
+ public IModelElement getTarget() {
+ return target;
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/ModelElementFactory.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/ModelElementFactory.java
new file mode 100644
index 0000000..186d2d0
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/ModelElementFactory.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class ModelElementFactory {
+ static class ElementInfo {
+ Class<? extends IModelElement> implType;
+ String name;
+ String groupName;
+ String groupURI;
+
+ public ElementInfo(Class<? extends IModelElement> implType, String name, String groupName, String groupURI) {
+ this.implType = implType;
+ this.name = name;
+ this.groupName = groupName;
+ this.groupURI = groupURI;
+ }
+
+ public Class<? extends IModelElement> getImplType() {
+ return implType;
+ }
+ public String getName() {
+ return name;
+ }
+
+ public String getGroupName() {
+ return groupName;
+ }
+
+ public String getGroupURI() {
+ return groupURI;
+ }
+
+ public String toString() {
+ return "ElementInfo[" + name + ":" + groupName + ":" + groupURI + ":" + implType.getCanonicalName() + "]";
+ }
+ }
+
+ static class ModelInfo implements IModelInfo {
+
+ private ElementInfo e;
+
+ public ModelInfo(ElementInfo e) {
+ this.e = e;
+ }
+
+ public String getGroupName() {
+ return e.getGroupName();
+ }
+
+ public String getGroupURI() {
+ return e.getGroupURI();
+ }
+
+ public String getName() {
+ return e.getName();
+ }
+
+ }
+
+ static class DefaultModelElementFactory extends ModelElementFactory {
+ private HashMap<Class<? extends IModelElement>, ElementInfo> elementInfo = new HashMap<Class<? extends IModelElement>, ElementInfo>();
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T extends IModelElement> T newModelElement( Class<T> type ) throws ModelElementFactoryException {
+ ElementInfo info = elementInfo.get( type );
+ if ( info == null ) {
+ throw new ModelElementFactoryException( "No implementation registered for " + type );
+ }
+ try {
+ return (T) info.getImplType().newInstance();
+ } catch (InstantiationException e) {
+ throw new ModelElementFactoryException(e);
+ } catch (IllegalAccessException e) {
+ throw new ModelElementFactoryException(e);
+ }
+ }
+
+ @Override
+ public <T extends IModelElement> void register(Class<T> type, Class<? extends T> impl, String name, String groupName, String groupURI ) {
+ elementInfo.put( type, new ElementInfo( impl, name, groupName, groupURI ) );
+ }
+
+ @Override
+ public <T extends IModelElement> void unregister(Class<T> type,
+ Class<? extends T> impl) {
+ ElementInfo info = elementInfo.get( type );
+ if ( info != null && info.getImplType() == impl ) {
+ elementInfo.remove(type);
+ }
+ }
+
+ @Override
+ public IModelInfo getModelInfo(Class<? extends IModelElement> type) {
+ ElementInfo e = findElementInfo( type );
+
+ if ( e == null ) {
+ return null;
+ }
+
+ return new ModelInfo( e );
+ }
+
+ @Override
+ public IModelElement newModelElement(String namespaceURI, String localPart) throws ModelElementFactoryException {
+ for ( Map.Entry<Class<? extends IModelElement>, ElementInfo> e : elementInfo.entrySet() ) {
+ ElementInfo i = e.getValue();
+ if ( equal( namespaceURI, i.getGroupURI() ) && equal( i.getName(), localPart ) ) {
+ return newModelElement(e.getKey());
+ }
+ }
+
+ return null;
+ }
+
+ private boolean equal(String val1, String val2) {
+ return val1 == null ? val2 == null : val1.equals( val2 );
+ }
+
+ private ElementInfo findElementInfo( Class<? extends IModelElement> type ) {
+ for ( ElementInfo e : elementInfo.values() ) {
+ if ( e.getImplType() == type ) {
+ return e;
+ }
+ }
+
+ return null;
+ }
+
+ }
+
+ public abstract <T extends IModelElement> T newModelElement( Class<T> type ) throws ModelElementFactoryException;
+
+ public abstract IModelElement newModelElement(String namespaceURI, String localPart) throws ModelElementFactoryException;
+
+ public abstract <T extends IModelElement> void register( Class<T> type, Class<? extends T> impl, String name, String groupName, String groupURI );
+
+ public abstract <T extends IModelElement> void unregister( Class<T> type, Class<? extends T> impl );
+
+ public abstract IModelInfo getModelInfo( Class<? extends IModelElement> type );
+
+ private static ModelElementFactory instance = new DefaultModelElementFactory();
+
+ public static ModelElementFactory getInstance() {
+ return instance;
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/ModelElementFactoryException.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/ModelElementFactoryException.java
new file mode 100644
index 0000000..c450537
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/ModelElementFactoryException.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model;
+
+public class ModelElementFactoryException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ public ModelElementFactoryException(Throwable t) {
+ super(t);
+ }
+
+ public ModelElementFactoryException(String msg) {
+ super(msg);
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/ModelElementSupport.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/ModelElementSupport.java
new file mode 100644
index 0000000..40c64f0
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/ModelElementSupport.java
@@ -0,0 +1,728 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model;
+
+import java.io.Serializable;
+import java.lang.ref.SoftReference;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.logging.Logger;
+
+
+public class ModelElementSupport implements Serializable {
+
+ private static final Logger log = Logger.getLogger( ModelElementSupport.class.getName() );
+
+ private static final long serialVersionUID = 1L;
+
+ private static final PropertyAdapter[] EMPTY_PROPS = new PropertyAdapter[] {};
+ private static final IModelElement[] EMPTY_ELEMENTS = new IModelElement[] {};
+ private static final Object[] ZERO_ARGS = new Object[] {};
+ private static final Class<?>[] ZERO_PARAMS = new Class[] {};
+
+ private static final WeakHashMap<Class<?>, SoftReference<ChildAdapter[]>> adapterCache = new WeakHashMap<Class<?>, SoftReference<ChildAdapter[]>>();;
+ private static final WeakHashMap<Class<?>, SoftReference<PropertyAdapter[]>> propertyCache = new WeakHashMap<Class<?>, SoftReference<PropertyAdapter[]>>();;
+
+ private IModelElement target;
+
+ private transient SoftReference<PropertyAdapter[]> propertyReference;
+ private transient SoftReference<ChildAdapter[]> childrenReference;
+ private transient SoftReference<Set<String>> propertyNameReference;
+
+ public ModelElementSupport(IModelElement target) {
+ this.target = target;
+ }
+
+ public void setProperty(String name, Object value) throws NoSuchMethodException {
+ PropertyAdapter p = findProperty( name, value );
+ if ( p == null ) {
+ throw new NoSuchMethodException( "No such property " + name + " for type " + target.getClass() );
+ }
+ invoke( target, p.getWriteMethod(), value );
+ }
+
+ public void addProperty(String name, Object value) throws NoSuchMethodException {
+ PropertyAdapter p = findProperty( name, value );
+ if ( p == null ) {
+ throw new NoSuchMethodException( "No such property " + name + " for type " + target.getClass() );
+ }
+ invoke( target, p.getAddMethod(), value );
+ }
+
+ public void removeProperty(String name, Object value) throws NoSuchMethodException {
+ PropertyAdapter p = findProperty( name, value );
+ if ( p == null ) {
+ throw new NoSuchMethodException( "No such property " + name + " for type " + target.getClass() );
+ }
+ invoke( target, p.getRemoveMethod(), value );
+ }
+
+ public Object getProperty( String name ) throws NoSuchMethodException {
+ PropertyAdapter p = findProperty( name, null );
+ if ( p == null ) {
+ throw new NoSuchMethodException( "No such property " + name + " for type " + target.getClass() );
+ }
+ return invoke( target, p.getReadMethod(), ZERO_ARGS );
+ }
+
+ public Set<String> getPropertyNames() {
+ Set<String> names = propertyNameReference == null ? null : propertyNameReference.get();
+
+ if ( names == null ) {
+ names = new HashSet<String>();
+
+ PropertyAdapter[] props = cachedProps( target.getClass() );
+ for ( PropertyAdapter prop : props ) {
+ names.add( prop.getName() );
+ }
+
+ propertyNameReference = new SoftReference<Set<String>>(names);
+ }
+
+ return names;
+ }
+
+ public Object getDefaultPropertyValue(String name) {
+ try {
+ Method m = target.getClass().getMethod( makeDefaultPropertyValue( name ), ZERO_PARAMS );
+ return invoke( target, m, ZERO_ARGS );
+ } catch (SecurityException e) {
+ throw new UndeclaredThrowableException(e);
+ } catch (NoSuchMethodException e) {
+ // fine no default
+ return null;
+ }
+ }
+
+ public Class<?> getPropertyType(String name) throws NoSuchMethodException {
+ PropertyAdapter p = findProperty( name, null );
+ if ( p == null ) {
+ throw new NoSuchMethodException( "No such property " + name + " for type " + target.getClass() );
+ }
+ return p.getPropertyType();
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T extends IModelElement> T[] childrenOfType( Class<T> type ) {
+ ChildAdapter[] adapters = cachedAdapters();
+
+ if ( adapters.length == 0 ) {
+ // return (T[]) EMPTY_ELEMENTS;
+ return ((T[])Array.newInstance(type, 0));
+ }
+
+ ArrayList<T> elements = new ArrayList<T>();
+
+ for ( ChildAdapter adapter : adapters ) {
+ Collection<? extends IModelElement> val = adapter.members(target);
+
+ for ( IModelElement e : val ) {
+ if ( type.isInstance(e) ) {
+ elements.add( (T) e );
+ }
+ }
+ }
+
+ //return elements.toArray( (T[]) EMPTY_ELEMENTS );
+ return elements.toArray((T[])Array.newInstance(type, elements.size()));
+ }
+
+ public IModelElement[] children() {
+ ChildAdapter[] adapters = cachedAdapters();
+
+ if ( adapters.length == 0 ) {
+ return EMPTY_ELEMENTS;
+ }
+
+ ArrayList<IModelElement> elements = new ArrayList<IModelElement>();
+
+ for ( ChildAdapter adapter : adapters ) {
+ elements.addAll( adapter.members(target) );
+ }
+
+ return elements.toArray( EMPTY_ELEMENTS );
+ }
+
+ public boolean addChild(IModelElement element) throws InvalidModelException {
+ if ( element.getParent() == null ) {
+ ChildAdapter[] adapters = cachedAdapters();
+
+ if ( adapters.length > 0 ) {
+ for ( ChildAdapter adapter : adapters ) {
+ if ( adapter.add( target, element ) ) {
+ element.setParent(target);
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public boolean removeChild(IModelElement element) {
+ if ( element.getParent() == target ) {
+ ChildAdapter[] adapters = cachedAdapters();
+
+ if ( adapters.length > 0 ) {
+ for ( ChildAdapter adapter : adapters ) {
+ if ( adapter.remove( target, element ) ) {
+ element.setParent( null );
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public Set<Class<? extends IModelElement>> getChildrenTypes(boolean required) {
+ ChildAdapter[] adapters = cachedAdapters();
+
+ if ( adapters.length == 0 ) {
+ return Collections.emptySet();
+ }
+
+ HashSet<Class<? extends IModelElement>> types = new HashSet<Class<? extends IModelElement>>();
+
+ for ( ChildAdapter adapter : adapters ) {
+ if ( adapter.isRequired() == required ) {
+ Class<? extends IModelElement> type = adapter.getType();
+
+ if ( type != null ) {
+ types.add( type );
+ }
+ }
+ }
+
+ return types;
+ }
+
+ private PropertyAdapter findProperty(String name, Object value) {
+ PropertyAdapter[] props = propertyReference == null ? null : propertyReference.get();
+
+ if ( props == null ) {
+ props = cachedProps( target.getClass() );
+ propertyReference = new SoftReference<PropertyAdapter[]>( props );
+ }
+
+ for ( PropertyAdapter prop : props ) {
+ if ( prop.getName().equals( name ) && (value == null || prop.getRawType().isAssignableFrom(value.getClass() ) ) ) {
+ return prop;
+ }
+ }
+
+ return null;
+ }
+
+ private static PropertyAdapter[] cachedProps(
+ Class<? extends IModelElement> type) {
+ SoftReference<PropertyAdapter[]> ref = propertyCache.get( type );
+
+ PropertyAdapter[] props = ref == null ? null : ref.get();
+
+ if ( props == null ) {
+ props = loadProps( type );
+ propertyCache.put( type, new SoftReference<PropertyAdapter[]>( props ) );
+ }
+
+ return props;
+ }
+
+ private static PropertyAdapter[] loadProps(Class<? extends IModelElement> type) {
+ ArrayList<PropertyAdapter> props = new ArrayList<PropertyAdapter>();
+ for ( Method m : type.getMethods() ) {
+ if ( isValidProperty( m )) {
+ try {
+ PropertyAdapter p = new PropertyAdapter( m, type );
+ props.add( p );
+ } catch (NoSuchMethodException e) {
+ // fine not a bean method
+ log.finer( "Invalid bean property method " + m + ": " + e.getMessage() );
+ }
+ }
+ }
+
+ return props.toArray( EMPTY_PROPS );
+ }
+
+ private static boolean isValidProperty(Method m) {
+ return m.getName().startsWith( "get" ) && m.getParameterTypes().length == 0 && !m.getDeclaringClass().equals( Object.class ) && !IModelElement.class.isAssignableFrom(m.getReturnType());
+ }
+
+ private static String makeDefaultPropertyValue(String name) {
+ return "getDefault" + capitalise( name );
+ }
+
+ private static String capitalise(String name) {
+ return Character.toUpperCase(name.charAt(0)) + name.substring(1);
+ }
+
+ private static String decapitalise(String substring) {
+ return Character.toLowerCase(substring.charAt(0)) + substring.substring(1);
+ }
+
+ private ChildAdapter[] cachedAdapters() {
+ ChildAdapter[] adapters = childrenReference == null ? null : childrenReference.get();
+
+ if ( adapters == null ) {
+ adapters = loadAdapters( target );
+ childrenReference = new SoftReference<ChildAdapter[]>( adapters );
+ }
+
+ return adapters;
+ }
+
+ private static ChildAdapter[] loadAdapters(IModelElement target) {
+ Class<? extends IModelElement> type = target.getClass();
+ SoftReference<ChildAdapter[]> ref = adapterCache.get( type );
+
+ ChildAdapter[] adapters = ref == null ? null : ref.get();
+
+ if ( adapters == null ) {
+ adapters = buildAdapters( type );
+ adapterCache.put( type, new SoftReference<ChildAdapter[]>( adapters ) );
+ }
+
+ return adapters;
+ }
+
+ private static ChildAdapter[] buildAdapters(Class<? extends IModelElement> type) {
+ ArrayList<ChildAdapter> adapters = new ArrayList<ChildAdapter>();
+
+ for ( Method m : type.getMethods() ) {
+ ChildAdapter adapter = null;
+
+ if ( isValidGetProperty( m ) ) {
+ adapter = buildGetAdapter( m );
+ }
+ else if ( isValidSetProperty( m ) ) {
+ adapter = buildSetAdapter( m );
+ }
+ else if ( isValidAddProperty( m ) ) {
+ adapter = buildAddAdapter( m );
+ }
+ else if ( isValidRemoveProperty( m ) ) {
+ adapter = buildRemoveAdapter( m );
+ }
+
+ if ( adapter != null ) {
+ adapters.add( adapter );
+ }
+ }
+
+ return adapters.toArray( new ChildAdapter[adapters.size()] );
+ }
+
+ private static ChildAdapter buildGetAdapter(Method m) {
+ if ( IModelElement.class.isAssignableFrom( m.getReturnType() ) ) {
+ return new GetPropertyAdapter( m );
+ }
+ else if ( Collection.class.isAssignableFrom( m.getReturnType() ) ) {
+ return new GetCollectionAdapter( m );
+ }
+ else if ( isModelArray( m.getReturnType() ) ) {
+ return new GetArrayAdapter( m );
+ }
+ else {
+ return null;
+ }
+ }
+
+ private static ChildAdapter buildSetAdapter(Method m) {
+ if ( IModelElement.class.isAssignableFrom( m.getParameterTypes()[0] ) ) {
+ return new SetPropertyAdapter( m );
+ }
+ else {
+ return null;
+ }
+ }
+
+ private static ChildAdapter buildAddAdapter(Method m) {
+ if ( IModelElement.class.isAssignableFrom( m.getParameterTypes()[0] ) ) {
+ return new AddPropertyAdapter( m );
+ }
+ else {
+ return null;
+ }
+ }
+
+ private static ChildAdapter buildRemoveAdapter(Method m) {
+ if ( IModelElement.class.isAssignableFrom( m.getParameterTypes()[0] ) ) {
+ return new RemovePropertyAdapter( m );
+ }
+ else {
+ return null;
+ }
+ }
+
+ private static boolean isValidRemoveProperty(Method m) {
+ return m.getParameterTypes().length == 1 && m.getName().startsWith( "remove" ) && !isDeclared( ICompoundModelElement.class, m );
+ }
+
+ private static boolean isValidAddProperty(Method m) {
+ return m.getParameterTypes().length == 1 && m.getName().startsWith( "add" ) && !isDeclared( ICompoundModelElement.class, m );
+ }
+
+ private static boolean isDeclared(Class<? extends IModelElement> element, Method m) {
+ try {
+ element.getMethod( m.getName(), m.getParameterTypes() );
+ return true;
+ } catch (SecurityException e) {
+ throw new UndeclaredThrowableException( e );
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ private static boolean isValidSetProperty(Method m) {
+ return m.getParameterTypes().length == 1 && m.getName().startsWith( "set" ) && !isDeclared( IModelElement.class, m );
+ }
+
+ private static boolean isValidGetProperty(Method m) {
+ return m.getParameterTypes().length == 0 && m.getName().startsWith( "get" ) && !isDeclared( IModelElement.class, m ) && !isDeclared(ICompoundModelElement.class, m);
+ }
+
+ private static Object invoke( Object target, Method m, Object... args ) {
+ try {
+ return m.invoke(target, args);
+ } catch (IllegalArgumentException e) {
+ // this should already have been tested
+ throw new IllegalStateException(e);
+ } catch (IllegalAccessException e) {
+ throw new UndeclaredThrowableException( e );
+ } catch (InvocationTargetException e) {
+ throw new UndeclaredThrowableException( e.getCause() );
+ }
+ }
+
+ private static class PropertyAdapter {
+
+ String prop;
+ String name;
+ Method g;
+ Method s;
+ Method a;
+ Method r;
+ Class<?> propertyType;
+
+ public PropertyAdapter(Method g, Class<?> type) throws SecurityException, NoSuchMethodException {
+ if ( g.getReturnType().isArray() || Iterable.class.isAssignableFrom(g.getReturnType() ) ) {
+ prop = g.getName().substring(3);
+ // remove trailing s - as in addWibble, removeWibble, getWibbles
+ prop = prop.substring(0, prop.length() - 1);
+ name = decapitalise( prop );
+ a = find( "add", prop, g.getReturnType(), type );
+ propertyType = a.getParameterTypes()[0];
+ r = find( "remove", prop, g.getReturnType(), type );
+ if ( r.getParameterTypes()[0] != propertyType ) {
+ throw new NoSuchMethodException( "Add remove property method types do not match" );
+ }
+ propertyType = Array.newInstance(propertyType, 0).getClass();
+ }
+ else {
+ prop = g.getName().substring(3);
+ name = decapitalise( prop );
+ propertyType = g.getReturnType();
+ s = find( "set", prop, propertyType, type );
+ }
+
+ this.g = g;
+ }
+
+ public Class<?> getRawType() {
+ return propertyType.isArray() ? propertyType.getComponentType() : propertyType;
+ }
+
+ public Class<?> getPropertyType() {
+ return propertyType;
+ }
+
+ public Method getReadMethod() {
+ return g;
+ }
+
+ public Method getAddMethod() throws NoSuchMethodException {
+ if ( a == null ) {
+ throw new NoSuchMethodException( "No such method add" + prop );
+ }
+
+ return a;
+ }
+
+ public Method getRemoveMethod() throws NoSuchMethodException {
+ if ( r == null ) {
+ throw new NoSuchMethodException( "No such method remove" + prop );
+ }
+
+ return r;
+ }
+
+ public Method getWriteMethod() throws NoSuchMethodException {
+ if ( s == null ) {
+ throw new NoSuchMethodException( "No such method set" + prop );
+ }
+
+ return s;
+ }
+
+ @Override
+ public String toString() {
+ return "PropertyAdapter[" + name + "]";
+ }
+
+ private Method find(String prefix, String prop, Class<?> returnType, Class<?> type) throws SecurityException, NoSuchMethodException {
+ String methodName = prefix + prop;
+
+ if ( returnType.isArray() ) {
+ Class<?> t = returnType.getComponentType();
+ return type.getMethod( methodName, new Class[] { t } );
+ }
+ else if ( Iterable.class.isAssignableFrom( returnType ) ) {
+ Method f = null;
+ for ( Method m : type.getMethods() ) {
+ if ( m.getParameterTypes().length == 1 && m.getName().equals( methodName ) && !IModelElement.class.isAssignableFrom(m.getParameterTypes()[0]) ) {
+ if ( f == null ) {
+ f = m;
+ }
+ else {
+ throw new NoSuchMethodException( "Found duplicate " + methodName );
+ }
+ }
+ }
+ if ( f == null ) {
+ throw new NoSuchMethodException( "No such method " + methodName );
+ }
+
+ return f;
+ }
+ else {
+ return type.getMethod( methodName, new Class[] { returnType } );
+ }
+ }
+ public String getName() {
+ return name;
+ }
+
+ }
+
+ private static abstract class ChildAdapter {
+ Method m;
+
+ ChildAdapter( Method m ) {
+ this.m = m;
+ }
+
+ public boolean isRequired() {
+ return m.isAnnotationPresent(Required.class);
+ }
+
+ boolean add(Object target, IModelElement element) {
+ return false;
+ }
+
+ abstract Class<? extends IModelElement> getType();
+
+ boolean remove(Object target, IModelElement element) {
+ return false;
+ }
+
+ Collection<? extends IModelElement> members(Object target) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String toString() {
+ return "ChildAdapter[ " + m.getName() + "]";
+ }
+ }
+
+ private static class GetPropertyAdapter extends ChildAdapter {
+ GetPropertyAdapter(Method m) {
+ super(m);
+ }
+
+ @Override
+ Collection<? extends IModelElement> members(Object target) {
+ IModelElement member = (IModelElement) invoke( target, m, ZERO_ARGS );
+ if ( member == null ) {
+ return Collections.emptyList();
+ }
+ else {
+ return Collections.<IModelElement>singleton( member );
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ Class<? extends IModelElement> getType() {
+ return (Class<? extends IModelElement>) m.getReturnType();
+ }
+ }
+
+ private static class GetCollectionAdapter extends ChildAdapter {
+ public GetCollectionAdapter(Method m) {
+ super(m);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ Collection<? extends IModelElement> members(Object target) {
+ Collection members = (Collection) invoke( target, m, ZERO_ARGS );
+ if ( members == null ) {
+ return Collections.emptyList();
+ }
+ else {
+ ArrayList<IModelElement> safe = new ArrayList<IModelElement>(members.size());
+ for ( Object o : members ) {
+ if ( o instanceof IModelElement ) {
+ safe.add( (IModelElement) o );
+ }
+ }
+ return safe;
+ }
+ }
+
+ @Override
+ Class<? extends IModelElement> getType() {
+ // impossible to get type of a collection as erasure removes generics info
+ return null;
+ }
+
+ }
+
+ private static class GetArrayAdapter extends ChildAdapter {
+ public GetArrayAdapter(Method m) {
+ super(m);
+ }
+
+ @Override
+ Collection<? extends IModelElement> members(Object target) {
+ IModelElement[] array = (IModelElement[]) invoke( target, m, ZERO_ARGS );
+ if ( array == null || array.length == 0) {
+ return Collections.emptyList();
+ }
+ else {
+ return (Collection<? extends IModelElement>) Arrays.asList( array );
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ Class<? extends IModelElement> getType() {
+ return (Class<? extends IModelElement>) m.getReturnType().getComponentType();
+ }
+ }
+
+ private static class SetPropertyAdapter extends ChildAdapter {
+ public SetPropertyAdapter(Method m) {
+ super(m);
+ }
+
+ @Override
+ boolean add(Object target, IModelElement element) {
+ if ( m.getParameterTypes()[0].isAssignableFrom( element.getClass() ) ) {
+ invoke(target, m, new Object[] { element } );
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+
+ @Override
+ boolean remove(Object target, IModelElement element) {
+ if ( m.getParameterTypes()[0].isAssignableFrom( element.getClass() ) ) {
+ invoke(target, m, new Object[] { null } );
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ Class<? extends IModelElement> getType() {
+ return (Class<? extends IModelElement>) m.getParameterTypes()[0];
+ }
+ }
+
+ private static class AddPropertyAdapter extends ChildAdapter {
+ public AddPropertyAdapter(Method m) {
+ super(m);
+ }
+
+ @Override
+ boolean add(Object target, IModelElement element) {
+ if ( m.getParameterTypes()[0].isAssignableFrom( element.getClass() ) ) {
+ invoke(target, m, new Object[] { element } );
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ Class<? extends IModelElement> getType() {
+ return (Class<? extends IModelElement>) m.getParameterTypes()[0];
+ }
+ }
+
+ private static class RemovePropertyAdapter extends ChildAdapter {
+
+ public RemovePropertyAdapter(Method m) {
+ super(m);
+ }
+
+ @Override
+ boolean remove(Object target, IModelElement element) {
+ if ( m.getParameterTypes()[0].isAssignableFrom( element.getClass() ) ) {
+ invoke(target, m, new Object[] { element } );
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ Class<? extends IModelElement> getType() {
+ return (Class<? extends IModelElement>) m.getParameterTypes()[0];
+ }
+ }
+
+ private static boolean isModelArray(Class<?> returnType) {
+ return returnType.isArray() && IModelElement.class.isAssignableFrom(returnType.getComponentType() );
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/OverrideOptions.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/OverrideOptions.java
new file mode 100644
index 0000000..c82fea7
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/OverrideOptions.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author dave
+ *
+ */
+public enum OverrideOptions {
+ NO("no"), MAY("may"), MUST("must");
+
+ private String str;
+
+ private static Map<String, OverrideOptions> map = new HashMap<String, OverrideOptions>();
+
+ static {
+ for (OverrideOptions option : OverrideOptions.values()) {
+ map.put(option.str.toLowerCase(), option);
+ }
+ }
+
+ private OverrideOptions(String str) {
+ this.str = str;
+ }
+
+ public static OverrideOptions parse(String val) {
+ OverrideOptions option = map.get(val.toLowerCase());
+
+ if (option == null) {
+ throw new IllegalArgumentException("Invalid override value " + val);
+ }
+
+ return option;
+ }
+
+ @Override
+ public String toString() {
+ return str;
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/Required.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/Required.java
new file mode 100644
index 0000000..79b4278
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/Required.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Inherited
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Required {
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/And.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/And.java
new file mode 100644
index 0000000..28b83d0
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/And.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.common;
+
+import java.util.Map;
+
+public class And implements LDAPExpr {
+
+ /**
+ */
+ private static final long serialVersionUID = 1L;
+ private LDAPExpr[] children;
+
+ public static LDAPExpr apply(LDAPExpr... terms) {
+ if (terms == null) {
+ throw new NullPointerException("terms cannot be null");
+ }
+ else if (terms.length == 0) {
+ return Expressions.T;
+ }
+ else if (terms.length == 1) {
+ return terms[0];
+ }
+ LDAPExpr[] filtered = new LDAPExpr[terms.length];
+ int ctr = 0;
+ for (int i = 0; i < terms.length; i++) {
+ if (terms[i].equals(Expressions.F))
+ return Expressions.F;
+ if (terms[i].equals(Expressions.T))
+ continue;
+ filtered[ctr] = terms[i];
+ ctr++;
+ }
+ if (ctr == 0) {
+ return Expressions.T;
+ }
+ else if (ctr == 1) {
+ return filtered[0];
+ }
+ LDAPExpr[] andTerms = new LDAPExpr[ctr];
+ System.arraycopy(filtered, 0, andTerms, 0, ctr);
+
+ return new And(andTerms);
+ }
+
+ private And(LDAPExpr... children) {
+ this.children = children;
+ }
+
+ public boolean eval(Map<String, ?> map) {
+ for (int i = 0; i < children.length; i++) {
+ if (!children[i].eval(map)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void visit(ExprVisitor v) {
+ v.visitAnd(this);
+ }
+
+ public LDAPExpr[] getChildren() {
+ return children;
+ }
+
+ public void setChildren(LDAPExpr[] children) {
+ this.children = children;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof And) {
+ And that = (And) other;
+ if (children.length != that.children.length) {
+ return false;
+ }
+ for (int i = 0; i < children.length; i++) {
+ if (!children[i].equals(that.children[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer buf = new StringBuffer(256);
+ buf.append("(&");
+ for (int i = 0; i < children.length; i++) {
+ buf.append(" ").append(children[i]).append(" ");
+ }
+ buf.append(")");
+ return buf.toString();
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Cardinality.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Cardinality.java
new file mode 100644
index 0000000..ecf105e
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Cardinality.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.common;
+
+import java.io.Serializable;
+
+/**
+ * Immutable class representing cardinality constraints between two entities.
+ *
+ */
+public class Cardinality implements Serializable {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+ public static final Cardinality ZERO_TO_MANY = new Cardinality(0, -1);
+ public static final Cardinality ONE_TO_MANY = new Cardinality(1, -1);
+ public static final Cardinality ZERO_TO_ONE = new Cardinality(0, 1);
+ public static final Cardinality ONE_TO_ONE = new Cardinality(1, 1);
+
+ private int min;
+ private int max;
+
+ /**
+ * @param min
+ * >=0 (usually 0 or 1)
+ * @param max
+ * >=min or -1 to indicate an unbounded maximum
+ */
+ public Cardinality(int min, int max) {
+ if (min < 0) {
+ throw new IllegalArgumentException("Min cannot be less than 0");
+ }
+
+ if ((max < min) && (max != -1)) {
+ throw new IllegalArgumentException("Max cannot be less than min");
+ }
+
+ this.min = min;
+ this.max = max;
+ }
+
+ public int getMin() {
+ return min;
+ }
+
+ public int getMax() {
+ return max;
+ }
+
+ public String toString() {
+ return min + ".." + ((max == -1) ? ("n") : (Integer.toString(max)));
+ }
+
+ public boolean isDefined(Cardinality cardinality) {
+ return (min <= cardinality.min) && ((max == -1) || (max >= cardinality.max));
+ }
+
+ public boolean isSingleton() {
+ return (min == 1) && (max == 1);
+ }
+
+ public static Cardinality parse(String stringRep) throws IllegalArgumentException {
+ stringRep = stringRep.trim();
+
+ int dotdot = stringRep.indexOf("..");
+
+ if (dotdot == -1) {
+ throw new IllegalArgumentException("Invalid cardinality string representation, expected ..");
+ }
+
+ String minStr = stringRep.substring(0, dotdot);
+ String maxStr = stringRep.substring(dotdot + 2);
+
+ int min = Integer.parseInt(minStr);
+ int max = min;
+
+ if ("n".equals(maxStr)) {
+ max = -1;
+ }
+ else {
+ max = Integer.parseInt(maxStr);
+ }
+
+ return cardinality(min, max);
+ }
+
+ public static Cardinality cardinality(int min, int max) {
+ Cardinality c = null;
+
+ if (min == 0) {
+ if (max == 1) {
+ c = ZERO_TO_ONE;
+ }
+ else if (max == -1) {
+ c = ZERO_TO_MANY;
+ }
+ }
+ else if (min == 1) {
+ if (max == 1) {
+ c = ONE_TO_ONE;
+ }
+ else if (max == -1) {
+ c = ONE_TO_MANY;
+ }
+ }
+
+ if (c == null)
+ c = new Cardinality(min, max);
+
+ return c;
+ }
+
+ public int hashCode() {
+ return max ^ min;
+ }
+
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (o == null) {
+ return false;
+ }
+
+ try {
+ Cardinality c = (Cardinality) o;
+
+ return (min == c.min) && (max == c.max);
+ }
+ catch (ClassCastException cce) {
+ return false;
+ }
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/ExprVisitor.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/ExprVisitor.java
new file mode 100644
index 0000000..e112928
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/ExprVisitor.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.common;
+
+public interface ExprVisitor {
+
+ void visitAnd(And a);
+ void visitOr(Or o);
+ void visitNot(Not n);
+ void visitSimple(SimpleTerm st);
+ // if none of the above matches use this
+ void visitAny(LDAPExpr ex);
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Expressions.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Expressions.java
new file mode 100644
index 0000000..24ea698
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Expressions.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.common;
+
+import java.util.Map;
+
+public class Expressions {
+
+ public static LDAPExpr and(LDAPExpr... terms) {
+ return And.apply(terms);
+ }
+
+ public static LDAPExpr or(LDAPExpr... terms) {
+ return Or.apply(terms);
+ }
+
+ public static LDAPExpr not(LDAPExpr e) {
+ return Not.apply(e);
+ }
+
+ public static LDAPExpr T = Bool.TRUE;
+ public static LDAPExpr F = Bool.FALSE;
+
+ // supports direct use of wildcards for ease of testing, but not literal *s
+ public static SimpleTerm ex(String name, Ops op, String rhs) {
+
+ rhs = rhs.replace('*', SimpleTerm.WILDCARD);
+ return new SimpleTerm(name, op, rhs);
+ }
+
+}
+
+class Bool implements LDAPExpr {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+ public static final Bool TRUE = new Bool(true);
+ public static final Bool FALSE = new Bool(false);
+
+ private boolean bool;
+
+ public Bool(boolean bool) {
+ this.bool = bool;
+ }
+
+ public boolean eval(Map<String, ?> map) {
+ return bool;
+ }
+
+ public void visit(ExprVisitor v) {
+ }
+
+ public LDAPExpr[] getChildren() {
+ return CHILDLESS;
+ }
+
+ public String toString() {
+ return "(" + bool + ")";
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/FilterValidator.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/FilterValidator.java
new file mode 100644
index 0000000..8e3ebad
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/FilterValidator.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.common;
+
+public interface FilterValidator {
+
+ public static FilterValidator ACCEPT_ALL = new AcceptEverythingValidator();
+
+ boolean validate(LDAPExpr filter);
+
+ static class AcceptEverythingValidator implements FilterValidator {
+
+ public boolean validate(LDAPExpr filter) {
+ return true;
+ }
+
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/LDAPExpr.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/LDAPExpr.java
new file mode 100644
index 0000000..a009b76
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/LDAPExpr.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.common;
+
+import java.io.Serializable;
+import java.util.Map;
+
+public interface LDAPExpr extends Serializable {
+
+ public static final LDAPExpr[] CHILDLESS = new LDAPExpr[] {};
+ public static LDAPExpr ACCEPT_ALL = Expressions.T;
+
+ LDAPExpr[] getChildren();
+
+ void visit(ExprVisitor v);
+
+ boolean equals(Object other);
+
+ int hashCode();
+
+ boolean eval(Map<String, ?> map);
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/LDAPParseException.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/LDAPParseException.java
new file mode 100644
index 0000000..2e67024
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/LDAPParseException.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.common;
+
+public class LDAPParseException extends Exception {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ private ParseState ps;
+ private static final String LINE_SEPARATOR = System.getProperty("line.separator", "\\r\\n");
+
+ public LDAPParseException(String message, ParseState ps) {
+ super(message);
+ this.ps = ps;
+ }
+
+ public LDAPParseException(String message) {
+ super(message);
+ }
+
+ @Override
+ public String getMessage() {
+ if (ps == null) {
+ return super.getMessage();
+ }
+
+ String basicMessage = super.getMessage();
+ StringBuffer buf = new StringBuffer(basicMessage.length() + ps.str.length() * 2);
+ buf.append(basicMessage).append(LINE_SEPARATOR);
+ buf.append(ps.str).append(LINE_SEPARATOR);
+ for (int i = 0; i < ps.pos; i++) {
+ buf.append(" ");
+ }
+ buf.append("^");
+ return buf.toString();
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/LDAPParser.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/LDAPParser.java
new file mode 100644
index 0000000..8353041
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/LDAPParser.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.common;
+
+import static org.cauldron.sigil.model.common.Expressions.and;
+import static org.cauldron.sigil.model.common.Expressions.not;
+import static org.cauldron.sigil.model.common.Expressions.or;
+import static org.cauldron.sigil.model.common.Ops.APPROX;
+import static org.cauldron.sigil.model.common.Ops.EQ;
+import static org.cauldron.sigil.model.common.Ops.GE;
+import static org.cauldron.sigil.model.common.Ops.GT;
+import static org.cauldron.sigil.model.common.Ops.LE;
+import static org.cauldron.sigil.model.common.Ops.LT;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class LDAPParser {
+
+ private static final LDAPParser parser = new LDAPParser();
+
+ public static LDAPExpr parseExpression(String strExpr) throws LDAPParseException {
+ return parser.parse(strExpr);
+ }
+
+ public static void main(String[] args) {
+ for (String arg : args) {
+ try {
+ System.out.println(parseExpression(arg));
+ }
+ catch (LDAPParseException e) {
+ System.out.println("Failed to parse " + arg);
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public LDAPExpr parse(String strExpr) throws LDAPParseException {
+
+ if (strExpr == null || strExpr.trim().length() == 0) {
+ return LDAPExpr.ACCEPT_ALL;
+ }
+
+ ParseState ps = new ParseState(strExpr);
+ LDAPExpr expr = parseExpr(ps);
+ ps.skipWhitespace();
+ if (!ps.isEndOfString()) {
+ error("expected end of expression ", ps);
+ }
+ return expr;
+ }
+
+ public LDAPExpr parseExpr(ParseState ps) throws LDAPParseException {
+ ps.skipWhitespace();
+ if (!(ps.peek() == '(')) {
+ error("expected (", ps);
+ }
+ ps.read();
+ LDAPExpr expr = null;
+ ps.skipWhitespace();
+ char ch = ps.peek();
+ switch (ch) {
+ case '&':
+ ps.readAndSkipWhiteSpace();
+ List<LDAPExpr> andList = new ArrayList<LDAPExpr>();
+ while (ps.peek() == '(') {
+ andList.add(parseExpr(ps));
+ ps.skipWhitespace();
+ }
+ LDAPExpr[] andArr = andList.toArray(new LDAPExpr[andList.size()]);
+ expr = and(andArr);
+ break;
+ case '|':
+ ps.readAndSkipWhiteSpace();
+ List<LDAPExpr> orList = new ArrayList<LDAPExpr>();
+ while (ps.peek() == '(') {
+ orList.add(parseExpr(ps));
+ ps.skipWhitespace();
+ }
+ LDAPExpr[] orArray = orList.toArray(new LDAPExpr[orList.size()]);
+ expr = or(orArray);
+ break;
+ case '!':
+ ps.readAndSkipWhiteSpace();
+ expr = not(parseExpr(ps));
+ break;
+ default:
+ if (isNameChar(ch)) {
+ expr = parseSimple(ps);
+ }
+ else {
+ error("unexpected character: '" + ch + "'", ps);
+ }
+ }
+ ps.skipWhitespace();
+ if (ps.peek() != ')') {
+ error("expected )", ps);
+ }
+ ps.read();
+ return expr;
+
+ }
+
+ void error(String message, ParseState ps) throws LDAPParseException {
+ throw new LDAPParseException(message, ps);
+ }
+
+ private SimpleTerm parseSimple(ParseState ps) throws LDAPParseException {
+ // read name
+ StringBuffer name = new StringBuffer(16);
+ for (char c = ps.peek(); !ps.isEndOfString() && isNameChar(c); c = ps.peek()) {
+ ps.read();
+ name.append(c);
+ }
+ ps.skipWhitespace();
+ Ops op = null;
+ // read op
+ if (ps.lookingAt("=")) {
+ op = EQ;
+ ps.skip(1);
+ }
+ else if (ps.lookingAt(">=")) {
+ op = GE;
+ ps.skip(2);
+ }
+ else if (ps.lookingAt("<=")) {
+ op = LE;
+ ps.skip(2);
+ }
+ else if (ps.lookingAt(">")) {
+ op = GT;
+ ps.skip(1);
+ }
+ else if (ps.lookingAt("<")) {
+ op = LT;
+ ps.skip(1);
+ }
+ else if (ps.lookingAt("-=")) {
+ op = APPROX;
+ ps.skip(2);
+ }
+ else if (ps.isEndOfString()) {
+ error("unexpected end of expression", ps);
+ }
+ else {
+ error("unexpected character: '" + ps.peek() + "'", ps);
+ }
+ ps.skipWhitespace();
+
+ boolean escaped = false;
+ StringBuffer value = new StringBuffer(16);
+
+ while (!ps.isEndOfString() && !Character.isWhitespace(ps.peek()) && !(ps.peek() == ')' && !escaped)) {
+
+ char ch = ps.peek();
+
+ if (ch == '\\') {
+ escaped = true;
+ ps.read();
+ }
+ else if (ch == '*') {
+ if (escaped) {
+ value.append(ch);
+ escaped = false;
+ }
+ else {
+ value.append(SimpleTerm.WILDCARD);
+ }
+ ps.read();
+ }
+ else if (isLiteralValue(ch)) {
+ if (escaped) {
+ error("incorrectly applied escape of '" + ch + "'", ps);
+ }
+ value.append(ps.read());
+ }
+ else if (isEscapedValue(ch)) {
+ if (!escaped) {
+ error("missing escape for '" + ch + "'", ps);
+ }
+ value.append(ps.read());
+ escaped = false;
+ }
+ else {
+ error("unexpected character: '" + ps.peek() + "'", ps);
+ }
+ }
+ ps.skipWhitespace();
+
+ SimpleTerm expr = new SimpleTerm(name.toString(), op, value.toString());
+
+ return expr;
+ }
+
+ private boolean isNameChar(int ch) {
+ return !(Character.isWhitespace(ch) || (ch == '(') || (ch == ')') || (ch == '<') || (ch == '>') || (ch == '=')
+ || (ch == '~') || (ch == '*') || (ch == '\\'));
+ }
+
+ private boolean isLiteralValue(int ch) {
+ return !(Character.isWhitespace(ch) || (ch == '(') || (ch == ')') || (ch == '*'));
+ }
+
+ private boolean isEscapedValue(int ch) {
+ return (ch == '(') || (ch == ')') || (ch == '*');
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Not.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Not.java
new file mode 100644
index 0000000..a3b59c3
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Not.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.common;
+
+import java.util.Map;
+
+public class Not implements LDAPExpr {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+ private LDAPExpr[] children;
+
+ public static LDAPExpr apply(LDAPExpr e) {
+ if (e == null) {
+ throw new NullPointerException("cannot apply Not to a null expression");
+ }
+ if (e.equals(Expressions.T)) {
+ return Expressions.F;
+ }
+ if (e.equals(Expressions.F)) {
+ return Expressions.T;
+ }
+ return new Not(e);
+ }
+
+ private Not(LDAPExpr child) {
+ this.children = new LDAPExpr[] { child };
+ }
+
+ public boolean eval(Map<String, ?> map) {
+ return !children[0].eval(map);
+ }
+
+ public LDAPExpr getEx() {
+ return children[0];
+ }
+
+ public void visit(ExprVisitor v) {
+ v.visitNot(this);
+ }
+
+ public LDAPExpr[] getChildren() {
+ return children;
+ }
+
+ public void setChild(LDAPExpr child) {
+ this.children = new LDAPExpr[] { child };
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof Not) {
+ Not that = (Not) other;
+ return children[0].equals(that.children[0]);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer buf = new StringBuffer(256);
+ buf.append("(!");
+ buf.append(" ").append(children[0]).append(" ");
+ buf.append(")");
+ return buf.toString();
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Ops.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Ops.java
new file mode 100644
index 0000000..570fe8d
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Ops.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.common;
+
+public enum Ops {
+ EQ, GE, LE, GT, LT, APPROX;
+
+ @Override
+ public String toString() {
+ switch (this) {
+ case EQ:
+ return "=";
+ case GE:
+ return ">=";
+ case LE:
+ return "<=";
+ case GT:
+ return ">";
+ case LT:
+ return "<";
+ case APPROX:
+ return "~=";
+ default:
+ return super.toString();
+ }
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Or.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Or.java
new file mode 100644
index 0000000..39f3470
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Or.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.common;
+
+import java.util.Map;
+
+public class Or implements LDAPExpr {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+ private LDAPExpr[] children;
+
+ public static LDAPExpr apply(LDAPExpr... terms) {
+ if (terms == null) {
+ throw new NullPointerException("terms cannot be null");
+ }
+ else if (terms.length == 0) {
+ return Expressions.T;
+ }
+ else if (terms.length == 1) {
+ return terms[0];
+ }
+ LDAPExpr[] filtered = new LDAPExpr[terms.length];
+ int ctr = 0;
+ for (int i = 0; i < terms.length; i++) {
+ if (terms[i].equals(Expressions.T))
+ return Expressions.T;
+ if (terms[i].equals(Expressions.F))
+ continue;
+ filtered[ctr] = terms[i];
+ ctr++;
+ }
+ if (ctr == 0) {
+ return Expressions.F;
+ }
+ else if (ctr == 1) {
+ return filtered[0];
+ }
+ LDAPExpr[] orTerms = new LDAPExpr[ctr];
+ System.arraycopy(filtered, 0, orTerms, 0, ctr);
+
+ return new Or(orTerms);
+ }
+
+ private Or(LDAPExpr... children) {
+ this.children = children;
+ }
+
+ public boolean eval(Map<String, ?> map) {
+ for (int i = 0; i < children.length; i++) {
+ if (children[i].eval(map)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void visit(ExprVisitor v) {
+ v.visitOr(this);
+ }
+
+ public LDAPExpr[] getChildren() {
+ return children;
+ }
+
+ public void setChildren(LDAPExpr[] children) {
+ this.children = children;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof Or) {
+ Or that = (Or) other;
+ if (children.length != that.children.length) {
+ return false;
+ }
+ for (int i = 0; i < children.length; i++) {
+ if (children[i].equals(that.children[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer buf = new StringBuffer(256);
+ buf.append("(|");
+ for (int i = 0; i < children.length; i++) {
+ buf.append(" ").append(children[i]).append(" ");
+ }
+ buf.append(")");
+ return buf.toString();
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/ParseState.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/ParseState.java
new file mode 100644
index 0000000..a6f23ac
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/ParseState.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.common;
+
+import java.io.Serializable;
+
+/**
+ * @author dave
+ *
+ */
+class ParseState implements Serializable {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ int pos;
+
+ String str;
+
+ ParseState(String str) {
+ this.str = str;
+ }
+
+ public boolean lookingAt(String start) {
+ return str.substring(pos).startsWith(start);
+ }
+
+ public CharSequence skip(int n) {
+ int end = pos + n < str.length() ? pos + n : str.length();
+ int start = pos;
+ pos = end;
+ return str.subSequence(start, end);
+ }
+
+ public char read() {
+ char ch = str.charAt(pos);
+ if (pos < str.length()) {
+ pos++;
+ }
+ return ch;
+ }
+
+ public char readAndSkipWhiteSpace() {
+ char ch = read();
+ skipWhitespace();
+ return ch;
+ }
+
+ char peek() {
+ if (isEndOfString()) {
+ return (char) -1;
+ }
+ return str.charAt(pos);
+ }
+
+ boolean isEndOfString() {
+ return pos == str.length();
+ }
+
+ void skipWhitespace() {
+ while (pos < str.length() && Character.isWhitespace(str.charAt(pos))) {
+ pos++;
+ }
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/SimpleTerm.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/SimpleTerm.java
new file mode 100644
index 0000000..ebc4517
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/SimpleTerm.java
@@ -0,0 +1,281 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.common;
+
+import java.lang.reflect.Constructor;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Vector;
+
+public class SimpleTerm implements LDAPExpr {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+ public static final char WILDCARD = 2 ^ 16 - 1;
+ private static final String WILDCARD_STRING = new String(new char[] { SimpleTerm.WILDCARD });
+
+ private Ops op;
+ private String name;
+ private String rval;
+
+ public SimpleTerm(String name, Ops op, String value) {
+ this.op = op;
+ this.name = name.intern();
+ this.rval = value.intern();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Ops getOp() {
+ return op;
+ }
+
+ public String getRval() {
+ return rval;
+ }
+
+ public boolean eval(Map<String, ?> map) {
+
+ Object lval = map.get(name);
+ if (lval == null) {
+ return false;
+ }
+ else if (Ops.EQ == op && WILDCARD_STRING.equals(lval)) {
+ return true;
+ }
+ // any match in the vector will do
+ else if (lval instanceof Vector<?>) {
+ Vector<?> vec = (Vector<?>) lval;
+ for (Iterator<?> i = vec.iterator(); i.hasNext();) {
+ if (check(i.next())) {
+ return true;
+ }
+ }
+ return false;
+ }
+ // any match in the array will do
+ else if (lval instanceof Object[]) {
+ Object[] arr = (Object[]) lval;
+ for (int i = 0; i < arr.length; i++) {
+ if (check(arr[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return check(lval);
+ }
+
+ @SuppressWarnings("unchecked")
+ private boolean check(Object lval) {
+ if (lval == null) {
+ return false;
+ }
+ else if (Ops.EQ == op && WILDCARD_STRING.equals(lval)) {
+ return true;
+ }
+
+ Object rhs = null;
+
+ if (lval instanceof String) {
+
+ if (Ops.APPROX == op) {
+ rhs = collapseWhiteSpace(rval);
+ lval = collapseWhiteSpace((String) lval);
+ }
+
+ if (Ops.EQ == op || Ops.APPROX == op) {
+ return stringCheck((String) lval);
+ }
+ // rhs already a string
+
+ }
+ else if (lval.getClass() == Byte.class) {
+ rhs = Byte.valueOf(rval);
+ }
+ else if (lval.getClass() == Short.class) {
+ rhs = Short.valueOf(rval);
+ }
+ else if (lval.getClass() == Integer.class) {
+ rhs = Integer.valueOf(rval);
+ }
+ else if (lval.getClass() == Long.class) {
+ rhs = Long.valueOf(rval);
+ }
+ else if (lval.getClass() == Float.class) {
+ rhs = Float.valueOf(rval);
+ }
+ else if (lval.getClass() == Double.class) {
+ rhs = Double.valueOf(rval);
+ }
+ else {
+ try {
+ Constructor<?> stringCtor = lval.getClass().getConstructor(new Class[] { String.class });
+ rhs = stringCtor.newInstance(rval);
+ }
+ catch (Exception e) {
+ // log it
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ if (!(lval instanceof Comparable)) {
+ return Ops.EQ == op && lval.equals(rval);
+ }
+ else {
+
+ Comparable<? super Object> lhs = (Comparable<? super Object>) lval;
+
+ int compare = lhs.compareTo(rhs);
+
+ switch (op) {
+ case EQ:
+ return compare == 0;
+ case APPROX:
+ return compare == 0;
+ case GE:
+ return compare >= 0;
+ case LE:
+ return compare <= 0;
+ case GT:
+ return compare > 0;
+ case LT:
+ return compare < 0;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean stringCheck(String lhs) {
+
+ String rhs;
+ switch (op) {
+ case EQ:
+ case APPROX:
+ rhs = rval;
+ break;
+ default:
+ return false;
+ }
+
+ int valLength = lhs.length();
+ int patLength = rval.length();
+
+ if (valLength == 0 && patLength == 0) {
+ return true;
+ }
+
+ boolean wc = false;
+ int j = 0;
+ for (int i = 0; i < patLength; i++) {
+ // trailing wildcards
+ char pc = rhs.charAt(i);
+ if (j == valLength) {
+ if (pc != SimpleTerm.WILDCARD) {
+ return false;
+ }
+ continue;
+ }
+ if (pc == SimpleTerm.WILDCARD) {
+ wc = true;
+ continue;
+ }
+ while (wc && j < valLength - 1 && lhs.charAt(j) != pc) {
+ j++;
+ }
+ if (lhs.charAt(j) != pc) {
+ return false;
+ }
+ else {
+ wc = false;
+ j++;
+ }
+ }
+ return (wc || j == valLength);
+
+ }
+
+ private String collapseWhiteSpace(String in) {
+ StringBuffer out = new StringBuffer(in.trim().length());
+ boolean white = false;
+ for (int i = 0; i < in.length(); i++) {
+ char ch = in.charAt(i);
+ if (Character.isWhitespace(ch)) {
+ white = true;
+ }
+ else {
+ if (white) {
+ out.append(" ");
+ white = false;
+ }
+ out.append(ch);
+ }
+ }
+ return out.toString();
+ }
+
+ public void visit(ExprVisitor v) {
+ v.visitSimple(this);
+ }
+
+ public LDAPExpr[] getChildren() {
+ return CHILDLESS;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof SimpleTerm) {
+ SimpleTerm that = (SimpleTerm) other;
+ return name.equals(that.name) && op.equals(that.op) && rval.equals(that.rval);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "(" + name + " " + op.toString() + " " + escape(rval) + ")";
+ }
+
+ private String escape(String raw) {
+ StringBuffer buf = new StringBuffer(raw.length() + 10);
+ for (int i = 0; i < raw.length(); i++) {
+ char ch = raw.charAt(i);
+ switch (ch) {
+ case SimpleTerm.WILDCARD:
+ buf.append("*");
+ break;
+ case '(':
+ case ')':
+ case '*':
+ buf.append("\\").append(ch);
+ break;
+ default:
+ buf.append(ch);
+ }
+ }
+ return buf.toString();
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Utils.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Utils.java
new file mode 100644
index 0000000..0b6b30b
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/Utils.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.common;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Utils {
+ public static MapBuilder map(String name, Object value) {
+ return new MapBuilder().put(name, value);
+ }
+
+ public static String toString(Map<String, Object> attrs) {
+ if (attrs == null) {
+ return "NULL";
+ }
+
+ StringBuffer buf = new StringBuffer(128);
+ List<String> keys = new ArrayList<String>(attrs.keySet());
+ Collections.sort(keys);
+ buf.append("{");
+
+ for (int i = 0; i < keys.size(); i++) {
+ Object name = keys.get(i);
+ Object value = attrs.get(name);
+ buf.append(name).append("=").append(value).append(",");
+ }
+
+ if (buf.length() > 1) {
+ buf.delete(buf.length() - 1, buf.length());
+ }
+
+ buf.append("}");
+
+ return buf.toString();
+ }
+
+ public static class MapBuilder {
+ private Map<String, Object> map = new HashMap<String, Object>();
+
+ public MapBuilder put(String name, Object value) {
+ map.put(name, value);
+
+ return this;
+ }
+
+ public Map<String, Object> toMap() {
+ return map;
+ }
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/VersionRange.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/VersionRange.java
new file mode 100644
index 0000000..3505df4
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/VersionRange.java
@@ -0,0 +1,252 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.common;
+
+import java.io.Serializable;
+
+import org.osgi.framework.Version;
+
+public class VersionRange implements Serializable {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+ public static final Version INFINITE_VERSION = new Version(Integer.MAX_VALUE, Integer.MAX_VALUE,
+ Integer.MAX_VALUE, "");
+ public static final VersionRange ANY_VERSION = new VersionRange(false, Version.emptyVersion, INFINITE_VERSION, true);
+
+ private boolean openFloor;
+ private Version floor;
+ private Version ceiling;
+ private boolean openCeiling;
+
+ /**
+ * Interval constructor
+ *
+ * @param openFloor Whether the lower bound of the range is inclusive (false) or exclusive (true).
+ * @param floor The lower bound version of the range.
+ * @param ceiling The upper bound version of the range.
+ * @param openCeiling Whether the upper bound of the range is inclusive (false) or exclusive (true).
+ */
+ public VersionRange(boolean openFloor, Version floor, Version ceiling, boolean openCeiling) {
+ this.openFloor = openFloor;
+ this.floor = floor;
+ this.ceiling = ceiling;
+ this.openCeiling = openCeiling;
+ }
+
+ /**
+ * atLeast constructor
+ *
+ * @param openFloor
+ * @param floor
+ */
+ public VersionRange(Version atLeast) {
+ this.openFloor = false;
+ this.floor = atLeast;
+ this.ceiling = INFINITE_VERSION;
+ this.openCeiling = true;
+ }
+
+ public static VersionRange parseVersionRange(String val) throws IllegalArgumentException, NumberFormatException {
+ if ( val == null || val.trim().length() == 0 ) {
+ return ANY_VERSION;
+ }
+
+ boolean openFloor;
+ boolean openCeiling;
+ val = val.replaceAll("\\s", "");
+ val = val.replaceAll("\"", "");
+ int fst = val.charAt(0);
+ if (fst == '[') {
+ openFloor = false;
+ }
+ else if (fst == '(') {
+ openFloor = true;
+ }
+ else {
+ Version atLeast = Version.parseVersion(val);
+ return new VersionRange(atLeast);
+ }
+
+ int lst = val.charAt(val.length() - 1);
+ if (lst == ']') {
+ openCeiling = false;
+ }
+ else if (lst == ')') {
+ openCeiling = true;
+ }
+ else {
+ throw new IllegalArgumentException("illegal version range syntax " + val + ": range must end in ')' or ']'");
+ }
+
+ String inner = val.substring(1, val.length() - 1);
+ String[] floorCeiling = inner.split(",");
+ if (floorCeiling.length != 2) {
+ throw new IllegalArgumentException("illegal version range syntax " + "too many commas");
+ }
+ Version floor = Version.parseVersion(floorCeiling[0]);
+ Version ceiling = "*".equals( floorCeiling[1] ) ? INFINITE_VERSION : Version.parseVersion(floorCeiling[1]);
+ return new VersionRange(openFloor, floor, ceiling, openCeiling);
+ }
+ public Version getCeiling() {
+ return ceiling;
+ }
+
+ public Version getFloor() {
+ return floor;
+ }
+
+ public boolean isOpenCeiling() {
+ return openCeiling;
+ }
+
+ public boolean isOpenFloor() {
+ return openFloor;
+ }
+
+ public boolean isPointVersion() {
+ return !openFloor && !openCeiling && floor.equals(ceiling);
+ }
+
+ /**
+ * test a version to see if it falls in the range
+ *
+ * @param version
+ * @return
+ */
+ public boolean contains(Version version) {
+ if (version.equals(INFINITE_VERSION)) {
+ return ceiling.equals(INFINITE_VERSION);
+ }
+ else {
+ return (version.compareTo(floor) > 0 && version.compareTo(ceiling) < 0)
+ || (!openFloor && version.equals(floor)) || (!openCeiling && version.equals(ceiling));
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((ceiling == null) ? 0 : ceiling.hashCode());
+ result = prime * result + ((floor == null) ? 0 : floor.hashCode());
+ result = prime * result + (openCeiling ? 1231 : 1237);
+ result = prime * result + (openFloor ? 1231 : 1237);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ final VersionRange other = (VersionRange) obj;
+ if (ceiling == null) {
+ if (other.ceiling != null)
+ return false;
+ }
+ else if (!ceiling.equals(other.ceiling))
+ return false;
+ if (floor == null) {
+ if (other.floor != null)
+ return false;
+ }
+ else if (!floor.equals(other.floor))
+ return false;
+ if (openCeiling != other.openCeiling)
+ return false;
+ if (openFloor != other.openFloor)
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ if (ANY_VERSION.equals(this)) {
+ return makeString(openFloor, Version.emptyVersion, INFINITE_VERSION, openCeiling);
+ }
+ return makeString(openFloor, floor, ceiling, openCeiling);
+ }
+
+ private String makeString(boolean openFloor, Version floor, Version ceiling, boolean openCeiling) {
+ StringBuffer vr = new StringBuffer(32);
+ if ( INFINITE_VERSION.equals(ceiling) ) {
+ vr.append( Version.emptyVersion.equals(floor) ? "0" : floor.toString() );
+ }
+ else {
+ vr.append(openFloor ? "(" : "[");
+ String floorStr = Version.emptyVersion.equals(floor) ? "0" : floor.toString();
+ String ceilingStr = ceiling.toString();
+ vr.append(floorStr).append(",").append(ceilingStr);
+ vr.append(openCeiling ? ")" : "]");
+ }
+ return vr.toString();
+ }
+
+
+ public static VersionRange newInstance(Version pointVersion, VersionRangeBoundingRule lowerBoundRule, VersionRangeBoundingRule upperBoundRule) {
+ Version floor = null;
+ switch (lowerBoundRule) {
+ case Any:
+ floor = new Version(0, 0, 0);
+ break;
+ case Major:
+ floor = new Version(pointVersion.getMajor(), 0, 0);
+ break;
+ case Minor:
+ floor = new Version(pointVersion.getMajor(), pointVersion.getMinor(), 0);
+ break;
+ case Micro:
+ floor = new Version(pointVersion.getMajor(), pointVersion.getMinor(), pointVersion.getMicro());
+ break;
+ case Exact:
+ floor = pointVersion;
+ break;
+ }
+
+ Version ceiling = null;
+ boolean openCeiling = true;
+ switch (upperBoundRule) {
+ case Any:
+ ceiling = INFINITE_VERSION;
+ break;
+ case Major:
+ ceiling = new Version(pointVersion.getMajor() + 1, 0, 0);
+ break;
+ case Minor:
+ ceiling = new Version(pointVersion.getMajor(), pointVersion.getMinor() + 1, 0);
+ break;
+ case Micro:
+ ceiling = new Version(pointVersion.getMajor(), pointVersion.getMinor(), pointVersion.getMicro() + 1);
+ break;
+ case Exact:
+ ceiling = pointVersion;
+ openCeiling = false;
+ break;
+ }
+
+ return new VersionRange(false, floor, ceiling, openCeiling);
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/VersionRangeBoundingRule.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/VersionRangeBoundingRule.java
new file mode 100644
index 0000000..a92139d
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/common/VersionRangeBoundingRule.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.common;
+
+public enum VersionRangeBoundingRule {
+ Exact, Micro, Minor, Major, Any
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/IDownloadJar.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/IDownloadJar.java
new file mode 100644
index 0000000..aa2b25a
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/IDownloadJar.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.eclipse;
+
+import java.util.Set;
+
+import org.cauldron.sigil.model.IModelElement;
+import org.eclipse.core.runtime.IPath;
+
+public interface IDownloadJar extends IModelElement {
+ void addEntry(IPath entry);
+ void removeEntry(IPath entry);
+ // XXX bad spelling on purpose so that ModelElementSupport picks up method
+ // TODO fix in ModelElementSupport
+ Set<IPath> getEntrys();
+
+ void clearEntries();
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/ILibrary.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/ILibrary.java
new file mode 100644
index 0000000..b89dd1c
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/ILibrary.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.eclipse;
+
+import java.util.Collection;
+
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.osgi.framework.Version;
+
+public interface ILibrary extends IModelElement {
+ String getName();
+ void setName(String name);
+ Version getVersion();
+ void setVersion(Version version);
+ void addImport(IPackageImport pi);
+ void removeImport(IPackageImport pi);
+ Collection<IPackageImport> getImports();
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/ILibraryImport.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/ILibraryImport.java
new file mode 100644
index 0000000..8d53c39
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/ILibraryImport.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.eclipse;
+
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.common.VersionRange;
+
+public interface ILibraryImport extends IModelElement {
+ String getLibraryName();
+ void setLibraryName(String name);
+ VersionRange getVersions();
+ void setVersions(VersionRange range);
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/INewtonSystem.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/INewtonSystem.java
new file mode 100644
index 0000000..38e14c6
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/INewtonSystem.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.eclipse;
+
+import org.cauldron.sigil.model.IModelElement;
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * @author dave
+ *
+ */
+public interface INewtonSystem extends IModelElement {
+ IPath getLocation();
+
+ void setLocation(IPath location);
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/ISCAComposite.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/ISCAComposite.java
new file mode 100644
index 0000000..6205744
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/ISCAComposite.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.eclipse;
+
+import org.cauldron.sigil.model.IModelElement;
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * @author dave
+ *
+ */
+public interface ISCAComposite extends IModelElement {
+ IPath getLocation();
+
+ void setLocation(IPath location);
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/ISigilBundle.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/ISigilBundle.java
new file mode 100644
index 0000000..54a80dd
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/eclipse/ISigilBundle.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.eclipse;
+
+import java.io.IOException;
+import java.util.Set;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.cauldron.sigil.model.ICompoundModelElement;
+import org.cauldron.sigil.model.osgi.IBundleModelElement;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IVersionedModelElement;
+
+/**
+ * @author dave
+ *
+ */
+public interface ISigilBundle extends ICompoundModelElement, IVersionedModelElement {
+ void synchronize(IProgressMonitor monitor) throws IOException;
+
+ boolean isSynchronized();
+
+ IBundleModelElement getBundleInfo();
+
+ String getSymbolicName();
+
+ void setBundleInfo(IBundleModelElement bundle);
+
+ IDownloadJar getDownloadJar();
+
+ void setDownloadJar(IDownloadJar download);
+
+ void addComposite(ISCAComposite composite);
+
+ void removeComposite(ISCAComposite composite);
+
+ Set<ISCAComposite> getComposites();
+
+ void addLibraryPath(IPath path);
+
+ void removeLibraryPath(IPath path);
+
+ Set<IPath> getLibraryPaths();
+
+ void addSourcePath(IPath path);
+
+ void removeSourcePath(IPath path);
+
+ Set<IPath> getSourcePaths();
+
+ void clearSourcePaths();
+
+ Set<String> getClasspathEntrys();
+
+ void addClasspathEntry(String encodedClasspath);
+
+ void removeClasspathEntry(String encodedClasspath);
+
+ IPath getLocation();
+
+ void setLocation(IPath location);
+
+ IPath getSourcePathLocation();
+
+ void setSourcePathLocation( IPath location );
+
+ IPath getSourceRootPath();
+
+ void setSourceRootPath( IPath location );
+
+ void setLicencePathLocation(IPath cacheSourceLocation);
+
+ IPath getLicencePathLocation();
+
+ /**
+ * get package names included in bundle.
+ * Can contain wildcards e.g. org.foo.*
+ */
+ Set<String> getPackages();
+
+ /**
+ * remove package name from those included in bundle.
+ */
+ boolean removePackage(String pkg);
+
+ /**
+ * add package name to be included in bundle.
+ */
+ void addPackage(String pkg);
+
+
+ /**
+ * get package names included in download jar.
+ * Can contain wildcards e.g. org.foo.*
+ */
+ Set<String> getDownloadPackages();
+
+ /**
+ * remove package name from those included in download jar.
+ */
+ boolean removeDownloadPackage(String pkg);
+
+ /**
+ * add package name to be included in download jar.
+ */
+ void addDownloadPackage(String pkg);
+
+ /**
+ * Attempt to find a package export that matches the given name or return null if none specified
+ *
+ * @param elementName
+ * @return
+ */
+ IPackageExport findExport(String elementName);
+
+ /**
+ * Attempt to find a package import that matches the given name or return null if none specified
+ * @param packageName
+ * @return
+ */
+ IPackageImport findImport(String packageName);
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IBundleModelElement.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IBundleModelElement.java
new file mode 100644
index 0000000..1f2b8ad
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IBundleModelElement.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.osgi;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.Set;
+
+import org.cauldron.sigil.model.ICompoundModelElement;
+import org.cauldron.sigil.model.INamedModelElement;
+import org.cauldron.sigil.model.eclipse.ILibraryImport;
+import org.osgi.framework.Version;
+
+public interface IBundleModelElement extends INamedModelElement, ICompoundModelElement, IVersionedModelElement {
+
+ String getActivator();
+
+ void setActivator(String activator);
+
+ String getCategory();
+
+ void setCategory(String category);
+
+ String getContactAddress();
+
+ void setContactAddress(String contactAddress);
+
+ String getCopyright();
+
+ void setCopyright(String copyright);
+
+ URI getDocURI();
+
+ void setDocURI(URI docURI);
+
+ Set<IPackageExport> getExports();
+
+ void addExport(IPackageExport packageExport);
+
+ void removeExport(IPackageExport packageExport);
+
+ Set<IPackageImport> getImports();
+
+ void addImport(IPackageImport packageImport);
+
+ void removeImport(IPackageImport packageImport);
+
+ Set<IRequiredBundle> getRequiredBundles();
+
+ void addRequiredBundle(IRequiredBundle bundle);
+
+ void removeRequiredBundle(IRequiredBundle bundle);
+
+ void addLibraryImport(ILibraryImport library);
+
+ void removeLibraryImport(ILibraryImport library);
+
+ Set<ILibraryImport> getLibraryImports();
+
+ URI getLicenseURI();
+
+ void setLicenseURI(URI licenseURI);
+
+ URI getSourceLocation();
+
+ void setSourceLocation(URI sourceLocation);
+
+ String getSymbolicName();
+
+ void setSymbolicName(String symbolicName);
+
+ URI getUpdateLocation();
+
+ void setUpdateLocation(URI updateLocation);
+
+ String getVendor();
+
+ void setVendor(String vendor);
+
+ Version getVersion();
+
+ void setVersion(Version version);
+
+ void setDescription(String elementText);
+
+ String getDescription();
+
+ Collection<String> getClasspaths();
+
+ void addClasspath(String path);
+
+ void removeClasspath(String path);
+
+ void setFragmentHost(IRequiredBundle fragmentHost);
+
+ IRequiredBundle getFragmentHost();
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IPackageExport.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IPackageExport.java
new file mode 100644
index 0000000..008b56e
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IPackageExport.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.osgi;
+
+import java.util.Collection;
+
+import org.osgi.framework.Version;
+
+public interface IPackageExport extends IPackageModelElement, IVersionedModelElement, Comparable<IPackageExport> {
+ void addUse(String uses);
+
+ void removeUse(String uses);
+
+ Collection<String> getUses();
+
+ void setUses(Collection<String> asList);
+
+ Version getRawVersion();
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IPackageImport.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IPackageImport.java
new file mode 100644
index 0000000..c9a7704
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IPackageImport.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.osgi;
+
+import org.cauldron.sigil.model.IRequirementModelElement;
+
+public interface IPackageImport extends IPackageModelElement, IVersionRangeModelElement, IRequirementModelElement, Comparable<IPackageImport> {
+ /**
+ * indicates whether the OSGi attribute "resolution=optional" is specified.
+ */
+ boolean isOptional();
+
+ void setOptional(boolean optional);
+
+ /**
+ * indicates whether import is needed at compile-time.
+ * Default true. Used in conjunction with OSGiHeader.ALWAYS,
+ * to add an OSGI import, without creating a dependency.
+ */
+ boolean isDependency();
+
+ void setDependency(boolean dependency);
+
+ /**
+ * indicates whether import should be added to OSGi Package-Import header.
+ * Default: AUTO.
+ */
+ OSGiImport getOSGiImport();
+
+ void setOSGiImport(OSGiImport osgiImport);
+
+ enum OSGiImport {
+ /**
+ * only add to OSGi header, if it appears to be needed.
+ */
+ AUTO,
+
+ /**
+ * always add to OSGi header, even if it appears unnecessary.
+ */
+ ALWAYS,
+
+ /**
+ * never add to OSGi header.
+ */
+ NEVER
+ }
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IPackageModelElement.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IPackageModelElement.java
new file mode 100644
index 0000000..3e40d45
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IPackageModelElement.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.osgi;
+
+import org.cauldron.sigil.model.IModelElement;
+
+public interface IPackageModelElement extends IModelElement {
+
+ String getPackageName();
+
+ void setPackageName(String packageName);
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IRequiredBundle.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IRequiredBundle.java
new file mode 100644
index 0000000..9f75a43
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IRequiredBundle.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.osgi;
+
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.IRequirementModelElement;
+import org.cauldron.sigil.model.common.VersionRange;
+
+public interface IRequiredBundle extends IModelElement, IRequirementModelElement, Comparable<IRequiredBundle> {
+ String getSymbolicName();
+
+ void setSymbolicName(String symbolicName);
+
+ VersionRange getVersions();
+
+ void setVersions(VersionRange versions);
+
+ boolean isOptional();
+
+ void setOptional(boolean optional);
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IVersionRangeModelElement.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IVersionRangeModelElement.java
new file mode 100644
index 0000000..dfc7382
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IVersionRangeModelElement.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.osgi;
+
+import org.cauldron.sigil.model.common.VersionRange;
+
+public interface IVersionRangeModelElement {
+
+ VersionRange getVersions();
+
+ void setVersions(VersionRange version);
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IVersionedModelElement.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IVersionedModelElement.java
new file mode 100644
index 0000000..10c60c9
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/model/osgi/IVersionedModelElement.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.model.osgi;
+
+import org.osgi.framework.Version;
+
+public interface IVersionedModelElement {
+
+ Version getVersion();
+
+ void setVersion(Version version);
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/AbstractBundleRepository.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/AbstractBundleRepository.java
new file mode 100644
index 0000000..850de43
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/AbstractBundleRepository.java
@@ -0,0 +1,380 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.repository;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import org.cauldron.bld.core.BldCore;
+import org.cauldron.bld.core.licence.ILicenseManager;
+import org.cauldron.bld.core.licence.ILicensePolicy;
+import org.cauldron.bld.core.util.QuoteUtil;
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.ModelElementFactory;
+import org.cauldron.sigil.model.ModelElementFactoryException;
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.model.eclipse.ILibrary;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IBundleModelElement;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.osgi.framework.Version;
+
+public abstract class AbstractBundleRepository implements IBundleRepository {
+
+ private final String id;
+ private final HashSet<IBundleRepositoryListener> listeners = new HashSet<IBundleRepositoryListener>();
+
+ public AbstractBundleRepository(String id) {
+ this.id = id;
+ }
+
+ public abstract void accept(IRepositoryVisitor visitor, int options);
+
+ public void addBundleRepositoryListener(IBundleRepositoryListener listener) {
+ synchronized(listeners) {
+ listeners.add(listener);
+ }
+ }
+
+ public void removeBundleRepositoryListener(IBundleRepositoryListener listener) {
+ synchronized(listeners) {
+ listeners.remove(listener);
+ }
+ }
+
+ protected void notifyChange() {
+ for ( IBundleRepositoryListener l : listeners ) {
+ l.notifyChange(this);
+ }
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void accept(IRepositoryVisitor visitor) {
+ accept( visitor, 0 );
+ }
+
+ public void writeOBR(OutputStream out) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ public Collection<ISigilBundle> findProviders(final ILibrary library, int options) {
+ final ArrayList<ISigilBundle> found = new ArrayList<ISigilBundle>();
+
+ final ILicensePolicy policy = findPolicy(library);
+
+ IRepositoryVisitor visitor = new IRepositoryVisitor() {
+ public boolean visit(ISigilBundle bundle) {
+ if (policy.accept(bundle)) {
+ IBundleModelElement info = bundle.getBundleInfo();
+ for ( IPackageImport pi : library.getImports() ) {
+ for ( IPackageExport e : info.getExports() ) {
+ if ( pi.getPackageName().equals( e.getPackageName() ) && pi.getVersions().contains( e.getVersion() ) ) {
+ found.add(bundle);
+ break;
+ }
+ }
+ }
+ }
+ return true;
+ }
+ };
+
+ accept( visitor, options );
+
+ return found;
+ }
+
+ public Collection<ISigilBundle> findAllProviders(final IRequiredBundle req, int options) {
+ final ArrayList<ISigilBundle> found = new ArrayList<ISigilBundle>();
+
+ final ILicensePolicy policy = findPolicy(req);
+
+ IRepositoryVisitor visitor = new IRepositoryVisitor() {
+ public boolean visit(ISigilBundle bundle) {
+ if (policy.accept(bundle)) {
+ IBundleModelElement info = bundle.getBundleInfo();
+ if ( req.getSymbolicName().equals( info.getSymbolicName() ) && req.getVersions().contains( info.getVersion() ) ) {
+ found.add(bundle);
+ }
+ }
+ return true;
+ }
+ };
+
+ accept( visitor, options );
+
+ return found;
+ }
+
+ public Collection<ISigilBundle> findAllProviders(final IPackageImport pi, int options) {
+ final ArrayList<ISigilBundle> found = new ArrayList<ISigilBundle>();
+
+ final ILicensePolicy policy = findPolicy(pi);
+
+ IRepositoryVisitor visitor = new IRepositoryVisitor() {
+
+ public boolean visit(ISigilBundle bundle) {
+ if (policy.accept(bundle)) {
+ IBundleModelElement info = bundle.getBundleInfo();
+ if ( info != null ) {
+ for ( IPackageExport e : info.getExports() ) {
+ if ( pi.getPackageName().equals( e.getPackageName() ) ) {
+ if ( pi.getVersions().contains( e.getVersion() ) ) {
+ found.add(bundle);
+ break;
+ }
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ };
+
+ accept( visitor, options );
+
+ return found;
+ }
+
+ public ISigilBundle findProvider(final IPackageImport pi, int options) {
+ final ArrayList<ISigilBundle> found = new ArrayList<ISigilBundle>();
+
+ final ILicensePolicy policy = findPolicy(pi);
+
+ IRepositoryVisitor visitor = new IRepositoryVisitor() {
+ public boolean visit(ISigilBundle bundle) {
+ if (policy.accept(bundle)) {
+ IBundleModelElement info = bundle.getBundleInfo();
+ for ( IPackageExport e : info.getExports() ) {
+ if ( pi.getPackageName().equals( e.getPackageName() ) && pi.getVersions().contains( e.getVersion() ) ) {
+ found.add( bundle );
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ };
+
+ accept( visitor, options );
+
+ return found.isEmpty() ? null : found.iterator().next();
+ }
+
+ public ISigilBundle findProvider(final IRequiredBundle req, int options) {
+ final ArrayList<ISigilBundle> found = new ArrayList<ISigilBundle>();
+
+ final ILicensePolicy policy = findPolicy(req);
+
+ IRepositoryVisitor visitor = new IRepositoryVisitor() {
+
+ public boolean visit(ISigilBundle bundle) {
+ if (policy.accept(bundle)) {
+ IBundleModelElement info = bundle.getBundleInfo();
+ if ( req.getSymbolicName().equals( info.getSymbolicName() ) && req.getVersions().contains( info.getVersion() ) ) {
+ found.add( bundle );
+ return false;
+ }
+ }
+ return true;
+ }
+
+ };
+
+ accept( visitor, options );
+
+ return found.isEmpty() ? null : found.iterator().next();
+ }
+
+ public IBundleModelElement buildBundleModelElement(Manifest mf) {
+ IBundleModelElement info = null;
+
+ if ( mf != null ) {
+ Attributes attrs = mf.getMainAttributes();
+ String name = attrs.getValue("Bundle-SymbolicName");
+ if (name == null) {
+ // framework.jar doesn't have Bundle-SymbolicName!
+ name = attrs.getValue("Bundle-Name");
+ }
+
+ if (name != null) {
+ try {
+ info = ModelElementFactory.getInstance().newModelElement( IBundleModelElement.class );
+ info.setSymbolicName( name.split(";")[0] );
+ info.setVersion( Version.parseVersion( attrs.getValue( "Bundle-Version" ) ) );
+ info.setName( attrs.getValue( "Bundle-Name" ) );
+ info.setDescription( attrs.getValue( "Bundle-Description" ) );
+ info.setVendor( attrs.getValue( "Bundle-Vendor" ) );
+
+ String importStr = attrs.getValue( "Import-Package" );
+ if ( importStr != null ) {
+ addImports( info, importStr );
+ }
+ String exportStr = attrs.getValue( "Export-Package" );
+ if ( exportStr != null ) {
+ addExports( info, exportStr );
+ }
+
+ String reqStr = attrs.getValue( "Require-Bundle" );
+ if ( reqStr != null ) {
+ addRequires( info, reqStr );
+ }
+
+ String cpStr = attrs.getValue( "Bundle-Classpath" );
+
+ if ( cpStr != null ) {
+ addClasspath( info, cpStr );
+ }
+ }
+ catch (RuntimeException e) {
+ BldCore.error( "Failed to read info from bundle " + name, e );
+ // clear elements as clearly got garbage
+ info = null;
+ }
+ }
+ }
+
+ return info;
+ }
+
+ protected ILicensePolicy findPolicy(IModelElement elem) {
+ ILicenseManager man = BldCore.getLicenseManager();
+
+/* ISigilProjectModel p = elem.getAncestor(ISigilProjectModel.class);
+
+ ILicensePolicy policy = null;
+
+ if ( p != null ) {
+ policy = man.getPolicy(p);
+ }
+ else {
+ policy = man.getDefaultPolicy();
+ }
+
+ return policy; */
+
+ return man.getDefaultPolicy();
+ }
+
+ private void addClasspath(IBundleModelElement info, String cpStr) {
+ for ( String cp : cpStr.split( "," ) ) {
+ info.addClasspath( cp );
+ }
+ }
+
+ private void addExports(IBundleModelElement info, String exportStr) throws ModelElementFactoryException {
+ for ( String exp : QuoteUtil.split( exportStr ) ) {
+ try {
+ String[] parts = exp.split( ";" );
+ IPackageExport pe = ModelElementFactory.getInstance().newModelElement(IPackageExport.class);
+ pe.setPackageName( parts[0].trim() );
+
+ if ( parts.length > 1 ) {
+ for (int i = 1; i < parts.length; i++ ) {
+ String check = parts[i];
+ if ( check.toLowerCase().startsWith( "version=" ) ) {
+ pe.setVersion( parseVersion(check.substring("version=".length())));
+ }
+ else if ( check.toLowerCase().startsWith( "specification-version=" ) ) {
+ pe.setVersion( parseVersion( check.substring("specification-version=".length()) ) );
+ }
+ else if ( check.toLowerCase().startsWith( "uses:=" ) ) {
+ for (String use : parseUses( check.substring( "uses:=".length() ) ) ) {
+ pe.addUse(use);
+ }
+ }
+ }
+ }
+ info.addExport(pe);
+ }
+ catch (RuntimeException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private Collection<String> parseUses(String uses) {
+ if ( uses.startsWith( "\"") ) {
+ uses = uses.substring(1, uses.length() - 2 );
+ }
+
+ return Arrays.asList( uses.split(",") );
+ }
+
+ private Version parseVersion(String val) {
+ val = val.replaceAll("\"", "");
+ return new Version(val);
+ }
+
+ private void addImports(IBundleModelElement info, String importStr) throws ModelElementFactoryException {
+ for ( String imp : QuoteUtil.split( importStr ) ) {
+ String[] parts = imp.split( ";" );
+ IPackageImport pi = ModelElementFactory.getInstance().newModelElement(IPackageImport.class);
+ pi.setPackageName( parts[0].trim() );
+
+ if ( parts.length > 1 ) {
+ for ( int i = 1; i < parts.length; i++ ) {
+ String p = parts[i];
+ if ( p.toLowerCase().startsWith( "version=" ) ) {
+ pi.setVersions( VersionRange.parseVersionRange(p.substring("version=".length())));
+ }
+ else if ( p.toLowerCase().startsWith( "specification-version=" ) ) {
+ pi.setVersions( VersionRange.parseVersionRange( p.substring("specification-version=".length()) ));
+ }
+ else if ( p.toLowerCase().startsWith( "resolution:=" ) ) {
+ pi.setOptional( p.toLowerCase().substring("resolution:=".length()).equals( "optional") );
+ }
+ }
+ }
+ info.addImport(pi);
+ }
+ }
+
+ private void addRequires(IBundleModelElement info, String reqStr) throws ModelElementFactoryException {
+ for ( String imp : QuoteUtil.split( reqStr ) ) {
+ String[] parts = imp.split( ";" );
+ IRequiredBundle req = ModelElementFactory.getInstance().newModelElement(IRequiredBundle.class);
+ req.setSymbolicName( parts[0] );
+
+ if ( parts.length > 1 ) {
+ if ( parts[1].toLowerCase().startsWith( "version=" ) ) {
+ req.setVersions( VersionRange.parseVersionRange(parts[1].substring("version=".length())));
+ }
+ else if ( parts[1].toLowerCase().startsWith( "specification-version=" ) ) {
+ req.setVersions( VersionRange.parseVersionRange( parts[1].substring("specification-version=".length()) ));
+ }
+ }
+ info.addRequiredBundle(req);
+ }
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/AbstractRepositoryManager.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/AbstractRepositoryManager.java
new file mode 100644
index 0000000..a4d09e8
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/AbstractRepositoryManager.java
@@ -0,0 +1,330 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.repository;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.cauldron.bld.core.repository.BundleResolver;
+import org.cauldron.sigil.model.IModelWalker;
+import org.cauldron.sigil.model.eclipse.ILibrary;
+import org.cauldron.sigil.model.eclipse.ILibraryImport;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.repository.RepositoryChangeEvent.Type;
+
+public abstract class AbstractRepositoryManager implements IRepositoryManager, IBundleRepositoryListener {
+
+ private HashSet<IRepositoryChangeListener> listeners = new HashSet<IRepositoryChangeListener>();
+
+ private boolean initialised;
+
+ private HashMap<String, IBundleRepository> repositories = new HashMap<String, IBundleRepository>();
+ private ArrayList<IBundleRepository> order = new ArrayList<IBundleRepository>();
+ private TreeMap<Integer, HashSet<IBundleRepository>> levelMap = new TreeMap<Integer, HashSet<IBundleRepository>>();
+ private int[] levels;
+
+ private BundleResolver resolver = new BundleResolver(this);
+
+ private ArrayList<ILibrary> libraries = new ArrayList<ILibrary>();
+
+ public void initialise() {
+ synchronized( repositories ) {
+ if ( !initialised ) {
+ initialised = true;
+ loadRepositories();
+ }
+ }
+ }
+
+ protected abstract void loadRepositories();
+
+ public void addRepositoryChangeListener(IRepositoryChangeListener listener) {
+ synchronized(listeners) {
+ listeners.add(listener);
+ }
+ }
+
+ public void removeRepositoryChangeListener(IRepositoryChangeListener listener) {
+ synchronized(listeners) {
+ listeners.remove(listener);
+ }
+ }
+
+ public void notifyChange(IBundleRepository repository) {
+ notifyListeners( new RepositoryChangeEvent(repository, Type.CHANGED ) );
+ }
+
+ private void notifyListeners(RepositoryChangeEvent event) {
+ ArrayList<IRepositoryChangeListener> safe = null;
+ synchronized(listeners) {
+ safe = new ArrayList<IRepositoryChangeListener>(listeners);
+ }
+ for ( IRepositoryChangeListener l : safe ) {
+ l.repositoryChanged(event);
+ }
+ }
+
+ protected void setRepositories(IBundleRepository[] repos) {
+ synchronized( repositories ) {
+ repositories.clear();
+ order.clear();
+ levelMap.clear();
+ resetLevels();
+ if ( repos != null ) {
+ for ( int i = 0; i < repos.length; i++ ) {
+ addRepository(repos[i], i);
+ }
+ }
+ }
+ }
+
+ protected void addRepository(IBundleRepository rep, int level) {
+ Type type = null;
+
+ synchronized( repositories ) {
+ IBundleRepository old = repositories.put(rep.getId(), rep);
+ if ( old == null ) {
+ type = Type.ADDED;
+ rep.addBundleRepositoryListener(this);
+ }
+ else {
+ old.removeBundleRepositoryListener(this);
+ type = Type.CHANGED;
+ order.remove(old);
+ clearLevel(rep);
+ }
+
+ order.add(rep);
+
+ HashSet<IBundleRepository> set = levelMap.get(level);
+
+ if ( set == null ) {
+ set = new HashSet<IBundleRepository>();
+ levelMap.put( level, set );
+ }
+
+ set.add( rep );
+ resetLevels();
+ }
+
+ notifyListeners( new RepositoryChangeEvent(rep, type ) );
+ }
+
+ protected void removeRepository(IBundleRepository rep) {
+ Type type = null;
+
+ synchronized( repositories ) {
+ if ( repositories.remove(rep.getId()) != null ) {
+ order.remove(rep);
+ type = Type.REMOVED;
+ clearLevel(rep);
+ resetLevels();
+ }
+ }
+
+ if ( type != null ) {
+ notifyListeners( new RepositoryChangeEvent(rep, type ) );
+ }
+ }
+
+ private void clearLevel(IBundleRepository rep) {
+ for ( Iterator<Map.Entry<Integer, HashSet<IBundleRepository>>> iter = levelMap.entrySet().iterator(); iter.hasNext(); ) {
+ Map.Entry<Integer, HashSet<IBundleRepository>> e = iter.next();
+ if ( e.getValue().remove(rep) ) {
+ if ( e.getValue().isEmpty() ) {
+ iter.remove();
+ }
+ break;
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.repository.IRepositoryManager#getRepositories()
+ */
+ public Collection<IBundleRepository> getRepositories() {
+ initialise();
+ ArrayList<IBundleRepository> safe = null;
+
+ synchronized( repositories ) {
+ safe = new ArrayList<IBundleRepository>( order );
+ }
+
+ return safe;
+ }
+
+ private void resetLevels() {
+ Collections.sort(order, new Comparator<IBundleRepository>() {
+ public int compare(IBundleRepository o1, IBundleRepository o2) {
+ int l1 = findLevel(o1);
+ int l2 = findLevel(o2);
+
+ if ( l1 < l2 ) {
+ return -1;
+ }
+ else if ( l1 > l2 ) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ private int findLevel(IBundleRepository rep) {
+ for ( Map.Entry<Integer, HashSet<IBundleRepository>> e : levelMap.entrySet() ) {
+ if ( e.getValue().contains( rep ) ) {
+ return e.getKey();
+ }
+ }
+ throw new IllegalStateException();
+ }
+ });
+ levels = new int[levelMap.size()];
+ int i = 0;
+ for ( Integer v : levelMap.keySet() ) {
+ levels[i++] = v;
+ }
+ }
+
+
+ public int[] getPriorityLevels() {
+ initialise();
+ synchronized( repositories ) {
+ return levels;
+ }
+ }
+
+ public Collection<IBundleRepository> getRepositories(int priorityLevel) {
+ initialise();
+ List<IBundleRepository> found = null;
+
+ synchronized (repositories) {
+ HashSet<IBundleRepository> b = levelMap.get(priorityLevel);
+ if ( b == null ) {
+ found = Collections.emptyList();
+ }
+ else {
+ found = new ArrayList<IBundleRepository>(b);
+ }
+ }
+
+ return found;
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.repository.IRepositoryManager#addLibrary(org.cauldron.sigil.model.eclipse.ILibrary)
+ */
+ public void addLibrary(ILibrary library) {
+ synchronized( libraries ) {
+ libraries.add(library);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.repository.IRepositoryManager#removeLibrary(org.cauldron.sigil.model.eclipse.ILibrary)
+ */
+ public void removeLibrary(ILibrary library) {
+ synchronized( libraries ) {
+ libraries.remove(library);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.repository.IRepositoryManager#getLibraries()
+ */
+ public Collection<ILibrary> getLibraries() {
+ synchronized( libraries ) {
+ return libraries;
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.cauldron.sigil.internal.repository.IRepositoryManager#resolveLibrary(org.cauldron.sigil.model.eclipse.ILibraryImport)
+ */
+ public ILibrary resolveLibrary(final ILibraryImport l) {
+ final ArrayList<ILibrary> found = new ArrayList<ILibrary>(1);
+ //ISigilProjectModel p = l.getAncestor(ISigilProjectModel.class);
+ //
+ //IModelWalker w = new IModelWalker() {
+ // public boolean visit(IModelElement element) {
+ // if ( element instanceof ILibrary ) {
+ // updateLibrary(l, (ILibrary) element, found);
+ // return false;
+ // }
+ //
+ // return true;
+ // }
+ //};
+
+ //p.visit( w );
+
+ //if ( found.isEmpty() ) { // no project specific libraries - check workspace definitions
+ synchronized( libraries ) {
+ for ( ILibrary lib : libraries ) {
+ if ( l.getLibraryName().equals( lib.getName() ) && l.getVersions().contains(lib.getVersion()) ) {
+ updateLibrary(l, lib, found);
+ }
+ }
+ }
+ //}
+
+ return found.isEmpty() ? null : found.get(0);
+ }
+
+ protected void updateLibrary(ILibraryImport li, ILibrary l, ArrayList<ILibrary> found) {
+ if ( li.getLibraryName().equals( l.getName() ) && li.getVersions().contains(l.getVersion()) ) {
+ if ( found.isEmpty() ) {
+ found.add( l );
+ }
+ else {
+ ILibrary c = found.get(0);
+ if ( l.getVersion().compareTo(c.getVersion()) > 0 ) {
+ found.remove(0);
+ found.add(l);
+ }
+ }
+ }
+ }
+
+ public IBundleResolver getBundleResolver() {
+ return resolver;
+ }
+
+ public void visit(final IModelWalker walker) {
+ for (IBundleRepository rep : getRepositories()) {
+ IRepositoryVisitor wrapper = new IRepositoryVisitor() {
+ public boolean visit(ISigilBundle bundle) {
+ bundle.visit(walker);
+ // return true as still want to visit other bundles
+ return true;
+ }
+ };
+ rep.accept(wrapper);
+ }
+ }
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IBundleRepository.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IBundleRepository.java
new file mode 100644
index 0000000..ed7ddc1
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IBundleRepository.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.repository;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collection;
+
+import org.cauldron.sigil.model.eclipse.ILibrary;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+
+public interface IBundleRepository {
+ static final int NORMAL_PRIORITY = 0;
+ static final int MAXIMUM_PRIORITY = -500;
+ static final int MINIMUM_PRIORITY = 500;
+
+ String getId();
+
+ void addBundleRepositoryListener(IBundleRepositoryListener listener);
+
+ void removeBundleRepositoryListener(IBundleRepositoryListener listener);
+
+ void accept(IRepositoryVisitor visitor);
+
+ void accept(IRepositoryVisitor visitor, int options);
+
+ void writeOBR(OutputStream out) throws IOException;
+
+ void refresh();
+
+ ISigilBundle findProvider(IPackageImport packageImport, int options);
+
+ ISigilBundle findProvider(IRequiredBundle bundle, int options);
+
+ Collection<ISigilBundle> findAllProviders(IRequiredBundle bundle, int options);
+
+ Collection<ISigilBundle> findAllProviders(IPackageImport packageImport, int options);
+
+ Collection<ISigilBundle> findProviders(ILibrary library, int options);
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IBundleRepositoryListener.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IBundleRepositoryListener.java
new file mode 100644
index 0000000..33c8e6c
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IBundleRepositoryListener.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.repository;
+
+public interface IBundleRepositoryListener {
+ void notifyChange(IBundleRepository repository);
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IBundleResolver.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IBundleResolver.java
new file mode 100644
index 0000000..f42d372
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IBundleResolver.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.repository;
+
+import org.cauldron.sigil.model.IModelElement;
+
+public interface IBundleResolver {
+ IResolution resolve(IModelElement element, ResolutionConfig config, IResolutionMonitor monitor) throws ResolutionException;
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IProviderChangeListener.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IProviderChangeListener.java
new file mode 100644
index 0000000..7f2eba3
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IProviderChangeListener.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.repository;
+
+public interface IProviderChangeListener {
+ void notifyChange(IRepositoryProvider provider);
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IRepositoryChangeListener.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IRepositoryChangeListener.java
new file mode 100644
index 0000000..f8964f3
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IRepositoryChangeListener.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.repository;
+
+public interface IRepositoryChangeListener {
+ void repositoryChanged(RepositoryChangeEvent event);
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IRepositoryManager.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IRepositoryManager.java
new file mode 100644
index 0000000..f4a5ede
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IRepositoryManager.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.repository;
+
+import java.util.Collection;
+
+import org.cauldron.sigil.model.IModelWalker;
+import org.cauldron.sigil.model.eclipse.ILibrary;
+import org.cauldron.sigil.model.eclipse.ILibraryImport;
+import org.eclipse.core.runtime.CoreException;
+
+public interface IRepositoryManager {
+ void addRepositoryChangeListener(IRepositoryChangeListener listener);
+
+ void removeRepositoryChangeListener(IRepositoryChangeListener listener);
+
+ Collection<IBundleRepository> getRepositories();
+
+ Collection<IBundleRepository> getRepositories(int level);
+
+ void addLibrary(ILibrary library);
+
+ void removeLibrary(ILibrary library);
+
+ Collection<ILibrary> getLibraries();
+
+ ILibrary resolveLibrary(final ILibraryImport l);
+
+ IBundleResolver getBundleResolver();
+
+ int[] getPriorityLevels();
+
+ void visit(IModelWalker modelWalker);
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IRepositoryProvider.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IRepositoryProvider.java
new file mode 100644
index 0000000..856a8b4
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IRepositoryProvider.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.repository;
+
+import java.util.Properties;
+
+public interface IRepositoryProvider {
+ IBundleRepository createRepository(String id, Properties properties) throws RepositoryException;
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IRepositoryVisitor.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IRepositoryVisitor.java
new file mode 100644
index 0000000..1768815
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IRepositoryVisitor.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.repository;
+
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+
+public interface IRepositoryVisitor {
+ /**
+ * Visit the next bundle in the repository.
+ * Return true if should continue visiting other bundles, false otherwise.
+ * @param bundle
+ * @return
+ */
+ boolean visit(ISigilBundle bundle);
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IResolution.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IResolution.java
new file mode 100644
index 0000000..51360c7
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IResolution.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.repository;
+
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+
+public interface IResolution {
+ Set<ISigilBundle> getBundles();
+ ISigilBundle getProvider(IModelElement requirement);
+ List<IModelElement> getMatchedRequirements(ISigilBundle bundle);
+ void synchronize(IProgressMonitor monitor);
+ boolean isSynchronized();
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IResolutionMonitor.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IResolutionMonitor.java
new file mode 100644
index 0000000..9a739c2
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/IResolutionMonitor.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.repository;
+
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+
+public interface IResolutionMonitor {
+ boolean isCanceled();
+ void startResolution(IModelElement requirement);
+ void endResolution(IModelElement requirement, ISigilBundle sigilBundle);
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/RepositoryChangeEvent.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/RepositoryChangeEvent.java
new file mode 100644
index 0000000..476b219
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/RepositoryChangeEvent.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.repository;
+
+public class RepositoryChangeEvent {
+ public static enum Type { ADDED, CHANGED, REMOVED };
+
+ private Type type;
+ private IBundleRepository repository;
+
+ public RepositoryChangeEvent(IBundleRepository repository, Type type) {
+ this.repository = repository;
+ this.type = type;
+ }
+
+ public Type getType() {
+ return type;
+ }
+ public IBundleRepository getRepository() {
+ return repository;
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/RepositoryException.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/RepositoryException.java
new file mode 100644
index 0000000..a9a71c5
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/RepositoryException.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.repository;
+
+public class RepositoryException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public RepositoryException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+ public RepositoryException(String message) {
+ super(message);
+ }
+
+ public RepositoryException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/ResolutionConfig.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/ResolutionConfig.java
new file mode 100644
index 0000000..ae90a75
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/ResolutionConfig.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.repository;
+
+public class ResolutionConfig {
+ private int options;
+
+ public static final int INCLUDE_DEPENDENTS = 1;
+ public static final int INCLUDE_OPTIONAL = 2;
+ public static final int IGNORE_ERRORS = 4;
+ /** Return only bundles that are indexed locally */
+ public static final int INDEXED_ONLY = 8;
+ /** Return only bundles that are stored or cached locally */
+ public static final int LOCAL_ONLY = 16;
+
+ public ResolutionConfig() {
+ this(INCLUDE_DEPENDENTS);
+ }
+
+ public ResolutionConfig(int options) {
+ this.options = options;
+ }
+
+ public int getOptions() {
+ return options;
+ }
+
+ public boolean isDependents() {
+ return (options & INCLUDE_DEPENDENTS) != 0;
+ }
+
+ public boolean isIgnoreErrors() {
+ return (options & IGNORE_ERRORS) != 0;
+ }
+
+ public boolean isOptional() {
+ return (options & INCLUDE_OPTIONAL) != 0;
+ }
+
+ public boolean isCalculateLocalDependencies() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/ResolutionException.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/ResolutionException.java
new file mode 100644
index 0000000..c0c093d
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/ResolutionException.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.repository;
+
+import org.cauldron.sigil.model.IModelElement;
+
+public class ResolutionException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ private IModelElement[] parsed;
+
+ public ResolutionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ResolutionException(String message) {
+ super(message);
+ }
+
+ public ResolutionException(Throwable cause) {
+ super(cause);
+ }
+
+ public ResolutionException(IModelElement root, IModelElement[] parsed) {
+ super(buildMessage(root, parsed));
+ this.parsed = parsed;
+ }
+
+ private static String buildMessage(IModelElement root, IModelElement[] parsed) {
+ StringBuilder b = new StringBuilder();
+ b.append( "Failed to resolve " );
+ b.append( root );
+
+ if ( parsed.length > 0 ) {
+ b.append( " due to missing provider for " );
+ b.append( parsed[parsed.length - 1] );
+ }
+
+ return b.toString();
+ }
+
+ public IModelElement[] getParsed() {
+ return parsed;
+ }
+}
diff --git a/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/ResolutionMonitorAdapter.java b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/ResolutionMonitorAdapter.java
new file mode 100644
index 0000000..06348f9
--- /dev/null
+++ b/sigil/org.cauldron.bld.core/src/org/cauldron/sigil/repository/ResolutionMonitorAdapter.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.repository;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+
+public class ResolutionMonitorAdapter implements IResolutionMonitor {
+
+ private IProgressMonitor monitor;
+
+ public ResolutionMonitorAdapter(IProgressMonitor monitor) {
+ this.monitor = monitor;
+ }
+
+ public boolean isCanceled() {
+ return monitor.isCanceled();
+ }
+
+ public void startResolution(IModelElement requirement) {
+ monitor.subTask( "Resolving " + requirement);
+ }
+
+ public void endResolution(IModelElement requirement, ISigilBundle provider) {
+ monitor.subTask( (provider == null ? "Failed to resolve " : "Resolved ") + requirement);
+ }
+
+}