//------------------------------------------------------------------------------
// 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.library.xmi;

import java.io.File;
import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EventObject;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.common.command.CommandStackListener;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.edit.command.AddCommand;
import org.eclipse.emf.edit.command.CreateChildCommand;
import org.eclipse.emf.edit.command.PasteFromClipboardCommand;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.INotifyChangedListener;
import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory;
import org.eclipse.epf.common.serviceability.DebugTrace;
import org.eclipse.epf.common.utils.FileUtil;
import org.eclipse.epf.library.ILibraryManager;
import org.eclipse.epf.library.InvalidMethodElementNameException;
import org.eclipse.epf.library.LibraryAlreadyExistsException;
import org.eclipse.epf.library.LibraryNotFoundException;
import org.eclipse.epf.library.LibraryPlugin;
import org.eclipse.epf.library.LibraryService;
import org.eclipse.epf.library.LibraryServiceException;
import org.eclipse.epf.library.edit.TngAdapterFactory;
import org.eclipse.epf.library.edit.command.IActionManager;
import org.eclipse.epf.library.edit.util.Suppression;
import org.eclipse.epf.library.events.ILibraryChangeListener;
import org.eclipse.epf.library.layout.LayoutResources;
import org.eclipse.epf.library.prefs.PreferenceConstants;
import org.eclipse.epf.library.project.MethodLibraryProject;
import org.eclipse.epf.library.services.LibraryModificationHelper;
import org.eclipse.epf.library.services.SafeUpdateController;
import org.eclipse.epf.library.util.LibraryUtil;
import org.eclipse.epf.library.util.ModelStorage;
import org.eclipse.epf.library.util.ResourceUtil;
import org.eclipse.epf.persistence.FileManager;
import org.eclipse.epf.persistence.MethodLibraryPersister;
import org.eclipse.epf.persistence.MultiFileResourceSetImpl;
import org.eclipse.epf.persistence.MultiFileSaveUtil;
import org.eclipse.epf.persistence.MultiFileXMISaveImpl;
import org.eclipse.epf.persistence.refresh.IRefreshEvent;
import org.eclipse.epf.persistence.refresh.IRefreshListener;
import org.eclipse.epf.persistence.refresh.RefreshJob;
import org.eclipse.epf.persistence.util.LibrarySchedulingRule;
import org.eclipse.epf.uma.MethodConfiguration;
import org.eclipse.epf.uma.MethodElement;
import org.eclipse.epf.uma.MethodLibrary;
import org.eclipse.epf.uma.MethodPackage;
import org.eclipse.epf.uma.MethodPlugin;
import org.eclipse.epf.uma.UmaPackage;
import org.eclipse.epf.uma.ecore.impl.MultiResourceEObject;
import org.eclipse.epf.uma.util.AssociationHelper;
import org.eclipse.epf.uma.util.ContentDescriptionFactory;
import org.eclipse.epf.uma.util.IMethodLibraryPersister;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.IPropertyListener;

/**
 * The default XMI Library Manager implementation.
 * 
 * @author Kelvin Low
 * @author Jinhua Xi
 * @author Phong Nguyen Le
 *   added code to updated cached elements if some of the library resources got reloaded.
 * @since 1.0
 */
public class XMILibraryManager implements ILibraryManager {

	public static final int PROP_DIRTY = 1;

	/**
	 * The supported library type.
	 */
	public static final String LIBRARY_TYPE = "xmi";

	/**
	 * The library XMI file name.
	 */
	public static final String LIBRARY_XMI = "library.xmi";

	/**
	 * The library name.
	 */
	public static final String ARG_LIBRARY_NAME = "library.name";

	/**
	 * The library path.
	 */
	public static final String ARG_LIBRARY_PATH = "library.path";

	// The name of the lock file for a library.
	protected static final String LIBRARY_LOCK_FILENAME = ".lock"; //$NON-NLS-1$

	// If true, generate debug traces.
	protected static boolean debug = XMILibraryPlugin.getDefault()
			.isDebugging();

	// The managed library.
	protected MethodLibrary library;

	// The absolute path to the managed library.
	protected String path;

	// The default editing domain for the managed library.
	protected AdapterFactoryEditingDomain editingDomain;

	// A list of listeners that monitor changes to the managed library.
	private List libraryChangedListeners = new ArrayList();

	// A list of listeners that have been detached from the managed library.
	private List detachedLibraryChangedListeners = new ArrayList();

	// A list of listeners that monitor resource changes in the managed library.
	private ListenerList resourceChangeListeners = new ListenerList();

	// The file lock for the managed library.
	private FileLock lock;

	// The save library options.
	private Map saveOptions;

	// If true, skip all event processing.
	private boolean skipEventProcessing = false;

	// TODO: find a better way to notify the change in library instead of
	// relying on the command stack listener
	private CommandStackListener commandStackListener = new CommandStackListener() {
		public void commandStackChanged(final EventObject event) {
			if (debug) {
				DebugTrace.print(this, "commandStackChanged", "event=" + event); //$NON-NLS-1$ //$NON-NLS-2$
			}
			if (!skipEventProcessing) {
				SafeUpdateController.asyncExec(new Runnable() {
					public void run() {
						// Try to select the affected objects.
						Command mostRecentCommand = LibraryUtil
								.unwrap(((CommandStack) event.getSource())
										.getMostRecentCommand());
						if (mostRecentCommand != null) {
							if (mostRecentCommand instanceof AddCommand) {
								AddCommand cmd = (AddCommand) mostRecentCommand;
								EObject owner = cmd.getOwner();
								if (owner instanceof MethodConfiguration) {
									Collection objs = new ArrayList();
									objs.add(owner);
									notifyListeners(
											ILibraryChangeListener.OPTION_CHANGED,
											objs);
								} else {
									Collection objs = mostRecentCommand
											.getResult();
									notifyListeners(
											ILibraryChangeListener.OPTION_NEWCHILD,
											objs);

									// Update the configuration selection if the
									// object is a newly added method package.
									if (owner instanceof MethodPackage) {
										objs = LibraryUtil
												.getContainedElements(owner,
														objs);
										if (!objs.isEmpty()) {
											addNewPackagesToConfiguration(objs);
										}
									}
								}
							} else if (mostRecentCommand instanceof PasteFromClipboardCommand) {
								Collection objs = mostRecentCommand.getResult();
								notifyListeners(
										ILibraryChangeListener.OPTION_NEWCHILD,
										objs);
								PasteFromClipboardCommand cmd = ((PasteFromClipboardCommand) mostRecentCommand);

								// Update the configuration selection if the
								// object is a newly added method package.
								if (cmd.getOwner() instanceof MethodPackage) {
									objs = LibraryUtil.getContainedElements(cmd
											.getOwner(), objs);
									if (!objs.isEmpty()) {
										addNewPackagesToConfiguration(objs);
									}
								}
							} else if (mostRecentCommand instanceof CreateChildCommand) {
								notifyListeners(
										ILibraryChangeListener.OPTION_NEWCHILD,
										mostRecentCommand.getAffectedObjects());
							} else if (mostRecentCommand != null) {
								notifyListeners(
										ILibraryChangeListener.OPTION_CHANGED,
										mostRecentCommand.getAffectedObjects());
							}
						}
					}
				});
			}
		}
	};

	// Listen to changes to the managed method library.
	private INotifyChangedListener notifyChangedListener = new INotifyChangedListener() {
		public void notifyChanged(Notification notification) {
			if (debug) {
				DebugTrace.print(this,
						"notifyChanged", "notification=" + notification); //$NON-NLS-1$ //$NON-NLS-2$
			}
			if (!skipEventProcessing) {
				int eventType = notification.getEventType();
				switch (eventType) {
				case Notification.ADD: {
					// A method element, typically a method plug-in, has been
					// added to the managed library without using an editing
					// command.
					Object notifier = notification.getNotifier();
					Object value = notification.getNewValue();
					if ((notifier instanceof MethodLibrary)
							&& (value instanceof MethodPlugin)) {
						Collection affectedObjects = new ArrayList();
						affectedObjects.add(value);
						notifyListeners(ILibraryChangeListener.OPTION_NEWCHILD,
								affectedObjects);
					}
					break;
				}

				case Notification.SET: {
					Object notifier = notification.getNotifier();
					if (notifier != null) {
						Collection affectedObjects = new ArrayList();
						affectedObjects.add(notifier);
						notifyListeners(ILibraryChangeListener.OPTION_CHANGED,
								affectedObjects);
					}
					break;
				}

				case Notification.REMOVE: {
					// Either a method element has been removed from the
					// containing element, or a method element reference has
					// been deleted.
					Object notifier = notification.getNotifier();
					Object oldValue = notification.getOldValue();
					Collection affectedObjects = new ArrayList();
					if (oldValue instanceof EObject
							&& ((EObject) oldValue).eContainer() == null) {
						// A method element has been deleted.
						affectedObjects.add(oldValue);
						notifyListeners(ILibraryChangeListener.OPTION_DELETED,
								affectedObjects);
					} else {
						// A method element reference has been deleted, notify
						// the listeners that the containing method element has
						// changed.
						affectedObjects.add(notifier);
						notifyListeners(ILibraryChangeListener.OPTION_CHANGED,
								affectedObjects);
					}
					break;
				}

				case Notification.REMOVE_MANY: {
					// Two or more method elements have been removed from
					// the containing element, or tw or more method element
					// reference have been deleted.
					List oldValue = new ArrayList((Collection) notification
							.getOldValue());
					ArrayList deletedElements = new ArrayList();
					ArrayList removedReferences = new ArrayList();
					if (!oldValue.isEmpty()) {
						for (Iterator iter = oldValue.iterator(); iter
								.hasNext();) {
							Object element = iter.next();
							if (element instanceof EObject) {
								if (((EObject) element).eContainer() == null) {
									deletedElements.add(element);
								} else {
									removedReferences.add(element);
								}
							}
						}
					}
					if (!deletedElements.isEmpty()) {
						// Two or more method elements have been deleted.
						notifyListeners(ILibraryChangeListener.OPTION_DELETED,
								deletedElements);
					}
					if (!removedReferences.isEmpty()) {
						// Two or more method element reference has been
						// deleted.
						notifyListeners(ILibraryChangeListener.OPTION_CHANGED,
								removedReferences);
					}
					break;
				}
				}
			}
		}
	};

	// Listen to managed method library resource changes.
	private Adapter resourceChangedListener = new AdapterImpl() {
		public void notifyChanged(Notification msg) {
			if (debug) {
				DebugTrace.print(this, "notifyChanged", "msg=" + msg); //$NON-NLS-1$ //$NON-NLS-2$
			}
			if (msg.getFeatureID(null) == Resource.RESOURCE__IS_MODIFIED
					&& msg.getEventType() == org.eclipse.emf.common.notify.Notification.SET) {
				firePropertyChange(msg.getNotifier(), PROP_DIRTY);
			}
		}
	};

	// Listen to persistence refresh events.
	private IRefreshListener refreshListener = new IRefreshListener() {
		public void notifyRefreshed(IRefreshEvent event) {
			if (debug) {
				DebugTrace.print(this, "notifyRefreshed", "event=" + event); //$NON-NLS-1$ //$NON-NLS-2$
			}
			handleRefreshEvent(event);
		}
	};

	// Listen to preference store changes.
	private IPropertyChangeListener preferenceStoreChangeListener = new IPropertyChangeListener() {
		public void propertyChange(PropertyChangeEvent event) {
			if (event.getProperty().equals(
					PreferenceConstants.PREF_BACK_UP_BEFORE_SAVE)) {
				saveOptions.put(MultiFileXMISaveImpl.BACK_UP_BEFORE_SAVE, event
						.getNewValue());
			} else if (event.getProperty().equals(
					PreferenceConstants.PREF_DISCARD_UNRESOLVED_REFERENCES)) {
				saveOptions.put(
						MultiFileXMISaveImpl.DISCARD_UNRESOLVED_REFERENCES,
						event.getNewValue());
			}
		}
	};

	/**
	 * Creates a new instance.
	 */
	public XMILibraryManager() {
		init();
	}

	/**
	 * Performs the necessary initialization.
	 */
	protected void init() {
		if (debug) {
			DebugTrace.print(this, "init"); //$NON-NLS-1$
		}

		LibraryPlugin.getDefault().getPreferenceStore()
				.addPropertyChangeListener(preferenceStoreChangeListener);

		// Create the adapter factory.
		List factories = new ArrayList();
		factories.add(new ResourceItemProviderAdapterFactory());
		factories.add(new ReflectiveItemProviderAdapterFactory());
		ComposedAdapterFactory adapterFactory = new ComposedAdapterFactory(
				factories);

		// Create the command stack.
		BasicCommandStack commandStack = new BasicCommandStack();

		// Create the resource set.
		MultiFileResourceSetImpl resourceSet = new MultiFileResourceSetImpl();
		resourceSet.addRefreshListener(refreshListener);
		RefreshJob.getInstance().setResourceSet(resourceSet);

		// Initialize the library save options.
		saveOptions = resourceSet.getDefaultSaveOptions();
		boolean b = LibraryPlugin.getDefault().getPreferenceStore().getBoolean(
				PreferenceConstants.PREF_BACK_UP_BEFORE_SAVE);
		saveOptions.put(MultiFileXMISaveImpl.BACK_UP_BEFORE_SAVE, Boolean
				.valueOf(b));
		b = LibraryPlugin.getDefault().getPreferenceStore().getBoolean(
				PreferenceConstants.PREF_DISCARD_UNRESOLVED_REFERENCES);
		saveOptions.put(MultiFileXMISaveImpl.DISCARD_UNRESOLVED_REFERENCES,
				Boolean.valueOf(b));

		// Create the editing domain.
		editingDomain = new AdapterFactoryEditingDomain(adapterFactory,
				commandStack, resourceSet);

		// TODO: Review implementation.
		IMethodLibraryPersister contentDescPersister = new MethodLibraryPersister();
		ContentDescriptionFactory
				.setMethodLibraryPersister(contentDescPersister);
		ContentDescriptionFactory.setFileManager(FileManager.getInstance());

		// Register the editing domain.
		registerEditingDomain(editingDomain);
	}

	/**
	 * Creates a new method library.
	 * 
	 * @param name
	 *            a name for the new method library
	 * @param args
	 *            method library specific arguments
	 * @return a method library
	 * @throw <code>LibraryServiceException</code> if an error occurs while
	 *        performing the operation
	 */
	public MethodLibrary createMethodLibrary(String name, Map args)
			throws LibraryServiceException {
		if (debug) {
			DebugTrace.print(this, "createMethodLibrary", "name=" + name); //$NON-NLS-1$ //$NON-NLS-2$
		}

		if (name == null || name.length() == 0 || args == null) {
			throw new IllegalArgumentException();
		}

		String path = (String) args.get(ARG_LIBRARY_PATH);
		if (path == null || path.length() == 0) {
			throw new IllegalArgumentException();
		}

		File libraryPath = new File(path);
		File libraryXMIFile = new File(libraryPath, LIBRARY_XMI);
		if (libraryXMIFile.exists()) {
			throw new LibraryAlreadyExistsException();
		}

		if (!libraryPath.exists()) {
			libraryPath.mkdirs();
		}

		try {
			skipEventProcessing = true;

			// Lock the method library to prevent access by another EPF composer
			// instance.
			lockMethodLibrary(libraryPath);

			// Open the method library project file.
			MethodLibraryProject.openProject(libraryPath.getAbsolutePath(),
					null);

			// Create the resource set.
			MultiFileResourceSetImpl resourceSet = (MultiFileResourceSetImpl) editingDomain
					.getResourceSet();

			// Create a new method library.
			ModelStorage.newLibrary(resourceSet, name, libraryPath
					.getAbsolutePath(), true);
			library = resourceSet.getMethodLibrary();

			// Add a listener to monitor library resource changes.
			addResourceChangedListeners();

//			// this is already moved to LibraryService
//			notifyListeners(ILibraryChangeListener.OPTION_LOADED, null);

			if (debug) {
				DebugTrace.print(this,
						"createMethodLibrary", "library=" + library); //$NON-NLS-1$ //$NON-NLS-2$
			}

			return library;
		} catch (Exception e) {
			throw new LibraryServiceException(e);
		} finally {
			skipEventProcessing = false;
			
//			// event processed in LibraryService
//			notifyListeners(ILibraryChangeListener.OPTION_CREATED, null);
		}
	}

	/**
	 * Opens a method library.
	 * 
	 * @param args
	 *            method library specific arguments
	 * @return a method library
	 * @throw <code>LibraryServiceException</code> if an error occurs while
	 *        performing the operation
	 */
	public MethodLibrary openMethodLibrary(Map args)
			throws LibraryServiceException {
		if (debug) {
			DebugTrace.print(this, "openMethodLibrary");
		}

		if (args == null) {
			throw new IllegalArgumentException();
		}

		String path = (String) args.get(ARG_LIBRARY_PATH);
		if (path == null || path.length() == 0) {
			throw new IllegalArgumentException();
		}

		library = openMethodLibrary(new File(path));

		if (debug) {
			DebugTrace.print(this, "openMethodLibrary", "library=" + library); //$NON-NLS-1$ //$NON-NLS-2$
		}

		return library;
	}

	/**
	 * Reopens the managed method library.
	 * 
	 * @return a method library
	 * @throw <code>LibraryServiceException</code> if an error occurs while
	 *        performing the operation
	 */
	public MethodLibrary reopenMethodLibrary() throws LibraryServiceException {
		if (debug) {
			DebugTrace.print(this, "reopenMethodLibrary");
		}

		library = openMethodLibrary(new File(getMethodLibraryPath()));

		if (debug) {
			DebugTrace.print(this, "reopenMethodLibrary", "library=" + library); //$NON-NLS-1$ //$NON-NLS-2$
		}

		return library;
	}

	/**
	 * Saves the managed method library.
	 * 
	 * @throw <code>LibraryServiceException</code> if an error occurs while
	 *        performing the operation
	 */
	public void saveMethodLibrary() throws LibraryServiceException {
		if (debug) {
			DebugTrace.print(this, "saveMethodLibrary"); //$NON-NLS-1$
		}

		try {
			// TODO: Get the library from the resource?
			if (library != null) {
				skipEventProcessing = true;

				MultiFileResourceSetImpl resourceSet = ((MultiFileResourceSetImpl) editingDomain
						.getResourceSet());
				resourceSet.save(saveOptions);

				((BasicCommandStack) editingDomain.getCommandStack())
						.saveIsDone();

				skipEventProcessing = false;

				firePropertyChange(library, PROP_DIRTY);
				
//				// no one is interested in this event, removed 
//				notifyListeners(ILibraryChangeListener.OPTION_SAVED, null);
			}
		} catch (Exception e) {
			throw new LibraryServiceException(e);
		} finally {
			skipEventProcessing = false;
		}
	}

	/**
	 * Discards all changes made to the managed method library.
	 */
	public void discardMethodLibraryChanges() {
		if (debug) {
			DebugTrace.print(this, "discardMethodLibraryChanges"); //$NON-NLS-1$
		}

		for (Iterator it = getEditingDomain().getResourceSet().getResources()
				.iterator(); it.hasNext();) {
			Resource resource = (Resource) it.next();
			resource.setModified(false);
		}
	}

	
	/**
	 * Closes the managed method library.
	 * 
	 * @return a method library
	 * @throw <code>LibraryServiceException</code> if an error occurs while
	 *        performing the operation
	 */
	public void closeMethodLibrary() throws LibraryServiceException {
		if (debug) {
			String msg = "library=" + library + ", memory on entry=" //$NON-NLS-1$ //$NON-NLS-2$
					+ (Runtime.getRuntime().totalMemory() - Runtime
							.getRuntime().freeMemory());
			DebugTrace.print(this, "closeMethodLibrary", msg); //$NON-NLS-1$
		}

		// remove the configuration managers associated with this library
		LibraryService.getInstance().removeConfigurationManagers(library);
		
		removeResourceChangedListeners();

//		// this is already moved to LibraryService
//		notifyListeners(ILibraryChangeListener.OPTION_LIBRARY_CLOSED, null);

		// TODO: Dispose item providers.
		// TngAdapterFactory.INSTANCE.reset();
		// if (FilterConstants.hashMap != null) {
		// FilterConstants.hashMap.clear();
		// }

		// Clear the temp layout resources.
		LayoutResources.clear();

		MultiFileResourceSetImpl resourceSet = (MultiFileResourceSetImpl) editingDomain
				.getResourceSet();
		resourceSet.reset();

		// Unlocks the method library.
		unlockMethodLibrary();

		RefreshJob.getInstance().reset();

		// Activates the garbage collector.
		Runtime.getRuntime().gc();

		if (debug) {
			String msg = "library=" + library + ", memory on exit=" //$NON-NLS-1$ //$NON-NLS-2$
					+ (Runtime.getRuntime().totalMemory() - Runtime
							.getRuntime().freeMemory());
			DebugTrace.print(this, "closeMethodLibrary", msg); //$NON-NLS-1$
		}
	}

	/**
	 * Gets the managed method library.
	 * 
	 * @return a method library
	 */
	public MethodLibrary getMethodLibrary() {
		if (debug) {
			DebugTrace.print(this, "getMethodLibrary", "library=" + library); //$NON-NLS-1$ //$NON-NLS-2$
		}

		return library;
	}

	/**
	 * Sets the managed method library.
	 * 
	 * @param library
	 *            a method library
	 */
	public void setMethodLibrary(MethodLibrary library) {
		if (debug) {
			DebugTrace.print(this, "setMethodLibrary", "library=" + library); //$NON-NLS-1$ //$NON-NLS-2$
		}

		if ( this.library != null ) {
			Resource resource = (Resource) this.library.eResource();
			if ( resource != null ) {
				resource.getContents().clear();
				resource.getContents().add(library);
			}
		}
		
		this.library = library;
	}

	/**
	 * Gets the absolute path to the managed method library.
	 * 
	 * @return an absolute path to the method library
	 */
	public String getMethodLibraryPath() {
		if (debug) {
			DebugTrace.print(this, "getMethodLibraryPath"); //$NON-NLS-1$
		}

		URI libraryURI = getLibraryURI();
		if (libraryURI != null) {
			File libraryXMIFile = new File(libraryURI.toFileString());
			return libraryXMIFile.getParentFile().getAbsolutePath();
		}
		return null;
	}

	/**
	 * Gets the adapter factory for the managed method library.
	 * 
	 * @return an adapter factory
	 */
	public ComposedAdapterFactory getAdapterFactory() {
		if (debug) {
			DebugTrace.print(this, "getAdapterFactory"); //$NON-NLS-1$
		}

		return (ComposedAdapterFactory) getEditingDomain().getAdapterFactory();
	}

	/**
	 * Gets the editing domain for the managed method library.
	 * 
	 * @return an editing domain
	 */
	public AdapterFactoryEditingDomain getEditingDomain() {
		if (debug) {
			DebugTrace.print(this,
					"getEditingDomain", "editingDomain=" + editingDomain); //$NON-NLS-1$ //$NON-NLS-2$
		}

		return editingDomain;
	}

	/**
	 * Registers an editing domain with the managed method library.
	 * 
	 * @param domain
	 *            an editing domain
	 */
	public void registerEditingDomain(AdapterFactoryEditingDomain domain) {
		if (debug) {
			DebugTrace.print(this, "registerEditingDomain", "domain=" + domain); //$NON-NLS-1$ //$NON-NLS-2$
		}

		// Add a listener to monitor library changes made in the given editing
		// domain.
		((ComposedAdapterFactory) domain.getAdapterFactory())
				.addListener(notifyChangedListener);

		// Add a listener to monitor changes made to the command stack.
		// This is used to select the most recently affected objects in the
		// viewer.
		domain.getCommandStack().addCommandStackListener(commandStackListener);
	}

	/**
	 * Adds a listener to monitor changes to the managed method library.
	 * 
	 * @param listener
	 *            a library change listener
	 */
	public void addListener(ILibraryChangeListener listener) {
		synchronized (libraryChangedListeners) {
			if (debug) {
				DebugTrace.print(this, "addListener", "listener=" + listener); //$NON-NLS-1$ //$NON-NLS-2$
			}

			if (!libraryChangedListeners.contains(listener)) {
				libraryChangedListeners.add(listener);
			}
		}
	}

	/**
	 * Removes a listener that was added to monitor changes to the managed
	 * method library.
	 * 
	 * @param listener
	 *            a library change listener
	 */
	public void removeListener(ILibraryChangeListener listener) {
		synchronized (detachedLibraryChangedListeners) {
			if (debug) {
				DebugTrace
						.print(this, "removeListener", "listener=" + listener);
			}

			// Cache the listener and remove it just before dispatching the
			// library changed events.
			if (!detachedLibraryChangedListeners.contains(listener)) {
				detachedLibraryChangedListeners.add(listener);
			}
		}
	}

	/**
	 * Adds a listener to monitor resource changes in the managed method
	 * library.
	 * 
	 * @param listener
	 *            a property change listener
	 */
	public void addPropertyListener(IPropertyListener listener) {
		if (debug) {
			DebugTrace.print(this,
					"addPropertyListener", "listener=" + listener); //$NON-NLS-1$ //$NON-NLS-2$
		}

		resourceChangeListeners.add(listener);
	}

	/**
	 * Removes a listener that was added to monitor resource changes in the
	 * managed method library.
	 * 
	 * @param listener
	 *            a property change listener
	 */
	public void removePropertyListener(IPropertyListener listener) {
		if (debug) {
			DebugTrace.print(this,
					"removePropertyListener", "listener=" + listener); //$NON-NLS-1$ //$NON-NLS-2$
		}

		resourceChangeListeners.remove(listener);
	}

	/**
	 * Starts listening to command processing on a command stack.
	 * 
	 * @param commandStack
	 *            a command stack
	 */
	public void startListeningTo(CommandStack commandStack) {
		if (debug) {
			DebugTrace.print(this,
					"startListeningTo", "commandStack=" + commandStack); //$NON-NLS-1$ //$NON-NLS-2$
		}

		commandStack.addCommandStackListener(commandStackListener);
	}

	/**
	 * Stops listening to command processing on a command stack.
	 * 
	 * @param commandStack
	 *            a command stack
	 */
	public void stopListeningTo(CommandStack commandStack) {
		if (debug) {
			DebugTrace.print(this,
					"stopListeningTo", "commandStack=" + commandStack); //$NON-NLS-1$ //$NON-NLS-2$
		}

		commandStack.removeCommandStackListener(commandStackListener);
	}

	/**
	 * Starts listening to change notifications sent from an adapter factory.
	 * 
	 * @param adapterFactory
	 *            an adapter factory
	 */
	public void startListeningTo(ComposedAdapterFactory adapterFactory) {
		if (debug) {
			DebugTrace.print(this,
					"startListeningTo", "adapterFactory=" + adapterFactory); //$NON-NLS-1$ //$NON-NLS-2$
		}

		adapterFactory.addListener(notifyChangedListener);
	}

	/**
	 * Stops listening to change notifications sent from an adapter factory.
	 * 
	 * @param adapterFactory
	 *            an adapter factory
	 */
	public void stopListeningTo(ComposedAdapterFactory adapterFactory) {
		if (debug) {
			DebugTrace.print(this,
					"stopListeningTo", "adapterFactory=" + adapterFactory); //$NON-NLS-1$ //$NON-NLS-2$
		}

		adapterFactory.removeListener(notifyChangedListener);
	}

	/**
	 * Gets a method element from the managed method library.
	 * 
	 * @param guid
	 *            the method element's GUID.
	 * 
	 * @return a method element of <code>null</code>
	 */
	public MethodElement getMethodElement(String guid) {
		if (debug) {
			DebugTrace.print(this, "getMethodElement", "guid=" + guid); //$NON-NLS-1$ //$NON-NLS-2$
		}

		try {
			MultiFileResourceSetImpl resourceSet = (MultiFileResourceSetImpl) library
					.eResource().getResourceSet();
			if (resourceSet != null) {
				return (MethodElement) resourceSet.getEObject(guid);
			}
		} catch (Throwable th) {
		}
		return null;
	}

	/**
	 * Gets the relative URI of a method element in the managed method library.
	 * 
	 * @param element
	 *            a method element
	 * @return a relative URI
	 */
	public URI getElementRelativeURI(MethodElement element) {
		if (debug) {
			DebugTrace.print(this,
					"getElementRelativeURI", "element=" + element); //$NON-NLS-1$ //$NON-NLS-2$
		}

		if (element != null) {
			Resource resource = library.eResource();
			if (resource != null) {
				URI libraryURI = resource.getURI();
				URI elementURI = element.eResource().getURI();
				return elementURI.deresolve(libraryURI);
			}
		}
		return null;
	}

	/**
	 * Checks whether the managed method library is locked.
	 * 
	 * @return <code>true</code> if the method library is locked
	 */
	public boolean isMethodLibraryLocked() {
		if (debug) {
			DebugTrace.print(this, "isMethodLibraryLocked"); //$NON-NLS-1$
		}

		return XMILibraryUtil.isMethodLibraryLocked(getMethodLibraryPath());
	}

	/**
	 * Checks whether the managed method library is read only.
	 * 
	 * @return <code>true</code> if the method library is read only
	 */
	public boolean isMethodLibraryReadOnly() {
		if (debug) {
			DebugTrace.print(this, "isMethodLibraryReadOnly"); //$NON-NLS-1$
		}

		URI libraryURI = library.eResource().getURI();
		File libraryXMIFile = new File(libraryURI.toFileString());
		return libraryXMIFile.exists() && !libraryXMIFile.canWrite();
	}

	/**
	 * Checks whether the managed method library content has been modified.
	 * 
	 * @return <code>true</code> if the managed method library content has
	 *         been modified
	 */
	public boolean isMethodLibraryModified() {
		if (debug) {
			DebugTrace.print(this, "isMethodLibraryModified"); //$NON-NLS-1$
		}

		for (Iterator it = getEditingDomain().getResourceSet().getResources()
				.iterator(); it.hasNext();) {
			Resource resource = (Resource) it.next();
			if (resource.isModified()) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Checks whether the managed method library has any unresolved proxy.
	 * 
	 * @return <code>true</code> if the managed method library has an
	 *         unresolved proxy.
	 */
	public boolean hasUnresolvedProxy() {
		if (debug) {
			DebugTrace.print(this, "hasUnresolvedProxy"); //$NON-NLS-1$
		}

		MultiFileResourceSetImpl resourceSet = ((MultiFileResourceSetImpl) editingDomain
				.getResourceSet());
		return resourceSet.hasUnresolvedProxy();
	}

	/**
	 * Reloads the given resources.
	 * 
	 * @param resources
	 *            a collection of resources
	 * @return a collection of resources that have reloaded
	 */
	public Collection reloadResources(final Collection resources) {
		if(debug) {
			System.out.println("XMILibraryManager.reloadResources(): START"); //$NON-NLS-1$
		}
		try {
			final ArrayList reloadedResources = new ArrayList();
			IWorkspaceRunnable runnable = new IWorkspaceRunnable() {

				public void run(IProgressMonitor monitor) throws CoreException {
					reloadedResources.addAll(doReloadResources(resources));

				}

			};
			try {
				ResourcesPlugin.getWorkspace().run(runnable, new LibrarySchedulingRule(library), 
						IWorkspace.AVOID_UPDATE, new NullProgressMonitor());
			} catch (Exception e) {
				XMILibraryPlugin.getDefault().getLogger().logError(e);
			}
			return reloadedResources;
		}
		finally {
			if(debug) {
				System.out.println("XMILibraryManager.doReloadResources(): END"); //$NON-NLS-1$
			}
		}
	}
	
	private Collection doReloadResources(Collection resources) {
		if (debug) {
			DebugTrace.print(this, "reloadResources"); //$NON-NLS-1$
		}
		if(library == null) {
			return Collections.EMPTY_LIST;
		}
		
		// check if resources to reload contains any elements cached in LibraryService
		// to update them
		//
		LibraryService libSvc = (LibraryService) LibraryService.getInstance();
		Resource currentLibResource = null;
		ILibraryManager currentLibMgr = null;
		Resource currentConfigResource = null;
		MethodConfiguration currentConfig = null;
		List configResources = new ArrayList();
		List configs = new ArrayList();
		for (Iterator iter = resources.iterator(); iter.hasNext();) {
			Resource resource = (Resource) iter.next();
			MethodElement e = MultiFileSaveUtil.getMethodElement(resource);
			if(e == libSvc.getCurrentMethodLibrary()) {
				currentLibMgr = libSvc.getCurrentLibraryManager();
				currentLibResource = resource;
			}
			else if(e == libSvc.getCurrentMethodConfiguration()) {
				currentConfigResource = resource;
				currentConfig = libSvc.getCurrentMethodConfiguration();
			}
			else if(e instanceof MethodConfiguration) {
				configResources.add(resource);
				configs.add(e);
			}
		}
		
		MultiFileResourceSetImpl resourceSet = (MultiFileResourceSetImpl) library
				.eResource().getResourceSet();
		Collection reloadedResources = resourceSet.reloadResources(resources);
		if (!reloadedResources.isEmpty()) {
			if(currentLibResource != null || currentConfigResource != null) {
				// update cached elements in LibraryService and this library manager
				//
				for (Iterator iter = reloadedResources.iterator(); iter.hasNext();) {
					Resource resource = (Resource) iter.next();
					if(resource == currentLibResource) {
						MethodElement e = MultiFileSaveUtil.getMethodElement(resource);
						if(e instanceof MethodLibrary) {
							MethodLibrary newLib = (MethodLibrary) e;
							libSvc.setCurrentMethodLibrary(newLib);
							if(currentLibMgr instanceof XMILibraryManager) {
								libSvc.removeLibraryManager(currentLibMgr);
								((XMILibraryManager)currentLibMgr).updateMethodLibrary(newLib);
								libSvc.setLibraryManager(currentLibMgr);
							}
						}
					}
					if(resource == currentConfigResource) {
						MethodElement e = MultiFileSaveUtil.getMethodElement(resource);
						if(e instanceof MethodConfiguration) {
							// remove config manager of old current config
							//
							libSvc.removeConfigurationManager(currentConfig);
							MethodConfiguration	config = (MethodConfiguration) e;
							libSvc.setCurrentMethodConfiguration(config);
						}
					}
					else if(!configResources.isEmpty()){
						int id = configResources.indexOf(resource);
						if(id != -1) {
							// remove config manager of old config
							//
							libSvc.removeConfigurationManager((MethodConfiguration) configs.get(id));
						}
					}
				}
			}
			
			// TODO: Review implementation.
			Suppression.cleanUp();
		}
		return reloadedResources;
	}

	/**
	 * @param newLib
	 */
	private void updateMethodLibrary(MethodLibrary newLib) {
		library = newLib;
	}

	/**
	 * Gets the options used for saving the managed method library.
	 * 
	 * @return a map of method library specific save options
	 */
	public Map getSaveOptions() {
		if (debug) {
			DebugTrace.print(this, "getSaveOptions"); //$NON-NLS-1$
		}

		return saveOptions;
	}

	/**
	 * Adds a new method plug-in to the managed method library.
	 * 
	 * @param plugin
	 *            a method plug-in
	 * @throw <code>LibraryServiceException</code> if an error occurs while
	 *        performing the operation
	 */
	public void addMethodPlugin(final MethodPlugin plugin)
			throws LibraryServiceException {
		if (debug) {
			DebugTrace.print(this, "addMethodPlugin", "plugin=" + plugin); //$NON-NLS-1$ //$NON-NLS-2$
		}

		// This operation will cause an UI update. It must be executed in
		// the main UI to aoid an Invalid Thread Access exception.
		final Exception[] exceptions = new Exception[1];

		try {
			SafeUpdateController.syncExec(new Runnable() {
				public void run() {
					library.getMethodPlugins().add(plugin);

					IMethodLibraryPersister.FailSafeMethodLibraryPersister persister = ContentDescriptionFactory
							.getMethodLibraryPersister().getFailSafePersister();
					try {
						persister.save(library.eResource());
						persister.commit();
					} catch (Exception e) {
						persister.rollback();
						exceptions[0] = e;
						return;
					}

					plugin.eResource().eAdapters().add(resourceChangedListener);
				}
			});
		} catch (Exception e) {
			throw new LibraryServiceException(e);
		}

		if (exceptions[0] != null) {
			throw new LibraryServiceException(exceptions[0]);
		}
	}

	/**
	 * Disposes all resources allocated by this library manager.
	 */
	public void dispose() {
		if (preferenceStoreChangeListener != null) {
			LibraryPlugin
					.getDefault()
					.getPreferenceStore()
					.removePropertyChangeListener(preferenceStoreChangeListener);
		}

		if (libraryChangedListeners.size() > 0) {
			libraryChangedListeners.clear();
		}

		if (detachedLibraryChangedListeners.size() > 0) {
			detachedLibraryChangedListeners.clear();
		}

		if (resourceChangeListeners.size() > 0) {
			resourceChangeListeners.clear();
		}

		editingDomain = null;
		library = null;
	}

	/**
	 * Checks the arguments used for creating a new method element.
	 * 
	 * @param containingElement
	 *            the parent/containing method element
	 * @param name
	 *            a name for the new method element
	 * @throw <code>LibraryServiceException</code> if an error occurs while
	 *        performing the operation.
	 */
	protected void checkElementCreationArguments(
			MethodElement containingElement, String name)
			throws LibraryServiceException {
		if (containingElement == null) {
			throw new IllegalArgumentException();
		}
		if (name == null || name.length() == 0) {
			throw new InvalidMethodElementNameException();
		}
		// TODO: Check for illegal characters.
	}

	/**
	 * Locks the managed method library to prevent access by another EPF
	 * Composer instance.
	 * <p>
	 * The .lock file in the method library folder will be locked. If it does
	 * not exists, a new .lock file will be created.
	 * 
	 * @param path
	 *            a <code>File</code> object that contains the path to the
	 *            method library.
	 */
	protected void lockMethodLibrary(File path) {
		File lockFile = new File(path, ResourceUtil.LOCK_FILENAME);
		lock = FileUtil.lockFile(lockFile);
	}

	/**
	 * Unlocks the managed method library.
	 */
	protected void unlockMethodLibrary() {
		try {
			if (lock != null) {
				lock.release();
				lock.channel().close();
			}
		} catch (Exception e) {
			LibraryPlugin.getDefault().getLogger().logError(e);
			if (debug) {
				DebugTrace.print(this, "unlockMethodLibrary", e); //$NON-NLS-1$
			}
		}
	}

	/**
	 * Handles a persistence refresh event.
	 * 
	 * @param event
	 *            a refresh event
	 */
	protected void handleRefreshEvent(IRefreshEvent event) {
		if (debug) {
			DebugTrace.print(this, "handleRefreshEvent", "refreshedResources=" //$NON-NLS-1$ //$NON-NLS-2$
					+ event.getRefreshedResources());
		}

		if (!event.getUnloadedObjects().isEmpty()) {
			TngAdapterFactory.INSTANCE.cleanUp();
		}
	}

	/**
	 * Notifies all library changed listeners attached to the managed library.
	 * 
	 * @param type
	 *            the type of change that has occurred
	 * @param changedElements
	 *            a collection of method elements that have changed
	 */
	protected synchronized void notifyListeners(final int option,
			final Collection collection) {
		if (debug) {
			DebugTrace.print(this, "notifyListeners", "option=" + option); //$NON-NLS-1$ //$NON-NLS-2$
		}

		try {
			// Remove the changed listeners that have been dettached.
			if (detachedLibraryChangedListeners.size() > 0) {
				for (Iterator it = detachedLibraryChangedListeners.iterator(); it
						.hasNext();) {
					Object l = it.next();
					if (libraryChangedListeners.contains(l)) {
						libraryChangedListeners.remove(l);
					}
				}
				detachedLibraryChangedListeners.clear();
			}

			// Notify the changed listeners.
			// Note: more changed listeners may be added while each listener is
			// being notified. However,
			// they will be added to the end of the list which does no harm.
			int i = 0;
			while (i < libraryChangedListeners.size()) {
				final ILibraryChangeListener listener = (ILibraryChangeListener) libraryChangedListeners
						.get(i);
				if (listener != null) {
					// Since this may trigger an update to the UI, the
					// notification must be executed in the UI thread to avoid
					// getting an Invalid Thread Access exception. The
					// notification must also be executed in sync mode to
					// gurantee delivery of the event before a listener is
					// disposed.
					SafeUpdateController.syncExec(new Runnable() {
						public void run() {
							if (debug) {
								DebugTrace
										.print(
												this,
												"notifyListeners", "listener=" + listener); //$NON-NLS-1$ //$NON-NLS-2$
							}
							listener.libraryChanged(option, collection);
						}
					});
				}
				i++;
			}
		} catch (Exception e) {
			if (debug) {
				DebugTrace.print(this, "notifyListeners", e); //$NON-NLS-1$
			}
		}
	}

	/**
	 * Fires a property changed event.
	 * 
	 * @param propertyId
	 *            the id of the changed property
	 */
	protected void firePropertyChange(final Object source, final int propertyId) {
		if (debug) {
			DebugTrace.print(this, "firePropertyChange", "source=" + source); //$NON-NLS-1$ //$NON-NLS-2$
		}

		Object[] array = resourceChangeListeners.getListeners();
		for (int i = 0; i < array.length; i++) {
			final IPropertyListener listener = (IPropertyListener) array[i];

			// This operation will cause an UI update. It must be executed in
			// the main UI to aoid an Invalid Thread Access exception.
			SafeUpdateController.asyncExec(new Runnable() {
				public void run() {
					if (debug) {
						DebugTrace.print(this,
								"firePropertyChange", "listener=" + listener); //$NON-NLS-1$ //$NON-NLS-2$
					}
					listener.propertyChanged(source, propertyId);
				}
			});
		}
	}

	/**
	 * Adds a resource changed listener to the managed method library resources.
	 */
	protected void addResourceChangedListeners() {
		if (library == null || library.eResource() == null) {
			return;
		}

		if (!library.eResource().eAdapters().contains(resourceChangedListener)) {
			library.eResource().eAdapters().add(resourceChangedListener);
		}

		for (Iterator it = library.getMethodPlugins().iterator(); it.hasNext();) {
			MethodPlugin plugin = (MethodPlugin) it.next();
			if (!plugin.eResource().eAdapters().contains(
					resourceChangedListener)) {
				plugin.eResource().eAdapters().add(resourceChangedListener);
			}
		}

		for (Iterator it = library.getPredefinedConfigurations().iterator(); it
				.hasNext();) {
			MethodConfiguration config = (MethodConfiguration) it.next();
			if (!config.eResource().eAdapters().contains(
					resourceChangedListener)) {
				config.eResource().eAdapters().add(resourceChangedListener);
			}
		}
	}

	/**
	 * Removes the resource changed listener to the managed method library
	 * resource and method plug-ins.
	 */
	protected void removeResourceChangedListeners() {
		if (library == null || library.eResource() == null) {
			return;
		}

		library.eResource().eAdapters().remove(resourceChangedListener);

		for (Iterator iter = library.getMethodPlugins().iterator(); iter
				.hasNext();) {
			MethodPlugin plugin = (MethodPlugin) iter.next();
			plugin.eResource().eAdapters().remove(resourceChangedListener);
		}

		for (Iterator it = library.getPredefinedConfigurations().iterator(); it
				.hasNext();) {
			MethodConfiguration config = (MethodConfiguration) it.next();
			config.eResource().eAdapters().remove(resourceChangedListener);
		}
	}

	/*
	 * public MethodLibrary getLibrary() { Resource resource =
	 * getLibraryResource(); if (resource != null &&
	 * resource.getContents().size() > 0) { for (Iterator it =
	 * resource.getContents().iterator(); it.hasNext();) { Object o = it.next();
	 * if (o instanceof MethodLibrary) { return (MethodLibrary) o; } } }
	 * 
	 * return null; }
	 */

	/**
	 * Opens a method library.
	 * 
	 * @param path
	 *            a <code>File</code> object that contains the path to the
	 *            method library.
	 * @return a <code>MethodLibrary</code>.
	 * @throw <code>LibraryServiceException</code> if an error occurred while
	 *        performing the operation.
	 */
	protected MethodLibrary openMethodLibrary(File path)
			throws LibraryServiceException {
		File libraryXMIFile = new File(path, LIBRARY_XMI);
		if (!libraryXMIFile.exists()) {
			throw new LibraryNotFoundException();
		}

		try {
			skipEventProcessing = true;

			// Lock the method library to prevent access by another EPF composer
			// instance.
			lockMethodLibrary(path);

			// Open the method library project file.
			MethodLibraryProject.openProject(path.getAbsolutePath(), null);

			// Create the resource set.
			MultiFileResourceSetImpl resourceSet = ((MultiFileResourceSetImpl) editingDomain
					.getResourceSet());

			// Load the method library.
			MethodLibrary library = resourceSet.loadLibrary(libraryXMIFile
					.getAbsolutePath());

			// Add a listener to monitor library resource changes.
			addResourceChangedListeners();

//			// already moved to Library Service.
//			notifyListeners(ILibraryChangeListener.OPTION_LOADED, null);

			return library;
		} catch (Exception e) {
			if (debug) {
				DebugTrace.print(e);
			}
			throw new LibraryServiceException(e);
		} finally {
			firePropertyChange(library, PROP_DIRTY);
			skipEventProcessing = false;
		}
	}

	/**
	 * Gets the managed method library resource.
	 * 
	 * @return a method library resource.
	 */
	protected Resource getMethodLibraryResource() {
		List res = getEditingDomain().getResourceSet().getResources();
		if (res.size() > 0) {
			return (Resource) res.get(0);
		}

		return null;
	}

	/**
	 * Gets the managed method library URI.
	 * 
	 * @return the managed method library UI.
	 */
	protected URI getLibraryURI() {
		Resource savedResource = getMethodLibraryResource();
		if (savedResource != null) {
			return savedResource.getURI();
		}

		return null;
	}

	/**
	 * Adds the new packages into the configurations if the parent is in the
	 * configuration.
	 */
	private void addNewPackagesToConfiguration(Collection newobjs) {
		if (newobjs == null || newobjs.size() == 0) {
			return;
		}

		LibraryModificationHelper helper = new LibraryModificationHelper();

		try {
			EObject e, parent;
			for (Iterator it = newobjs.iterator(); it.hasNext();) {
				e = (EObject) it.next();
				if ((e instanceof MethodPackage)
						&& ((parent = e.eContainer()) != null)
						&& (parent instanceof MethodPackage)) {
					Object configs = ((MultiResourceEObject) parent)
							.getOppositeFeatureValue(AssociationHelper.MethodPackage_MethodConfigurations);
					if (configs instanceof List) {
						for (Iterator itconfig = ((List) configs).iterator(); itconfig
								.hasNext();) {
							MethodConfiguration config = (MethodConfiguration) itconfig
									.next();
							List pkgs = config.getMethodPackageSelection();
							if (!pkgs.contains(e)) {
								// pkgs.add(e);
								helper
										.getActionManager()
										.doAction(
												IActionManager.ADD,
												config,
												UmaPackage.eINSTANCE
														.getMethodConfiguration_MethodPackageSelection(),
												e, -1);
							}
						}
					}
				}
			}

			helper.save();

		} catch (RuntimeException e) {
			LibraryPlugin.getDefault().getLogger().logError(e);
		} finally {
			helper.dispose();
		}
	}
}
