//------------------------------------------------------------------------------
// 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.internal;

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

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
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.common.util.WrappedException;
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.utils.FileUtil;
import org.eclipse.epf.library.LibraryPlugin;
import org.eclipse.epf.library.LibraryResources;
import org.eclipse.epf.library.edit.TngAdapterFactory;
import org.eclipse.epf.library.edit.command.IActionManager;
import org.eclipse.epf.library.edit.itemsfilter.FilterConstants;
import org.eclipse.epf.library.events.ILibraryChangeListener;
import org.eclipse.epf.library.layout.ElementLayoutManager;
import org.eclipse.epf.library.layout.LayoutResources;
import org.eclipse.epf.library.prefs.PreferenceConstants;
import org.eclipse.epf.library.services.LibraryModificationHelper;
import org.eclipse.epf.library.services.SafeUpdateController;
import org.eclipse.epf.library.services.VersionManager;
import org.eclipse.epf.library.util.CopyAttachmentsToNewLocation;
import org.eclipse.epf.library.util.LibraryUtil;
import org.eclipse.epf.library.util.Log;
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.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.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.ProcessFamily;
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.epf.uma.util.UmaUtil;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.ListenerList;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.IPropertyListener;

/**
 * Manages a Method Library.
 * 
 * @author Jinhua Xi
 * @author Phong Nguyen Le
 * @author Kelvin Low
 * @since 1.0
 */
public class LibraryProcessor {

	public static final int PROP_DIRTY = 1;

	public static LibraryProcessor instance = null;

	protected AdapterFactoryEditingDomain editingDomain;

	// maintain the currently selected configuration
	// user should call set/getCurrentConfiguration() methods
	// protected String currentConfigName = null;

	private Map saveOptions;

	// private Map saveAsOptions;
	// private Set eClasses;

	private List listeners = new ArrayList();

	// keep the listeners to be deleted, do the delete before each notification
	// and empty this list.
	// don't delete in the middle of the notification since we don't want to
	// change the listener list at this time
	private List tmp_listeners_tobe_deleted = new ArrayList();

	// private boolean isMultipleFiles = false;

	// private boolean dirty = false;

	boolean loaded = false;

	private boolean inOperation = false;

	private VersionManager versionMgr = null;

	private ElementLayoutManager layoutMgr = null;;

	// RATLC00384598 - Export: Infinite loop NullpointerException after
	// exporting and unchecking rup plugin in configuration
	// add this flag to treat the instance as singleton instacne or not
	// only the singleton instance can dispose shared objects.
	private boolean isSingletonInstance = false;

	// the lock on the library's .project file
	private FileLock lock;

	//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 (!inOperation) {
				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());
							}
						}
					}
				});
			}
		}
	};

	/**
	 * add the new packages into the configurations if the parent is in the
	 * configuration
	 * 
	 * @param newobjs
	 */
	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();
		}
	}

	private INotifyChangedListener notifyChangedListener = new INotifyChangedListener() {
		public void notifyChanged(Notification notification) {
			if (!inOperation) {
				int eventType = notification.getEventType();
				switch (eventType) {
				case Notification.ADD: {
					// only for add MethodPlugin, or maybe some other missing
					// stuff (if any) that are not created by a command.
					// anything created by a command are handled by the
					// commandStackListener
					Object notifier = notification.getNotifier();
					Object v = notification.getNewValue();
					if ((notifier instanceof MethodLibrary)
							&& (v instanceof MethodPlugin)) {
						Collection affectedObjects = new ArrayList();
						affectedObjects.add(v);
						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;
				}

				// note: this is the remove of elements from the parent,
				// it might be a delete action, or might be just a remove of
				// references
				// for delete action, the deleted element has no container
				// for removed reference, the removed element still have it's
				// parent
				case Notification.REMOVE: {
					Object notifier = notification.getNotifier();
					Object oldValue = notification.getOldValue();
					Collection affectedObjects = new ArrayList();
					if (oldValue instanceof EObject
							&& ((EObject) oldValue).eContainer() == null) {
						// the element is deleted, sent a delete event
						affectedObjects.add(oldValue);
						notifyListeners(ILibraryChangeListener.OPTION_DELETED,
								affectedObjects);
					} else {
						// otherwise, it's just a remove of references, the
						// notifier is the changed element
						affectedObjects.add(notifier);
						notifyListeners(ILibraryChangeListener.OPTION_CHANGED,
								affectedObjects);
					}
					break;
				}
				case Notification.REMOVE_MANY: {
					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()) {
						// the element is deleted, sent a delete event
						notifyListeners(ILibraryChangeListener.OPTION_DELETED,
								deletedElements);
					}
					if (!removedReferences.isEmpty()) {
						// otherwise, it's just a remove of references, the
						// notifier is the changed element
						notifyListeners(ILibraryChangeListener.OPTION_CHANGED,
								removedReferences);
					}
					break;
				}
				}
			}
		}
	};

	private boolean listenerAdded = false;

	private Adapter libChangedListener = new AdapterImpl() {
		public void notifyChanged(Notification msg) {
			switch (msg.getFeatureID(MethodLibrary.class)) {
			case UmaPackage.METHOD_LIBRARY__METHOD_PLUGINS:
				MethodPlugin plugin;
				switch (msg.getEventType()) {
				case Notification.ADD:
					plugin = (MethodPlugin) msg.getNewValue();
					plugin.eResource().eAdapters().add(resourceChangedListener);
					break;
				case Notification.ADD_MANY:
					for (Iterator iter = ((Collection) msg.getNewValue())
							.iterator(); iter.hasNext();) {
						plugin = (MethodPlugin) iter.next();
						plugin.eResource().eAdapters().add(
								resourceChangedListener);
					}
					break;
				case Notification.REMOVE:
					plugin = (MethodPlugin) msg.getOldValue();
					plugin.eResource().eAdapters().remove(
							resourceChangedListener);
					break;
				case Notification.REMOVE_MANY:
					for (Iterator iter = ((Collection) msg.getOldValue())
							.iterator(); iter.hasNext();) {
						plugin = (MethodPlugin) iter.next();
						plugin.eResource().eAdapters().remove(
								resourceChangedListener);
					}
					break;
				}
			}
		}
	};

	private Adapter resourceChangedListener = new AdapterImpl() {
		public void notifyChanged(org.eclipse.emf.common.notify.Notification msg) {
			if (msg.getFeatureID(null) == Resource.RESOURCE__IS_MODIFIED
					&& msg.getEventType() == org.eclipse.emf.common.notify.Notification.SET) {
				// dirty = true;
				firePropertyChange(msg.getNotifier(), PROP_DIRTY);
			}
		}
	};

	private ListenerList propChangeListeners = new ListenerList();

	private IRefreshListener refreshListener = new IRefreshListener() {

		public void notifyRefreshed(IRefreshEvent event) {
			handleRefreshEvent(event);
		}
		
	};

	public static LibraryProcessor getInstance() {
		if (instance == null) {
			synchronized (LibraryProcessor.class) {
				if (instance == null) {
					instance = new LibraryProcessor();
				}
			}
		}

		return instance;
	}

	/**
	 * @param event
	 */
	protected void handleRefreshEvent(IRefreshEvent event) {
		if(Log.DEBUG) {
			System.out.println("LibraryProcessor.handleRefreshEvent()"); //$NON-NLS-N$
			System.out.println("  refreshed resources: " + event.getRefreshedResources());
		}
		if(!event.getUnloadedObjects().isEmpty()) {
			TngAdapterFactory.INSTANCE.cleanUp();
		}
	}

	private LibraryProcessor() {
		init();

		// this is the main instance, will be treated as singleton
		isSingletonInstance = true;
	}

	public LibraryProcessor(Resource res) {
		this();
		if (res != null) {
			setResource(res);
		}

		isSingletonInstance = false;
	}

	/**
	 * this construct should be called only if you want to maintain your own
	 * LibraryProcessor, such as when for library importing or exporting.
	 * Otherwise, call the static getInstance() method to retrieve the singleton
	 * 
	 * @param libraryPath
	 */
	public LibraryProcessor(String libraryPath) throws Exception {
		this();
		openLibrary(libraryPath);

		isSingletonInstance = false;
	}
	
	public void addPropertyListener(IPropertyListener l) {
		propChangeListeners.add(l);
	}

	public void removePropertyListener(IPropertyListener l) {
		propChangeListeners.remove(l);
	}

	/**
	 * Fires a property changed event.
	 * 
	 * @param propertyId
	 *            the id of the property that changed
	 */
	protected void firePropertyChange(final Object source, final int propertyId) {
		Object[] array = propChangeListeners.getListeners();
		for (int nX = 0; nX < array.length; nX++) {
			final IPropertyListener l = (IPropertyListener) array[nX];

			// since this will cause UI update, which must be executed in the UI
			// thread
			// otherwise, will cause Invalid Thread Access exception
			SafeUpdateController.asyncExec(new Runnable() {
				public void run() {
					l.propertyChanged(source, propertyId);
				}
			});
		}
	}

	private void addLibraryListeners() {
		MethodLibrary library = getLibrary();

		if (library == null || library.eResource() == null)
			return;
		// if(!library.eAdapters().contains(libChangedListener)) {
		// library.eAdapters().add(libChangedListener);
		// }
		if (!library.eResource().eAdapters().contains(resourceChangedListener)) {
			library.eResource().eAdapters().add(resourceChangedListener);
		}
		for (Iterator iter = library.getMethodPlugins().iterator(); iter
				.hasNext();) {
			MethodPlugin plugin = (MethodPlugin) iter.next();
			if (!plugin.eResource().eAdapters().contains(
					resourceChangedListener)) {
				plugin.eResource().eAdapters().add(resourceChangedListener);
			}
		}
	}

	private void removeLibraryListeners() {
		MethodLibrary library = getLibrary();

		if (library == null || library.eResource() == null)
			return;
		// library.eAdapters().remove(libChangedListener);
		library.eResource().eAdapters().remove(resourceChangedListener);
		for (Iterator iter = library.getMethodPlugins().iterator(); iter
				.hasNext();) {
			MethodPlugin plugin = (MethodPlugin) iter.next();
			plugin.eResource().eAdapters().remove(resourceChangedListener);
		}
	}

	public void addListener(ILibraryChangeListener listener) {
		synchronized (listeners) {
			if (!listeners.contains(listener)) {
				listeners.add(listener);
			}
		}
	}

	public void removeListener(ILibraryChangeListener listener) {
		synchronized (tmp_listeners_tobe_deleted) {
			// delay the remove till a notification is requested
			if (!tmp_listeners_tobe_deleted.contains(listener)) {
				tmp_listeners_tobe_deleted.add(listener);
			}
		}
	}

	private synchronized void notifyListeners(final int option,
			final Collection collection) {
		loaded = true;

		try {
			// remove the listeners to be deleted
			if (tmp_listeners_tobe_deleted.size() > 0) {
				for (Iterator it = tmp_listeners_tobe_deleted.iterator(); it
						.hasNext();) {
					Object l = it.next();
					if (listeners.contains(l)) {
						listeners.remove(l);
					}
				}
				tmp_listeners_tobe_deleted.clear();
			}

			// notify listeners,
			// note, the size may change since notify a listener may caused new
			// listeners being added
			// those new listeners will be added to the end of the list so we
			// are ok
			int i = 0;
			while (i < listeners.size()) {
				final ILibraryChangeListener listener = (ILibraryChangeListener) listeners
						.get(i);
				if (listener != null) {
					// since this will cause UI update, which must be executed
					// in the UI thread
					// otherwise, will cause Invalid Thread Access exception

					// execute it in sync mode, otherwise may cause problem
					// since the notification might be executed too late.
					// for example, after the listener object is disposed
					SafeUpdateController.syncExec(new Runnable() {
						public void run() {
							listener.libraryChanged(option, collection);
						}
					});
				}

				i++;
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	private void beginOperation() {
		inOperation = true;
	}

	private void endOperation() {
		inOperation = false;
	}
	
	/**
	 * Checks if the given object is valid in the current resource set
	 * 
	 * @param object
	 * @return
	 */
	public boolean isValid(EObject object) {
		if(object != null && object.eIsProxy()) {
			EObject e = RefreshJob.getInstance().resolve(object);
			return (e instanceof MethodElement
					&& !e.eIsProxy()
					&& UmaUtil.getMethodLibrary(e) == getLibrary());
		}
		return true;

	}
	
	/**
	 * Checks if the library's project is still open. If not, open it
	 */
	/*
	public boolean ensureLibraryProjectOpen(IProgressMonitor monitor) {
		MethodLibrary lib = getLibrary();
		if(lib != null) {
			IProject prj = ResourceUtil.findProject(lib);
			if(prj != null && !prj.isOpen()) {
				try {
					prj.open(IResource.BACKGROUND_REFRESH, monitor);
					return true;
				} catch (CoreException e) {
					LibraryPlugin.getDefault().getLogger().logError(e);
				}
			}
		}
		return false;
	}
	*/
	
	public void openLibrary() throws Exception {
		String openLibrary = getLibraryRootPath().getAbsolutePath()
				+ File.separator + "library.xmi"; //$NON-NLS-1$
		if (openLibrary != null && !openLibrary.equals("")) { //$NON-NLS-1$
			openLibrary(openLibrary);
		}
	}

	public IStatus openLibrary(String libraryPath) throws Exception {
		try {
			beginOperation();
			
			// open the method library as a project in the workspace so it will
			// show up in the Resource view.
			//
			File libDir = new File(libraryPath).getParentFile();
			
			// only close previous library if the new library's project was
			// opened successfully
			// close previous library's project only if it is different than the
			// new one
			closeOpenedLibrary();

			// lock the library
			//
			lockLibrary(libDir);

			ResourceUtil.open(libDir.getAbsolutePath(), null);

			MultiFileResourceSetImpl resourceSet = ((MultiFileResourceSetImpl) editingDomain
					.getResourceSet());
			assignLibrary(resourceSet.loadLibrary(libraryPath));

			IStatus status;
			if (!resourceSet.hasUnresolvedProxy()) {
				String msg = LibraryResources
						.getString("Library.unresolvedProxyError.reason"); //$NON-NLS-1$
				status = new Status(IStatus.WARNING, LibraryPlugin.getDefault()
						.getId(), 0, msg, null);
			} else {
				status = Status.OK_STATUS;
			}

			notifyListeners(ILibraryChangeListener.OPTION_LOADED, null);
			return status;
		} finally {
			endOperation();

			// dirty = false;
			firePropertyChange(getLibrary(), PROP_DIRTY);

			// setCurrentConfiguration(null);
		}

	}

	/**
	 * Assigns library to a new value and attach the change listeners to it.
	 * 
	 * @param lib
	 */
	private void assignLibrary(MethodLibrary lib) {
		removeLibraryListeners();
		addLibraryListeners();
	}

	public void newLibrary(String libraryName, String libraryPath) throws Exception {
		try {
			closeOpenedLibrary();

			// isMultipleFiles = bMultiple;
			beginOperation();

			File lib_path = new File(libraryPath);
			libraryPath = lib_path.getAbsolutePath();

			// open the method library as a project in the workspace so it will
			// show up in the Resource view.
			ResourceUtil.open(libraryPath, null);

			MultiFileResourceSetImpl resourceSet = (MultiFileResourceSetImpl) editingDomain
					.getResourceSet();
			ModelStorage.newLibrary(resourceSet, libraryName, libraryPath, true);
			assignLibrary(resourceSet.getMethodLibrary());

			// lock the library
			lockLibrary(lib_path);
		} finally {
			endOperation();
			// setDirty(true);
			// setCurrentConfiguration(null);
			notifyListeners(ILibraryChangeListener.OPTION_CREATED, null);
		}
	}

	/**
	 * Saves the given resource if it belong to the currently open library
	 * 
	 * @param resource
	 * @throws Exception
	 */
	public void save(Resource resource) throws Exception {
		MultiFileResourceSetImpl resourceSet = ((MultiFileResourceSetImpl) editingDomain
				.getResourceSet());
		if (resourceSet.getResources().contains(resource)) {
			resourceSet.save(resource, saveOptions);
		}
	}

	public void saveLibrary() throws Exception {
		MethodLibrary library = getLibrary();
		if (library != null) {
			doSave(saveOptions);
			firePropertyChange(library, PROP_DIRTY);

			notifyListeners(ILibraryChangeListener.OPTION_SAVED, null);
		}
	}

	/**
	 * Saves the open library under a new location
	 * 
	 * @param newDir
	 *            directory to save the open library to
	 * @param regenerateGUID
	 *            if true, regenerate GUID for all MethodElements in the open
	 *            library
	 */
	public void saveLibraryAs(String newDir, boolean regenerateGUID,
			IProgressMonitor monitor) throws Exception {
		String oldLibDir = getLibraryRootPath()
				.getAbsolutePath();

		// create new IProject
		ResourceUtil.open(newDir, monitor);

		MultiFileResourceSetImpl resourceSet = (MultiFileResourceSetImpl) getLibrary()
				.eResource().getResourceSet();
		resourceSet.saveAs(newDir, regenerateGUID, monitor);

		// handle attachments here
		CopyAttachmentsToNewLocation.copyAttachmentsLibrary(oldLibDir, newDir,
				null, monitor);
	}

	/**
	 * 
	 * @param libraryPath
	 * @param bMultiple
	 * @throws Exception
	 * @deprecated
	 */
	public void saveLibraryAs(String libraryPath, boolean bMultiple)
			throws Exception {
		// Resource res =
		// ((Resource)editingDomain.getResourceSet().getResources().get(0));
		// res.setURI(URI.createFileURI(libraryPath));
		// res.setModified(true);
		//
		// setSaveAsMultipleFolder(bMultiple);
		//        	
		// doSave(saveAsOptions);
		// setDirty(false);
		// isMultipleFiles = bMultiple;
		//        
		// notifyListeners(ILibraryChangeListener.OPTION_SAVED, null);

		throw new UnsupportedOperationException();
	}

	private void setResource(Resource res) {
		if (res != null) {
			MultiFileResourceSetImpl resourceSet = (MultiFileResourceSetImpl) editingDomain
					.getResourceSet();
			resourceSet.reset();
			resourceSet.getResources().add(res);
			assignLibrary(resourceSet.getMethodLibrary());
			notifyListeners(ILibraryChangeListener.OPTION_CHANGED, null);
		}
	}

	private Resource getLibraryResource() {
		List res = getEditingDomain().getResourceSet().getResources();
		if (res.size() > 0) {
			return (Resource) res.get(0);
		}

		return null;
	}

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

	public URI getLibraryURI() {
		Resource savedResource = getLibraryResource();
		if (savedResource != null) {
			return savedResource.getURI();
		}

		return null;
	}

	public URI getRelativeURI(MethodElement element) {
		URI libURI = getLibraryURI();
		URI elemetnURI = element.eResource().getURI();
		URI relUri = elemetnURI.deresolve(libURI);

		return relUri;
	}

	public synchronized ElementLayoutManager getLayoutManager() {
		// no ElementLayoutManager id library is not loaded
		if (layoutMgr == null) {
			if (getLibrary() != null) {
				layoutMgr = new ElementLayoutManager();
			}
		}

		return layoutMgr;
	}

	/*
	public VersionManager getVersionManager() {
		if (versionMgr == null) {
			synchronized (this) {
				if (versionMgr == null) {
					versionMgr = new VersionManager(this);
				}
			}
		}

		return versionMgr;
	}
	*/

	public void setLibrary(MethodLibrary newLib) {
		assignLibrary(newLib);
		Resource res = (Resource) getEditingDomain().getResourceSet()
				.getResources().get(0);
		res.getContents().clear();
		res.getContents().add(newLib);
		notifyListeners(ILibraryChangeListener.OPTION_LOADED, null);
	}

	public AdapterFactoryEditingDomain getEditingDomain() {
		return editingDomain;
	}

	private void init() {
		LibraryPlugin.getDefault().getPreferenceStore()
				.addPropertyChangeListener(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());
						}
					}

				});

		// create the default editing domain for library operation
		List factories = new ArrayList();
		factories.add(new ResourceItemProviderAdapterFactory());
		factories.add(new ReflectiveItemProviderAdapterFactory());

		ComposedAdapterFactory adapterFactory = new ComposedAdapterFactory(
				factories);

		// Create the command stack that will notify this editor as commands are
		// executed.
		BasicCommandStack commandStack = new BasicCommandStack();

		// Create the editing domain with a special command stack.
		MultiFileResourceSetImpl resourceSet = new MultiFileResourceSetImpl();
		
		resourceSet.addRefreshListener(refreshListener);
		RefreshJob.getInstance().setResourceSet(resourceSet);

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

		editingDomain = new AdapterFactoryEditingDomain(adapterFactory,
				commandStack, resourceSet);

		// Create new IContentDescriptionPersister and set it to the
		// ContentDescriptionFactory for use.
		//
		IMethodLibraryPersister contentDescPersister = new MethodLibraryPersister();
		ContentDescriptionFactory
				.setMethodLibraryPersister(contentDescPersister);

		ContentDescriptionFactory.setFileManager(FileManager.getInstance());

		registerEditingDomain(editingDomain);
	}

	public void doSave(Map options) throws Exception {
		if (Log.DEBUG) {
			System.out.println("Save options: " + options); //$NON-NLS-1$
		}

		// List removedResources = new ArrayList();

		// File libDir = new
		// File(((Resource)editingDomain.getResourceSet().getResources().get(0)).getURI().toFileString()).getParentFile();

		try {
			beginOperation();
			MultiFileResourceSetImpl resourceSet = ((MultiFileResourceSetImpl) editingDomain
					.getResourceSet());
			// Save the resource(s)
			//
			resourceSet.save(options);

			((BasicCommandStack) editingDomain.getCommandStack()).saveIsDone();
		} catch (Exception exception) {
			exception.printStackTrace();
			throw exception;
		} finally {
			endOperation();

		}
	}

	/*
	public void discardChanges() {
		for (Iterator iter = editingDomain.getResourceSet().getResources()
				.iterator(); iter.hasNext();) {
			Resource resource = (Resource) iter.next();
			resource.setModified(false);
		}
	}
	*/

	public boolean isLibraryLoaded() {
		return loaded;
	}

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

	private List getMethodModels() {
		MethodLibrary library = getLibrary();
		return (library == null) ? new ArrayList() : library.getMethodPlugins();
	}

	public MethodPlugin getMethodPlugin(String guid) {
		if (guid != null) {	
			for (Iterator it = getMethodModels().iterator(); it.hasNext();) {
				MethodPlugin p = (MethodPlugin) it.next();
				if (guid.equals(p.getGuid())) {
					return p;
				}
			}
		}

		return null;
	}

	/**
	 * get process family by name
	 * 
	 * @param name
	 * @return ProcessFamily
	 */
	public ProcessFamily getProcessFamily(String name) {
		MethodConfiguration config;
		MethodLibrary library = getLibrary();
		if (library != null) {
			List configs = library.getPredefinedConfigurations();
			for (Iterator it = configs.iterator(); it.hasNext();) {
				config = (MethodConfiguration) it.next();
				if ((config instanceof ProcessFamily)
						&& config.getName().equals(name)) {
					return (ProcessFamily) config;
				}
			}
		}

		return null;
	}

	/**
	 * register edting domain with this library
	 * 
	 * @param domain
	 */
	public void registerEditingDomain(AdapterFactoryEditingDomain domain) {
		/**
		 * this notifys all changes, not just the content change, only pick the
		 * ones for element updates. The other operations are colvered by the
		 * Command events
		 */
		((ComposedAdapterFactory) domain.getAdapterFactory())
				.addListener(notifyChangedListener);

		// Add a listener to set the most recent command's affected objects to
		// be the selection of the viewer with focus.
		domain.getCommandStack().addCommandStackListener(commandStackListener);

	}

	public void listenTo(ComposedAdapterFactory adapterFactory) {
		adapterFactory.addListener(notifyChangedListener);
	}

	public void stopListeningTo(ComposedAdapterFactory adapterFactory) {
		adapterFactory.removeListener(notifyChangedListener);
	}

	public void listenTo(CommandStack commandStack) {
		commandStack.addCommandStackListener(commandStackListener);
	}

	public void stopListeningTo(CommandStack commandStack) {
		commandStack.removeCommandStackListener(commandStackListener);
	}

	/**
	 * @return Returns the saveOptions.
	 */
	public Map getSaveOptions() {
		return saveOptions;
	}

	public File getLibraryRootPath() {
		URI uri = this.getLibraryURI();
		if (uri != null) {
			String path = uri.toFileString();
			File f = new File(path);
			return f.getParentFile();
		}

		return null;
	}

	public void addMethodPlugin(final MethodPlugin model) {

		// since this will cause UI update, which must be executed in the UI
		// thread
		// otherwise, will cause Invalid Thread Access exception
		final Exception[] exceptions = new Exception[1];

		SafeUpdateController.syncExec(new Runnable() {
			public void run() {
				getLibrary().getMethodPlugins().add(model);

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

				model.eResource().eAdapters().add(resourceChangedListener);
			}
		});

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

	/**
	 * Enables/disables notification entirely.
	 * 
	 * @param b
	 */
	public void setNotificationEnabled(boolean b) {
		editingDomain.getResourceSet().eSetDeliver(b);
	}

	public boolean isNotificationEnabled() {
		return editingDomain.getResourceSet().eDeliver();
	}

	/**
	 * call this method when the current library is to be closed. for example,
	 * when you try to open a nother library for create a new library
	 */
	public void closeOpenedLibrary() {
		notifyListeners(ILibraryChangeListener.OPTION_LIBRARY_CLOSED, null);

		if (Log.DEBUG) {
			System.out
					.println("Used memory before closing library: " + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())); //$NON-NLS-1$
		}
		// RATLC00384598 - Export: Infinite loop NullpointerException after
		// exporting and unchecking rup plugin in configuration
		// so don't dispose it if not the main singleton instance
		if (isSingletonInstance) {
			// dispose all item providers
			//
			TngAdapterFactory.INSTANCE.reset();

			// clear filter constants hashMap
			if (FilterConstants.hashMap != null) {
				FilterConstants.hashMap.clear();
			}
		}

		if (this.layoutMgr != null) {
			this.layoutMgr.clear();
		}


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

		/*
		 * if ( this.depMgr != null ) { this.depMgr.clear(); } this.depMgr =
		 * null;
		 */

		this.layoutMgr = null;

		// this.invalidGuids.clear();

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

		resourceSet.clearErrors();
		MethodLibrary library = getLibrary();

		unlockLibrary();

		resourceSet.reset();
		RefreshJob.getInstance().reset();

		Runtime.getRuntime().gc();
		if (Log.DEBUG) {
			System.out
					.println("Used memory after closing library: " + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())); //$NON-NLS-1$
		}

	}

	private void unlockLibrary() {
		// release lock on old lib's project
		try {
			if (lock != null) {
				lock.release();
				lock.channel().close();
			}
		} catch (Exception e) {
			LibraryPlugin.getDefault().getLogger().logError(e);
		}
	}

	/**
	 * Locks the .lock file in the given path. Creates a .lock file if one does
	 * not exist
	 * 
	 * @param file
	 *            path to library
	 */
	public void lockLibrary(File libDirFile) {
		File lockFile = new File(libDirFile, ResourceUtil.LOCK_FILENAME);
		lock = FileUtil.lockFile(lockFile);
	}

}