content: /**
   *  Copyright (c) 2013-2016 Angelo ZERR.
   *  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:
   *  Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
   */
  package tern.eclipse.ide.internal.core;
  
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import org.eclipse.core.resources.IProject;
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.IConfigurationElement;
  import org.eclipse.core.runtime.IExtensionDelta;
  import org.eclipse.core.runtime.IExtensionRegistry;
  import org.eclipse.core.runtime.IRegistryChangeEvent;
  import org.eclipse.core.runtime.IRegistryChangeListener;
  import org.eclipse.core.runtime.Platform;
  import org.eclipse.core.runtime.preferences.DefaultScope;
  import org.eclipse.core.runtime.preferences.IScopeContext;
  import org.eclipse.core.runtime.preferences.InstanceScope;
  
  import tern.eclipse.ide.core.DefaultTernModule;
  import tern.eclipse.ide.core.IDefaultTernModulesProvider;
  import tern.eclipse.ide.core.ITernNatureCapability;
  import tern.eclipse.ide.core.ITernRepositoryManager;
  import tern.eclipse.ide.core.TernCorePlugin;
  import tern.eclipse.ide.core.preferences.TernCorePreferenceConstants;
  import tern.eclipse.ide.internal.core.resources.IDETernProject;
  import tern.metadata.TernModuleMetadata;
  import tern.resources.TernProject;
  import tern.server.ITernModule;
  import tern.utils.StringUtils;
  import tern.utils.TernModuleHelper;
  
  import com.eclipsesource.json.JsonObject;
  
  /**
   * Manager of tern nature adapters loaded by the extension point
   * "ternNatureAdapters"
   *
   */
  public class TernNatureAdaptersManager implements IRegistryChangeListener {
  
  	private static final String EXTENSION_TERN_NATURE_ADAPTERS = "ternNatureAdapters";
  
  	private static final TernNatureAdaptersManager INSTANCE = new TernNatureAdaptersManager();
  
  	// cached copy of all tern nature adapaters
  	private Map<ITernNatureCapability, List<DefaultTernModule>> ternNatureAdapters;
  
  	private boolean registryListenerIntialized;
  
  	public static TernNatureAdaptersManager getManager() {
  		return INSTANCE;
  	}
  
  	public TernNatureAdaptersManager() {
  		this.registryListenerIntialized = false;
  	}
  
  	@Override
  	public void registryChanged(final IRegistryChangeEvent event) {
  		IExtensionDelta[] deltas = event.getExtensionDeltas(
  				TernCorePlugin.PLUGIN_ID, EXTENSION_TERN_NATURE_ADAPTERS);
  		if (deltas != null) {
  			for (IExtensionDelta delta : deltas)
  				handleTernNatureAdapterDelta(delta);
  		}
  	}
  
  	/**
  	 * Load the tern server types.
  	 */
  	private synchronized void loadTernNatureAdapters() {
  		if (ternNatureAdapters != null)
  			return;
  
  		Trace.trace(Trace.EXTENSION_POINT,
  				"->- Loading .ternNatureAdapters extension point ->-");
  
  		IExtensionRegistry registry = Platform.getExtensionRegistry();
  		IConfigurationElement[] cf = registry.getConfigurationElementsFor(
  				TernCorePlugin.PLUGIN_ID, EXTENSION_TERN_NATURE_ADAPTERS);
  		Map<ITernNatureCapability, List<DefaultTernModule>> map = new HashMap<ITernNatureCapability, List<DefaultTernModule>>(
  				cf.length);
  		addTernNatureAdapters(cf, map);
  		addRegistryListenerIfNeeded();
  		ternNatureAdapters = map;
  
  		Trace.trace(Trace.EXTENSION_POINT,
  				"-<- Done loading .ternNatureAdapters extension point -<-");
  	}
  
  	/**
  	 * Load the tern project describers.
  	 */
  	private synchronized void addTernNatureAdapters(IConfigurationElement[] cf,
  			Map<ITernNatureCapability, List<DefaultTernModule>> map) {
  		for (IConfigurationElement ce : cf) {
  			String id = ce.getAttribute("id");
  			String className = ce.getAttribute("class");
  			try {
  				if (!StringUtils.isEmpty(className)) {
  					map.put((ITernNatureCapability) ce
  							.createExecutableExtension("class"),
  							getDefaultModules(ce));
  				} else if (!StringUtils.isEmpty(id)) {
  					map.put(new DefaultTernNatureAdapter(id),
  							getDefaultModules(ce));
  				}
  				Trace.trace(Trace.EXTENSION_POINT,
  						"  Loaded project describer: " + id != null ? id
  								: className != null ? className : "");
  			} catch (Throwable t) {
  				Trace.trace(
  						Trace.SEVERE,
  						"  Could not load project describers: " + id != null ? id
  								: className != null ? className : "", t);
  			}
  		}
  	}
  
  	private List<DefaultTernModule> getDefaultModules(IConfigurationElement ce) {
  		List<DefaultTernModule> defaultModules = new ArrayList<DefaultTernModule>();
  		for (IConfigurationElement dmce : ce.getChildren("defaultModules")) {
  			for (IConfigurationElement mce : dmce.getChildren("module")) {
  				String module = mce.getAttribute("name");
  				if (module != null && !module.trim().isEmpty()) {
  					String name = module.trim();
  					boolean withDependencies = StringUtils.asBoolean(
  							mce.getAttribute("withDependencies"), false);
  					JsonObject options = getOptions(mce.getAttribute("options"));
  					defaultModules.add(new DefaultTernModule(name,
  							withDependencies, options));
  				}
  			}
  		}
  		return defaultModules;
  	}
  
  	private JsonObject getOptions(String options) {
  		if (!StringUtils.isEmpty(options)) {
  			try {
  				return JsonObject.readFrom(options);
  			} catch (Throwable e) {
  
  			}
  		}
  		return null;
  	}
  
  	protected void handleTernNatureAdapterDelta(IExtensionDelta delta) {
  		if (ternNatureAdapters == null) // not loaded yet
  			return;
  
  		IConfigurationElement[] cf = delta.getExtension()
  				.getConfigurationElements();
  
  		Map<ITernNatureCapability, List<DefaultTernModule>> map = new HashMap<ITernNatureCapability, List<DefaultTernModule>>(
  				ternNatureAdapters);
  		if (delta.getKind() == IExtensionDelta.ADDED) {
  			addTernNatureAdapters(cf, map);
  		} else {
  		}
  		ternNatureAdapters = map;
  	}
  
  	private void addRegistryListenerIfNeeded() {
  		if (registryListenerIntialized)
  			return;
  
  		IExtensionRegistry registry = Platform.getExtensionRegistry();
  		registry.addRegistryChangeListener(this, TernCorePlugin.PLUGIN_ID);
  		registryListenerIntialized = true;
  	}
  
  	public void initialize() {
  
  	}
  
  	public void destroy() {
  		if (ternNatureAdapters == null) // not loaded yet
  			return;
  		Platform.getExtensionRegistry().removeRegistryChangeListener(this);
  	}
  
  	/**
  	 * Returns true if the given project has tern nature and false otherwise.
  	 * 
  	 * @param project
  	 * @return
  	 */
  	public boolean hasTernNature(IProject project) {
  		if (project.isAccessible()) {
  			try {
  				// test if project has a .tern-project file
  				if (project.getFile(TernProject.TERN_PROJECT_FILE).exists())
  					return true;
  
  				// use tern nature adapaters
  				Map<ITernNatureCapability, List<DefaultTernModule>> ternNatureAdapters = getTernNatureAdapters();
  				for (ITernNatureCapability natureAdapter : ternNatureAdapters
  						.keySet()) {
  					if (natureAdapter.hasTernNature(project)) {
  						return true;
  					}
  				}
  			} catch (CoreException e) {
  				Trace.trace(Trace.SEVERE, "Error tern nature", e);
  			}
  		}
  		return false;
  	}
  
  	private Map<ITernNatureCapability, List<DefaultTernModule>> getTernNatureAdapters() {
  		if (ternNatureAdapters == null) {
  			loadTernNatureAdapters();
  		}
  		return ternNatureAdapters;
  	}
  
  	/**
  	 * Add default modules for the given tern project.
  	 *
  	 * @param ternProject
  	 *            tern project
  	 * @throws CoreException
  	 */
  	public void addDefaultModules(IDETernProject ternProject)
  			throws CoreException {
  		if (ternProject.hasModules()) {
  			// tern project is not empty, don't add default modules
  			return;
  		}
  
  		List<ITernModule> contributedModules = new ArrayList<ITernModule>();
  		Map<ITernModule, JsonObject> moduleOptions = new HashMap<ITernModule, JsonObject>();
  
  		// add default module from preferences
  		ITernModule moduleFromPreferences = null;
  		ITernModule[] modulesFromPreferences = getModulesFromPreferences(ternProject);
  		for (int i = 0; i < modulesFromPreferences.length; i++) {
  			moduleFromPreferences = modulesFromPreferences[i];
  			if (!contributedModules.contains(moduleFromPreferences)) {
  				contributedModules.add(moduleFromPreferences);
  			}
  		}
  
  		// Add default module from extension point.
  		ITernRepositoryManager repositoryManager = TernCorePlugin
  				.getTernRepositoryManager();
  		Map<ITernNatureCapability, List<DefaultTernModule>> ternNatureAdapters = getTernNatureAdapters();
  		for (ITernNatureCapability natureAdapter : ternNatureAdapters.keySet()) {
  			if (natureAdapter.hasTernNature(ternProject.getProject())) {
  				Collection<DefaultTernModule> defaultModules = ternNatureAdapters
  						.get(natureAdapter);
  				// collect static default modules
  				for (DefaultTernModule defaultModule : defaultModules) {
  					collectionDefaultModule(defaultModule, ternProject,
  							contributedModules, moduleOptions,
  							repositoryManager);
  				}
  				// collect dynamic default modules
  				if (natureAdapter instanceof IDefaultTernModulesProvider) {
  					defaultModules = ((IDefaultTernModulesProvider) natureAdapter)
  							.getTernModules(ternProject.getProject());
  					if (defaultModules != null) {
  						for (DefaultTernModule defaultModule : defaultModules) {
  							collectionDefaultModule(defaultModule, ternProject,
  									contributedModules, moduleOptions,
  									repositoryManager);
  						}
  					}
  				}
  			}
  		}
  
  		// sort modules
  		TernModuleHelper.sort(contributedModules);
  
  		// loop for collected sorted modules
  		JsonObject options = null;
  		for (ITernModule module : contributedModules) {
  			options = moduleOptions.get(module);
  			TernModuleHelper.update(module, options, ternProject);
  		}
  
  	}
  
  	private void collectionDefaultModule(DefaultTernModule defaultModule,
  			IDETernProject ternProject, List<ITernModule> contributedModules,
  			Map<ITernModule, JsonObject> moduleOptions,
  			ITernRepositoryManager repositoryManager) {
  		ITernModule module = repositoryManager.findTernModule(
  				defaultModule.getName(), ternProject);
  		if (module != null) {
  			if (!contributedModules.contains(module)) {
  				contributedModules.add(module);
  				if (defaultModule.getOptions() != null) {
  					moduleOptions.put(module, defaultModule.getOptions());
  				}
  			}
  			if (defaultModule.isWithDependencies()) {
  				// add modules dependencies
  				TernModuleMetadata metadata = module.getMetadata();
  				if (metadata != null) {
  					Collection<String> dependencies = metadata
  							.getDependencies(module.getVersion());
  					for (String dependency : dependencies) {
  						ITernModule dependencyModule = repositoryManager
  								.findTernModule(dependency, ternProject);
  						if (dependencyModule != null) {
  							if (!contributedModules.contains(dependencyModule)) {
  								contributedModules.add(dependencyModule);
  							}
  						}
  					}
  				}
  			}
  		}
  	}
  
  	private ITernModule[] getModulesFromPreferences(IDETernProject ternProject) {
  		IScopeContext[] lookupOrder = new IScopeContext[] {
  				InstanceScope.INSTANCE, DefaultScope.INSTANCE };
  		String moduleNames = Platform.getPreferencesService().getString(
  				TernCorePlugin.getDefault().getBundle().getSymbolicName(),
  				TernCorePreferenceConstants.DEFAULT_TERN_MODULES,
  				TernCorePreferenceConstants.DEFAULT_TERN_MODULES_VALUE,
  				lookupOrder);
  		return TernCorePlugin.getTernRepositoryManager().getTernModules(
  				moduleNames, ternProject);
  	}
  
  }
  