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.ui.views;
  
  import java.util.ArrayList;
  import java.util.List;
  
  import org.eclipse.core.resources.IFile;
  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.jface.text.IDocument;
  import org.eclipse.jface.viewers.TreePath;
  import org.eclipse.jface.viewers.TreeViewer;
  import org.eclipse.swt.widgets.Control;
  import org.eclipse.swt.widgets.Display;
  import org.eclipse.ui.navigator.CommonViewer;
  
  import tern.eclipse.ide.internal.ui.TernUIMessages;
  import tern.eclipse.ide.ui.TernUIPlugin;
  import tern.server.protocol.outline.IJSNode;
  import tern.server.protocol.outline.IJSNodeRoot;
  import tern.server.protocol.outline.TernOutlineCollector;
  
  /**
   * Job used to refresh tern outline.
   *
   */
  class RefreshOutlineJob extends Job implements IOutlineProvider {
  
  	private static final int UPDATE_DELAY = 500;
  
  	private final AbstractTernOutlineView view;
  	private State state;
  	private TernOutlineCollector outline;
  
  	public RefreshOutlineJob(AbstractTernOutlineView view) {
  		super(TernUIMessages.refreshOutline);
  		super.setSystem(true);
  		super.setPriority(Job.SHORT);
  		this.view = view;
  	}
  
  	@Override
  	protected IStatus run(IProgressMonitor monitor) {
  		final CommonViewer viewer = view.getCurrentViewer();
  		if (viewer == null) {
  			return Status.OK_STATUS;
  		}
  		AbstractTernContentOutlinePage page = view.getCurrentTernPage();
  		IFile file = page.getCurrentFile();
  		if (!view.isOutlineAvailable(file)) {
  			state = State.Unavailable;
  		} else {
  			state = State.Computing;
  		}
  		if (viewer.getInput() == null) {
  			Display.getDefault().syncExec(new Runnable() {
  				@Override
  				public void run() {
  					Control refreshControl = viewer.getControl();
  					if ((refreshControl != null) && !refreshControl.isDisposed()) {
  						viewer.setInput(RefreshOutlineJob.this);
  					}
  				}
  			});
  		}
  		if (state != State.Unavailable) {
  			try {
  				IDocument document = page.getCurrentDocument();
  				outline = view.loadOutline(file, document);
  				if (outline != null) {
  					state = State.Done;
  					Display.getDefault().syncExec(new Runnable() {
  						@Override
  						public void run() {
  							Control refreshControl = viewer.getControl();
  							if ((refreshControl != null) && !refreshControl.isDisposed()) {
  								TreePath[] expendedPaths = null;
  								if (viewer instanceof TreeViewer) {
  									expendedPaths = ((TreeViewer) viewer).getExpandedTreePaths();
  								}
  								viewer.refresh();
  								if (viewer instanceof TreeViewer && expendedPaths != null) {
  									((TreeViewer) viewer)
  											.setExpandedTreePaths(toNewTreePaths(expendedPaths, outline.getRoot()));
  								}
  							}
  						}
  					});
  				}
  			} catch (Exception e) {
  				return new Status(Status.ERROR, TernUIPlugin.PLUGIN_ID, "Error while loading tern outline...", e);
  			}
  		}
  
  		return Status.OK_STATUS;
  	}
  
  	private TreePath[] toNewTreePaths(TreePath[] originExpandedPaths, IJSNode newRoot) {
  		List<TreePath> res = new ArrayList<TreePath>();
  		for (TreePath originExpanded : originExpandedPaths) {
  			int i = 0;
  			List<Object> newPathItems = new ArrayList<Object>();
  			IJSNode previousJSNode = null;
  			while (i < originExpanded.getSegmentCount()) {
  				Object originSegment = originExpanded.getSegment(i);
  				if (originSegment instanceof IJSNode) {
  					IJSNode originNode = (IJSNode) originSegment;
  					IJSNode matchingNode = null;
  					if (previousJSNode == null) {
  						if (originNode instanceof IJSNodeRoot) {
  							matchingNode = newRoot;
  						} else {
  							matchingNode = findSimilarChild(newRoot, originNode);
  						}
  					} else {
  						matchingNode = findSimilarChild(previousJSNode, originNode);
  					}
  					if (matchingNode != null) {
  						newPathItems.add(matchingNode);
  						previousJSNode = matchingNode;
  					}
  				} else {
  					newPathItems.add(originSegment);
  				}
  				i++;
  			}
  			res.add(new TreePath(newPathItems.toArray()));
  		}
  		return res.toArray(new TreePath[res.size()]);
  	}
  
  	private IJSNode findSimilarChild(IJSNode newParentNode, IJSNode originChildNode) {
  		IJSNode matchingNode = null;
  		// First search node with same name
  		if (originChildNode.getName() != null) {
  			for (IJSNode child : newParentNode.getChildren()) {
  				if (child.getName() != null && child.getName().equals(originChildNode.getName())) {
  					matchingNode = child;
  				}
  			}
  		}
  		// If not found, fail back to index
  		if (matchingNode == null) {
  			int index = originChildNode.getParent().getChildren().indexOf(originChildNode);
  			if (index >= 0 && newParentNode.getChildren().size() > index) {
  				matchingNode = newParentNode.getChildren().get(index);
  			}
  		}
  		return matchingNode;
  	}
  
  	@Override
  	public State getOutlineState() {
  		return state;
  	}
  
  	@Override
  	public IJSNode getRoot() {
  		return outline != null ? outline.getRoot() : null;
  	}
  
  	public void refreshOutline() {
  		if (getState() != Job.NONE) {
  			cancel();
  		}
  		schedule(UPDATE_DELAY);
  	}
  
  }
  