/**
* 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.jsdt.internal.core;
import java.io.IOException;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
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.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.wst.jsdt.core.ElementChangedEvent;
import org.eclipse.wst.jsdt.core.IElementChangedListener;
import org.eclipse.wst.jsdt.core.IIncludePathEntry;
import org.eclipse.wst.jsdt.core.IJavaScriptElement;
import org.eclipse.wst.jsdt.core.IJavaScriptElementDelta;
import org.eclipse.wst.jsdt.core.IJavaScriptProject;
import org.eclipse.wst.jsdt.core.JavaScriptCore;
import org.eclipse.wst.jsdt.core.JavaScriptModelException;
import org.eclipse.wst.jsdt.internal.core.JavaProject;
import tern.eclipse.ide.core.IIDETernProject;
import tern.eclipse.ide.core.ITernProjectLifecycleListener;
import tern.eclipse.ide.core.TernCorePlugin;
import tern.scriptpath.ITernScriptPath.ScriptPathsType;
/**
* JSDT Class Path manager used to synchronize tern script paths with JSDT
* "Include Paths".
*
*/
public class JSDTClassPathManager implements IElementChangedListener,
ITernProjectLifecycleListener {
private static final String JSDT_EXTERNAL_LABEL = "jsdt";
private static final JSDTClassPathManager INSTANCE = new JSDTClassPathManager();
public static JSDTClassPathManager getManager() {
return INSTANCE;
}
/**
* Observe the changes of the JSDT "Include Path" to synchronize tern script
* paths.
*/
@Override
public void elementChanged(ElementChangedEvent event) {
IJavaScriptElementDelta delta = event.getDelta();
if (delta.getKind() == IJavaScriptElementDelta.CHANGED) {
// retrieve the JSDT Project if delta is about "Includes Path"
// changes.
IJavaScriptProject jsProject = getJavaScriptProjectIfClassPathChanged(delta);
if (jsProject != null) {
Job configJob = new ConfigureJob(jsProject);
configJob.setRule(jsProject.getProject());
configJob.schedule();
}
}
}
private class ConfigureJob extends WorkspaceJob {
private final IJavaScriptProject jsProject;
private ConfigureJob(IJavaScriptProject jsProject) {
super("Tern Project configuration job");
this.jsProject = jsProject;
}
@Override
public IStatus runInWorkspace(IProgressMonitor monitor)
throws CoreException {
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
monitor.beginTask("Confuguring the project", 1);
// JSDT "Includes Path", has changed.
if (jsProject != null) {
IProject project = jsProject.getProject();
if (TernCorePlugin.hasTernNature(project)) {
// It's a tern project
try {
IIDETernProject ternProject = TernCorePlugin
.getTernProject(project);
// Synchronize tern script paths with JSDT
// "Include Path"
synchTernScriptPaths(jsProject, ternProject);
} catch (Exception e) {
Trace.trace(Trace.SEVERE,
"Error while JSDT ClassPath changed.", e);
}
}
}
monitor.worked(1);
monitor.done();
return Status.OK_STATUS;
}
}
/**
* Retrieve the JSDT Project from the delta, if delta is about
* "Includes Path" changes.
*
* @param delta
* the JSDT delta.
* @return
*/
private IJavaScriptProject getJavaScriptProjectIfClassPathChanged(
IJavaScriptElementDelta delta) {
if (delta.getKind() != IJavaScriptElementDelta.CHANGED) {
return null;
}
IJavaScriptElement element = delta.getElement();
switch (element.getElementType()) {
case IJavaScriptElement.JAVASCRIPT_MODEL:
IJavaScriptElementDelta[] children = delta.getAffectedChildren();
for (int i = 0, length = children.length; i < length; i++) {
IJavaScriptElementDelta child = children[i];
IJavaScriptProject project = getJavaScriptProjectIfClassPathChanged(child);
if (project != null) {
return project;
}
}
return null;
case IJavaScriptElement.JAVASCRIPT_PROJECT:
int kind = delta.getKind();
switch (kind) {
case IJavaScriptElementDelta.ADDED:
case IJavaScriptElementDelta.REMOVED:
return null;
case IJavaScriptElementDelta.CHANGED:
int flags = delta.getFlags();
if ((flags & IJavaScriptElementDelta.F_CLOSED) != 0
|| (flags & IJavaScriptElementDelta.F_OPENED) != 0) {
return null;
} else {
children = delta.getAffectedChildren();
for (int i = 0, length = children.length; i < length; i++) {
IJavaScriptElementDelta child = children[i];
IJavaScriptProject project = getJavaScriptProjectIfClassPathChanged(child);
if (project != null) {
return project;
}
}
}
return null;
}
return null;
case IJavaScriptElement.PACKAGE_FRAGMENT_ROOT:
kind = delta.getKind();
switch (kind) {
case IJavaScriptElementDelta.ADDED:
case IJavaScriptElementDelta.REMOVED:
return null;
case IJavaScriptElementDelta.CHANGED:
int flags = delta.getFlags();
if ((flags & IJavaScriptElementDelta.F_ADDED_TO_CLASSPATH) > 0
|| (flags & IJavaScriptElementDelta.F_REMOVED_FROM_CLASSPATH) > 0) {
// "Include Path" has changed, returns the JSDT project.
return element.getJavaScriptProject();
}
return null;
}
break;
}
return null;
}
public void startup() {
JavaScriptCore.addElementChangedListener(this,
ElementChangedEvent.POST_CHANGE);
// the JSDT Class Path Manager is registered as listener with the
// ternProjectLifecycleListeners extension point
// to be sure when TernProject fires listener, this JSDT Class Path
// manager is registered.
// TernCorePlugin.addTernProjectLifeCycleListener(this);
}
public void shutdown() {
JavaScriptCore.removeElementChangedListener(this);
TernCorePlugin.removeTernProjectLifeCycleListener(this);
}
/**
* Synchronize on the tern project load, tern script pats with JSDT
* "Include Path".
*/
@Override
public void handleEvent(IIDETernProject ternProject,
LifecycleEventType eventType) {
if (eventType != LifecycleEventType.onLoadAfter) {
return;
}
// the given tern project is loading...
IProject project = ternProject.getProject();
if (JavaProject.hasJavaNature(project)) {
// The project is a JSDT project.
IJavaScriptProject jsProject = JavaScriptCore.create(project);
// Synchronize tern script paths with JSDT "Include Path"
synchTernScriptPaths(jsProject, ternProject);
}
}
/**
* Synchronize tern script paths with JSDT "Include Path"
*
* @param jsProject
* @param ternProject
*/
private void synchTernScriptPaths(IJavaScriptProject jsProject,
IIDETernProject ternProject) {
try {
ternProject.removeExternalScriptPaths(JSDT_EXTERNAL_LABEL);
IIncludePathEntry[] entries = jsProject.getRawIncludepath();
for (int i = 0; i < entries.length; i++) {
IIncludePathEntry entry = entries[i];
switch (entry.getEntryKind()) {
case IIncludePathEntry.CPE_LIBRARY:
// TODO : manage JSDT library
// JSDT Source file => Tern script path file.
/*
* IFolder libFolder =
* ResourcesPlugin.getWorkspace().getRoot()
* .getFolder(entry.getPath()); try {
* ternProject.addExternalScriptPath(libFolder,
* ScriptPathsType.FOLDER, JSDT_EXTERNAL_LABEL); } catch
* (IOException e) { Trace.trace(Trace.SEVERE,
* "Error while adding external tern script path for project "
* + ternProject.getProject().getName(), e); }
*/
break;
case IIncludePathEntry.CPE_SOURCE:
if (entry.getPath().segmentCount() == 1) {
// It's a project
synchTernProjectScriptPaths(ternProject, entry);
} else {
// It's a folder
// JSDT Source folder => Tern script path folder.
IFolder folder = ResourcesPlugin.getWorkspace()
.getRoot().getFolder(entry.getPath());
try {
String[] inclusionPatterns = toTernPatterns(entry
.getInclusionPatterns());
String[] exclusionPatterns = toTernPatterns(entry
.getExclusionPatterns());
ternProject.addExternalScriptPath(folder,
ScriptPathsType.FOLDER, inclusionPatterns,
exclusionPatterns, JSDT_EXTERNAL_LABEL);
} catch (IOException e) {
Trace.trace(Trace.SEVERE,
"Error while adding external tern script path for project "
+ ternProject.getProject()
.getName(), e);
}
}
break;
case IIncludePathEntry.CPE_PROJECT:
// JS file?
synchTernProjectScriptPaths(ternProject, entry);
break;
}
}
} catch (JavaScriptModelException e) {
Trace.trace(Trace.SEVERE,
"Error while getting JSDT ClassPath for project "
+ ternProject.getProject().getName(), e);
}
}
private String[] toTernPatterns(IPath[] paths) {
if (paths == null || paths.length < 1) {
return null;
}
String[] patterns = new String[paths.length];
for (int i = 0; i < paths.length; i++) {
patterns[i] = paths[i].toString();
}
return patterns;
}
/**
* Synchronize tern project script paths with JSDT "Include Path"
*
* @param ternProject
* @param entry
*/
private void synchTernProjectScriptPaths(IIDETernProject ternProject,
IIncludePathEntry entry) {
// JSDT Project => Tern script path project.
IProject project = ResourcesPlugin.getWorkspace().getRoot()
.getProject(entry.getPath().lastSegment());
try {
String[] inclusionPatterns = toTernPatterns(entry
.getInclusionPatterns());
String[] exclusionPatterns = toTernPatterns(entry
.getExclusionPatterns());
ternProject.addExternalScriptPath(project, ScriptPathsType.PROJECT,
inclusionPatterns, exclusionPatterns, JSDT_EXTERNAL_LABEL);
} catch (IOException e) {
Trace.trace(Trace.SEVERE,
"Error while adding external tern script path for project "
+ ternProject.getProject().getName(), e);
}
}
}