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.resources;
  
  import org.eclipse.core.resources.IProject;
  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.IResourceDeltaVisitor;
  import org.eclipse.core.resources.ResourcesPlugin;
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.jobs.Job;
  
  import tern.eclipse.ide.internal.core.Trace;
  import tern.resources.TernProject;
  
  /**
   * Tern project synchronizer used to :
   * 
   * <ul>
   * <li>dispose the tern project when the Eclipse project is removed or closed.</li>
   * <li>synchronize tern file indexes</li>
   * </ul>
   */
  public class IDETernProjectSynchronizer implements IResourceChangeListener,
  		IResourceDeltaVisitor {
  
  	private static final IDETernProjectSynchronizer INSTANCE = new IDETernProjectSynchronizer();
  
  	public static IDETernProjectSynchronizer getInstance() {
  		return INSTANCE;
  	}
  
  	public IDETernProjectSynchronizer() {
  
  	}
  
  	public void initialize() {
  		ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
  	}
  
  	/**
  	 * Dispose the file manager.
  	 */
  	public void dispose() {
  		ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
  	}
  
  	@Override
  	public void resourceChanged(IResourceChangeEvent event) {
  		try {
  			IResource resource = event.getResource();
  			switch (event.getType()) {
  			case IResourceChangeEvent.PRE_DELETE:
  				// called when project is deleted.
  			case IResourceChangeEvent.PRE_CLOSE:
  				// called when project is closed.
  				if (resource != null && resource.getType() == IResource.PROJECT) {
  					IProject project = (IProject) resource;
  					disposeTernProject(project);
  				}
  				break;
  			case IResourceChangeEvent.POST_CHANGE:
  				IResourceDelta delta = event.getDelta();
  				if (delta != null) {
  					delta.accept(this);
  				}
  				break;
  			}
  		} catch (Throwable e) {
  			Trace.trace(Trace.SEVERE, "Error while tern file synchronization",
  					e);
  		}
  	}
  
  	/**
  	 * Dispose the tern project linked to the given project if it is linked to a
  	 * tern project.
  	 * 
  	 * @param project
  	 * @throws CoreException
  	 */
  	private void disposeTernProject(IProject project) throws CoreException {
  		IDETernProject ternProject = IDETernProject.getTernProject(project);
  		if (ternProject != null) {
  			ternProject.dispose();
  		}
  	}
  
  	@Override
  	public boolean visit(IResourceDelta delta) throws CoreException {
  		IResource resource = delta.getResource();
  		if (resource == null) {
  			return false;
  		}
  		switch (resource.getType()) {
  		case IResource.ROOT:
  			return true;
  		case IResource.PROJECT:
  			IProject project = (IProject) resource;
  			// if project is created, or removed stop the synchronization
  			switch (delta.getKind()) {
  			case IResourceDelta.ADDED:
  			case IResourceDelta.REMOVED:
  				return false;
  			}
  			// check if the current project has tern nature
  			return IDETernProject.hasTernNature(project);
  		case IResource.FOLDER:
  			return true;
  		case IResource.FILE:
  			IDETernProject ternProject = IDETernProject.getTernProject(resource
  					.getProject());
  			if (ternProject == null) {
  				// tern project was not loaded, none synchronization is
  				// required.
  				return false;
  			}
  			if (isTernProjectFile(resource)) {
  				switch (delta.getKind()) {
  				case IResourceDelta.CHANGED:
  					// refresh tern project outside the resource delta to avoid
  					// having problem
  					// "org.eclipse.core.internal.resources.ResourceException: The resource tree is locked for modifications"
  					// See https://github.com/angelozerr/tern.java/issues/161
  					Job configJob = new RefreshTernProjectJob(ternProject);
  					
  					// We cannot use a project-based rule, because IProject.setDescription() uses a workspace-wide operation,
  					// so a workspace-based rule is to be used here
  					// See: https://github.com/angelozerr/tern.java/issues/251
  //					configJob.setRule(ternProject.getProject());
  					configJob.setRule(ternProject.getProject().getWorkspace().getRoot());
  					configJob.schedule();
  					break;
  				case IResourceDelta.REMOVED:
  					ternProject.dispose();
  					break;
  				}
  			} else {
  				ternProject.getFileSynchronizer().refresh(resource);
  			}
  			return true;
  		}
  		return false;
  	}
  
  	private boolean isTernProjectFile(IResource resource) {
  		// check if file name is .tern-project
  		if (!TernProject.TERN_PROJECT_FILE.equals(resource.getName())) {
  			return false;
  		}
  		// check if parent of file is a project
  		if (resource.getParent() == null
  				|| resource.getParent().getType() != IResource.PROJECT) {
  			return false;
  		}
  		return true;
  	}
  }
  