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);
+	}
+
+}