Bug 533300: Module loading shall not re-wrap dependencies

  fixed module wrapping
  added support to display any wrapped instance in modules dropin
  removed listModules() from environment

Change-Id: I1334682ec5790167b41523fb5a984c0eec90ce02
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/modules/ui/ModulesDragListener.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/modules/ui/ModulesDragListener.java
index 401f03f..beb3f49 100644
--- a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/modules/ui/ModulesDragListener.java
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/modules/ui/ModulesDragListener.java
@@ -75,7 +75,7 @@
 		}
 	}
 
-	private IStructuredSelection getSelection() {
+	protected IStructuredSelection getSelection() {
 		if (fTreeViewer != null)
 			return fTreeViewer.getStructuredSelection();
 
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/view/ModuleStackDropin.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/view/ModuleStackDropin.java
index cd30d39..73c0335 100644
--- a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/view/ModuleStackDropin.java
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/view/ModuleStackDropin.java
@@ -32,6 +32,7 @@
 import org.eclipse.jface.viewers.TableViewerColumn;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.dnd.DND;
+import org.eclipse.swt.dnd.DragSourceEvent;
 import org.eclipse.swt.dnd.TextTransfer;
 import org.eclipse.swt.dnd.Transfer;
 import org.eclipse.swt.graphics.Image;
@@ -79,14 +80,14 @@
 			if (inputElement instanceof IScriptEngine) {
 				final IEnvironment environment = IEnvironment.getEnvironment((IScriptEngine) inputElement);
 				if (environment != null) {
-					final List<ModuleDefinition> loadedModules = new ArrayList<>();
-					for (final Object element : new ArrayList<>(environment.getModules())) {
-						final ModuleDefinition module = getDefinition(element);
-						if (module != null)
-							loadedModules.add(module);
+					final List<Object> loadedModules = new ArrayList<>();
+
+					for (final Object instance : new ArrayList<>(environment.getModules())) {
+						final ModuleDefinition definition = getDefinition(instance);
+						loadedModules.add((definition != null) ? definition : instance);
 					}
 
-					return loadedModules.toArray(new ModuleDefinition[loadedModules.size()]);
+					return loadedModules.toArray();
 				}
 			}
 
@@ -103,6 +104,9 @@
 				if (element instanceof ModuleDefinition)
 					return ((ModuleDefinition) element).getName();
 
+				if (element != null)
+					return element.getClass().getCanonicalName();
+
 				return super.getText(element);
 			}
 
@@ -116,6 +120,9 @@
 					return Activator.getImage(Activator.PLUGIN_ID, "/icons/eobj16/module.png", true);
 				}
 
+				if (element != null)
+					return Activator.getImage(Activator.PLUGIN_ID, "/icons/eobj16/debug_java_class.png", true);
+
 				return super.getImage(element);
 			}
 		});
@@ -123,7 +130,15 @@
 		fModulesTable.setInput(fEngine);
 
 		fModulesTable.addDragSupport(DND.DROP_MOVE | DND.DROP_COPY, new Transfer[] { LocalSelectionTransfer.getTransfer(), TextTransfer.getInstance() },
-				new ModulesDragListener(fModulesTable));
+				new ModulesDragListener(fModulesTable) {
+					@Override
+					public void dragStart(DragSourceEvent event) {
+						super.dragStart(event);
+
+						final Object firstElement = getSelection().getFirstElement();
+						event.doit = (firstElement instanceof ModuleDefinition);
+					}
+				});
 
 		return composite;
 	}
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/EnvironmentModule.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/EnvironmentModule.java
index 25e29b8..a674c31 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/EnvironmentModule.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/EnvironmentModule.java
@@ -19,7 +19,6 @@
 import java.lang.reflect.Method;
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -38,7 +37,7 @@
 import org.eclipse.ease.Logger;
 import org.eclipse.ease.Script;
 import org.eclipse.ease.modules.ModuleDefinition.ModuleDependency;
-import org.eclipse.ease.service.IScriptService;
+import org.eclipse.ease.modules.ModuleTracker.ModuleState;
 import org.eclipse.ease.service.ScriptService;
 import org.eclipse.ease.tools.ResourceTools;
 import org.eclipse.swt.widgets.Display;
@@ -65,11 +64,13 @@
 		instance.wrap(instance, false);
 	}
 
+	private final ModuleTracker fModuleTracker = new ModuleTracker();
+
 	/** Loaded module instances. Used as cache so that each module instance gets created only once. Maps moduleID -> instance. */
-	private final Map<ModuleDefinition, Object> fLoadedModuleInstances = new HashMap<>();
+	// private final Map<ModuleDefinition, Object> fLoadedModuleInstances = new HashMap<>();
 
 	/** Stores ordering of wrapped elements. Index 0 contains the newest element */
-	private final List<Object> fWrappedElements = new ArrayList<>();
+	// private final List<Object> fWrappedElements = new ArrayList<>();
 
 	/** Stores beautified names of loaded modules. */
 	// private final Map<String, Object> fModuleNames = new HashMap<>();
@@ -83,45 +84,9 @@
 	private final ListenerList<IModuleCallbackProvider> fModuleCallbacks = new ListenerList<>();
 
 	public EnvironmentModule() {
-		fLoadedModuleInstances.put(ModuleHelper.resolveModuleName(MODULE_NAME), this);
-	}
-
-	private Object createModuleInstance(final ModuleDefinition definition) {
-
-		if (!fLoadedModuleInstances.containsKey(definition)) {
-			if (definition.isDeprecated())
-				printError("Module \"" + definition.getName() + "\" is deprecated. Consider updating your code.");
-
-			loadModuleDependencies(definition);
-			final Object instance = definition.createModuleInstance();
-
-			// check that module class got initialized correctly
-			if (instance == null)
-				throw new RuntimeException("Could not create module instance, see workspace log for more details");
-
-			if (instance instanceof IScriptModule)
-				((IScriptModule) instance).initialize(getScriptEngine(), this);
-
-			fLoadedModuleInstances.put(definition, instance);
-		}
-
-		return fLoadedModuleInstances.get(definition);
-	}
-
-	private void loadModuleDependencies(ModuleDefinition parentModuleDefinition) {
-
-		final IScriptService scriptService = ScriptService.getService();
-
-		for (final ModuleDependency dependency : parentModuleDefinition.getDependencies()) {
-			final ModuleDefinition requiredModule = scriptService.getModuleDefinition(dependency.getId());
-
-			if (requiredModule == null)
-				throw new RuntimeException("Could not resolve module dependency \"" + dependency.getId() + "\"");
-
-			Object instance = fLoadedModuleInstances.get(requiredModule);
-			if (instance == null)
-				instance = createModuleInstance(requiredModule);
-		}
+		final ModuleDefinition moduleDefinition = ModuleHelper.resolveModuleName(MODULE_NAME);
+		final ModuleState state = fModuleTracker.addModule(moduleDefinition);
+		state.setInstance(this);
 	}
 
 	@Override
@@ -132,33 +97,72 @@
 		if (definition == null)
 			throw new RuntimeException("Could not find module \"" + moduleIdentifier + "\"");
 
-		Object module = fLoadedModuleInstances.get(definition);
-		if (module == null) {
-			// not loaded yet
-			module = createModuleInstance(definition);
-		}
+		final ModuleState moduleState = fModuleTracker.getOrCreateModuleState(definition);
+
+		if (!moduleState.isLoaded())
+			createModuleInstance(moduleState);
 
 		if (!useCustomNamespace)
 			wrapModuleDependencies(definition);
 
 		// create function wrappers
-		return wrap(module, useCustomNamespace);
+		return wrap(moduleState.getInstance(), useCustomNamespace);
+	}
+
+	private Object createModuleInstance(final ModuleState state) {
+
+		final ModuleDefinition definition = state.getModuleDefinition();
+		if (definition != null) {
+			if (definition.isDeprecated())
+				printError("Module \"" + definition.getName() + "\" is deprecated. Consider updating your code.");
+
+			createModuleDependencies(definition);
+			final Object instance = definition.createModuleInstance();
+			state.setInstance(instance);
+
+			// check that module class got initialized correctly
+			if (instance == null)
+				throw new RuntimeException("Could not create module instance, see workspace log for more details");
+
+			if (instance instanceof IScriptModule)
+				((IScriptModule) instance).initialize(getScriptEngine(), this);
+		}
+
+		return state.getInstance();
+	}
+
+	private void createModuleDependencies(ModuleDefinition parentModuleDefinition) {
+
+		for (final ModuleDependency dependency : parentModuleDefinition.getDependencies()) {
+			final ModuleDefinition dependencyDefinition = dependency.getDefinition();
+
+			if (dependencyDefinition == null)
+				throw new RuntimeException("Could not resolve module dependency \"" + dependency.getId() + "\"");
+
+			createModuleDependencies(dependencyDefinition);
+
+			final ModuleState dependencyState = fModuleTracker.getOrCreateModuleState(dependencyDefinition);
+			if (!dependencyState.isLoaded())
+				createModuleInstance(dependencyState);
+		}
 	}
 
 	/**
-	 * Find and wrap instances of module dependencies. All found dependencies (and dependencies of dependencies) are wrapped into the root scope.
+	 * Find and wrap instances of module dependencies. All found dependencies (and dependencies of dependencies) are wrapped into the root scope if they were
+	 * not wrapped before.
 	 *
-	 * @param definition
+	 * @param parentModuleDefinition
 	 *            parent definition to find dependencies for
 	 */
-	private void wrapModuleDependencies(ModuleDefinition definition) {
-		for (final ModuleDependency dependency : definition.getDependencies()) {
+	private void wrapModuleDependencies(ModuleDefinition parentModuleDefinition) {
+		for (final ModuleDependency dependency : parentModuleDefinition.getDependencies()) {
 			final ModuleDefinition dependencyDefinition = dependency.getDefinition();
+
 			wrapModuleDependencies(dependencyDefinition);
 
-			final Object instance = fLoadedModuleInstances.get(dependencyDefinition);
-			if (instance != null)
-				wrap(instance, false);
+			final ModuleState dependencyState = fModuleTracker.getOrCreateModuleState(dependencyDefinition);
+			if (!dependencyState.isWrapped())
+				wrap(dependencyState.getInstance(), false);
 		}
 	}
 
@@ -176,7 +180,8 @@
 		if (definition == null)
 			return null;
 
-		return fLoadedModuleInstances.get(definition);
+		final ModuleState moduleState = fModuleTracker.getModuleState(definition);
+		return (moduleState != null) ? moduleState.getInstance() : null;
 	}
 
 	/**
@@ -189,9 +194,11 @@
 	@SuppressWarnings("unchecked")
 	@Override
 	public <T extends Object, U extends Class<T>> T getModule(final U clazz) {
-		for (final Object module : fLoadedModuleInstances.values()) {
-			if (clazz.isAssignableFrom(module.getClass()))
-				return (T) module;
+		for (final ModuleState state : fModuleTracker.getAvailableModules()) {
+			if (state.getInstance() != null) {
+				if (clazz.isAssignableFrom(state.getInstance().getClass()))
+					return (T) state.getInstance();
+			}
 		}
 
 		return null;
@@ -199,47 +206,7 @@
 
 	@Override
 	public List<Object> getModules() {
-		return fWrappedElements.stream().filter(instance -> fLoadedModuleInstances.values().contains(instance)).collect(Collectors.toList());
-	}
-
-	/**
-	 * List all available (visible) modules. Returns a list of visible modules. Loaded modules are indicated.
-	 *
-	 * @return string containing module information
-	 */
-	@WrapToScript
-	public final String listModules() {
-
-		final IScriptService scriptService = PlatformUI.getWorkbench().getService(IScriptService.class);
-		final List<ModuleDefinition> moduleDefinitions = new ArrayList<>(scriptService.getAvailableModules());
-
-		moduleDefinitions.sort((m1, m2) -> {
-			return m1.getPath().toString().compareTo(m2.getPath().toString());
-		});
-
-		final StringBuilder output = new StringBuilder();
-
-		// add header
-		output.append("available modules\n=================\n\n");
-
-		// add modules
-		for (final ModuleDefinition definition : moduleDefinitions) {
-
-			if (definition.isVisible()) {
-				output.append('\t');
-
-				output.append(definition.getPath().toString());
-				if (fLoadedModuleInstances.containsKey(definition))
-					output.append(" [LOADED]");
-
-				output.append('\n');
-			}
-		}
-
-		// write to default output
-		print(output, true);
-
-		return output.toString();
+		return fModuleTracker.getAvailableModules().stream().filter(state -> state.isLoaded()).map(state -> state.getInstance()).collect(Collectors.toList());
 	}
 
 	/**
@@ -329,8 +296,19 @@
 		// create function wrappers
 		final Object result = createWrappers(toBeWrapped, identifier, reloaded, useCustomNamespace);
 
-		fWrappedElements.remove(toBeWrapped);
-		fWrappedElements.add(0, toBeWrapped);
+		boolean isTrackedModule = false;
+		for (final ModuleState state : fModuleTracker.getAvailableModules()) {
+			if (toBeWrapped.equals(state.getInstance())) {
+				state.setWrapped(true);
+				isTrackedModule = true;
+				break;
+			}
+		}
+
+		if (!isTrackedModule) {
+			final ModuleState state = fModuleTracker.addInstance(toBeWrapped);
+			state.setWrapped(true);
+		}
 
 		// notify listeners
 		fireModuleEvent(toBeWrapped, reloaded ? IModuleListener.RELOADED : IModuleListener.LOADED);
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/ModuleTracker.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/ModuleTracker.java
new file mode 100644
index 0000000..1d4cbc4
--- /dev/null
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/ModuleTracker.java
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Christian Pontesegger 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:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.modules;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ModuleTracker {
+
+	public class ModuleState {
+
+		private Object fInstance;
+		private ModuleDefinition fModuleDefinition;
+
+		private boolean fWrapped = false;
+
+		public void setInstance(Object instance) {
+			fInstance = instance;
+			pushUp();
+		}
+
+		public Object getInstance() {
+			return fInstance;
+		}
+
+		public void setModuleDefinition(ModuleDefinition moduleDefinition) {
+			fModuleDefinition = moduleDefinition;
+		}
+
+		public ModuleDefinition getModuleDefinition() {
+			return fModuleDefinition;
+		}
+
+		public boolean isLoaded() {
+			return fInstance != null;
+		}
+
+		public boolean isWrapped() {
+			return fWrapped;
+		}
+
+		public void setWrapped(boolean wrapped) {
+			fWrapped = wrapped;
+			pushUp();
+		}
+
+		private void pushUp() {
+			fAvailableModules.remove(this);
+			fAvailableModules.add(0, this);
+		}
+	}
+
+	private final List<ModuleState> fAvailableModules = new ArrayList<>();
+
+	public ModuleState addModule(ModuleDefinition definition) {
+		final ModuleState state = new ModuleState();
+		state.setModuleDefinition(definition);
+
+		fAvailableModules.add(0, state);
+
+		return state;
+	}
+
+	public ModuleState addInstance(Object instance) {
+		final ModuleState state = new ModuleState();
+
+		fAvailableModules.add(0, state);
+		state.setInstance(instance);
+
+		return state;
+	}
+
+	public ModuleState getModuleState(ModuleDefinition definition) {
+		for (final ModuleState state : fAvailableModules) {
+			if (state.getModuleDefinition() != null) {
+				if (definition.getId().equals(state.getModuleDefinition().getId()))
+					return state;
+			}
+		}
+
+		return null;
+	}
+
+	public ModuleState getOrCreateModuleState(ModuleDefinition definition) {
+		final ModuleState state = getModuleState(definition);
+
+		return (state != null) ? state : addModule(definition);
+	}
+
+	public List<ModuleState> getAvailableModules() {
+		return fAvailableModules;
+	}
+}