/*******************************************************************************
 * Copyright (c) 2006, 2010 Abstratt Technologies
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Rafael Chaves (Abstratt Technologies) - initial API and implementation
 *    Thipor Kong - running outside of Eclipse/OSGI
 *******************************************************************************/
package com.abstratt.mdd.internal.core;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
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 org.apache.commons.lang.StringUtils;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.RegistryFactory;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.WrappedException;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.Resource.Diagnostic;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.XMIResource;
import org.eclipse.emf.ecore.xmi.XMLParserPool;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.query.conditions.eobjects.EObjectCondition;
import org.eclipse.emf.query.statements.FROM;
import org.eclipse.emf.query.statements.IQueryResult;
import org.eclipse.emf.query.statements.SELECT;
import org.eclipse.emf.query.statements.WHERE;
import org.eclipse.uml2.common.util.UML2Util;
import org.eclipse.uml2.uml.Classifier;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.ElementImport;
import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.Namespace;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.PackageImport;
import org.eclipse.uml2.uml.ParameterableElement;
import org.eclipse.uml2.uml.Profile;
import org.eclipse.uml2.uml.TemplateParameter;
import org.eclipse.uml2.uml.TemplateSignature;
import org.eclipse.uml2.uml.TemplateableElement;
import org.eclipse.uml2.uml.UMLPackage;
import org.eclipse.uml2.uml.UMLPackage.Literals;
import org.eclipse.uml2.uml.VisibilityKind;
import org.eclipse.uml2.uml.util.UMLUtil;

import com.abstratt.mdd.core.IRepository;
import com.abstratt.mdd.core.IRepository.IElementVisitor.VisitorResult;
import com.abstratt.mdd.core.MDDCore;
import com.abstratt.mdd.core.NamedElementLookupCache;
import com.abstratt.mdd.core.RepositoryService;
import com.abstratt.mdd.core.isv.IModelWeaver;
import com.abstratt.mdd.core.util.MDDExtensionUtils;
import com.abstratt.mdd.core.util.MDDUtil;
import com.abstratt.mdd.core.util.StereotypeUtils;
import com.abstratt.pluginutils.ISharedContextRunnable;
import com.abstratt.pluginutils.LogUtils;

/**
 * A model set groups together models that can refer to each other's elements.
 */
public class Repository implements IRepository {

    /** Reference to the repository being built in the current thread */
    private static ThreadLocal<IRepository> inProgressRepository = new ThreadLocal<IRepository>();

    private static final Map<String, Object> LOAD_OPTIONS = new HashMap<String, Object>();

    static {
        LOAD_OPTIONS.put(XMLResource.OPTION_DISABLE_NOTIFY, Boolean.TRUE);
        LOAD_OPTIONS.put(XMLResource.OPTION_USE_PARSER_POOL, XMLParserPool.class.getName());
    }

    private NamedElementLookupCache lookup = new NamedElementLookupCache(this);

    protected static List<Package> getAllImportedPackages(Package package_, List<Package> allImportedPackages) {
        for (PackageImport packageImport : package_.getPackageImports())
            if (packageImport.getVisibility() == VisibilityKind.PUBLIC_LITERAL) {
                Package importedPackage = packageImport.getImportedPackage();
                if (importedPackage != null && !allImportedPackages.contains(importedPackage)) {
                	allImportedPackages.add(importedPackage);
                    getAllImportedPackages(importedPackage, allImportedPackages);
                }
            }
        return allImportedPackages;
    }

    public static IRepository getInProgress() {
        IRepository inProgress = inProgressRepository.get();
        if (inProgress != null)
            return inProgress;
        if (RepositoryService.ENABLED)
            return RepositoryService.DEFAULT.getCurrentRepository();
        return null;
    }

    private URI baseURI;

    private ResourceSet resourceSet;

    private Set<Resource> systemResources = new HashSet<Resource>();

    private Properties properties;

    private IModelWeaver weaver;

    /**
     * Creates a model set.
     * 
     * @param baseURI
     * @throws CoreException
     */
    public Repository(URI baseURI, boolean systemPackages) throws CoreException {
        Assert.isTrue(!baseURI.isRelative(), "Repository base URI must be absolute: " + baseURI);
        // it seems trailing slash causes cross-references to become relative
        // URIs preserving parent paths (/common/parent/../sibling/foo.uml
        // instead of ../sibling/foo.uml)
        if (baseURI.hasTrailingPathSeparator())
            baseURI = baseURI.trimSegments(1);
        @SuppressWarnings("unused")
        UMLPackage umlPackage = UMLPackage.eINSTANCE;
        this.baseURI = baseURI;
        properties = MDDUtil.loadRepositoryProperties(this.baseURI);
        init(systemPackages);
        load();
    }

    /**
     * (non-Javadoc)
     * 
     * @see com.abstratt.mdd.core.IRepository#accept(com.abstratt.mdd.core.IRepository.IElementVisitor)
     */
    public void accept(IElementVisitor visitor) {
        accept(visitor, resourceSet.getAllContents());
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.abstratt.mdd.core.IRepository#accept(com.abstratt.mdd.core.IRepository
     * .IElementVisitor, org.eclipse.uml2.uml.Element)
     */
    public void accept(IElementVisitor visitor, Element root) {
        // visit the root itself
        if (visitor.visit(root) != VisitorResult.CONTINUE)
            return;
        // visit the children if warranted
        TreeIterator<?> allContents = root.eAllContents();
        accept(visitor, allContents);
    }

    private void accept(IElementVisitor visitor, TreeIterator<?> allContents) {
        while (allContents.hasNext()) {
            final Object current = allContents.next();
            if (current instanceof Element) {
                Element element = (Element) current;
                switch (visitor.visit(element)) {
                case STOP:
                    return;
                case SKIP:
                    allContents.prune();
                case CONTINUE:
                }
            }
        }
    }

    public Element findFirst(EObjectCondition condition) {
        for (Resource currentResource : resourceSet.getResources()) {
            if (systemResources.contains(currentResource))
                continue;
            IQueryResult partial = new SELECT(1, new FROM(currentResource.getContents()), new WHERE(condition))
                    .execute();
            if (!partial.isEmpty())
                return (Element) partial.iterator().next();
        }
        return null;
    }

    @Override
    public List<Element> findAll(EObjectCondition condition, boolean internalOnly) {
        List<Element> result = new ArrayList<Element>();
        for (Resource currentResource : resourceSet.getResources()) {
            if (systemResources.contains(currentResource))
                continue;
            if (internalOnly && !currentResource.getURI().toString().startsWith(baseURI.toString()))
                continue;
            IQueryResult partial = new SELECT(new FROM(currentResource.getContents()), new WHERE(condition)).execute();
            for (EObject object : partial)
                result.add((Element) object);
        }
        return result;
    }

    @Override
    public <T extends Element> List<T> findInAnyPackage(EObjectCondition condition) {
        List<Package> startingPoints = new ArrayList<Package>();
        startingPoints.addAll(Arrays.asList(getTopLevelPackages(null)));
        for (Package top : getTopLevelPackages(null))
            getAllImportedPackages(top, startingPoints);
        return MDDUtil.findAllFrom(condition, startingPoints);
    }

    private boolean addPackage(Resource resource, Package newPackage) {
        return resource.getContents().add(newPackage);
    }

    private void addSystemPackage(Package systemPackage) {
        if (!systemResources.add(systemPackage.eResource()))
            // already added
            return;
        for (PackageImport current : systemPackage.getPackageImports())
            addSystemPackage(current.getImportedPackage());
    }

    @Override
    public boolean isSystemPackage(Package toCheck) {
        return systemResources.contains(toCheck.eResource());
    }
    
    @Override
    public boolean isOwnPackage(Package toCheck) {
    	return isManaged(toCheck.eResource());
    }

    private void basicSaveResource(Resource resource) throws IOException {
        final URI uri = resource.getURI();
        BufferedOutputStream outputStream = new BufferedOutputStream(resourceSet.getURIConverter().createOutputStream(
                uri), 8192);
        try {
            resource.save(outputStream, null);
        } finally {
            outputStream.close();
        }
    }

    private URI computePackageURI(String packageName) {
        return baseURI.appendSegment(packageName).appendFileExtension("uml");
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.abstratt.mdd.core.IRepository#createPackage(java.lang.String)
     */
    public Package createPackage(String packageName) {
        Assert.isNotNull(packageName);
        Package existing = findPackage(packageName, PACKAGE.getPackage());
        if (existing != null)
            // package already exists
            return existing;
        if (!MDDUtil.isQualifiedName(packageName))
            return createTopLevelPackage(packageName, null, null);
        String parentName = MDDUtil.removeLastSegment(packageName);
        Assert.isLegal(parentName != null, "Qualified name expected");
        Package parentNamespace = findPackage(parentName, PACKAGE.getPackage());
        Assert.isLegal(parentNamespace != null, "Parent namespace not found");
        return (Package) parentNamespace.createPackagedElement(MDDUtil.getLastSegment(packageName),
                PACKAGE.getPackage());
    }

    @Override
    public Package createTopLevelPackage(String qualifiedName, EClass packageClass) {
        return createTopLevelPackage(qualifiedName, packageClass, null);
    }

    public Package createTopLevelPackage(String packageName, EClass packageClass, URI packageURI) {
        Assert.isNotNull(packageName);
        if (packageClass == null)
            packageClass = UMLPackage.Literals.PACKAGE;
        Package existing = findPackage(packageName, packageClass);
        if (existing != null) {
            // package already exists
            if (packageURI != null) {
                URI existingURI = existing.eResource().getURI();
                Assert.isLegal(existingURI.equals(packageURI), "Wrong package location for " + packageName
                        + " (existing: " + existingURI + ", expected: " + packageURI + ")");
            }
            return existing;
        }
        Package newPackage = (Package) FACTORY.create(packageClass);
        addTopLevelPackage(newPackage, packageName, packageURI);
        return newPackage;
    }

    @Override
	public void addTopLevelPackage(Package newPackage, String packageName, URI packageURI) {
        if (packageURI == null)
            packageURI = computePackageURI(packageName);
		Resource resource = resourceSet.createResource(packageURI);
        addPackage(resource, newPackage);
        MDDUtil.markGenerated(newPackage);
        newPackage.setName(packageName);
        newPackage.setURI(getBaseURI().lastSegment() + '/' + packageName);
        if (Boolean.parseBoolean(getProperties().getProperty(ENABLE_EXTENSIONS,
                System.getProperty(ENABLE_EXTENSIONS, Boolean.FALSE.toString())))) {
            Profile extensions = (Profile) findPackage(EXTENSIONS_NAMESPACE, Literals.PROFILE);
            if (extensions != null && extensions.isDefined())
                StereotypeUtils.safeApplyProfile(newPackage, extensions);
        }
        if (getWeaver() != null)
            getWeaver().packageCreated(this, newPackage);
	}
    
    @Override
    public void dispose() {
        systemResources.clear();
        if (!isOpen())
            return;
        LogUtils.debug(MDDCore.PLUGIN_ID, "Disposing repository at " + getBaseURI());
        if (RepositoryService.ENABLED)
            // nothing to do
            ;
        else
            MDDUtil.unloadResources(resourceSet);
        resourceSet = null;
    }

    @Override
    public void close() {
    	LogUtils.debug(MDDCore.PLUGIN_ID, () -> "Closing " + this.hashCode());
        // with resource management, we don't need to unload resources
        resourceSet = null;
        systemResources.clear();
    }

    @Override
    public boolean isOpen() {
        return resourceSet != null;
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        if (!isOpen())
            return;
        if (RepositoryService.ENABLED)
            LogUtils.logWarning(MDDCore.PLUGIN_ID, "Repository being finalized but still open: " + this.getBaseURI(),
                    null);
        else
            dispose();
    }

    /*
     * @see com.abstratt.mdd.core.IRepository#findNamedElement(String, EClass,
     * Package)
     */
    public <T extends NamedElement> T findNamedElement(String name, EClass class_, Namespace namespace) {
    	if (class_ == null)
			class_ = UMLPackage.Literals.NAMED_ELEMENT;
        if (namespace == null) {
            T cached = lookup.findInCache(name, class_);
            if (cached != null)
                return cached;
        }
        final T found = internalFindNamedElement(name, class_, namespace, true, new HashSet<Namespace>());
        if (found != null)
            lookup.addToCache(namespace == null ? name : found.getQualifiedName(), found);
        return found;
    }

    @Override
    public NamedElementLookupCache getLookupCache() {
        return lookup;
    }

    @Override
    public String getBuild() {
        if (!isOpen())
            return "";
        for (Package package1 : getTopLevelPackages(null))
            if (!isSystemPackage(package1)) {
                String timestamp = MDDUtil.getGeneratedTimestamp(package1);
                if (timestamp != null)
                    return timestamp;
            }
        return null;
    }

    /*
     * Does a best effort search in order to find the given object. Recurse to
     * parent if cannot find the requested symbol.
     */
    private <T extends NamedElement> T internalFindNamedElement(String name, EClass class_, Namespace namespace,
            boolean deep, Set<Namespace> visited) {
        Assert.isNotNull(name);
        if (namespace != null && !visited.add(namespace))
            // already visited, avoid infinite recursion
            return null;
        // tries to resolve the name regardless the contextual namespace
        NamedElement element = internalFindNamedElement(name, class_);
        if (element != null && (namespace == null || isVisible(namespace, element, MDDUtil.isQualifiedName(name))))
            // element found or is fully qualified, no need to look further
            return (T) element;
        // no contextual namespace provided, there is nothing else we can do
        if (namespace == null) {
            if (MDDUtil.isQualifiedName(name)) {
                namespace = loadPackage(MDDUtil.getFirstSegment(name));
                return (T) ((namespace == null) ? null : findNamedElement(MDDUtil.removeFirstSegment(name), class_,
                        namespace));
            } else
                return null;
        }
        if (namespace instanceof TemplateableElement) {
            TemplateableElement asTemplate = (TemplateableElement) namespace;
            if (asTemplate.isTemplate()) {
                TemplateSignature signature = asTemplate.getOwnedTemplateSignature();
                // TODO a template may not have a signature (descendant)
                if (signature != null)
                    for (TemplateParameter parameter : signature.getParameters()) {
                        final ParameterableElement parameteredElement = parameter.getParameteredElement();
                        if (parameteredElement instanceof NamedElement
                                && name.equals(((NamedElement) parameteredElement).getName()))
                            return (T) parameteredElement;
                    }

            }
        }
        // uses the contextual namespace to derive a qualified name (assuming
        // 'name' is not fully qualified)
        if (namespace.getQualifiedName() != null) {
            final String fullyQualifiedName = MDDUtil.appendSegment(namespace.getQualifiedName(), name);
            element = internalFindNamedElement(fullyQualifiedName, class_);
            if (element != null && isVisible(namespace, element, MDDUtil.isQualifiedName(name)))
                // element found, no need to look further
                return (T) element;
            for (ElementImport elementImport : namespace.getElementImports())
                if (elementImport.getName().equals(name))
                    return (T) elementImport.getImportedElement();
            if (deep) {
                // check imported packages now
                List<PackageImport> imports = namespace.getPackageImports();
                for (PackageImport packageImport : imports) {
                    final Package importedPackage = packageImport.getImportedPackage();
                    element = internalFindNamedElement(name, class_, importedPackage,
                            packageImport.getVisibility() != VisibilityKind.PRIVATE_LITERAL, visited);
                    if ((element != null && isVisible(namespace, element, false)))
                        return (T) element;
                }
            }
        }
        // search the parent now
        if (namespace.getOwner() == null)
            return null;
        return (T) internalFindNamedElement(name, class_, (Namespace) namespace.getOwner(), deep, visited);
    }

    public String resolveAlias(String qualifiedName) {
        return resolveAlias(qualifiedName, "");
    }

    public void makeAlias(String source, String target) {
        properties.setProperty(ALIASES + '.' + source, target);
    }

    private String resolveAlias(String toResolve, String tail) {
        if (hasAlias(toResolve))
            return getAlias(toResolve) + tail;
        int separatorIndex = toResolve.lastIndexOf(NamedElement.SEPARATOR);
        return separatorIndex < 0 ? (toResolve + tail) : (resolveAlias(toResolve.substring(0, separatorIndex),
                toResolve.substring(separatorIndex)) + tail);
    }

    private String getAlias(String qualifiedName) {
        return properties.getProperty(ALIASES + '.' + qualifiedName);
    }

    private boolean hasAlias(String qualifiedName) {
        return properties.containsKey(ALIASES + '.' + qualifiedName);
    }

    private IFileStore getBaseStore() throws CoreException {
        return EFS.getStore(java.net.URI.create(baseURI.toString()));
    }

    public URI getBaseURI() {
        return baseURI;
    }

    public Operation getEntryPointOperation(Package package_) {
        for (NamedElement element : package_.getOwnedMembers()) {
            if (!(element instanceof Classifier))
                continue;
            Classifier classifier = (Classifier) element;
            for (Operation operation : classifier.getOperations())
                if (MDDExtensionUtils.isEntryPoint(operation))
                    return operation;
        }
        return null;
    }

    private Package getPackage(URI packageURI, boolean onDemand) {
        if (packageURI.isRelative())
            // when resolving, making sure we have the trailing slash
            packageURI = packageURI.resolve(baseURI.appendSegment(""));
        Resource resource;
        try {
            resource = resourceSet.getResource(packageURI, true);
        } catch (WrappedException e) {
            if (!(e instanceof Diagnostic))
                throw e;
            return null;
        }
        if (resource == null)
            return null;
        if (!onDemand)
            try {
                resource.load(null, LOAD_OPTIONS);
            } catch (IOException e) {
                LogUtils.logError(MDDCore.PLUGIN_ID, "Error loading resource " + packageURI, e);
            }
        return (Package) EcoreUtil.getObjectByType(resource.getContents(), UMLPackage.Literals.PACKAGE);
    }

    public ResourceSet getResourceSet() {
        return resourceSet;
    }

    public Package[] getTopLevelPackages(EClass packageClass) {
        if (packageClass == null)
            packageClass = PACKAGE.getPackage();
        List<Resource> results = resourceSet.getResources();
        List<Package> result = new ArrayList<Package>(results.size());
        for (Resource currentResource : results)
            for (EObject element : currentResource.getContents())
                if (packageClass.isInstance(element))
                    result.add((Package) element);
        return result.toArray(new Package[result.size()]);
    }

    /**
     * Overridable factory method for ResourceSet. Can be used to customize
     * ResourceSet, e.g. for standalone uses.
     * 
     * @return the ResourceSet
     */
    protected ResourceSet newResourceSet() {
        return new ResourceSetImpl();
    }

    private void init(boolean systemPackages) {
        resourceSet = newResourceSet();
        resourceSet.eSetDeliver(false);
        if (systemPackages)
            loadSystemPackages();
    }

    /**
     * Finds a named element given its fully qualified name and class.
     * <p>
     * Ensures the resource containing the element is loaded.
     * </p>
     * 
     * @param qualifiedName
     * @param eClass
     * @return
     */
    private NamedElement internalFindNamedElement(String qualifiedName, EClass eClass) {
        Assert.isNotNull(qualifiedName);
        // make sure we loaded the resource for the model potentially containing
        // the element to be found
        if (MDDUtil.isQualifiedName(qualifiedName)) {
            String firstSegment = MDDUtil.getFirstSegment(qualifiedName);
            Package package_ = loadPackage(firstSegment);
            if (package_ == null) {
                // try an alias
                String resolvedAlias = resolveAlias(firstSegment);
                if (firstSegment.equals(resolvedAlias))
                    // no alias
                    return null;
                package_ = loadPackage(resolvedAlias);
                if (package_ == null)
                    // could not find the root package
                    return null;
            }
        }
        Collection<NamedElement> found = internalFindNamedElements(qualifiedName, eClass);
        return found.isEmpty() ? null : found.iterator().next();
    }

    /**
     * Straightforward name-based element lookup.
     */
    private Collection<NamedElement> internalFindNamedElements(String qualifiedName, EClass eClass) {
        qualifiedName = resolveAlias(qualifiedName);
        Collection<NamedElement> found = UMLUtil.findNamedElements(resourceSet, qualifiedName, false, eClass);
        return filterByQualifiedName(qualifiedName, found);
    }

    private Collection<NamedElement> filterByQualifiedName(String qualifiedName, Collection<NamedElement> toFilter) {
        List<NamedElement> matching = new ArrayList<NamedElement>(toFilter.size());
        for (NamedElement current : toFilter)
            if (qualifiedName.equals(current.getQualifiedName()))
                matching.add(current);
        return matching;
    }

    private boolean isManaged(Resource resource) {
        URI resourceURI = resource.getURI();
        if (resourceURI.isRelative())
            resourceURI = resourceURI.resolve(baseURI);
        return resourceURI.toString().startsWith(baseURI.toString());
    }

    private boolean isSystemResource(Resource resource) {
        return systemResources.contains(resource);
    }

    private <T extends NamedElement> boolean isVisible(Namespace current, T element, boolean fullyQualified) {
        // TODO support protected and package visibility
        if (PACKAGE.getPackage().isInstance(element))
            return true;
        Package elementPackage = element.getNearestPackage();
        if (elementPackage == current.getNearestPackage())
            return true;
        if (fullyQualified)
            return element.getVisibility() == VisibilityKind.PUBLIC_LITERAL;
        List<Package> importedPackages = current.getImportedPackages();
        if (importedPackages.contains(elementPackage))
            return true;
        return current instanceof Package && ((Package) current).visibleMembers().contains(element);
    }

    private void load() throws CoreException {
        IFileStore baseStore = getBaseStore();
        loadFrom(baseStore);
    }

    private void loadFrom(IFileStore store) throws CoreException {
        if (!store.fetchInfo().exists())
            return;
        if (store.fetchInfo().isDirectory())
            for (IFileStore current : store.childStores(EFS.NONE, null))
                loadFrom(current);
        else if (store.getName().endsWith(".uml"))
            this.loadPackage(URI.createURI(store.toURI().toString()));
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.abstratt.mdd.core.IRepository#loadPackage(java.lang.String)
     */
    public Package loadPackage(String packageName) {
        Assert.isNotNull(packageName);
        // tries first to resolve to a built-in package
        Package builtIn = findPackage(packageName, PACKAGE.getPackage());
        if (builtIn != null)
            return builtIn;
        for (Package topPackage : getTopLevelPackages(null))
            for (Package imported : topPackage.getImportedPackages())
                if (imported.getName() != null && imported.getName().equals(packageName))
                    return loadPackage(URI.createURI(imported.getURI()));
        return loadPackage(computePackageURI(packageName));
    }

    private Package getRootPackage(Package current) {
    	if (current.eContainer() instanceof Package)
    		return getRootPackage((Package) current.eContainer());
		return current;
	}

	/*
     * (non-Javadoc)
     * 
     * @see
     * com.abstratt.mdd.core.IRepository#loadPackage(org.eclipse.emf.common.
     * util.URI)
     */
    public Package loadPackage(URI packageURI) {
        // tries first to resolve to a built-in package
        String packageName = packageURI.trimFileExtension().lastSegment();
        if (packageName == null)
            return null;
        Package builtIn = findPackage(packageName, PACKAGE.getPackage());
        if (builtIn != null)
            return builtIn;
        return getPackage(packageURI, true);
    }

    private void loadSystemPackages() {
        systemResources.clear();
        IExtensionPoint xp = RegistryFactory.getRegistry().getExtensionPoint(MDDCore.PLUGIN_ID, "systemPackage");
        IConfigurationElement[] configElems = xp.getConfigurationElements();
        for (int i = 0; i < configElems.length; i++) {
            boolean autoLoad = !Boolean.FALSE.toString().equals(configElems[i].getAttribute("autoLoad"));
            if (!autoLoad)
                continue;
            String uriValue = configElems[i].getAttribute("uri");
            URI uri = URI.createURI(uriValue);
            String requirements = configElems[i].getAttribute("requires");
            if (requirements != null) {
                String[] propertyEntries = requirements.split(",");
                if (propertyEntries.length > 0) {
                    boolean satisfied = true;
                    for (String current : propertyEntries) {
                    	String[] components = StringUtils.split(current, '=');
                    	String propertyName = components[0];
                    	String expectedValue = components.length > 1 ? components[1] : Boolean.TRUE.toString();
                        String actualValue = properties.getProperty(propertyName, System.getProperty(current));
						if (!expectedValue.equals(actualValue)) {
                            satisfied = false;
                            break;
                        }
                    }
                    if (!satisfied) {
                    	LogUtils.debug(MDDCore.PLUGIN_ID, () -> "System package skipped: " + uri);
                        continue;
                    }
                }
            }
            try {
                boolean lazyLoad = !Boolean.FALSE.toString().equals(configElems[i].getAttribute("lazyLoad"));
                Package systemPackage = getPackage(uri, lazyLoad);
                if (systemPackage != null) {
                    addSystemPackage(systemPackage);
                    LogUtils.debug(MDDCore.PLUGIN_ID, "Loading system package: " + uri);
                } else {
                	LogUtils.logError(MDDCore.PLUGIN_ID, "System package not found: " + uri, null);
                }
            } catch (WrappedException e) {
                if (!(e instanceof Diagnostic))
                    throw e;
                LogUtils.logError(MDDCore.PLUGIN_ID, "Unexpected  exception loading system package: " + uri, e);
            }
        }
    }

    public void removePackage(String name) {
        Assert.isNotNull(name);
        URI uri = computePackageURI(name);
        Package found = getPackage(uri, false);
        if (found == null)
            return;
        if (isSystemResource(found.eResource()))
            throw new IllegalArgumentException("Cannot remove system package: " + uri);
        Resource resource = resourceSet.getResource(uri, false);
        if (resource != null) {
            resource.unload();
            resourceSet.getResources().remove(resource);
        }
    }

    @Override
    public <R, E extends Throwable> R buildRepository(ISharedContextRunnable<IRepository, R> runnable,
            IProgressMonitor monitor) throws CoreException {
        IRepository previousRepository = inProgressRepository.get();
        Assert.isTrue(previousRepository == null, "Another repository being built in context");
        inProgressRepository.set(this);
        try {
            R result = runnable.runInContext(this);
            save(monitor);
            if (RepositoryService.ENABLED && RepositoryService.DEFAULT.isInSession())
                RepositoryService.DEFAULT.synchronizeCurrent();
            return result;
        } finally {
            inProgressRepository.remove();
        }
    }

    public void save(IProgressMonitor monitor) throws CoreException {
        if (monitor == null)
            monitor = new NullProgressMonitor();
        monitor.beginTask("Saving repository", resourceSet.getResources().size());
        try {
            getBaseStore().mkdir(EFS.NONE, null);

            for (Resource resource : resourceSet.getResources()) {
                if (!isManaged(resource))
                    continue;
                if (resource.getContents().isEmpty())
                    // EMF bug 225939 - our ticket 166
                    continue;
                // tries to ensure IDs are stable - does not work with method
                // names made of symbols
                if (resource instanceof XMIResource) {
                    for (TreeIterator<EObject> ti = resource.getAllContents(); ti.hasNext();) {
                        EObject current = ti.next();
                        String identifier = UML2Util.getXMIIdentifier((InternalEObject) current);
                        ((XMIResource) resource).setID(current, identifier);
                    }
                }
            }

            for (Resource resource : resourceSet.getResources()) {
                monitor.worked(1);
                if (!isManaged(resource))
                    continue;
                if (resource.getContents().isEmpty())
                    // EMF bug 225939 - our ticket 166
                    continue;
                try {
                    basicSaveResource(resource);
                } catch (IOException e) {
                    throw new CoreException(new Status(IStatus.ERROR, MDDCore.PLUGIN_ID, 0, "Error saving resource '"
                            + resource.getURI() + "'", e));
                }
            }
        } finally {
            monitor.done();
        }
    }

    public void storeModels(OutputStream out) throws IOException {
        for (Resource current : resourceSet.getResources())
            if (isManaged(current))
                current.save(out, null);
    }

    @Override
    public String toString() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            storeModels(baos);
            return new String(baos.toByteArray());
        } catch (IOException e) {
            return e.toString();
        }
    }

    public Properties getProperties() {
        return properties;
    }

    private void initWeaver() {
        String weaverName = getProperties().getProperty(WEAVER);
        IExtensionPoint point = RegistryFactory.getRegistry().getExtensionPoint(MDDCore.PLUGIN_ID, "modelWeaver");
        IConfigurationElement[] elements = point.getConfigurationElements();
        for (IConfigurationElement current : elements)
            if (weaverName.equals(current.getAttribute("name"))) {
                try {
                    weaver = (IModelWeaver) current.createExecutableExtension("class");
                } catch (CoreException e) {
                    LogUtils.logError(MDDCore.PLUGIN_ID, "Could not instantiate weaver: " + weaverName, e);
                }
                return;
            }
    }

    public IModelWeaver getWeaver() {
        if (weaver != null || !getProperties().containsKey(WEAVER))
            return weaver;
        initWeaver();
        return weaver;
    }
}