//------------------------------------------------------------------------------
// Copyright (c) 2005, 2006 IBM Corporation and others.
// 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:
// IBM Corporation - initial implementation
//------------------------------------------------------------------------------
package org.eclipse.epf.persistence;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.common.CommonPlugin;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.InternalEList;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceImpl;
import org.eclipse.epf.common.serviceability.MsgBox;
import org.eclipse.epf.common.utils.StrUtil;
import org.eclipse.epf.persistence.refresh.RefreshJob;
import org.eclipse.epf.persistence.util.PersistenceResources;
import org.eclipse.epf.resourcemanager.ResourceDescriptor;
import org.eclipse.epf.resourcemanager.ResourceManager;
import org.eclipse.epf.resourcemanager.ResourcemanagerFactory;
import org.eclipse.epf.uma.BreakdownElementDescription;
import org.eclipse.epf.uma.CapabilityPattern;
import org.eclipse.epf.uma.ContentDescription;
import org.eclipse.epf.uma.DeliveryProcess;
import org.eclipse.epf.uma.MethodConfiguration;
import org.eclipse.epf.uma.MethodElement;
import org.eclipse.epf.uma.MethodLibrary;
import org.eclipse.epf.uma.MethodPlugin;
import org.eclipse.epf.uma.MethodUnit;
import org.eclipse.epf.uma.ProcessComponent;
import org.eclipse.epf.uma.ecore.impl.MultiResourceEObject;
import org.eclipse.epf.uma.util.UmaUtil;
import org.eclipse.osgi.util.NLS;

import com.ibm.icu.text.SimpleDateFormat;
import com.ibm.icu.util.Calendar;

/**
 * @author Phong Nguyen Le
 * @since 1.0
 */
public final class MultiFileSaveUtil {

	public static final String DEFAULT_MODEL_FILENAME = "model.xmi"; //$NON-NLS-1$

	public static final String DEFAULT_CONTENT_FILENAME = "content.xmi"; //$NON-NLS-1$

	public static final String DEFAULT_LIBRARY_MODEL_FILENAME = "library.xmi"; //$NON-NLS-1$

	public static final String DEFAULT_PLUGIN_MODEL_FILENAME = "plugin.xmi"; //$NON-NLS-1$

	public static final String DEFAULT_FILE_EXTENSION = ".xmi"; //$NON-NLS-1$

	public static final String LIBRARY_FILE_EXTENSION = "uma"; //$NON-NLS-1$

	public static final String CAPABILITY_PATTERN_PATH = "capabilitypatterns"; //$NON-NLS-1$

	public static final String DELIVERY_PROCESS_PATH = "deliveryprocesses"; //$NON-NLS-1$

	public static final String PROCESS_CONTRIBUTION_PATH = "processcontributions"; //$NON-NLS-1$

	public static final String METHOD_CONFIGURATION_FOLDER_NAME = "configurations"; //$NON-NLS-1$

	public static final boolean DEBUG = PersistencePlugin.getDefault()
			.isDebugging();

	static final boolean PROFILE = false;

	private static final SimpleDateFormat dateFormatter = new SimpleDateFormat(
			"yyMMddHHmmss.S"); //$NON-NLS-1$

	public static boolean isValidFileName(String name) {
		if (name == null)
			return false;
		if (name.indexOf('/') != -1 || name.indexOf('\\') != -1
				|| name.indexOf(':') != -1 || name.indexOf('*') != -1
				|| name.indexOf('?') != -1 || name.indexOf('"') != -1
				|| name.indexOf('<') != -1 || name.indexOf('>') != -1
				|| name.indexOf('|') != -1)
			return false;
		return true;
	}

	public static String getPath(MethodElement e) {
		StringBuffer strBuff = new StringBuffer(String.valueOf(e.getName()));
		for (e = (MethodElement) ((EObject) e).eContainer(); e != null; e = (MethodElement) ((EObject) e)
				.eContainer()) {
			strBuff.insert(0, String.valueOf(e.getName()) + " > "); //$NON-NLS-1$
		}
		return strBuff.toString();
	}

	public static String createDirName(MethodElement obj) {
		String name = ((MethodElement) obj).getName();
		if (isValidFileName(name))
			return name;
		throw new MultiFileIOException(NLS.bind(
				PersistenceResources.invalidNameError_msg, name), obj);
	}

	public static EObject resolve(EObject proxy) {
		if (!(proxy instanceof InternalEObject))
			return proxy;
		InternalEObject obj = (InternalEObject) proxy;
		if (obj.eProxyURI() == null)
			return proxy;
		XMIResourceImpl res = new XMIResourceImpl(obj.eProxyURI());
		try {
			res.load(null);
			return getMethodElement(res);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return proxy;
	}

	static Resource save(EObject o, URI uri, Map options) {
		ResourceSet resourceSet = o.eResource().getResourceSet();
		MultiFileXMIResourceImpl resource = save(resourceSet, o, uri, options);

		if (options == null) {
			options = ((MultiFileResourceSetImpl) resourceSet)
					.getDefaultSaveOptions();
		}
		String str = (String) options
				.get(MultiFileXMISaveImpl.REFRESH_NEW_RESOURCE);
		if (str != null && Boolean.valueOf(str).booleanValue()) {
			// notify RefreshJob the this resource is saved so it will not be
			// reloaded after refreshing it
			//
			RefreshJob.getInstance().resourceSaved(resource);

			// refresh the newly created resource so it is in synch with the
			// workspace
			//
			FileManager.getInstance().refresh(resource);
		}

		return resource;
	}

	public static boolean canSaveTogether(Map options, Object obj) {
		Set saveTogetherClasses = (Set) options
				.get(MultiFileXMISaveImpl.SAVE_TOGETHER_CLASS_SET);
		if (saveTogetherClasses == null)
			return false;
		for (Iterator iter = saveTogetherClasses.iterator(); iter.hasNext();) {
			EClass eCls = (EClass) iter.next();
			if (eCls.isInstance(obj))
				return true;
		}
		return false;
	}

	/**
	 * Adds the given object to the given resource while still preserving
	 * object's container reference.
	 * 
	 * @param resource
	 * @param o
	 */
	private static void addTo(Resource resource, MultiResourceEObject o) {
		BasicEList contents = ((BasicEList) resource.getContents());
		if (contents.isEmpty()) {
			// this will flag resource as loaded
			//
			contents.clear();

			contents.setData(1, new Object[] { o });
		} else {
			Object[] data = contents.toArray();
			Object[] newData = new Object[data.length + 1];
			System.arraycopy(data, 0, newData, 0, data.length);
			newData[data.length] = o;
			contents.setData(newData.length, newData);
		}
		o.eSetResource((Resource.Internal) resource);
	}

	/**
	 * Saves a new contained EObject in its own new resource.
	 * 
	 * @param o
	 * @param uri
	 * @param options
	 */
	private static MultiFileXMIResourceImpl save(ResourceSet resourceSet,
			EObject o, URI uri, Map options) {
		return save(resourceSet, o, uri, options, true);
	}
	
	static MultiFileXMIResourceImpl save(ResourceSet resourceSet,
			EObject o, URI uri, Map options, boolean registerWithResourceManager) {
		// Detach the object from container resource
		//
		MultiResourceEObject mrEObj = (MultiResourceEObject) o;
		MultiFileXMIResourceImpl currentResource = (MultiFileXMIResourceImpl) o
				.eResource();

		if (currentResource != null) {
			currentResource.detached(o);
		}

		List newResourceDescriptors = null;
		Set modifiedResources = (Set) options
				.get(MultiFileXMISaveImpl.MODIFIED_RESOURCE_SET);

		MultiFileXMIResourceImpl res = null;
		if (canSaveTogether(options, o)) {
			MultiFileResourceSetImpl mfResourceSet = ((MultiFileResourceSetImpl) resourceSet);

			// if the uri has a temporary URI, use it instead to locate the
			// resource that is in process of saving
			//
			URI tempURI = (URI) mfResourceSet.getURIToTempURIMap().get(uri);
			if (tempURI != null) {
				uri = tempURI;
			}

			res = (MultiFileXMIResourceImpl) mfResourceSet.getResource(uri);
			if (res != null) {
				if (!res.getContents().contains(o)) {
					if (res.getFinalURI() == res.getURI()) {
						if (MultiFileXMISaveImpl.checkModifyRequired(options)) {
							// not a temp file of fail-safe persistence
							// transaction
							// check for writeable
							//
							MultiFileSaveUtil.checkModify(res);
						}
					}

					addTo(res, mrEObj);

					ResourceDescriptor desc = null;
					try {
						if(registerWithResourceManager) {
							// register this new object with ResourceManager
							//
							ResourceManager resMgr = getResourceManagerFor(o,
									modifiedResources);
							desc = registerWithResourceManager(resMgr, o, res
									.getFinalURI());
							if (desc != null && modifiedResources != null) {
								modifiedResources.add(resMgr.eResource());
							}
						}

						res.setModified(true);
						mfResourceSet.save(res, options, false);

						res.attachedAll(o);
					} catch (Exception e) {
						// rollback
						//

						// remove this object from the resource
						res.getContents().remove(mrEObj);
						mrEObj.eSetResource((Resource.Internal) null);

						// remove the created ResourceDescriptor
						if (desc != null) {
							EcoreUtil.remove(desc);
						}

						// re-attach to container resource
						if (currentResource != null) {
							currentResource.attached(o);
						}

						throw new MultiFileIOException(e.getMessage());
					}
				}
				return res;
			}
		}

		res = (MultiFileXMIResourceImpl) resourceSet.createResource(uri);
		addTo(res, mrEObj);

		Map objToContainerMap = null;
		try {
			if(registerWithResourceManager) {
				newResourceDescriptors = registerWithResourceManager(res,
					modifiedResources);
			}

			// back up the container references and set content object's
			// container to null
			//
			objToContainerMap = removeContainers(res);

			res.save(options);

			res.attachedAll(o);
		} catch (Exception e) {
			CommonPlugin.INSTANCE.log(e);

			// rollback
			//

			// remove the failed reousrce from the resource set
			resourceSet.getResources().remove(res);
			mrEObj.eSetResource((Resource.Internal) null);

			// remove the created ResourceDescriptors
			if(newResourceDescriptors != null && !newResourceDescriptors.isEmpty()) {
				for (Iterator iter = newResourceDescriptors.iterator(); iter
				.hasNext();) {
					EcoreUtil.remove((EObject) iter.next());
				}
			}

			// re-attach to container resource
			if (currentResource != null) {
				currentResource.attached(o);
			}

			throw new MultiFileIOException(e.getMessage());
		} finally {
			// restore the container references for the content objects
			//
			restoreContainers(res, objToContainerMap);
		}

		if(registerWithResourceManager) {
			ResourceManager resMgr = addNewResourceManager(res);
			if (resMgr == null) {
				if (o instanceof MethodPlugin) {
					// create new ResourceManager for new MethodPlugin
					//
					resMgr = getResourceManagerFor(o, modifiedResources);
				}
			} else if (modifiedResources != null) {
				modifiedResources.add(resMgr.eContainer().eResource());
			}
		}

		res.updateTimeStamps();

		return res;
	}

	private static Map removeContainers(Resource resource) {
		int size = resource.getContents().size();
		Map objToContainerMap = new HashMap();
		for (int i = 0; i < size; i++) {
			MultiResourceEObject multiResEObj = (MultiResourceEObject) resource
					.getContents().get(i);
			InternalEObject container = (InternalEObject) multiResEObj
					.eContainer();
			if (container != null) {
				objToContainerMap.put(multiResEObj, container);
				multiResEObj.eBasicSetContainer(null, multiResEObj
						.eContainerFeatureID());
			}
		}
		return objToContainerMap;
	}

	/**
	 * Gets the containers of objects in resource's contents
	 * 
	 * @param resource
	 * @return map of object to container entries
	 */
	static Map getContainers(Resource resource) {
		int size = resource.getContents().size();
		Map objToContainerMap = new HashMap();
		for (int i = 0; i < size; i++) {
			MultiResourceEObject multiResEObj = (MultiResourceEObject) resource
					.getContents().get(i);
			InternalEObject container = (InternalEObject) multiResEObj
					.eContainer();
			if (container != null) {
				objToContainerMap.put(multiResEObj, container);
			}
		}
		return objToContainerMap;

	}

	/**
	 * Restores the containers of the objects in resource's contents that
	 * previously saved in a map returned from getContainers(Resource resource)
	 * or removeContainers(Resource resource)
	 * 
	 * @param resource
	 * @param objToContainerMap
	 * @see #getContainers(Resource)
	 * @see #removeContainers(Resource)
	 */
	private static void restoreContainers(Resource resource,
			Map objToContainerMap) {
		int size = resource.getContents().size();
		for (int i = 0; i < size; i++) {
			MultiResourceEObject multiResEObj = (MultiResourceEObject) resource
					.getContents().get(i);
			InternalEObject container = (InternalEObject) objToContainerMap
					.get(multiResEObj);
			if (container != null) {
				multiResEObj.eBasicSetContainer(container, multiResEObj
						.eContainerFeatureID());
			}
		}
	}

	public static void checkModify(Resource resource) {
		// don't check if the resource is currently being saved to a temporary
		// file
		//
		if (resource instanceof MultiFileXMIResourceImpl
				&& ((MultiFileXMIResourceImpl) resource).hasTempURI()) {
			return;
		}
		String path = resource.getURI().toFileString();
		IStatus status = FileManager.getInstance().checkModify(path,
				MsgBox.getDefaultShell());
		if (!status.isOK()) {
			String msg = UmaUtil.getMessage(status);
			if (msg == null) {
				msg = NLS.bind(PersistenceResources.modifyFileError_msg, path);
			}
			throw new MultiFileIOException(msg);
		}
	}

	public static void checkModify(Collection resources) {
		String[] paths = new String[resources.size()];
		int i = 0;
		for (Iterator iter = resources.iterator(); iter.hasNext();) {
			Resource resource = (Resource) iter.next();
			paths[i++] = resource.getURI().toFileString();
		}
		IStatus status = FileManager.getInstance().checkModify(paths,
				MsgBox.getDefaultShell());
		if (!status.isOK()) {
			String msg = UmaUtil.getMessage(status);
			if (msg == null) {
				msg = NLS.bind(PersistenceResources.modifyFileError_msg, Arrays
						.asList(paths));
			}
			throw new MultiFileIOException(msg);
		}
	}

	public static void checkOutOfSynch(Collection resources) {
		// check for out-of-synch
		//
		for (Iterator iter = resources.iterator(); iter.hasNext();) {
			MultiFileXMIResourceImpl res = (MultiFileXMIResourceImpl) iter
					.next();
			long lastModified = new File(res.getURI().toFileString())
					.lastModified();
			if (res.getFileLastModified() != lastModified) {
				String msg = NLS.bind(
						PersistenceResources.resourceOutOfSynch_msg, res
								.getURI().toFileString());
				throw new MultiFileIOException(msg);
			}
		}
	}

	/**
	 * Saves the existing own resource of a contained EObject. Adds resources
	 * that have been modifed after this call to the MODIFIED_RESOURCE_SET of
	 * the save options.
	 * 
	 * @param resource
	 * @param options
	 */
	public static void save(Resource resource, Map options) {
		if (resource.getContents().isEmpty())
			return;

		Set modifiedResources = (Set) options
				.get(MultiFileXMISaveImpl.MODIFIED_RESOURCE_SET);

		// should not change resource location automatically without letting
		// user know about it
		//
		// adjustLocation(resource, modifiedResources);

		// back up the container references and set content object's container
		// to null
		//
		Map objToContainerMap = removeContainers(resource);
		try {
			resource.save(options);
		} catch (IOException e) {
			e.printStackTrace();
			throw new MultiFileIOException(e.getMessage());
		} finally {
			// restore the container references for the content objects
			//
			restoreContainers(resource, objToContainerMap);
		}

		ResourceManager resMgr = addNewResourceManager(resource);
		if (resMgr != null && modifiedResources != null) {
			modifiedResources.add(resMgr.eContainer().eResource());
		}
	}

	/**
	 * 
	 * @param resource
	 * @return ResourceManager that is just added to the resource manager tree
	 *         or null.
	 */
	private static ResourceManager addNewResourceManager(Resource resource) {
		ResourceManager resMgr = getResourceManager(resource);
		MethodElement e = getMethodElement(resource);
		EObject container = e.eContainer();
		if (resMgr != null && container != null && resMgr.eContainer() == null) {
			// new ResourceManager is added to the resource
			//
			ResourceManager parentResMgr = getResourceManager(container
					.eResource());

			// check if resMgr is already a sub manager of parentResMgr before
			// adding it
			//
			if (!parentResMgr.getSubManagers().contains(resMgr)) {
				parentResMgr.getSubManagers().add(resMgr);
				return resMgr;
			}

			// registerWithResourceManager(parentResMgr, resMgr,
			// resMgr.eResource().getURI().appendFragment(resMgr.getGuid()));
			// return resMgr;
		}
		return null;
	}

	private static String toFileString(EObject eObj) {
		return eObj.eResource().getResourceSet().getURIConverter().normalize(
				eObj.eResource().getURI()).toFileString();
	}

	static boolean hasOwnFolder(Object e) {
		return e instanceof MethodPlugin || e instanceof ProcessComponent;
	}

	static boolean hasOwnResource(Object obj, Collection saveSeparatelyClassSet) {
		if (obj instanceof MethodUnit)
			return true;
		if (saveSeparatelyClassSet == null)
			return false;
		for (Iterator iter = saveSeparatelyClassSet.iterator(); iter.hasNext();) {
			EClass eCls = (EClass) iter.next();
			if (eCls.isInstance(obj))
				return true;
		}
		return false;
	}

	static URI createFileURI(MethodElement e) {
		if (e.eContainer() == null)
			return ((MultiFileXMIResourceImpl) e.eResource()).getFinalURI();

		// Handle ProcessComponent specially. ProcessComponent objects are
		// stored as following in the method library:
		// <Method Library>
		// |_ <Method Plugin>
		// |_processes
		// |_capability_patterns
		// |_delivery_processes
		// |_process_contributions
		//
		if (e instanceof ProcessComponent) {
			MethodPlugin plugin = UmaUtil.getMethodPlugin(e);
			MethodLibrary lib = (MethodLibrary) plugin.eContainer();
			String pluginDir;
			if (lib != null) {
				String libDir = new File(((MultiFileXMIResourceImpl) lib
						.eResource()).getFinalURI().toFileString()).getParent();
				pluginDir = libDir + File.separator + plugin.getName();
			} else {
				if (plugin.eResource() == null) {
					// plugin is already deleted
					//
					return null;
				}
				pluginDir = new File(((MultiFileXMIResourceImpl) plugin
						.eResource()).getFinalURI().toFileString()).getParent();
			}
			String relativeDir;
			org.eclipse.epf.uma.Process proc = ((ProcessComponent) e)
					.getProcess();
			if (proc instanceof CapabilityPattern) {
				relativeDir = CAPABILITY_PATTERN_PATH;
			} else if (proc instanceof DeliveryProcess) {
				relativeDir = DELIVERY_PROCESS_PATH;
			} else {
				relativeDir = ""; //$NON-NLS-1$
			}

			String path = pluginDir + File.separator + relativeDir
					+ File.separator + e.getName() + File.separator
					+ DEFAULT_MODEL_FILENAME;
			return URI.createFileURI(path);
		} else if (e instanceof BreakdownElementDescription) {
			String dir = null;
			try {
				dir = new File(((MultiFileXMIResourceImpl) UmaUtil
						.getProcessComponent(e).eResource()).getFinalURI()
						.toFileString()).getParent();
			} catch (RuntimeException ex) {
				throw ex;
			}
			return URI.createFileURI(dir + File.separator
					+ DEFAULT_CONTENT_FILENAME);
		} else if (e instanceof ContentDescription) {
			URI uri;
			ContentDescription content = (ContentDescription) e;
			String path = MethodLibraryPersister.getCorrectPath(content);
			if (path == null) {
				String dir = ((MultiFileXMIResourceImpl) e.eResource())
						.getFinalURI().trimSegments(1).toFileString()
						+ File.separator;
				path = MethodLibraryPersister.getNextAvailableFileName(dir,
						content);
			}
			uri = URI.createFileURI(path);
			return uri;
		} else if (e instanceof MethodConfiguration) {
			String dir = new StringBuffer(((MultiFileXMIResourceImpl) e
					.eContainer().eResource()).getFinalURI().trimSegments(1)
					.toFileString()).append(File.separator).append(
					METHOD_CONFIGURATION_FOLDER_NAME).append(File.separator)
					.toString();
			String path = MethodLibraryPersister.getNextAvailableFileName(dir,
					StrUtil.makeValidFileName(e.getName()),
					(MultiResourceEObject) e);
			return URI.createFileURI(path);
		}

		StringBuffer path = new StringBuffer();
		EObject lastContainer = null;
		for (MethodElement obj = (MethodElement) e.eContainer(); obj != null; obj = (MethodElement) obj
				.eContainer()) {
			lastContainer = obj;
			if (obj instanceof MethodLibrary) {
				path.insert(0, new File(((MultiFileXMIResourceImpl) obj
						.eResource()).getFinalURI().toFileString())
						.getParentFile().getAbsolutePath());
				break;
			} else {
				path.insert(0, obj.getName()).insert(0, File.separatorChar);
			}
		}
		if (lastContainer == null) {
			return e.eResource() != null ? ((MultiFileXMIResourceImpl) e
					.eResource()).getFinalURI() : null;
		}
		if (!(lastContainer instanceof MethodLibrary)) {
			path.insert(0, new File(toFileString(lastContainer))
					.getParentFile().getParentFile().getAbsolutePath());
		}

		String modelFileName;
		if (e instanceof MethodPlugin) {
			modelFileName = DEFAULT_PLUGIN_MODEL_FILENAME;
		} else {
			modelFileName = MultiFileSaveUtil.DEFAULT_MODEL_FILENAME;
		}
		URI uri = URI.createFileURI(path.toString() + File.separator
				+ MultiFileSaveUtil.createDirName(e) + File.separator
				+ modelFileName);
		return uri;
	}

	public static void delete(File file) {
		File[] files = file.listFiles();
		if (files != null) {
			for (int i = 0; i < files.length; i++) {
				delete(files[i]);
			}
		}
		file.delete();
	}

	/**
	 * 
	 * @param e
	 * @param uri
	 * @param modifiedResources
	 *            output of resources that have been changed after this call.
	 */
	static void setURIMapping(EObject e, URI uri, Set modifiedResources) {
		MultiFileURIConverter uriConverter = (MultiFileURIConverter) e
				.eResource().getResourceSet().getURIConverter();
		uriConverter.setURIMapping(e, uri, modifiedResources);
	}

	/**
	 * 
	 * @param resource
	 * @param modifiedResources
	 *            output of resources that have been changed after this call
	 */
	static void updateURIMappings(MultiFileXMIResourceImpl resource,
			Set modifiedResources) {
		updateURIMappings(resource, modifiedResources, true);
	}

	/**
	 * Checks if the given resourceSet has the loaded resource with the given
	 * uri
	 * 
	 * @param resourceSet
	 * @param uri
	 * @return
	 */
	static boolean hasLoadedResource(ResourceSet resourceSet, URI uri) {
		for (Iterator iter = resourceSet.getResources().iterator(); iter
				.hasNext();) {
			Resource resource = (Resource) iter.next();
			if (resource.isLoaded() && resource.getURI().equals(uri)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Update the URIMappings with the final URI of the given resource
	 * 
	 * @param resource
	 */
	static void updateURIMappings(MultiFileXMIResourceImpl resource,
			Set modifiedResources, boolean afterMove) {
		updateURIMappings(resource, resource.getFinalURI(), modifiedResources,
				afterMove);
	}

	static void updateURIMappings(MultiFileXMIResourceImpl resource, URI uri,
			Set modifiedResources, boolean afterMove) {
		// the resource URI has been changed, reset all the cached resolved URI
		// in all offstring resource descriptors
		// of its manager, if it has one.
		//
		HashMap oldURIToResourceDescriptorMap = null;
		ResourceManager resMgr = getResourceManager(resource);
		if (resMgr != null) {
			oldURIToResourceDescriptorMap = new HashMap();
			for (Iterator iter = resMgr.eAllContents(); iter.hasNext();) {
				Object obj = iter.next();
				if (obj instanceof ResourceDescriptor) {
					ResourceDescriptor desc = ((ResourceDescriptor) obj);
					oldURIToResourceDescriptorMap.put(desc.getResolvedURI(),
							desc);
					if (afterMove) {
						desc.clearResolvedURI();
					}
				}
			}
		}

		if (!resource.getContents().isEmpty()) {
			EObject element = getMethodElement(resource);
			// setURIMapping(element, resource.getFinalURI(),
			// modifiedResources);
			MultiFileURIConverter uriConverter = (MultiFileURIConverter) resource
					.getResourceSet().getURIConverter();
			uriConverter.setURIMapping(element, uri, modifiedResources,
					afterMove);
		}

		// for (Iterator iter = resource.getContents().iterator();
		// iter.hasNext();) {
		// EObject element = (EObject) iter.next();
		// setURIMapping(element, resource.getURI(), modifiedResources);
		// }

		if (oldURIToResourceDescriptorMap != null) {
			// go thru the list of loaded resources in resource set to update
			// the URI
			//
			for (Iterator iter = resource.getResourceSet().getResources()
					.iterator(); iter.hasNext();) {
				Resource res = (Resource) iter.next();
				ResourceDescriptor desc = (ResourceDescriptor) oldURIToResourceDescriptorMap
						.get(res.getURI());
				if (desc != null) {
					if (afterMove) {
						res.setURI(desc.getResolvedURI());
					} else if (res.isLoaded()) {
						desc.clearResolvedURI();
						res.setURI(desc.getResolvedURI());
					}
				}
			}
		}
	}

	/**
	 * @param resMgr
	 * @param o
	 * @param uri
	 */
	// private static void setUri(ResourceManager resMgr, MethodElement e, URI
	// uri) {
	// for (Iterator iter = resMgr.getResourceDescriptors().iterator();
	// iter.hasNext();) {
	// ResourceDescriptor resDesc = (ResourceDescriptor) iter.next();
	// if(resDesc.getId().equals(e.getGuid())) {
	// // change other URIs that are changed as result of this URI change
	// //
	// if(uri.fragment() == null) {
	// URI oldDir = URI.createFileURI(new
	// File(resDesc.getResolvedURI().toFileString()).getParent() +
	// File.separator);
	// URI newDir = URI.createFileURI(new File(uri.toFileString()).getParent() +
	// File.separator);
	// for (Iterator iterator = resMgr.getResourceDescriptors().iterator();
	// iterator
	// .hasNext();) {
	// ResourceDescriptor element = (ResourceDescriptor) iterator.next();
	// URI currentUri = element.getResolvedURI();
	// URI newUri = currentUri.replacePrefix(oldDir, newDir);
	// if(newUri != null) {
	// element.setResolvedURI(newUri);
	// }
	// }
	// }
	// resDesc.setResolvedURI(uri);
	// return;
	// }
	// }
	// ResourceDescriptor resDesc =
	// ResourcemanagerFactory.eINSTANCE.createResourceDescriptor();
	// resDesc.setId(e.getGuid());
	// resDesc.setResolvedURI(uri);
	// resMgr.getResourceDescriptors().add(resDesc);
	// }
	/**
	 * Gets the right ResourceManager for the given EObject, creates new
	 * ResourceManager if it does not exist yet.
	 * 
	 * @param modifiedResources
	 *            output of resources that have been changed after this call.
	 */
	static ResourceManager getResourceManagerFor(EObject eObj,
			Set modifiedResources) {
		Resource resource = eObj.eContainer() != null ? eObj.eContainer()
				.eResource() : eObj.eResource();
		ResourceManager resMgr = getResourceManager(resource);
		if (resMgr == null) {
			resMgr = ResourcemanagerFactory.eINSTANCE.createResourceManager();

			// add to beginning of the resource's contents
			//
			resource.getContents().add(0, resMgr);

			if (modifiedResources != null) {
				modifiedResources.add(resource);
			}
			EObject container = getContainerWithDirectResource((InternalEObject) eObj);
			if (container != null && container.eResource() != resource) {
				ResourceManager parentResMgr = getResourceManagerFor(container,
						modifiedResources);
				if (parentResMgr != null) {
					parentResMgr.getSubManagers().add(resMgr);
					if (modifiedResources != null) {
						modifiedResources.add(parentResMgr.eResource());
					}
				}
			}
		}

		return resMgr;

	}

	static List registerWithResourceManager(MultiFileXMIResourceImpl resource,
			Set modifiedResources) {
		ResourceManager resMgr = getResourceManagerFor(
				getMethodElement(resource), modifiedResources);

		List resourceDescriptors = new ArrayList();
		for (Iterator iter = resource.getContents().iterator(); iter.hasNext();) {
			Object element = iter.next();
			ResourceDescriptor resDesc = registerWithResourceManager(resMgr,
					element, resource.getFinalURI());
			if (resDesc != null) {
				resourceDescriptors.add(resDesc);
			}
		}
		if (!resourceDescriptors.isEmpty() && modifiedResources != null) {
			modifiedResources.add(resMgr.eResource());
		}
		return resourceDescriptors;
	}

	public static ResourceDescriptor registerWithResourceManager(
			ResourceManager resMgr, Object element, URI uri) {
		String guid = getGuid(element);
		if (resMgr.getResourceDescriptor(guid) == null) {
			return createResourceDescriptor(resMgr, guid, uri);
		} else {
			return null;
		}
	}

	// /**
	// * Creates a new ResourceDescriptor for the given MethodElement and add it
	// to the given ResourceManager
	// *
	// * @param e
	// * @return newly added ResourceDescriptor
	// */
	// private static ResourceDescriptor
	// createResourceDescriptor(ResourceManager resMgr, MethodElement e) {
	// return createResourceDescriptor(resMgr, e.getGuid(), createFileURI(e));
	// }

	/**
	 * Creates a new ResourceDescriptor with the given id and resolvedURI, then
	 * adds it to the given resMgr
	 * 
	 * @param resMgr
	 * @param id
	 * @param resolvedURI
	 * @return
	 */
	private static ResourceDescriptor createResourceDescriptor(
			ResourceManager resMgr, String id, URI resolvedURI) {
		ResourceDescriptor resDesc = ResourcemanagerFactory.eINSTANCE
				.createResourceDescriptor();
		resDesc.setId(id);
		resMgr.getResourceDescriptors().add(resDesc);
		resDesc.setResolvedURI(resolvedURI);
		return resDesc;
	}

	// static void setUri(MultiFileURIConverter uriConverter, MethodElement e,
	// URI uri) {
	// setUri(uriConverter, e, uri, false);
	// }

	// static void setUri(MultiFileURIConverter uriConverter, MethodElement e,
	// URI uri, boolean saveNow) {
	// ResourceManager resMgr = uriConverter.getResourceManager();
	// if(resMgr == null) {
	// resMgr = ResourcemanagerFactory.eINSTANCE.createResourceManager();
	// uriConverter.setResourceManager(resMgr);
	// }
	// setUri(resMgr, e, uri);
	// resMgr.eResource();
	// }

	/**
	 * @param element
	 */
	public static void setGuid(MethodElement e) {
		if (e.getGuid() == null || e.getGuid().trim().length() == 0) {
			e.setGuid(EcoreUtil.generateUUID());
		}
	}

	/**
	 * Saves the given MethodElement in its own file while still preserving the
	 * existing containment association if there is any
	 * 
	 * @param e
	 */
	public static void save(MethodElement e, Map options) {
		MultiFileXMIResourceImpl resource = (MultiFileXMIResourceImpl) e
				.eResource();
		MultiFileResourceSetImpl resourceSet = (MultiFileResourceSetImpl) resource
				.getResourceSet();
		if (options == null) {
			options = resourceSet.getDefaultSaveOptions();
		}
		EObject container = e.eContainer();

		Set modifiedResources = new HashSet();
		options.put(MultiFileXMISaveImpl.MODIFIED_RESOURCE_SET,
				modifiedResources);
		try {
			URI uri = MultiFileSaveUtil.createFileURI(e);
			boolean createResource = resource == null
					|| (container != null && resource == container.eResource());
			if (createResource) {
				MultiFileSaveUtil.save(e, uri, options);
			} else {
				URI oldUri = resource.getURI();
				if (!oldUri.equals(uri)) {

					// resource's location is changed.
					// move the resource, then change its URI if the resource
					// move is successful.
					//   
					if (FileManager.getInstance().move(oldUri.toFileString(),
							uri.toFileString())) {
						// MultiFileURIConverter uriConverter =
						// (MultiFileURIConverter)
						// resource.getResourceSet().getURIConverter();
						// MultiFileSaveUtil.setUri(uriConverter, e, uri);
						resource.setURI(uri);
						updateURIMappings(resource, modifiedResources);
					}
				}

				if (resource.isModified()) {
					MultiFileSaveUtil.save(resource, options);
				}
			}

			resourceSet.saveModifiedResources(options);
		} finally {

		}

	}

	public static void save(ResourceSet resourceSet, MethodElement e,
			Map options) {
		URI uri = createFileURI(e);
		save(resourceSet, e, uri, options);
	}

	static void adjustLocation(Resource resource, Set modifiedResources) {
		MethodElement e = getMethodElement(resource);
		if (e == null)
			return;
		URI newFile = createFileURI(e);
		if (newFile != null && adjustLocation(resource, newFile)) {
			resource.setURI(newFile);
			updateURIMappings((MultiFileXMIResourceImpl) resource,
					modifiedResources);
		}
	}

	static URI getNewURI(MultiFileXMIResourceImpl resource) {
		MethodElement e = getMethodElement(resource);
		if (e == null) {
			return null;
		}
		URI newFile = createFileURI(e);
		if (newFile != null && adjustLocationRequired(resource, newFile)) {
			return newFile;
		}
		return null;
	}

	static boolean prepareAdjustLocation(MultiFileXMIResourceImpl resource,
			Set modifiedResources) {
		URI newFile = getNewURI(resource);
		if (newFile != null) {
			resource.backUpURI();
			resource.setFinalURI(newFile);
			updateURIMappings((MultiFileXMIResourceImpl) resource,
					modifiedResources, false);
			return true;
		}
		return false;
	}

	public static String getGuid(Object e) {
		if (e instanceof MethodElement) {
			return ((MethodElement) e).getGuid();
		} else if (e instanceof ResourceManager) {
			return ((ResourceManager) e).getGuid();
		}
		return null;
	}

	static boolean adjustLocationRequired(MultiFileXMIResourceImpl resource,
			URI newURI) {
		File oldFile = new File(resource.getFinalURI().toFileString());
		File newFile = new File(newURI.toFileString());
		return !oldFile.equals(newFile);
	}

	/**
	 * Checks if the resource's URI has been changed and adjust the resource's
	 * location.
	 * 
	 * @param oldUri
	 * @param newUri
	 */
	static boolean adjustLocation(Resource resource, URI newUri) {
		File oldFile = new File(resource.getURI().toFileString());
		File newFile = new File(newUri.toFileString());
		if (oldFile.equals(newFile)) {
			return false;
		}
		return move(resource, oldFile, newFile);
	}

	static boolean move(Resource resource, File oldFile, File newFile) {
		boolean ret = true;
		if (oldFile.equals(newFile)) {
			return false;
		}
		Object obj = getMethodElement(resource);
		if (hasOwnFolder(obj)) {
			String oldDir = oldFile.getParentFile().toString();
			if (!oldFile.getParentFile().equals(newFile.getParentFile())) {
				if (FileManager.getInstance().rename(oldFile.getParentFile(),
						newFile.getParentFile())) {
					if (DEBUG) {
						System.out
								.println("Directory '" + oldDir + "' is successfully moved to '" + newFile.getParentFile() + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					}
				} else {
					ret = false;
					if (DEBUG) {
						System.out
								.println("Could not move directory '" + oldDir + "' to '" + newFile.getParentFile() + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					}
				}
			}
			if (!oldFile.getName().equals(newFile.getName())) {
				oldFile = new File(newFile.getParentFile(), oldFile.getName());
				if (FileManager.getInstance().rename(oldFile, newFile)) {
					if (DEBUG) {
						System.out
								.println("File '" + oldFile + "' is successfully moved to '" + newFile + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					}
				} else {
					ret = false;
					if (DEBUG) {
						System.out
								.println("Could not move file '" + oldFile + "' to '" + newFile + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					}
				}
			}
			return ret;
		} else if (oldFile.exists()) /* if(oldFile.getParentFile().equals(newFile.getParentFile())) */{
			String oldFileStr = oldFile.toString();
			if (FileManager.getInstance().rename(oldFile, newFile)) {
				if (DEBUG) {
					System.out
							.println("File '" + oldFileStr + "' is successfully moved to '" + newFile + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				}
				return true;
			} else {
				if (DEBUG) {
					System.out
							.println("Could not move file '" + oldFileStr + "' to '" + newFile + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				}
			}
		}
		return false;
	}

	/**
	 * 
	 * @param resMgr
	 * @param guid
	 * @param excludedResManagers
	 *            ResourceManager that are excluded from this search
	 * @return
	 */
	public static ResourceDescriptor findResourceDescriptor(
			ResourceManager resMgr, String guid,
			Collection excludedResManagers, boolean resolveProxy) {
		try {
			ResourceDescriptor desc = resMgr.getResourceDescriptor(guid);
			if (desc != null)
				return desc;
			// TODO: (Phong) performance improvement needed to avoid loading
			// resources excessively
			//
			Iterator iter;
			if (resolveProxy) {
				iter = new ArrayList(resMgr.getSubManagers()).iterator();
			} else {
				iter = ((InternalEList) resMgr.getSubManagers())
						.basicIterator();
			}
			while (iter.hasNext()) {
				ResourceManager mgr = (ResourceManager) iter.next();
				if (mgr != null
						&& !mgr.eIsProxy()
						&& (excludedResManagers == null || !excludedResManagers
								.contains(mgr))) {
					desc = findResourceDescriptor(mgr, guid,
							excludedResManagers, resolveProxy);
					if (desc != null)
						return desc;
				}
			}
			return null;
		} catch (RuntimeException e) {
			throw e;
		}
	}

	/**
	 * 
	 * @param resMgr
	 * @return how many ResourceDescriptors are in the resMgr and its
	 *         SubManagers
	 */
	public static int getCountResourceDescriptors(ResourceManager resMgr) {
		int count = resMgr.getResourceDescriptors().size();
		for (Iterator iter = resMgr.getSubManagers().iterator(); iter.hasNext();) {
			ResourceManager mgr = (ResourceManager) iter.next();
			count += getCountResourceDescriptors(mgr);
		}
		return count;
	}

	/**
	 * Gets the resource descriptor for the given resource
	 * 
	 * @param resource
	 * @return
	 */
	public static ResourceDescriptor getResourceDescriptor(Resource resource) {
		ResourceManager containerResMgr = null;
		MethodElement me = null;
		for (Iterator iter = resource.getContents().iterator(); iter.hasNext();) {
			Object element = iter.next();
			if (element instanceof MethodLibrary) {
				ResourceManager resMgr = getResourceManager(resource);
				if (resMgr != null) {
					ResourceDescriptor desc = resMgr
							.getResourceDescriptor(((MethodElement) element)
									.getGuid());
					// hack to add missing ResourceDescriptor for library's
					// resource
					//
					if (desc == null) {
						desc = registerWithResourceManager(resMgr, element,
								((MultiFileXMIResourceImpl) resource)
										.getFinalURI());
					}
					return desc;
				}
			}
			if (me == null && element instanceof MethodElement) {
				me = (MethodElement) element;
			}
			if (containerResMgr == null && element instanceof InternalEObject) {
				EObject container = getContainerWithDirectResource((InternalEObject) element);
				if (container != null) {
					containerResMgr = getResourceManager(container.eResource());
				}
			}
			if (me != null && containerResMgr != null) {
				break;
			}
		}

		if (containerResMgr != null && me != null) {
			return containerResMgr.getResourceDescriptor(((MethodElement) me)
					.getGuid());
		}

		return null;
	}

	// public static ResourceDescriptor findResourceDescriptor(MethodElement e)
	// {
	// ResourceManager resMgr = null;
	// if(e.eResource() != null) {
	// resMgr = getResourceManager(e.eResource());
	// }
	// ResourceDescriptor desc = null;
	// Collection excludedResMgrs = null;
	// if(resMgr != null) {
	// desc = findResourceDescriptor(resMgr, e.getGuid());
	// if(desc != null) return desc;
	// excludedResMgrs = Collections.singletonList(resMgr);
	// }
	//		
	// MethodLibrary lib = UmaUtil.getMethodLibrary(e);
	// resMgr = getResourceManager(lib.eResource());
	//		
	// return findResourceDescriptor(resMgr, e.getGuid(), excludedResMgrs);
	// }

	/**
	 * Gets the ResourceManager in the contents of the given resource
	 * 
	 * @param resource
	 * @return
	 */
	public static ResourceManager getResourceManager(Resource resource) {
		ResourceManager resMgr = null;
		for (Iterator iter = resource.getContents().iterator(); iter.hasNext();) {
			Object element = (Object) iter.next();
			if (element instanceof ResourceManager) {
				resMgr = (ResourceManager) element;
				break;
			}
		}

		return resMgr;
	}

	static InternalEObject getContainerWithDirectResource(InternalEObject obj) {
		if (obj.eContainer() == null)
			return null;
		InternalEObject container = (InternalEObject) ((InternalEObject) obj
				.eContainer());
		if (container.eDirectResource() != null) {
			return container;
		}
		return getContainerWithDirectResource(container);
	}

	static String getHREF(Resource resource, Object obj) {
		MethodElement owner = getMethodElement(resource);
		return MultiFileURIConverter.createURI(owner.getGuid()).appendFragment(
				getGuid(obj)).toString();
	}

	public static MethodElement getMethodElement(Resource resource) {
		for (Iterator iter = resource.getContents().iterator(); iter.hasNext();) {
			Object element = iter.next();
			if (element instanceof MethodElement) {
				return (MethodElement) element;
			}
		}
		return null;
	}

	public static String getBackupFileSuffix() {
		return "_" + dateFormatter.format(Calendar.getInstance().getTime()); //$NON-NLS-1$
	}

	/**
	 * Resolve the proxy identified by the given <code>guid</code> without
	 * loading all other proxies in <code>parent</code>
	 * 
	 * @param guid
	 * @param parent
	 * @return
	 */
	public static void resolveProxies(Collection GUIDs, EObject parent) {
		for (Iterator iterator = parent.eClass().getEAllContainments()
				.iterator(); !GUIDs.isEmpty() && iterator.hasNext();) {
			EStructuralFeature feature = (EStructuralFeature) iterator.next();
			if (feature.isMany()) {
				InternalEList list = (InternalEList) parent.eGet(feature);
				int index = 0;
				for (Iterator iter1 = list.basicIterator(); !GUIDs.isEmpty()
						&& iter1.hasNext(); index++) {
					InternalEObject child = (InternalEObject) iter1.next();
					if (child.eIsProxy()) {
						String guid = child.eProxyURI().fragment();
						if (GUIDs.contains(guid)) {
							// Object resolved = list.get(index);
							// System.out.println("resolved: " + resolved);
							GUIDs.remove(guid);
						}
					} else {
						resolveProxies(GUIDs, child);
					}
				}
			} else {
				InternalEObject child = (InternalEObject) parent.eGet(feature,
						false);
				if (child != null) {
					if (child.eIsProxy()) {
						String guid = child.eProxyURI().fragment();
						if (GUIDs.contains(guid)) {
							// Object resolved = parent.eGet(feature);
							// System.out.println("resolved: " + resolved);
							GUIDs.remove(guid);
						}
					} else {
						resolveProxies(GUIDs, child);
					}
				}
			}
		}
	}

	public static URI getFinalURI(Resource resource) {
		return resource instanceof MultiFileXMIResourceImpl ? ((MultiFileXMIResourceImpl) resource)
				.getFinalURI()
				: resource.getURI();
	}

}