<commit_before>/****************************************************************************************
 * Copyright (c) 2015 <NAME>.
 * 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:
 *     <NAME> - initial API and implementation and/or initial documentation
 ***************************************************************************************/


package mlm.eclipse.ide.jsworkingset.internal;


import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceRuleFactory;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.IWorkingSetUpdater;
import org.osgi.framework.Bundle;


/**
 *
 * Updater for the JavaScript-based working set.
 *
 * @author <NAME>
 *
 * @since mlm.eclipse.ide.jsworkingset 1.0
 *
 */

public class JSWorkingSetUpdater implements IWorkingSetUpdater {


	private static class WorkingSetData {


		IWorkingSet workingSet;


		IFile scriptFile;


		CompiledScript compiledScript;


	}


	/**
	 *
	 * The script engine manager to create script engines.
	 *
	 */

	private ScriptEngineManager mScriptEngineManager;


	/**
	 *
	 * Mapping from working set to working set data.
	 *
	 */

	private Map<IWorkingSet, WorkingSetData> mWorkingSets;


	/**
	 *
	 * The resource change listener that listens for changes in the workspace.
	 *
	 */

	private final IResourceChangeListener mResourceChangeListener = new IResourceChangeListener() {


		@Override
		public void resourceChanged( final IResourceChangeEvent pEvent ) {

			handleResourceChanged(pEvent);

		}


	};


	/**
	 *
	 * The property change listener that listens for changes in the preferences.
	 *
	 */

	private final IPropertyChangeListener mPropertyChangeListener = new IPropertyChangeListener() {


		@Override
		public void propertyChange( final PropertyChangeEvent pEvent ) {

			handlePropertyChange(pEvent);

		}


	};


	/**
	 *
	 * Constructs a new <code>JSWorkingSetUpdater</code>.
	 *
	 * @since mlm.eclipse.ide.jsworkingset 1.0
	 *
	 */

	public JSWorkingSetUpdater() {

		mWorkingSets = new HashMap<>();

		mScriptEngineManager = new ScriptEngineManager();

		readGlobalScripts();

		ResourcesPlugin.getWorkspace().addResourceChangeListener(mResourceChangeListener, IResourceChangeEvent.POST_CHANGE);

		Activator.getDefault().getPreferenceStore().addPropertyChangeListener(mPropertyChangeListener);

	}


	private void readGlobalScripts() {

		final Bindings globalBindings = mScriptEngineManager.getBindings();
		globalBindings.put("log", Activator.getDefault().getLog()); //$NON-NLS-1$

		final Bundle bundle = Activator.getDefault().getBundle();
		final Enumeration<URL> scriptUrls = bundle.findEntries("resources", "*.js", false); //$NON-NLS-1$ //$NON-NLS-2$
		while (scriptUrls.hasMoreElements()) {

			final URL scriptUrl = scriptUrls.nextElement();

			try (Reader reader = new InputStreamReader(scriptUrl.openStream(), StandardCharsets.UTF_8)) {

				final ScriptEngine engine = Activator.newScriptEngine(mScriptEngineManager);
				engine.put(ScriptEngine.FILENAME, FileLocator.resolve(scriptUrl).toString());
				engine.eval(reader);

				final Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
				final Bindings module = (Bindings) bindings.get("module");
				final Bindings moduleExports = (Bindings) module.get("exports");
				for (final Entry<String, Object> entry : moduleExports.entrySet()) {

					globalBindings.put(entry.getKey(), entry.getValue());

				}

			} catch (IOException | ScriptException ex) {

				Activator.log(IStatus.ERROR, "Failed to eval script!", ex); //$NON-NLS-1$

			}

		}

	}


	@Override
	public boolean contains( final IWorkingSet pWorkingSet ) {

		return mWorkingSets.containsKey(pWorkingSet);

	}


	@Override
	public void add( final IWorkingSet pWorkingSet ) {

		final WorkingSetData wsd = new WorkingSetData();
		wsd.workingSet = pWorkingSet;

		mWorkingSets.put(pWorkingSet, wsd);

		updateWorkingSetData(wsd);

	}


	@Override
	public boolean remove( final IWorkingSet pWorkingSet ) {

		JSWorkingSetPrefs.setScript(pWorkingSet, null);

		final WorkingSetData workingSetData = mWorkingSets.remove(pWorkingSet);

		resetWorkingSetData(workingSetData);

		return workingSetData != null;

	}


	private void handlePropertyChange( final PropertyChangeEvent pEvent ) {

		if (mWorkingSets.isEmpty()) {

			return;

		}

		final String property = pEvent.getProperty();
		if (!property.startsWith(JSWorkingSetPrefs.PREF_KEY__PREFIX)) {

			return;

		}

		final String workingSetName = property.substring(JSWorkingSetPrefs.PREF_KEY__PREFIX.length());

		for (final WorkingSetData workingSetData : mWorkingSets.values()) {

			if (workingSetName.equals(workingSetData.workingSet.getName())) {

				resetWorkingSetData(workingSetData);
				updateWorkingSetData(workingSetData);

			}

		}

	}


	private void handleResourceChanged( final IResourceChangeEvent pEvent ) {

		if (mWorkingSets.isEmpty()) {

			return;

		}

		// check for updates to projects
		final IResourceDelta delta = pEvent.getDelta();
		final int kind = IResourceDelta.ADDED | IResourceDelta.CHANGED | IResourceDelta.REMOVED;
		final int memberFlags = IResource.PROJECT;
		final IResourceDelta[] affectedChildren = delta.getAffectedChildren(kind, memberFlags);
		if (affectedChildren != null && affectedChildren.length > 0) {

			final int projectFlags = affectedChildren[0].getFlags();
			if ((projectFlags & ~IResourceDelta.MARKERS) != IResourceDelta.NO_CHANGE) {

				for (final WorkingSetData workingSetData : mWorkingSets.values()) {

					updateWorkingSetData(workingSetData);

				}

			}

		}

		// check for updates to scripts
		for (final WorkingSetData workingSetData : mWorkingSets.values()) {

			if (workingSetData.scriptFile != null) {

				final IPath scriptPath = workingSetData.scriptFile.getFullPath();
				final IResourceDelta scriptDelta = delta.findMember(scriptPath);
				if (scriptDelta != null) {

					final int scriptFlags = scriptDelta.getFlags();
					if ((scriptFlags & ~IResourceDelta.MARKERS) != IResourceDelta.NO_CHANGE) {

						resetWorkingSetData(workingSetData);
						updateWorkingSetData(workingSetData);

					}

				}

			}

		}

	}


	private void updateWorkingSetData( final WorkingSetData pWorkingSetData ) {

		final long startTime = System.currentTimeMillis();

		updateWorkingSetData0(pWorkingSetData);

		final long endTime = System.currentTimeMillis();

		final long elapsed = endTime - startTime;

		if (Activator.DEBUG) {

			final String label = pWorkingSetData.workingSet.getLabel();
			final String message = String.format("Working set '%s' updated in %d ms.", label, elapsed);
			Activator.log(IStatus.INFO, message);

		}

	}


	private void updateWorkingSetData0( final WorkingSetData pWorkingSetData ) {

		final IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();

		if (pWorkingSetData.scriptFile == null && pWorkingSetData.compiledScript == null) {

			final String scriptPathStr = JSWorkingSetPrefs.getScript(pWorkingSetData.workingSet);
			if (scriptPathStr == null) {

				// TODO change name/icon?
				final String name = JSWorkingSetPrefs.getName(pWorkingSetData.workingSet);
				pWorkingSetData.workingSet.setLabel(name != null ? name : "working set");
				pWorkingSetData.workingSet.setElements(new IAdaptable[0]);

				return;
<commit_msg>cosmetic changes<commit_after>/****************************************************************************************
 * Copyright (c) 2015 <NAME>.
 * 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:
 *     <NAME> - initial API and implementation and/or initial documentation
 ***************************************************************************************/


package mlm.eclipse.ide.jsworkingset.internal;


import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceRuleFactory;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.IWorkingSetUpdater;
import org.osgi.framework.Bundle;


/**
 *
 * Updater for the JavaScript-based working set.
 *
 * @author <NAME>
 *
 * @since mlm.eclipse.ide.jsworkingset 1.0
 *
 */

public class JSWorkingSetUpdater implements IWorkingSetUpdater {


	private static class WorkingSetData {


		IWorkingSet workingSet;


		IFile scriptFile;


		CompiledScript compiledScript;


	}


	/**
	 *
	 * The script engine manager to create script engines.
	 *
	 */

	private ScriptEngineManager mScriptEngineManager;


	/**
	 *
	 * Mapping from working set to working set data.
	 *
	 */

	private Map<IWorkingSet, WorkingSetData> mWorkingSets;


	/**
	 *
	 * The resource change listener that listens for changes in the workspace.
	 *
	 */

	private final IResourceChangeListener mResourceChangeListener = new IResourceChangeListener() {


		@Override
		public void resourceChanged( final IResourceChangeEvent pEvent ) {

			handleResourceChanged(pEvent);

		}


	};


	/**
	 *
	 * The property change listener that listens for changes in the preferences.
	 *
	 */

	private final IPropertyChangeListener mPropertyChangeListener = new IPropertyChangeListener() {


		@Override
		public void propertyChange( final PropertyChangeEvent pEvent ) {

			handlePropertyChange(pEvent);

		}


	};


	/**
	 *
	 * Constructs a new <code>JSWorkingSetUpdater</code>.
	 *
	 * @since mlm.eclipse.ide.jsworkingset 1.0
	 *
	 */

	public JSWorkingSetUpdater() {

		mWorkingSets = new HashMap<>();

		mScriptEngineManager = new ScriptEngineManager();

		readGlobalScripts();

		ResourcesPlugin.getWorkspace().addResourceChangeListener(mResourceChangeListener, IResourceChangeEvent.POST_CHANGE);

		Activator.getDefault().getPreferenceStore().addPropertyChangeListener(mPropertyChangeListener);

		// TODO register for working set changes (name)

	}


	private void readGlobalScripts() {

		final Bindings globalBindings = mScriptEngineManager.getBindings();
		globalBindings.put("log", Activator.getDefault().getLog()); //$NON-NLS-1$

		final Bundle bundle = Activator.getDefault().getBundle();
		final Enumeration<URL> scriptUrls = bundle.findEntries("resources", "*.js", false); //$NON-NLS-1$ //$NON-NLS-2$
		while (scriptUrls.hasMoreElements()) {

			final URL scriptUrl = scriptUrls.nextElement();

			try (Reader reader = new InputStreamReader(scriptUrl.openStream(), StandardCharsets.UTF_8)) {

				final ScriptEngine engine = Activator.newScriptEngine(mScriptEngineManager);
				engine.put(ScriptEngine.FILENAME, FileLocator.resolve(scriptUrl).toString());
				engine.eval(reader);

				final Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
				final Bindings module = (Bindings) bindings.get("module");
				final Bindings moduleExports = (Bindings) module.get("exports");
				for (final Entry<String, Object> entry : moduleExports.entrySet()) {

					globalBindings.put(entry.getKey(), entry.getValue());

				}

			} catch (IOException | ScriptException ex) {

				Activator.log(IStatus.ERROR, "Failed to eval script!", ex); //$NON-NLS-1$

			}

		}

	}


	@Override
	public boolean contains( final IWorkingSet pWorkingSet ) {

		return mWorkingSets.containsKey(pWorkingSet);

	}


	@Override
	public void add( final IWorkingSet pWorkingSet ) {

		final WorkingSetData wsd = new WorkingSetData();
		wsd.workingSet = pWorkingSet;

		mWorkingSets.put(pWorkingSet, wsd);

		updateWorkingSetData(wsd);

	}


	@Override
	public boolean remove( final IWorkingSet pWorkingSet ) {

		JSWorkingSetPrefs.setScript(pWorkingSet, null);

		final WorkingSetData workingSetData = mWorkingSets.remove(pWorkingSet);

		resetWorkingSetData(workingSetData);

		return workingSetData != null;

	}


	private void handlePropertyChange( final PropertyChangeEvent pEvent ) {

		if (mWorkingSets.isEmpty()) {

			return;

		}

		final String property = pEvent.getProperty();
		if (!property.startsWith(JSWorkingSetPrefs.PREF_KEY__PREFIX)) {

			return;

		}

		final String workingSetName = property.substring(JSWorkingSetPrefs.PREF_KEY__PREFIX.length());

		for (final WorkingSetData workingSetData : mWorkingSets.values()) {

			if (workingSetName.equals(workingSetData.workingSet.getName())) {

				resetWorkingSetData(workingSetData);
				updateWorkingSetData(workingSetData);

			}

		}

	}


	private void handleResourceChanged( final IResourceChangeEvent pEvent ) {

		if (mWorkingSets.isEmpty()) {

			return;

		}

		// check for updates to projects
		final IResourceDelta delta = pEvent.getDelta();
		final int kind = IResourceDelta.ADDED | IResourceDelta.CHANGED | IResourceDelta.REMOVED;
		final int memberFlags = IResource.PROJECT;
		final IResourceDelta[] affectedChildren = delta.getAffectedChildren(kind, memberFlags);
		if (affectedChildren != null && affectedChildren.length > 0) {

			final int projectFlags = affectedChildren[0].getFlags();
			if ((projectFlags & ~IResourceDelta.MARKERS) != IResourceDelta.NO_CHANGE) {

				for (final WorkingSetData workingSetData : mWorkingSets.values()) {

					updateWorkingSetData(workingSetData);

				}

			}

		}

		// check for updates to scripts
		for (final WorkingSetData workingSetData : mWorkingSets.values()) {

			if (workingSetData.scriptFile != null) {

				final IPath scriptPath = workingSetData.scriptFile.getFullPath();
				final IResourceDelta scriptDelta = delta.findMember(scriptPath);
				if (scriptDelta != null) {

					final int scriptFlags = scriptDelta.getFlags();
					if ((scriptFlags & ~IResourceDelta.MARKERS) != IResourceDelta.NO_CHANGE) {

						resetWorkingSetData(workingSetData);
						updateWorkingSetData(workingSetData);

					}

				}

			}

		}

	}


	private void updateWorkingSetData( final WorkingSetData pWorkingSetData ) {

		final long startTime = System.currentTimeMillis();

		updateWorkingSetData0(pWorkingSetData);

		final long endTime = System.currentTimeMillis();

		final long elapsed = endTime - startTime;

		if (Activator.DEBUG) {

			final String label = pWorkingSetData.workingSet.getLabel();
			final String message = String.format("Working set '%s' updated in %d ms.", label, elapsed); //$NON-NLS-1$
			Activator.log(IStatus.INFO, message);

		}

	}


	private void updateWorkingSetData0( final WorkingSetData pWorkingSetData ) {

		final IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();

		if (pWorkingSetData.scriptFile == null && pWorkingSetData.compiledScript == null) {

			final String scriptPathStr = JSWorkingSetPrefs.getScript(pWorkingSetData.workingSet);
			if (scriptPathStr == null) {

				// TODO change name/icon?
				final String name = JSWorkingSetPrefs.getName(pWorkingSetData.workingSet);
				pWorkingSetData.workingSet.setLabel(name != null ? name : "working set");
				pWorkingSetData.workingSet.setElements(new IAdaptable[0]);

				return;

{'fraction_non_alphanumeric': 0.1201783723522854, 'fraction_numerical': 0.002508361204013378, 'mean_word_length': 4.691941624365482, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4632993', 'n_tokens_mistral': 5898, 'n_tokens_neox': 5086, 'n_words': 1119}