/*
 * $RCSfile: ModelBusResourceSet.java,v $
 * $Date: 2006/01/26 14:48:59 $
 * $Revision: 1.1.2.3 $
 * $Author: andreys $
 */
 
/*
 * Copyright (c) 2002-2003 IST-2004-2006-511731 ModelWare - ModelBus.
 * All rights reserved.
 *
 * This software is published under the terms of the ModelBus Software License
 * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * A copy of ModelBus Software License is provided with this distribution in
 * doc/LICENSE.txt file.
 */
 
/*
 * ModelBusResourceFactory.java Add description 
 * 
 * @author Prawee Sriplakich
 * @version $Revision: 1.1.2.3 $ $Date: 2006/01/26 14:48:59 $
 * @see Add references
 *
 * TODO Add description
 * TODO Add references
 */
package org.eclipse.mddi.modelbus.adapter.infrastructure.model_manipulation;

import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

import org.apache.log4j.Logger;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.impl.EcorePackageImpl;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.URIConverter;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.resource.impl.URIConverterImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
import org.eclipse.mddi.modelbus.adapter.infrastructure.LoggerConfigurator;
import org.eclipse.mddi.modelbus.adapter.infrastructure.model_manipulation.wrapper.emf.EmfModelWrapper;
import org.eclipse.mddi.modelbus.adapter.infrastructure.serialize.modelbus_xmi.MBXmiResourceFactory;

import org.eclipse.mddi.modelbus.description.impl.DescriptionPackageImpl;

/**
 * @author P. Sriplakich
 * 
 * ModelBusResourceSet enables the link resolutions between multiple-resources.
 * It uses GlobalResourceRegistry for resolving shared resources, such as profiles or metamodels.
 * 
 * ModelBusResourceSet resolve a URI for a resource with the following mechanism.
 * 1. It looks in the resources contained in this set. If found, return this resource.
 * 2. It looks in the Global Resource Set. If found, return this resource.
 * 3. return null; (Resource not found) 
 * 
 * 
 * When the resource can not be resolved, 
 * EMF will create proxy objects for the unresolved objects contained in this resource.
 * A proxy object contains the unresolved URI but has empty content. 
 * It only enables the user to navigate, but he can not read its properties.
 *  
 */
public class ModelBusResourceSet extends ResourceSetImpl  {
    

    static GlobalResourceRegistry globalResourceRegistry = new GlobalResourceRegistry();
    
    /**
     * This URI is used for creating a resource for "orphan" EObjects 
     * (objects that are not contained in any resource)
     */
    public static final URI MB_DEFAULT_RES_URI = URI.createURI("default_resource.modelbus");

    
    private static Logger logger = Logger.getLogger("ModelBusResourceSet");
    
    /** 
     *  Force the initialization of this class.
     */
    public static void init() {
    }
    
    static {        
        Map m = Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap();
        m.put("*", new XMIResourceFactoryImpl());    
        MBXmiResourceFactory.init();
        
        ModelWrapper.addRegistry("EMF", new EmfModelWrapper());
        
        try {
            // add Ecore model to global resource
            EcorePackage p = EcorePackageImpl.init();
            Resource r = p.eResource();
            logger.debug("Ecore model in Resource " + r);
            logger.debug("Ecore model in Resource " + r.getResourceSet());     
            
            getGlobalResourceRegistry().registerResource(r.getURI(), r);
                        
        } catch(Exception e) {
            logger.error("cannot bind Ecore model to GlobalRegistry", e);
        }
          
        DescriptionPackageImpl.init();
        LoggerConfigurator.configure();        
    }
    
  
    
    /**
     * @return Returns the globalResourceSet.
     */
    public static GlobalResourceRegistry getGlobalResourceRegistry() {
        return globalResourceRegistry;
    }
    
    /**
     * 
     * Register all Ecore packages in the specified resource
     * 
     * @param r
     * 
     *
     */
    public static void registerEPackages(Resource r) {
        for(Iterator it = r.getContents().iterator(); it.hasNext();) {
            Object o = it.next();
            if(o instanceof EPackage) {
                ModelBusResourceSet.registerEPackage((EPackage)o);                
            }
        }        
    }
    
    /**
     * 
     * register an EPackage so that it can be used for creating instance models
     * 
     * @param p
     * 
     *
     */
    public static void registerEPackage(EPackage p) {
        String uri = p.getNsURI();
        if(uri==null || "".equals(uri) ) {
            EPackage superp = p.getESuperPackage();
            if(superp==null) {
              uri = "http://model/"  + p.getName();
            } else {
              uri = superp.getNsURI() +"/" +p.getName();
            }
            p.setNsURI(uri);
        }
        EPackage registered = EPackage.Registry.INSTANCE.getEPackage(p.getNsURI());
        if(registered!=null && registered!=p) {
           logger.warn("The EPackage for NS URI "+ p.getNsURI() +" changed: " 
                   +registered +" -> " +p);
        }
        EPackage.Registry.INSTANCE.put(p.getNsURI(), p);
        for(Iterator it = p.getESubpackages().iterator(); it.hasNext(); ) {
            EPackage subp = (EPackage) it.next();
            registerEPackage(subp);
        }
    }    
    
    /**
     * 
     * check whether the specified EPackage is registered.
     * 
     * @param p
     * @return
     * 
     *
     */
    public static boolean isRegistered(EPackage p) {
        return EPackage.Registry.INSTANCE.containsValue(p);
    }
    
    
    public ModelBusResourceSet() {
        //logger.debug("created");
    } 
    
    
    
    /**
     * Override the method of ResourceImpl
     * 
     * Returns a resolved resource available outside of the resource set.
     * It is called by {@link #getResource(URI, boolean) getResource(URI, boolean)} 
     * after it has determined that the URI cannot be resolved 
     * based on the existing contents of the resource set.
     * This implementation looks up the URI in the {#getPackageRegistry() local} package registry.
     * Clients may extend this as appropriate.
     * @param uri the URI
     * @param loadOnDemand whether demand loading is required.
     */
    protected Resource delegatedGetResource(URI uri, boolean loadOnDemand) {
      EPackage ePackage = getPackageRegistry().getEPackage(uri.toString());
      if(ePackage != null) return ePackage.eResource();      
      Resource r = globalResourceRegistry.getResource(uri);      
      return r;
    }
    
    
    /**
     * This is a modification of the default implementation of ResourceImpl,
     * in order to take into account the cached resources in the global registry.
     * <br>
     * First, we try to find the resource in the global registry.
     * If found, return it (avoid creating a duplicated resource)
     * Otherwise, create a new resource.
     * If this resource has the global scheme URI, 
     * then register it with the gobal registry.
     */
    public Resource createResource(URI uri)
    {
      Resource result = globalResourceRegistry.getResource(uri);
      if(result!=null) return result;
      result = super.createResource(uri);
      if(result==null) return null;
      if(ModelBusResourceSet.hasGlobalURIScheme(uri)) {
        try {
            globalResourceRegistry.registerResource(uri, result);
        } catch (GlobalResourceException e) {
            logger.error("createResource", e);
        } 
      } 
      return result;
    }  
    
    /**
        * 
        * Check whether this URI has the global scheme (pathmap or http)
        */
       public static boolean hasGlobalURIScheme(URI uri) {
           if(GlobalResourceRegistry.GLOBAL_URI_SCHEME.equals(uri.scheme())) return true;
           if("http".equals(uri.scheme())) return true;
           return false;
       }

    public static class GlobalResourceRegistry {
        
       /**
        * The default scheme for global URI : pathmap
        */
       public static final String GLOBAL_URI_SCHEME = "pathmap";
        
       private URIConverter uriConverter = new URIConverterImpl();
       private Map resourceMap = new Hashtable();
       
       /**
        * Returns all resources managed by this Registry.
        * The returned collection only supports removal operation (no add).
        * 
        * @return
        * 
        */       
       public Collection getResources() {
           return resourceMap.values();
       }
       
       
       /**
        * 
        * Register a shared resource so that ModelBus can create links to it 
        * when deserializing a model referencing this resource. 
        * <br>
        * After registration, the URI of the resource will change to the speicified global URI.
        * <br>
        * If, before registration, the resource is associated to a local URI 
        * (e.g. file URI), this URI will become the Alias to the global URI.
        * <br>
        * If the current URI of the resource has global scheme,
        * you can not register the resource with another URI. 
        * Otherwise IllegalArgumentException will the thrown.
        * <br>
        * A resource can be registerred only once.
        * 
        * 
        * @param globalURI The gobal URI must have the scheme "pathmap" or "http" 
        * (i.e. "pathmap://....." or "http://..")
        * @param r
        * @throws IllegalArgumentException
        */
       public void registerResource(URI globalURI, Resource r) throws GlobalResourceException {
           if(!ModelBusResourceSet.hasGlobalURIScheme(globalURI) ) {
               throw new GlobalResourceException("Global URI must have scheme 'pathmap' or 'http'" );               
           }
           if( ModelBusResourceSet.hasGlobalURIScheme(r.getURI()) && !globalURI.equals(r.getURI())) {
               throw new GlobalResourceException("Resource already associated to " + r.getURI() );               
           }
           if(resourceMap.containsKey(globalURI)) {
               Resource boundResource = (Resource) resourceMap.get(globalURI);
               if(boundResource==r ) {
                 logger.debug("Duplicated registration (ignored): " + globalURI.toString());
                 return;
               } 
               throw new GlobalResourceException("Conflict registration: " 
                       +globalURI +" is already bound to " +boundResource 
                       +". Cannot rebind to "+ r );
           }
           if(resourceMap.containsValue(r)) {
               throw new GlobalResourceException("Conflict registration: " 
                       +r +" is already registered with a different URI."
                       +" Cannot rebind to "+ globalURI );
           }      
           resourceMap.put(globalURI, r);
           
           logger.debug("register uri: " +globalURI);
           
           // Set the Resource URI to the global one.
           // The global URI needs to be used when ModelBus serializes this resource.
           if(!globalURI.equals(r.getURI())) {
              uriConverter.getURIMap().put(r.getURI(), globalURI); 
              r.setURI(globalURI);
           }
       }
       
       
       /**
        * 
        * Find a registerred resource
        * 
        * @param uri This URI can be either the global URI of the resource or the alias URI.
        * @return
        * 
        */
       public Resource getResource(URI uri) {
           // If this uri is global URI
           // check whether the corresponding resource exist if yes -> return the resouce
           Resource r = null;
           if(ModelBusResourceSet.hasGlobalURIScheme(uri)) {
               r = (Resource) resourceMap.get(uri);
               if(r!=null) return r;
           }
           // resolve the alias URI to global URI
           URI normalizedURI = uriConverter.normalize(uri);
           r = (Resource) resourceMap.get(normalizedURI);
           if(r==null && ModelBusResourceSet.hasGlobalURIScheme(normalizedURI)) {
                logger.debug("resolve uri: " +uri +" -> " +r);
           }           
           return r;
       }  
       
       
       /**
        * 
        * URIConverter allows Resource aliasing.
        * <br>
        * It enables you to avoid multiple reloading of the same model 
        * that is stored in different copies (with different file names). 
        * <br><br>
        * For example, the XMI file of model M1 references SharedModel1 with the file URI "File1".
        * The XMI file of model M2 also references another copy of SharedModel1 with the file URI "File2".
        * You should set aliases File1->SharedModel1_global_URI and File2->SharedModel1_global_URI.
        * Consequently, when you ask ModelBus to load M1 and M2 (using ModelBusResourceSet),
        * ModelBus will resolve the differnet file URIs (M1->SharedModel1) (M2-SharedModel1) to the same SharedModel1 objects.
        * Note that, without aliasing, SharedModel1 will be loaded twice 
        * (once for resolving the reference from M1 and another time for M2)
        * <br><br>
        * To set the alias URI, uses getURIConverter().getURIMap().put(aliasURI, globalURI);
        * 
        * 
        */
        public URIConverter getURIConverter() {
           return uriConverter;
        }

    } // end class ModelBusResourceRegistry
    
    
    public static class GlobalResourceException extends Exception {
        /**
         * 
         */
        private static final long serialVersionUID = -4580625527117814989L;

        public GlobalResourceException(String message) {
            super(message);
        } 
    }
    
    
}
