/*******************************************************************************
 * Copyright (c) 2005, 2008 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 API and implementation
 *	   David Schneider, david.schneider@unisys.com - [142500] WTP properties pages fonts don't follow Eclipse preferences
 *     Stefan Dimov, stefan.dimov@sap.com - bugs 207826, 222651
 *******************************************************************************/
package org.jboss.ide.eclipse.as.wtp.ui.propertypage;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableLayout;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.jst.j2ee.internal.plugin.J2EEUIMessages;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.eclipse.wst.common.componentcore.ComponentCore;
import org.eclipse.wst.common.componentcore.datamodel.properties.ICreateReferenceComponentsDataModelProperties;
import org.eclipse.wst.common.componentcore.internal.ComponentResource;
import org.eclipse.wst.common.componentcore.internal.DependencyType;
import org.eclipse.wst.common.componentcore.internal.StructureEdit;
import org.eclipse.wst.common.componentcore.internal.WorkbenchComponent;
import org.eclipse.wst.common.componentcore.internal.operation.CreateReferenceComponentsDataModelProvider;
import org.eclipse.wst.common.componentcore.internal.operation.RemoveReferenceComponentsDataModelProvider;
import org.eclipse.wst.common.componentcore.internal.resources.VirtualArchiveComponent;
import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
import org.eclipse.wst.common.componentcore.resources.IVirtualFolder;
import org.eclipse.wst.common.componentcore.resources.IVirtualReference;
import org.eclipse.wst.common.frameworks.datamodel.DataModelFactory;
import org.eclipse.wst.common.frameworks.datamodel.IDataModel;
import org.eclipse.wst.common.frameworks.datamodel.IDataModelOperation;
import org.eclipse.wst.common.frameworks.datamodel.IDataModelProvider;
import org.eclipse.wst.server.ui.wizard.WizardFragment;
import org.jboss.ide.eclipse.as.wtp.ui.Messages;
import org.jboss.ide.eclipse.as.wtp.ui.WTPOveridePlugin;
import org.jboss.ide.eclipse.as.wtp.ui.propertypage.DependencyPageExtensionManager.ReferenceExtension;
 
public class AddModuleDependenciesPropertiesPage implements Listener,
		IModuleDependenciesControl, ILabelProviderListener {

	private static final String DEPLOY_PATH_PROPERTY = new Integer(0).toString();
	private static final String SOURCE_PROPERTY = new Integer(1).toString();
	
	
	protected final String PATH_SEPARATOR = ComponentDependencyContentProvider.PATH_SEPARATOR;
	private boolean hasInitialized = false;
	protected final IProject project;
	protected final ModuleAssemblyRootPage propPage;
	protected IVirtualComponent rootComponent = null;
	protected Text componentNameText;
	protected Label availableModules;
	protected TableViewer availableComponentsViewer;
	protected Button addMappingButton, addReferenceButton, editReferenceButton, removeButton;
	protected Composite buttonColumn;
	protected static final IStatus OK_STATUS = IDataModelProvider.OK_STATUS;
	protected Listener tableListener;
	protected Listener labelListener;

	// Mappings that existed when the page was opened (or last saved)
	protected HashMap<IVirtualComponent, String> oldComponentToRuntimePath = new HashMap<IVirtualComponent, String>();

	// Mappings that are current
	protected HashMap<IVirtualComponent, String> objectToRuntimePath = new HashMap<IVirtualComponent, String>();

	protected ArrayList<IVirtualComponent> consumedReferences = new ArrayList<IVirtualComponent>();
	
	// A single list of wb-resource mappings. If there's any change, 
	// all old will be removed and new ones added
	protected ArrayList<ComponentResourceProxy> resourceMappings = new ArrayList<ComponentResourceProxy>();
	
	// keeps track if a change has occurred in wb-resource mappings
	protected boolean resourceMappingsChanged = false;
	
	/**
	 * Constructor for AddModulestoEARPropertiesControl.
	 */
	public AddModuleDependenciesPropertiesPage(final IProject project,
			final ModuleAssemblyRootPage page) {
		this.project = project;
		this.propPage = page;
		rootComponent = ComponentCore.createComponent(project);
	}

	/*
	 * UI Creation Methods
	 */

	public Composite createContents(final Composite parent) {
		Composite composite = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.marginWidth = 0;
		layout.marginWidth = 0;
		composite.setLayout(layout);
		composite.setLayoutData(new GridData(GridData.FILL_BOTH));
		ModuleAssemblyRootPage.createDescriptionComposite(composite,
			Messages.ModuleDependencyDescription);
		createListGroup(composite);
		refresh();
		Dialog.applyDialogFont(parent);
		return composite;
	}

	protected void createListGroup(Composite parent) {
		Composite listGroup = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.numColumns = 1;
		layout.marginWidth = 0;
		layout.marginHeight = 0;
		listGroup.setLayout(layout);
		GridData gData = new GridData(GridData.FILL_BOTH);
		gData.horizontalIndent = 5;
		listGroup.setLayoutData(gData);

		availableModules = new Label(listGroup, SWT.NONE);
		gData = new GridData(GridData.HORIZONTAL_ALIGN_FILL
				| GridData.VERTICAL_ALIGN_FILL);
		availableModules.setText("Module Assembly"); //$NON-NLS-1$ 
		availableModules.setLayoutData(gData);
		createTableComposite(listGroup);
	}

	protected void createTableComposite(Composite parent) {
		Composite composite = new Composite(parent, SWT.NONE);
		GridData gData = new GridData(GridData.FILL_BOTH);
		composite.setLayoutData(gData);
		fillComposite(composite);
	}

	public void fillComposite(Composite parent) {
		GridLayout layout = new GridLayout();
		layout.numColumns = 2;
		layout.marginHeight = 0;
		parent.setLayout(layout);
		parent.setLayoutData(new GridData(GridData.FILL_BOTH));
		createTable(parent);
		createButtonColumn(parent);
	}

	protected void createButtonColumn(Composite parent) {
		buttonColumn = createButtonColumnComposite(parent);
		GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END);
		buttonColumn.setLayoutData(data);
		createPushButtons();
	}

	protected void createPushButtons() {
		addMappingButton = createPushButton("Add Folder...");
		addReferenceButton = createPushButton("Add Reference...");
		editReferenceButton = createPushButton("Edit Reference...");
		removeButton = createPushButton("Remove selected");
	}

	protected Button createPushButton(String label) {
		Button aButton = new Button(buttonColumn, SWT.PUSH);
		aButton.setText(label);
		aButton.addListener(SWT.Selection, this);
		aButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		return aButton;
	}

	public Composite createButtonColumnComposite(Composite parent) {
		Composite aButtonColumn = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.numColumns = 1;
		layout.marginHeight = 0;
		layout.marginWidth = 0;
		aButtonColumn.setLayout(layout);
		GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL
				| GridData.VERTICAL_ALIGN_BEGINNING);
		aButtonColumn.setLayoutData(data);
		return aButtonColumn;
	}

	public Group createGroup(Composite parent) {
		return new Group(parent, SWT.NULL);
	}

	protected void createTable(Composite parent) {
		if (rootComponent != null) {
			availableComponentsViewer = createAvailableComponentsViewer(parent);
			GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL
					| GridData.FILL_VERTICAL);
			availableComponentsViewer.getTable().setLayoutData(gd);

			ComponentDependencyContentProvider provider = createProvider();
			provider.setRuntimePaths(objectToRuntimePath);
			provider.setResourceMappings(resourceMappings);
			availableComponentsViewer.setContentProvider(provider);
			availableComponentsViewer.setLabelProvider(provider);
			addTableListeners();
		}
	}

	/**
	 * Subclasses should over-ride this and extend the class
	 */
	protected ComponentDependencyContentProvider createProvider() {
		return new ComponentDependencyContentProvider(this);
	}

	/*
	 * Listeners of various events
	 */

	protected void addTableListeners() {
		addHoverHelpListeners();
		addDoubleClickListener();
		addSelectionListener();
	}

	protected void addHoverHelpListeners() {
		final Table table = availableComponentsViewer.getTable();
		createLabelListener(table);
		createTableListener(table);
		table.addListener(SWT.Dispose, tableListener);
		table.addListener(SWT.KeyDown, tableListener);
		table.addListener(SWT.MouseMove, tableListener);
		table.addListener(SWT.MouseHover, tableListener);
	}

	protected void createLabelListener(final Table table) {
		labelListener = new Listener() {
			public void handleEvent(Event event) {
				Label label = (Label) event.widget;
				Shell shell = label.getShell();
				switch (event.type) {
				case SWT.MouseDown:
					Event e = new Event();
					e.item = (TableItem) label.getData("_TABLEITEM"); //$NON-NLS-1$
					table.setSelection(new TableItem[] { (TableItem) e.item });
					table.notifyListeners(SWT.Selection, e);
					shell.dispose();
					table.setFocus();
					break;
				case SWT.MouseExit:
					shell.dispose();
					break;
				}
			}
		};
	}

	protected void createTableListener(final Table table) {
		tableListener = new Listener() {
			Shell tip = null;
			Label label = null;

			public void handleEvent(Event event) {
				switch (event.type) {
				case SWT.Dispose:
				case SWT.KeyDown:
				case SWT.MouseMove: {
					if (tip == null)
						break;
					tip.dispose();
					tip = null;
					label = null;
					break;
				}
				case SWT.MouseHover: {
					TableItem item = table.getItem(new Point(event.x, event.y));
					if (item != null && item.getData() != null && !canEditPath(item.getData())) {
						if (tip != null && !tip.isDisposed())
							tip.dispose();
						tip = new Shell(PlatformUI.getWorkbench()
								.getActiveWorkbenchWindow().getShell(),
								SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL);
						tip.setBackground(Display.getDefault().getSystemColor(
								SWT.COLOR_INFO_BACKGROUND));
						FillLayout layout = new FillLayout();
						layout.marginWidth = 2;
						tip.setLayout(layout);
						label = new Label(tip, SWT.WRAP);
						label.setForeground(Display.getDefault()
								.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
						label.setBackground(Display.getDefault()
								.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
						label.setData("_TABLEITEM", item); //$NON-NLS-1$
						label.setText(J2EEUIMessages
										.getResourceString(J2EEUIMessages.HOVER_HELP_FOR_DISABLED_LIBS));
						label.addListener(SWT.MouseExit, labelListener);
						label.addListener(SWT.MouseDown, labelListener);
						Point size = tip.computeSize(SWT.DEFAULT, SWT.DEFAULT);
						Rectangle rect = item.getBounds(0);
						Point pt = table.toDisplay(rect.x, rect.y);
						tip.setBounds(pt.x, pt.y - size.y, size.x, size.y);
						tip.setVisible(true);
					}
				}
				}
			}
		};
	}

	protected boolean canEditPath(Object data) {
		if( data == null ) return false;
		if( !(data instanceof VirtualArchiveComponent)) return true;
		
		VirtualArchiveComponent d2 = (VirtualArchiveComponent)data;
		boolean sameProject = d2.getWorkspaceRelativePath() != null
			&& d2.getWorkspaceRelativePath().segment(0)
				.equals(rootComponent.getProject().getName());
		return !(sameProject && isPhysicallyAdded(d2));
	}
	
	protected void addDoubleClickListener() {
		availableComponentsViewer.setColumnProperties(new String[] { 
				DEPLOY_PATH_PROPERTY, SOURCE_PROPERTY });
		
		CellEditor[] editors = new CellEditor[] { 
				new TextCellEditor(availableComponentsViewer.getTable()),
				new TextCellEditor()};
		availableComponentsViewer.setCellEditors(editors);
		availableComponentsViewer
				.setCellModifier(new RuntimePathCellModifier());
	}

	protected void addSelectionListener() {
		availableComponentsViewer.addSelectionChangedListener(
				new ISelectionChangedListener(){
					public void selectionChanged(SelectionChangedEvent event) {
						viewerSelectionChanged();
					}
				});
	}
	
	protected void viewerSelectionChanged() {
		editReferenceButton.setEnabled(hasEditWizardPage(getSelectedObject()));
		removeButton.setEnabled(getSelectedObject() != null && canEditPath(getSelectedObject()));
	}
	
	protected boolean hasEditWizardPage(Object o) {
		if( o == null || !(o instanceof IVirtualComponent)) 
			return false;
		WizardFragment wf = NewReferenceWizard.getFirstEditingFragment((IVirtualComponent)o);
		return wf != null;
	}
	
	protected Object getSelectedObject() {
		IStructuredSelection sel = (IStructuredSelection)availableComponentsViewer.getSelection();
		return sel.getFirstElement();
	}
	
	private class RuntimePathCellModifier implements ICellModifier {

		public boolean canModify(Object element, String property) {
			if( property.equals(DEPLOY_PATH_PROPERTY)) {
				if (element instanceof VirtualArchiveComponent) {
					try {
						return canEditPath(element);
					} catch (IllegalArgumentException iae) {
					}
				}
				return true;
			}
			return false;
		}

		public Object getValue(Object element, String property) {
			Object data = element; //((TableItem)element).getData();
			if( data instanceof IVirtualComponent ) {
				return objectToRuntimePath.get(element) == null ? new Path("/") //$NON-NLS-1$
						.toString() : objectToRuntimePath.get(element);
			} else if( data instanceof ComponentResourceProxy) {
				return ((ComponentResourceProxy)data).runtimePath.toString();
			}
			return new Path("/");
		}

		public void modify(Object element, String property, Object value) {
			if (property.equals(DEPLOY_PATH_PROPERTY)) {
				TableItem item = (TableItem) element;
				if( item.getData() instanceof IVirtualComponent) {
					objectToRuntimePath.put((IVirtualComponent)item.getData(), (String) value);
				} else if( item.getData() instanceof ComponentResourceProxy) {
					ComponentResourceProxy c = ((ComponentResourceProxy)item.getData());
					c.runtimePath = new Path((String)value);
					resourceMappingsChanged = true;
				}
				refresh();
			}
		}

	}

	public void handleEvent(Event event) {
		if( event.widget == addMappingButton) 
			handleAddMappingButton();
		else if( event.widget == addReferenceButton) 
			handleAddReferenceButton();
		else if( event.widget == editReferenceButton ) 
			handleEditReferenceButton();
		else if( event.widget == removeButton ) 
			handleRemoveSelectedButton();
	}

	protected void handleAddMappingButton() {
		AddFolderDialog afd = new AddFolderDialog(addMappingButton.getShell(), project);
		if( afd.open() == Window.OK) {
			IContainer c = afd.getSelected();
			if( c != null ) {
				IPath p = c.getProjectRelativePath();
				ComponentResourceProxy proxy = new ComponentResourceProxy(p, new Path("/"));
				resourceMappings.add(proxy);
				refresh();
			}
		}
	}
	
	protected void handleAddReferenceButton() {
		showReferenceWizard(false);
	}

	protected void handleEditReferenceButton() {
		showReferenceWizard(true);
	}

	protected ReferenceExtension[] getReferenceExtensions() {
		return DependencyPageExtensionManager.getManager().getReferenceExtensions();
	}
	
	protected void showReferenceWizard(boolean editing) {

		NewReferenceWizard wizard = new NewReferenceWizard(getReferenceExtensions());
		// fill the task model
		wizard.getTaskModel().putObject(NewReferenceWizard.PROJECT, project);
		wizard.getTaskModel().putObject(NewReferenceWizard.ROOT_COMPONENT, rootComponent);

		IVirtualComponent selected = null;
		if( editing ) {
			Object o = ((IStructuredSelection)availableComponentsViewer.getSelection()).getFirstElement();
			if( o instanceof IVirtualComponent ) {
				selected = (IVirtualComponent)o;
				wizard.getTaskModel().putObject(NewReferenceWizard.COMPONENT, selected);
				wizard.getTaskModel().putObject(NewReferenceWizard.COMPONENT_PATH, objectToRuntimePath.get(selected));
			}
		}
		
		WizardDialog wd = new WizardDialog(addReferenceButton.getShell(), wizard);
		if( wd.open() != Window.CANCEL) {
			if( editing && selected != null) {
				// remove old
				objectToRuntimePath.remove(selected); 
				consumedReferences.remove(selected);
			}
			
			Object c1 = wizard.getTaskModel().getObject(NewReferenceWizard.COMPONENT);
			Object p1 = wizard.getTaskModel().getObject(NewReferenceWizard.COMPONENT_PATH);
			DependencyType type = (DependencyType)wizard.getTaskModel().getObject(NewReferenceWizard.DEPENDENCY_TYPE);
			boolean consumed = type == null ? false : type.equals(DependencyType.CONSUMES_LITERAL);
			IVirtualComponent[] compArr = c1 instanceof IVirtualComponent ? 
					new IVirtualComponent[] { (IVirtualComponent)c1 } : 
						(IVirtualComponent[])c1; 
			String[] pathArr = p1 instanceof String ? 
							new String[] { (String)p1 } : 
								(String[])p1;
			for( int i = 0; i < compArr.length; i++ ) {
				objectToRuntimePath.put(compArr[i], pathArr[i]);
				if( consumed )
					consumedReferences.add(compArr[i]);
			}
			refresh();
		}
	}
	
	protected void handleRemoveSelectedButton() {
		ISelection sel = availableComponentsViewer.getSelection();
		if( sel instanceof IStructuredSelection ) {
			Object o = ((IStructuredSelection)sel).getFirstElement();
			if( o instanceof IVirtualComponent)
				objectToRuntimePath.remove(o);
			else if( o instanceof ComponentResourceProxy) 
				resourceMappings.remove(o);
			refresh();
		}
	}

	public TableViewer createAvailableComponentsViewer(Composite parent) {
		int flags = SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI;

		Table table = new Table(parent, flags);
		availableComponentsViewer = new TableViewer(table);

		// set up table layout
		TableLayout tableLayout = new org.eclipse.jface.viewers.TableLayout();
		tableLayout.addColumnData(new ColumnWeightData(400, true));
		tableLayout.addColumnData(new ColumnWeightData(500, true));
		table.setLayout(tableLayout);
		table.setHeaderVisible(true);
		table.setLinesVisible(true);
		availableComponentsViewer.setSorter(null);

		TableColumn bndColumn = new TableColumn(table, SWT.NONE, 0);
		bndColumn.setText(Messages.AddModuleDependenciesPropertiesPage_DeployPathColumn);
		bndColumn.setResizable(true);

		TableColumn projectColumn = new TableColumn(table, SWT.NONE, 1);
		projectColumn.setText(Messages.AddModuleDependenciesPropertiesPage_SourceColumn);
		projectColumn.setResizable(true);

		tableLayout.layout(table, true);
		return availableComponentsViewer;

	}

	protected boolean isPhysicallyAdded(VirtualArchiveComponent component) {
		try {
			component.getProjectRelativePath();
			return true;
		} catch (IllegalArgumentException e) {
			return false;
		}
	}

	/**
	 * This should only be called on changes, such as adding a project
	 * reference, adding a lib reference etc.
	 * 
	 * It will reset the input, manually re-add missing elements, and do other
	 * tasks
	 */
	public void refresh() {
		resetTableUI();
		if (!hasInitialized) {
			initialize();
			resetTableUI();
		}

	}

	protected void resetTableUI() {
		IWorkspaceRoot input = ResourcesPlugin.getWorkspace().getRoot();
		availableComponentsViewer.setInput(input);
		GridData data = new GridData(GridData.FILL_BOTH);
		int numlines = Math.min(10, availableComponentsViewer.getTable()
				.getItemCount());
		data.heightHint = availableComponentsViewer.getTable().getItemHeight()
				* numlines;
		availableComponentsViewer.getTable().setLayoutData(data);
		GridData btndata = new GridData(GridData.HORIZONTAL_ALIGN_FILL
				| GridData.VERTICAL_ALIGN_BEGINNING);
		buttonColumn.setLayoutData(btndata);
	}

	protected IVirtualReference[] getHardReferencesFromComponent(IVirtualComponent component) {
		return component.getReferences();
	}
	
	protected void initialize() {
		IVirtualReference[] refs = getHardReferencesFromComponent(rootComponent);
		IVirtualComponent comp;
		for( int i = 0; i < refs.length; i++ ) { 
			comp = refs[i].getReferencedComponent();
			IPath val = refs[i].getRuntimePath();
			if( refs[i].getDependencyType() != IVirtualReference.DEPENDENCY_TYPE_CONSUMES)
				if( refs[i].getArchiveName() != null )
					val = val.append(refs[i].getArchiveName());
			
			objectToRuntimePath.put(comp, val.toString());
			oldComponentToRuntimePath.put((IVirtualComponent) comp, val.toString());
			if( refs[i].getDependencyType() == DependencyType.CONSUMES)
				consumedReferences.add(comp);
		}

		ComponentResource[] allMappings = findAllMappings();
		for( int i = 0; i < allMappings.length; i++ ) {
			resourceMappings.add(new ComponentResourceProxy(
					allMappings[i].getSourcePath(), allMappings[i].getRuntimePath()
			));
		}
		removeButton.setEnabled(false);
		editReferenceButton.setEnabled(false);
		hasInitialized = true;
	}

	protected ComponentResource[] findAllMappings() {
		StructureEdit structureEdit = null;
		try {
			structureEdit = StructureEdit.getStructureEditForRead(project);
			WorkbenchComponent component = structureEdit.getComponent();
			Object[] arr = component.getResources().toArray();
			ComponentResource[] result = new ComponentResource[arr.length];
			for( int i = 0; i < arr.length; i++ )
				result[i] = (ComponentResource)arr[i];
			return result;
		} catch(Exception e) {
		} finally {
			structureEdit.dispose();
		}
		return new ComponentResource[]{};
	}
	
	public class ComponentResourceProxy {
		public IPath source, runtimePath;
		public ComponentResourceProxy(IPath source, IPath runtimePath) {
			this.source = source;
			this.runtimePath = runtimePath;
		}
	}
	
	/*
	 * Clean-up methods are below. These include performCancel, performDefaults,
	 * performOK, and any other methods that are called *only* by this one.
	 */
	public void setVisible(boolean visible) {
	}

	public void performDefaults() {
	}

	public boolean performCancel() {
		return true;
	}

	public void dispose() {
		Table table = null;
		if (availableComponentsViewer != null) {
			table = availableComponentsViewer.getTable();
		}
		if (table == null || tableListener == null)
			return; 
		table.removeListener(SWT.Dispose, tableListener);
		table.removeListener(SWT.KeyDown, tableListener);
		table.removeListener(SWT.MouseMove, tableListener);
		table.removeListener(SWT.MouseHover, tableListener);
	}

	
	
	/*
	 * This is where the OK work goes. Lots of it. Watch your head.
	 * xiao xin
	 */
	protected boolean preHandleChanges(IProgressMonitor monitor) {
		return true;
	}

	protected boolean postHandleChanges(IProgressMonitor monitor) {
		return true;
	}

	public boolean performOk() {
		boolean result = true;
		result &= saveResourceChanges();
		result &= saveReferenceChanges();
		return result;
	}
	
	protected boolean saveResourceChanges() {
		removeAllResourceMappings();
		addNewResourceMappings();
		return true;
	}
	protected boolean addNewResourceMappings() {
		ComponentResourceProxy[] proxies = (ComponentResourceProxy[]) resourceMappings.toArray(new ComponentResourceProxy[resourceMappings.size()]);
		IVirtualFolder rootFolder = rootComponent.getRootFolder();
		for( int i = 0; i < proxies.length; i++ ) {
			try {
				rootFolder.getFolder(proxies[i].runtimePath).createLink(proxies[i].source, 0, null);
			} catch( CoreException ce ) {
			}
		}
		resourceMappingsChanged = false;
		return true;
	}
	
	protected boolean removeAllResourceMappings() {
		StructureEdit moduleCore = null;
		try {
			moduleCore = StructureEdit.getStructureEditForWrite(project);
			moduleCore.getComponent().getResources().clear();
		}
		finally {
			if (moduleCore != null) {
				moduleCore.saveIfNecessary(new NullProgressMonitor());
				moduleCore.dispose();
			}
		}
		return true;
	}
	
	protected boolean saveReferenceChanges() {
		// Fill our delta lists
		ArrayList<IVirtualComponent> added = new ArrayList<IVirtualComponent>();
		ArrayList<IVirtualComponent> removed = new ArrayList<IVirtualComponent>();
		ArrayList<IVirtualComponent> changed = new ArrayList<IVirtualComponent>();

		Iterator<IVirtualComponent> j = oldComponentToRuntimePath.keySet().iterator();
		Object key, val;
		while (j.hasNext()) {
			key = j.next();
			val = oldComponentToRuntimePath.get(key);
			if( !objectToRuntimePath.containsKey(key))
				removed.add((IVirtualComponent)key);
			else if (!val.equals(objectToRuntimePath.get(key)))
				changed.add((IVirtualComponent)key);
		}

		j = objectToRuntimePath.keySet().iterator();
		while (j.hasNext()) {
			key = j.next();
			if (!oldComponentToRuntimePath.containsKey(key))
				added.add((IVirtualComponent)key);
		}

		NullProgressMonitor monitor = new NullProgressMonitor();
		boolean subResult = preHandleChanges(monitor);
		if( !subResult )
			return false;
		
		handleDeltas(removed, changed, added);
		subResult &= postHandleChanges(monitor);
		
		// Now update the variables
		oldComponentToRuntimePath.clear();
		ArrayList<IVirtualComponent> keys = new ArrayList<IVirtualComponent>();
		keys.addAll(objectToRuntimePath.keySet());
		Iterator<IVirtualComponent> i = keys.iterator();
		while(i.hasNext()) {
			IVirtualComponent vc = i.next();
			String path = objectToRuntimePath.get(vc);
			oldComponentToRuntimePath.put(vc, path);
		}
		return subResult;
	}
	
	// Subclass can override if it has a good way to handle changed elements
	protected void handleDeltas(ArrayList<IVirtualComponent> removed, 
			ArrayList<IVirtualComponent> changed, ArrayList<IVirtualComponent> added) {
		ArrayList<IVirtualComponent> removed2 = new ArrayList<IVirtualComponent>();
		ArrayList<IVirtualComponent> added2 = new ArrayList<IVirtualComponent>();
		removed2.addAll(removed);
		removed2.addAll(changed);
		added2.addAll(added);
		added2.addAll(changed);

		// meld the changed into the added / removed for less efficiency ;) 
		// basically we lack "change" operations and only have add / remove
		handleRemoved(removed2);
		handleAdded(added2);
	}	
	protected void handleRemoved(ArrayList<IVirtualComponent> removed) {
		// If it's removed it should *only* be a virtual component already
		Iterator<IVirtualComponent> i = removed.iterator();
		IVirtualComponent component;
		while(i.hasNext()) {
			try {
				component = i.next();
				IDataModelOperation operation = getRemoveComponentOperation(component);
				operation.execute(null, null);
			} catch( ExecutionException e) {
				WTPOveridePlugin.logError(e);
			}
		}
	}
	
	protected IDataModelOperation getRemoveComponentOperation(IVirtualComponent component) {
		String path, archiveName;
		path = archiveName = null;
		
		if( !consumedReferences.contains(component)) {
			path = new Path(oldComponentToRuntimePath.get(component)).removeLastSegments(1).toString();
			archiveName = new Path(oldComponentToRuntimePath.get(component)).lastSegment(); 
		} else {
			path = oldComponentToRuntimePath.get(component);
		}

		IDataModelProvider provider = getRemoveReferenceDataModelProvider(component);
		IDataModel model = DataModelFactory.createDataModel(provider);
		model.setProperty(ICreateReferenceComponentsDataModelProperties.SOURCE_COMPONENT, rootComponent);
		List<IVirtualComponent> modHandlesList = (List<IVirtualComponent>) model.getProperty(ICreateReferenceComponentsDataModelProperties.TARGET_COMPONENT_LIST);
		modHandlesList.add(component);
		model.setProperty(ICreateReferenceComponentsDataModelProperties.TARGET_COMPONENT_LIST, modHandlesList);
        model.setProperty(ICreateReferenceComponentsDataModelProperties.TARGET_COMPONENTS_DEPLOY_PATH, path);
		Map<IVirtualComponent, String> uriMap = new HashMap<IVirtualComponent, String>();
		uriMap.put(component, archiveName);
		model.setProperty(ICreateReferenceComponentsDataModelProperties.TARGET_COMPONENTS_TO_URI_MAP, uriMap);
		return model.getDefaultOperation();
	}
	
	protected IDataModelProvider getRemoveReferenceDataModelProvider(IVirtualComponent component) {
		return new RemoveReferenceComponentsDataModelProvider();
	}

	
	protected void handleChanged(ArrayList<IVirtualComponent> changed) {
		Iterator<IVirtualComponent> i = changed.iterator(); 
		IVirtualComponent component;
		IVirtualReference ref;
		IPath p;
		while(i.hasNext()) {
			component = i.next();
			ref = rootComponent.getReference(component.getName());
			p = new Path(objectToRuntimePath.get(component));
			ref.setRuntimePath(p);
		}
	}
	
	protected void handleAdded(ArrayList<IVirtualComponent> added) {
		final ArrayList<IVirtualComponent> components = new ArrayList<IVirtualComponent>();
		Iterator<IVirtualComponent> i = added.iterator();
		IVirtualComponent o;
		while(i.hasNext()) {
			o = i.next();
			components.add((IVirtualComponent)o);
		}
		
		IWorkspaceRunnable runnable = new IWorkspaceRunnable(){
			public void run(IProgressMonitor monitor) throws CoreException{
				addComponents(components);
			}
		};
		try {
			ResourcesPlugin.getWorkspace().run(runnable, new NullProgressMonitor());
		} catch( CoreException e ) {
			WTPOveridePlugin.logError(e);
		}
	}
	
	protected void addComponents(ArrayList<IVirtualComponent> components) throws CoreException {
		Iterator<IVirtualComponent> i = components.iterator();
		while(i.hasNext()) {
			addOneComponent(i.next());
		}
	}
	
	protected IDataModelProvider getAddReferenceDataModelProvider(IVirtualComponent component) {
		return new CreateReferenceComponentsDataModelProvider();
	}
	
	protected void addOneComponent(IVirtualComponent component) throws CoreException {
		String path, archiveName;
		path = archiveName = null; 
		if( !consumedReferences.contains(component)) {
			path = new Path(objectToRuntimePath.get(component)).removeLastSegments(1).toString();
			archiveName = new Path(objectToRuntimePath.get(component)).lastSegment(); 
		} else {
			path = objectToRuntimePath.get(component);
		}

		IDataModelProvider provider = getAddReferenceDataModelProvider(component);
		IDataModel dm = DataModelFactory.createDataModel(provider);
		
		dm.setProperty(ICreateReferenceComponentsDataModelProperties.SOURCE_COMPONENT, rootComponent);
		dm.setProperty(ICreateReferenceComponentsDataModelProperties.TARGET_COMPONENT_LIST, Arrays.asList(component));
		
		//[Bug 238264] the uri map needs to be manually set correctly
		if( archiveName != null ) {
			Map<IVirtualComponent, String> uriMap = new HashMap<IVirtualComponent, String>();
			uriMap.put(component, archiveName);
			dm.setProperty(ICreateReferenceComponentsDataModelProperties.TARGET_COMPONENTS_TO_URI_MAP, uriMap);
		}
        dm.setProperty(ICreateReferenceComponentsDataModelProperties.TARGET_COMPONENTS_DEPLOY_PATH, path);

		IStatus stat = dm.validateProperty(ICreateReferenceComponentsDataModelProperties.TARGET_COMPONENT_LIST);
		if (stat != OK_STATUS)
			throw new CoreException(stat);
		try {
			dm.getDefaultOperation().execute(new NullProgressMonitor(), null);
		} catch (ExecutionException e) {
			WTPOveridePlugin.logError(e);
		}	
	}
	/**
	 * Method returns the name of the given IVirtualComponent being sure the correct extension
	 * is on the end of the name, this is important for internal projects. Added for [Bug 241509]
	 * 
	 * Note (rs) :  I do not believe this ever gets called with a binary virtComp
	 *  
	 * @param virtComp the IVirtualComponent to get the name of with the correct extension
	 * @return the name of the given IVirtualComponent with the correct extension
	 */
	protected String getVirtualComponentNameWithExtension(IVirtualComponent virtComp) {
		String virtCompURIMapName = this.getURIMappingName(virtComp);
		String extension = ".jar";
		virtCompURIMapName += extension;
		return virtCompURIMapName;
	}
	
	/**
	 * [Bug 238264]
	 * determines a unique URI mapping name for a given component
	 * this is in case two components have the same name.
	 * 
	 * @return returns a valid (none duplicate) uri mapping name for the given component\
	 */
	private String getURIMappingName(IVirtualComponent archive) {
		
		//get the default uri map name for the given archive
		IPath componentPath = Path.fromOSString(archive.getName());
		String uriMapName = componentPath.lastSegment().replace(' ', '_');
		
		
		//check to be sure this uri mapping is not already in use by another reference
		boolean dupeArchiveName;
		String refedCompName;
		int lastDotIndex;
		String increment;
		IVirtualReference [] existingRefs = rootComponent.getReferences();
		for(int i=0;i<existingRefs.length;i++){
			refedCompName = existingRefs[i].getReferencedComponent().getName();
			
			//if uri mapping names of the refed component and the given archive are the same
			//  find a new uri map name for the given archive
			if(existingRefs[i].getArchiveName().equals(uriMapName)){
				dupeArchiveName = true;
				//find a new uriMapName for the given component
				for(int j=1; dupeArchiveName; j++){
					lastDotIndex = uriMapName.lastIndexOf('.');
					increment = "_"+j; //$NON-NLS-1$
					
					//create the new potential name
					if(lastDotIndex != -1){
						uriMapName = uriMapName.substring(0, lastDotIndex) + increment + uriMapName.substring(lastDotIndex);
					} else {
						uriMapName = uriMapName.substring(0)+increment;
					}
					
					//determine if the new potential name is valid
					for(int k=0; k<existingRefs.length; k++) {
						dupeArchiveName = existingRefs[k].getArchiveName().equals(uriMapName);
						if(dupeArchiveName) {
							break;
						}
					}
				}
			}
		}
		
		return uriMapName;
	}
	
	public void labelProviderChanged(LabelProviderChangedEvent event) {
		if(!availableComponentsViewer.getTable().isDisposed())
			availableComponentsViewer.refresh(true);
	}

}
