content: /**
   *  Copyright (c) 2013-2016 Angelo ZERR and Genuitec LLC.
   *  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
   *  Piotr Tomiak <piotr@genuitec.com> - refactoring of file management API
   */
  package tern.eclipse.ide.internal.core.resources;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import org.eclipse.core.resources.IContainer;
  import org.eclipse.core.resources.IFile;
  import org.eclipse.core.resources.IFolder;
  import org.eclipse.core.resources.IProject;
  import org.eclipse.core.resources.IResource;
  import org.eclipse.core.resources.IResourceVisitor;
  import org.eclipse.core.resources.ResourcesPlugin;
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.IPath;
  import org.eclipse.core.runtime.QualifiedName;
  
  import com.eclipsesource.json.JsonArray;
  import com.eclipsesource.json.JsonObject;
  import com.eclipsesource.json.WriterConfig;
  
  import tern.ITernFile;
  import tern.ITernProject;
  import tern.TernException;
  import tern.TernResourcesManager;
  import tern.eclipse.ide.core.IIDETernProject;
  import tern.eclipse.ide.core.IIDETernScriptPathReporter;
  import tern.eclipse.ide.core.IScopeContext;
  import tern.eclipse.ide.core.ITernConsoleConnector;
  import tern.eclipse.ide.core.ITernProjectLifecycleListener.LifecycleEventType;
  import tern.eclipse.ide.core.ITernServerPreferencesListener;
  import tern.eclipse.ide.core.ITernServerType;
  import tern.eclipse.ide.core.IWorkingCopy;
  import tern.eclipse.ide.core.ScopeContext;
  import tern.eclipse.ide.core.TernCorePlugin;
  import tern.eclipse.ide.core.utils.FileUtils;
  import tern.eclipse.ide.internal.core.TernConsoleConnectorManager;
  import tern.eclipse.ide.internal.core.TernNatureAdaptersManager;
  import tern.eclipse.ide.internal.core.TernProjectLifecycleManager;
  import tern.eclipse.ide.internal.core.TernRepositoryManager;
  import tern.eclipse.ide.internal.core.Trace;
  import tern.eclipse.ide.internal.core.WorkingCopy;
  import tern.eclipse.ide.internal.core.preferences.TernCorePreferencesSupport;
  import tern.eclipse.ide.internal.core.scriptpath.EclipseProjectScriptPath;
  import tern.eclipse.ide.internal.core.scriptpath.FolderScriptPath;
  import tern.eclipse.ide.internal.core.scriptpath.IIDETernScriptPath;
  import tern.eclipse.ide.internal.core.scriptpath.TernScriptPathComparator;
  import tern.repository.ITernRepository;
  import tern.resources.TernFileSynchronizer;
  import tern.resources.TernProject;
  import tern.scriptpath.ITernScriptPath;
  import tern.scriptpath.ITernScriptPath.ScriptPathsType;
  import tern.scriptpath.ITernScriptPathContainer;
  import tern.scriptpath.impl.JSFileScriptPath;
  import tern.scriptpath.impl.dom.DOMElementsScriptPath;
  import tern.server.ITernModule;
  import tern.server.ITernServer;
  import tern.server.ITernServerListener;
  import tern.server.TernServerAdapter;
  import tern.server.protocol.JsonHelper;
  import tern.utils.IOUtils;
  import tern.utils.StringUtils;
  import tern.utils.TernModuleHelper;
  
  /**
   * Eclipse IDE Tern project.
   * 
   */
  public class IDETernProject extends TernProject implements IIDETernProject, ITernServerPreferencesListener {
  
  	private static final QualifiedName TERN_PROJECT = new QualifiedName(TernCorePlugin.PLUGIN_ID + ".sessionprops", //$NON-NLS-1$
  			"TernProject"); //$NON-NLS-1$
  
  	private static final String PATH_JSON_FIELD = "path"; //$NON-NLS-1$
  
  	private static final String TYPE_JSON_FIELD = "type"; //$NON-NLS-1$
  
  	private static final String INCLUSION_PATTERNS_JSON_FIELD = "inclusionPatterns"; //$NON-NLS-1$
  
  	private static final String EXCLUSION_PATTERNS_JSON_FIELD = "exclusionPatterns"; //$NON-NLS-1$
  
  	private static final String SCRIPT_PATHS_JSON_FIELD = "scriptPaths"; //$NON-NLS-1$
  
  	private static final String IDE_JSON_FIELD = "ide"; //$NON-NLS-1$
  
  	private static final long serialVersionUID = 1L;
  
  	private final IProject project;
  
  	private ITernServer ternServer;
  
  	private final List<ITernScriptPath> scriptPaths;
  	private List<ITernScriptPath> sortedScriptPaths;
  
  	private final Map<String, Object> data;
  
  	private final List<ITernServerListener> listeners;
  
  	private final IWorkingCopy workingCopy;
  
  	IDETernProject(IProject project) throws CoreException {
  		super(project.getLocation().toFile());
  		this.project = project;
  		this.scriptPaths = new ArrayList<ITernScriptPath>();
  		this.sortedScriptPaths = Collections.emptyList();
  		this.data = new HashMap<String, Object>();
  		this.listeners = new ArrayList<ITernServerListener>();
  		this.workingCopy = new WorkingCopy(this);
  		TernCorePlugin.getTernServerTypeManager().addServerPreferencesListener(this);
  		project.setSessionProperty(TERN_PROJECT, this);
  	}
  
  	/**
  	 * Returns the Eclispe project.
  	 * 
  	 * @return
  	 */
  	@Override
  	public IProject getProject() {
  		return project;
  	}
  
  	@Override
  	public String getName() {
  		return project.getName();
  	}
  
  	/**
  	 * Returns the linked instance of tern server.
  	 * 
  	 * @return
  	 */
  	@Override
  	public ITernServer getTernServer() {
  		synchronized (serverLock) {
  			if (isServerDisposed()) {
  				try {
  					ITernServerType type = TernCorePreferencesSupport.getInstance().getServerType();
  					this.ternServer = type.createServer(this);
  					this.ternServer.setLoadingLocalPlugins(
  							TernCorePreferencesSupport.getInstance().isLoadingLocalPlugins(project));
  					this.ternServer.addServerListener(new TernServerAdapter() {
  						@Override
  						public void onStop(ITernServer server) {
  							getFileSynchronizer().cleanIndexedFiles();
  						}
  					});
  					if (!TernCorePreferencesSupport.getInstance().isDisableAsynchronousReques(project)) {
  						this.ternServer.setRequestProcessor(new IDETernServerAsyncReqProcessor(ternServer));
  					}
  					copyListeners();
  					copyMessageListeners();
  					configureConsole();
  				} catch (Exception e) {
  					// should be improved?
  					Trace.trace(Trace.SEVERE, "Error while creating tern server", e);
  				}
  
  			}
  			return ternServer;
  		}
  	}
  
  	public boolean isServerDisposed() {
  		synchronized (serverLock) {
  			return ternServer == null || ternServer.isDisposed();
  		}
  	}
  
  	/**
  	 * Return true if the given project have tern nature
  	 * "tern.eclipse.ide.core.ternnature" and false otherwise.
  	 * 
  	 * @param project
  	 *            Eclipse project.
  	 * @return true if the given project have tern nature
  	 *         "tern.eclipse.ide.core.ternnature" and false otherwise.
  	 */
  	public static boolean hasTernNature(IProject project) {
  		return TernNatureAdaptersManager.getManager().hasTernNature(project);
  	}
  
  	@Override
  	protected void doLoad() throws IOException {
  		try {
  
  			disposeServer();
  			TernProjectLifecycleManager.getManager().fireTernProjectLifeCycleListenerChanged(this,
  					LifecycleEventType.onLoadBefore);
  			super.doLoad();
  			// Load IDE informations of the tern project.
  			loadIDEInfos();
  
  			// the tern project is loaded on the first time, load default
  			// modules and save .tern-project.
  			initAdaptedNaturesInfos();
  
  		} finally {
  			TernProjectLifecycleManager.getManager().fireTernProjectLifeCycleListenerChanged(this,
  					LifecycleEventType.onLoadAfter);
  		}
  	}
  
  	@Override
  	protected void onLintersChanged() {
  		TernProjectLifecycleManager.getManager().fireTernProjectLifeCycleListenerChanged(this,
  				LifecycleEventType.onLintersChanged);
  	}
  
  	/**
  	 * Load IDE informations from the JSON .tern-project file.
  	 */
  	private void loadIDEInfos() {
  		// Load script paths
  		this.scriptPaths.clear();
  		JsonObject ide = (JsonObject) super.get(IDE_JSON_FIELD);
  		if (ide != null) {
  			// There is ide information.
  			JsonArray jsonScripts = (JsonArray) ide.get(SCRIPT_PATHS_JSON_FIELD);
  			if (jsonScripts != null) {
  				// There is scriptPaths defined.
  				JsonObject jsonScript = null;
  				String type = null;
  				String path = null;
  				// Loop for each script path.
  				for (Object object : jsonScripts) {
  					jsonScript = (JsonObject) object;
  					type = JsonHelper.getString(jsonScript, TYPE_JSON_FIELD);
  					path = JsonHelper.getString(jsonScript, PATH_JSON_FIELD);
  					if (type != null && path != null) {
  						ScriptPathsType pathType = ScriptPathsType.getType(type);
  						if (pathType == null) {
  							pathType = ScriptPathsType.FILE;
  						}
  						if (pathType != null) {
  							String[] inclusionPatterns = getPatterns(
  									JsonHelper.getString(jsonScript, INCLUSION_PATTERNS_JSON_FIELD));
  							String[] exclusionPatterns = getPatterns(
  									JsonHelper.getString(jsonScript, EXCLUSION_PATTERNS_JSON_FIELD));
  							// script path type exists.
  							IResource resource = getResource(path, pathType);
  							if (resource != null && resource.exists()) {
  								// the script path exists, add it.
  								this.scriptPaths.add(
  										createScriptPath(resource, pathType, inclusionPatterns, exclusionPatterns));
  							}
  						}
  					}
  				}
  			}
  
  		}
  		resetSortScriptPaths();
  	}
  
  	private void resetSortScriptPaths() {
  		this.sortedScriptPaths = null;
  	}
  
  	private String[] getPatterns(String patterns) {
  		if (StringUtils.isEmpty(patterns)) {
  			return null;
  		}
  		return patterns.split(",");
  	}
  
  	/*
  	 * Configures Tern Modules (Libraries and Plugins) that are default for Tern
  	 * Nature Adapters active on a project
  	 */
  	private void initAdaptedNaturesInfos() {
  		try {
  			TernNatureAdaptersManager.getManager().addDefaultModules(this);
  		} catch (CoreException e) {
  			Trace.trace(Trace.SEVERE, "Error while configuring default tern project modules", e);
  			return;
  		}
  
  		try {
  			save();
  		} catch (IOException e) {
  			Trace.trace(Trace.SEVERE, "Error while saving tern project", e);
  		}
  	}
  
  	/**
  	 * Returns the resource of the given path and type.
  	 * 
  	 * @param path
  	 *            the path of the script path
  	 * @param pathType
  	 *            the type of the script path.
  	 * @return
  	 */
  	private IResource getResource(String path, ScriptPathsType pathType) {
  		switch (pathType) {
  		case FILE:
  			return getProject().getFile(path);
  		case FOLDER:
  			return getProject().getFolder(path);
  		case PROJECT:
  			return ResourcesPlugin.getWorkspace().getRoot().getProject(path);
  		}
  		throw new UnsupportedOperationException(
  				"Cannot retrieve resource from the type=" + pathType + " of the path=" + path);
  	}
  
  	@Override
  	public IFile getIDEFile(String name) {
  		ITernFile tf = getFile(name);
  		if (tf != null) {
  			return (IFile) tf.getAdapter(IFile.class);
  		}
  		return null;
  	}
  
  	@Override
  	protected void doSave() throws IOException {
  		try {
  
  			TernProjectLifecycleManager.getManager().fireTernProjectLifeCycleListenerChanged(this,
  					LifecycleEventType.onSaveBefore);
  			// Store IDE tern project info.
  			saveIDEInfos();
  
  			if (isDirty()) {
  				// save .tern-project
  				IFile file = project.getFile(TERN_PROJECT_FILE);
  				InputStream content = null;
  				try {
  					content = IOUtils.toInputStream(super.toString(WriterConfig.PRETTY_PRINT),
  							file.exists() ? file.getCharset() : StringUtils.UTF_8);
  					if (!file.exists()) {
  						file.create(content, IResource.NONE, null);
  					} else {
  						file.setContents(content, true, false, null);
  					}
  				} catch (CoreException e) {
  					throw new IOException("Cannot save .tern-project", e);
  				} finally {
  					if (content != null) {
  						IOUtils.closeQuietly(content);
  					}
  				}
  				// .tern-project has changed, dispose the server.
  				disposeServer();
  			}
  		} finally {
  			TernProjectLifecycleManager.getManager().fireTernProjectLifeCycleListenerChanged(this,
  					LifecycleEventType.onSaveAfter);
  		}
  	}
  
  	@Override
  	public void handleException(Throwable t) {
  		Trace.trace(Trace.SEVERE, t.getMessage(), t);
  	}
  
  	/**
  	 * Save IDE informations in the JSON file .tern-project.
  	 */
  	private void saveIDEInfos() {
  		// script path
  		if (scriptPaths.size() > 0) {
  			JsonObject ide = new JsonObject();
  			JsonArray jsonScripts = new JsonArray();
  			// Loop for each script path and save it in the JSON file
  			// .tern-project.
  			for (ITernScriptPath scriptPath : scriptPaths) {
  				if (!scriptPath.isExternal()) {
  					JsonObject jsonScript = new JsonObject();
  					jsonScript.add(TYPE_JSON_FIELD, scriptPath.getType().name());
  					jsonScript.add(PATH_JSON_FIELD, scriptPath.getPath());
  					if (scriptPath instanceof ITernScriptPathContainer) {
  						ITernScriptPathContainer container = (ITernScriptPathContainer) scriptPath;
  						String exclusionPatterns = toString(container.getExclusionPatterns());
  						if (exclusionPatterns != null) {
  							jsonScript.add(EXCLUSION_PATTERNS_JSON_FIELD, exclusionPatterns);
  						}
  						String inclusionPatterns = toString(container.getInclusionPatterns());
  						if (inclusionPatterns != null) {
  							jsonScript.add(INCLUSION_PATTERNS_JSON_FIELD, inclusionPatterns);
  						}
  					}
  					jsonScripts.add(jsonScript);
  				}
  			}
  			ide.add(SCRIPT_PATHS_JSON_FIELD, jsonScripts);
  			super.set(IDE_JSON_FIELD, ide);
  		} else {
  			super.remove(IDE_JSON_FIELD);
  		}
  	}
  
  	private String toString(String[] patterns) {
  		if (patterns == null) {
  			return null;
  		}
  		StringBuilder result = new StringBuilder();
  		for (int i = 0; i < patterns.length; i++) {
  			if (i > 0) {
  				result.append(",");
  			}
  			result.append(patterns[i]);
  		}
  		return result.toString();
  	}
  
  	/**
  	 * Returns the list of script paths.
  	 * 
  	 * @return
  	 */
  	@Override
  	public List<ITernScriptPath> getScriptPaths() {
  		return scriptPaths;
  	}
  
  	/**
  	 * Create the script path instance from the given resource and type.
  	 * 
  	 * @param resource
  	 *            the root resource.
  	 * @param type
  	 *            of the script path.
  	 * @param inclusionPatterns
  	 *            include patterns or null
  	 * @param exclusionPatterns
  	 *            exclude patterns or null
  	 * @return
  	 */
  	@Override
  	public ITernScriptPath createScriptPath(IResource resource, ScriptPathsType type, String[] inclusionPatterns,
  			String[] exclusionPatterns) {
  		return createScriptPath(resource, type, inclusionPatterns, exclusionPatterns, null);
  	}
  
  	private ITernScriptPath createScriptPath(IResource resource, ScriptPathsType type, String[] inclusionPatterns,
  			String[] exclusionPatterns, String external) {
  		switch (type) {
  		case FOLDER:
  			return new FolderScriptPath(this, (IFolder) resource, inclusionPatterns, exclusionPatterns, external);
  		case FILE:
  			ITernFile file = getFile(resource);
  			if (file == null) {
  				break;
  			}
  			if (TernResourcesManager.isJSFile(file)) {
  				return new JSFileScriptPath(this, file, external);
  			}
  			return new DOMElementsScriptPath(this, file, external);
  		case PROJECT:
  			ITernProject project;
  			try {
  				project = TernCorePlugin.getTernProject((IProject) resource);
  				if (project != null) {
  					return new EclipseProjectScriptPath(project, this, inclusionPatterns, exclusionPatterns, external);
  				}
  			} catch (CoreException e) {
  				Trace.trace(Trace.SEVERE, "Project " + resource.getName() + " is not a Tern project", e);
  			}
  		}
  		throw new UnsupportedOperationException("Cannot create script path for the given type " + type);
  	}
  
  	/**
  	 * Set the new script paths to use.
  	 * 
  	 * @param scriptPaths
  	 * @throws IOException
  	 */
  	public void setScriptPaths(List<ITernScriptPath> scriptPaths) throws IOException {
  		this.scriptPaths.clear();
  		this.scriptPaths.addAll(scriptPaths);
  		resetSortScriptPaths();
  		save();
  	}
  
  	@Override
  	public ITernScriptPath addExternalScriptPath(IResource resource, ScriptPathsType type, String[] inclusionPatterns,
  			String[] exclusionPatterns, String external) throws IOException {
  		ITernScriptPath path = createScriptPath(resource, type, inclusionPatterns, exclusionPatterns, external);
  		scriptPaths.add(path);
  		resetSortScriptPaths();
  		return path;
  	}
  
  	@Override
  	public void removeExternalScriptPaths(String external) {
  		List<ITernScriptPath> initialScriptPaths = new ArrayList<ITernScriptPath>(scriptPaths);
  		for (ITernScriptPath scriptPath : initialScriptPaths) {
  			if (external.equals(scriptPath.getExternalLabel())) {
  				scriptPaths.remove(scriptPath);
  			}
  		}
  		resetSortScriptPaths();
  	}
  
  	@Override
  	public Object getAdapter(@SuppressWarnings("rawtypes") Class adapterClass) {
  		if (adapterClass == IProject.class || adapterClass == IContainer.class || adapterClass == IResource.class) {
  			return project;
  		}
  		return super.getAdapter(adapterClass);
  	}
  
  	@Override
  	public boolean equals(Object value) {
  		if (value instanceof IDETernProject) {
  			return ((IDETernProject) value).getProject().equals(getProject());
  		}
  		return super.equals(value);
  	}
  
  	/**
  	 * Returns the script path instance from the given path and null otherwise.
  	 * 
  	 * @param path
  	 *            of the script path resource.
  	 * @return the script path instance from the given path and null otherwise.
  	 */
  	public ITernScriptPath getScriptPath(String path) {
  		for (ITernScriptPath scriptPath : scriptPaths) {
  			if (scriptPath.getPath().equals(path)) {
  				return scriptPath;
  			}
  		}
  		return null;
  	}
  
  	/**
  	 * Returns true if trace of the tern server (JSON request/response) should
  	 * be displayed on the Eclipse console and false otherwise.
  	 * 
  	 * @return
  	 */
  	public boolean isTraceOnConsole() {
  		return TernCorePreferencesSupport.getInstance().isTraceOnConsole(project);
  	}
  
  	/**
  	 * Configure console to show/hide JSON request/response of the tern server.
  	 */
  	public void configureConsole() {
  		synchronized (serverLock) {
  			if (ternServer != null) {
  				// There is a tern server instance., Retrieve the well connector
  				// the
  				// the eclipse console.
  				ITernConsoleConnector connector = TernConsoleConnectorManager.getManager().getConnector(ternServer);
  				if (connector != null) {
  					if (isTraceOnConsole()) {
  						// connect the tern server to the eclipse console.
  						connector.connectToConsole(ternServer, this);
  					} else {
  						// disconnect the tern server to the eclipse console.
  						connector.disconnectToConsole(ternServer, this);
  					}
  				}
  			}
  		}
  	}
  
  	public void disposeServer() {
  		synchronized (serverLock) {
  			if (!isServerDisposed()) {
  				if (ternServer != null) {
  					// notify uploader that we are going to dispose the server,
  					// so that it can finish gracefully
  					((IDETernFileUploader) ((TernFileSynchronizer) getFileSynchronizer()).getTernFileUploader())
  							.serverToBeDisposed();
  					ternServer.dispose();
  					ternServer = null;
  				}
  			}
  		}
  	}
  
  	@SuppressWarnings("unchecked")
  	public <T> T getData(String key) {
  		synchronized (data) {
  			return (T) data.get(key);
  		}
  	}
  
  	public void setData(String key, Object value) {
  		synchronized (data) {
  			data.put(key, value);
  		}
  	}
  
  	@Override
  	public void serverPreferencesChanged(IProject project) {
  		if (project == null || getProject().equals(project)) {
  			disposeServer();
  		}
  	}
  
  	// ----------------------- Tern server listeners.
  
  	@Override
  	public void addServerListener(ITernServerListener listener) {
  		synchronized (listeners) {
  			if (!listeners.contains(listener)) {
  				listeners.add(listener);
  			}
  		}
  		copyListeners();
  	}
  
  	@Override
  	public void removeServerListener(ITernServerListener listener) {
  		synchronized (listeners) {
  			listeners.remove(listener);
  		}
  		synchronized (serverLock) {
  			if (ternServer != null) {
  				this.ternServer.removeServerListener(listener);
  			}
  		}
  	}
  
  	private void copyListeners() {
  		synchronized (serverLock) {
  			if (ternServer != null) {
  				for (ITernServerListener listener : listeners) {
  					this.ternServer.addServerListener(listener);
  				}
  			}
  		}
  	}
  
  	@Override
  	public List<ITernModule> getProjectModules() {
  		final List<ITernModule> modules = new ArrayList<ITernModule>();
  		final ITernRepository projectRepository = getRepository();
  		final ITernRepository defaultRepository = TernRepositoryManager.getManager().getDefaultRepository();
  		if (project.isAccessible() && TernCorePreferencesSupport.getInstance().isLoadingLocalPlugins(project)) {
  			try {
  				project.accept(new IResourceVisitor() {
  
  					@Override
  					public boolean visit(IResource resource) throws CoreException {
  						switch (resource.getType()) {
  						case IResource.PROJECT:
  							return true;
  						case IResource.FILE:
  							ITernModule module = TernModuleHelper.createModule(resource.getName(), projectRepository,
  									defaultRepository);
  							if (module != null) {
  								modules.add(module);
  							}
  							return false;
  						default:
  							return false;
  						}
  					}
  				});
  			} catch (CoreException e) {
  				Trace.trace(Trace.SEVERE, "Error while collecting tern plugin from the project root", e);
  			}
  		}
  		return modules;
  	}
  
  	@Override
  	public ITernRepository getRepository() {
  		return TernRepositoryManager.getManager().getRepository(getProject());
  	}
  
  	public void dispose() throws CoreException {
  		try {
  			TernProjectLifecycleManager.getManager().fireTernProjectLifeCycleListenerChanged(this,
  					LifecycleEventType.onDisposeBefore);
  			disposeServer();
  			getFileSynchronizer().dispose();
  			if (project.isAccessible()) {
  				project.setSessionProperty(TERN_PROJECT, null);
  			}
  		} finally {
  			TernProjectLifecycleManager.getManager().fireTernProjectLifeCycleListenerChanged(this,
  					LifecycleEventType.onDisposeAfter);
  		}
  	}
  
  	protected static IDETernProject getTernProject(IProject project) throws CoreException {
  		if (project.isAccessible()) {
  			return (IDETernProject) project.getSessionProperty(TERN_PROJECT);
  		}
  		return null;
  	}
  
  	@Override
  	public List<ITernModule> getAllModules() throws TernException {
  		// Add global tern module from the repository
  		List<ITernModule> allModules = new ArrayList<ITernModule>(Arrays.asList(getRepository().getModules()));
  		// Add local tern modules
  		List<ITernModule> projectModules = getProjectModules();
  		allModules.addAll(projectModules);
  		return allModules;
  	}
  
  	@Override
  	public IWorkingCopy getWorkingCopy(Object caller) throws TernException {
  		if (workingCopy.isDirty()) {
  			workingCopy.initialize();
  		}
  		workingCopy.call(caller);
  		return workingCopy;
  	}
  
  	@Override
  	public boolean isInScope(IResource resource, IScopeContext context) {
  		if (context == null) {
  			context = ScopeContext.DEFAULT;
  		}
  
  		// check if the resource is valid
  		if (!checkValidResource(resource, context)) {
  			return false;
  		}
  
  		// check if the resource is in scope with script paths.
  		return checkInScopeResource(resource, context);
  	}
  
  	/**
  	 * Returns true if the resource is valid and false otherwise.
  	 * 
  	 * @param resource
  	 * @param context
  	 * @return true if the resource is valid and false otherwise.
  	 */
  	private boolean checkValidResource(IResource resource, IScopeContext context) {
  		if (!FileUtils.isValidResource(resource)) {
  			return false;
  		}
  		IContainer parent = resource.getParent();
  		while ((parent.getType() & IResource.PROJECT) == 0) {
  			if (context.isExclude(parent)) {
  				return false;
  			}
  			if (context.isInclude(parent)) {
  				return true;
  			}
  			if (!FileUtils.isValidResource(parent)) {
  				context.addExclude(parent);
  				return false;
  			}
  			parent = parent.getParent();
  		}
  		return true;
  	}
  
  	/**
  	 * Returns true if the given resource is in the scope of tern script path
  	 * and false otherwise.
  	 * 
  	 * @param resource
  	 * @param context
  	 * @return true if the given resource is in the scope of tern script path
  	 *         and false otherwise.
  	 */
  	private boolean checkInScopeResource(IResource resource, IScopeContext context) {
  		List<ITernScriptPath> scriptPaths = getSortedScriptPaths();
  		if (scriptPaths.isEmpty()) {
  			// none script path, we consider that resource is in the scope?
  			return true;
  		}
  
  		// Loop for each tern script path
  		int resourceType = resource.getType();
  		IPath path = resource.getFullPath();
  		IIDETernScriptPath s = null;
  		IContainer parent = null;
  		for (ITernScriptPath scriptPath : scriptPaths) {
  			if (scriptPath instanceof IIDETernScriptPath) {
  				// script path is an Eclipse Project or Folder
  				s = (IIDETernScriptPath) scriptPath;
  				if (s.isBelongToContainer(path)) {
  					// the resource path belongs to the Project/folder of the
  					// tern script path
  					if (!s.isInScope(path, resourceType)) {
  						// the tern script exclude or don't include the resource
  						return false;
  					} else if (resourceType == IResource.FILE) {
  						// None exclusion, check if the parent folder exclude
  						// the file
  						parent = resource.getParent();
  						while ((parent.getType() & IResource.PROJECT) == 0) {
  							if (context.isInclude(parent)) {
  								return true;
  							}
  							if (!s.isInScope(parent.getFullPath(), parent.getType())) {
  								context.addExclude(parent);
  								return false;
  							}
  							parent = parent.getParent();
  						}
  					}
  					// The parent folder of the resource
  					context.addInclude(resource.getParent());
  					return true;
  				}
  			}
  		}
  		// resource is not inside a scritp path, exclude it.
  		return false;
  	}
  
  	private List<ITernScriptPath> getSortedScriptPaths() {
  		if (sortedScriptPaths == null) {
  			if (!scriptPaths.isEmpty()) {
  				sortedScriptPaths = new ArrayList<ITernScriptPath>(scriptPaths);
  				Collections.sort(sortedScriptPaths, TernScriptPathComparator.INSTANCE);
  			} else {
  				sortedScriptPaths = Collections.emptyList();
  			}
  		}
  		return sortedScriptPaths;
  	}
  
  	@Override
  	public IIDETernScriptPathReporter getScriptPathReporter() {
  		// Uncomment to have trace for include/exclude files.
  		return null; // SysErrScriptPathReporter.INSTANCE;
  	}
  
  }