/******************************************************************************* * Copyright (c) 2009 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation * Zend Technologies *******************************************************************************/ package org.eclipse.php.internal.ui.editor.adapter; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.viewers.StructuredViewer; import org.eclipse.php.internal.core.documentModel.parser.regions.PHPRegionTypes; import org.eclipse.php.internal.ui.outline.PHPOutlineContentProvider; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.PlatformUI; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode; import org.eclipse.wst.xml.ui.internal.XMLUIMessages; import org.w3c.dom.Document; import org.w3c.dom.Node; /** * This job holds a queue of updates (affected nodes) for multiple structured * viewers. When a new request comes in, the current run is cancelled, the new * request is added to the queue, then the job is re-scheduled. Support for * multiple structured viewers is required because refresh updates are usually * triggered by model changes, and the model may be visible in more than one * viewer. * * NOTICE: This class is exactly the same as * org.eclipse.wst.xml.ui.internal.contentoutline.RefreshStructureJob The * difference is in the function doRefresh - the explanation is provided there. * * @author guy.g */ class RefreshStructureJob extends Job { /** debug flag */ static final boolean DEBUG; private static final long UPDATE_DELAY = 250; static { String value = Platform.getDebugOption("org.eclipse.wst.sse.ui/debug/refreshStructure"); //$NON-NLS-1$ DEBUG = value != null && value.equalsIgnoreCase("true"); //$NON-NLS-1$ } /** List of refresh requests (Nodes) */ private final List<Node> fRequests; /** the structured viewers */ List<StructuredViewer> fViewers = new ArrayList<>(3); public RefreshStructureJob() { super(XMLUIMessages.refreshoutline_0); // $NON-NLS-1$ setPriority(Job.LONG); setSystem(true); fRequests = new ArrayList<>(2); } private synchronized void addRequest(Node node) { int size = fRequests.size(); if (size > 0) { for (int i = 0; i < size; i++) { /* * If we already have a request which contains the new request, * discard the new request */ Node node2 = (Node) fRequests.get(i); if (contains(node2, node)) return; /* * If new request contains any existing requests, replace it * with new request */ if (contains(node, node2)) { fRequests.set(i, node); return; } } } else { fRequests.add(node); } } private synchronized void addViewer(StructuredViewer viewer) { if (!fViewers.contains(viewer)) { fViewers.add(viewer); } } /** * @param root * @param possible * @return if the root is parent of possible, return true, otherwise return * false */ private boolean contains(Node root, Node possible) { if (DEBUG) { System.out.println( "=============================================================================================================="); //$NON-NLS-1$ System.out.println("recursive call w/ root: " + root.getNodeName() + " and possible: " + possible); //$NON-NLS-1$ //$NON-NLS-2$ System.out.println( "--------------------------------------------------------------------------------------------------------------"); //$NON-NLS-1$ } // the following checks are important // #document node will break the algorithm otherwise // can't contain the parent if it's null if (root == null) { if (DEBUG) System.out.println("returning false: root is null"); //$NON-NLS-1$ return false; } // nothing can be parent of Document node if (possible instanceof Document) { if (DEBUG) System.out.println("returning false: possible is Document node"); //$NON-NLS-1$ return false; } // document contains everything if (root instanceof Document) { if (DEBUG) System.out.println("returning true: root is Document node"); //$NON-NLS-1$ return true; } // depth first Node current = root; // loop siblings while (current != null) { if (DEBUG) System.out.println(" -> iterating sibling (" + current.getNodeName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ // found it if (possible.equals(current)) { if (DEBUG) System.out.println( " !!! found: " + possible.getNodeName() + " in subtree for: " + root.getNodeName()); //$NON-NLS-1$ //$NON-NLS-2$ return true; } // drop one level deeper if necessary if (current.getFirstChild() != null) { return contains(current.getFirstChild(), possible); } current = current.getNextSibling(); } // never found it return false; } /** * Refresh must be on UI thread because it's on a SWT widget. * * The deferance in the functionallty is this: If we are refreshing the * outline when its in PHPOutlineContentProvider.MODE_PHP mode then it * doesn't metter which node was changed (unless is not php node) - we need * to refresh the whole tree. otherwise - (using the default behaivor) * refreshing only the relevent items in the tree. * * @param node */ private void doRefresh(final Node node, final StructuredViewer[] viewers) { final Display display = PlatformUI.getWorkbench().getDisplay(); display.asyncExec(() -> { if (DEBUG) { System.out.println("refresh on: [" + node.getNodeName() + "]"); //$NON-NLS-1$ //$NON-NLS-2$ } for (int i = 0; i < viewers.length; i++) { if (!viewers[i].getControl().isDisposed()) { StructuredViewer viewer = viewers[i]; if (node.getNodeType() == Node.DOCUMENT_NODE || // this // was // the // original // condition ((node.getNodeType() == Node.ELEMENT_NODE) && (((IDOMNode) node).getFirstStructuredDocumentRegion() .getType() == PHPRegionTypes.PHP_CONTENT) && (viewer.getContentProvider() instanceof PHPOutlineContentProvider) /* * && * ( * ( * ( * PHPOutlineContentProvider * ) * viewer * . * getContentProvider * ( * ) * ) * . * getMode * ( * ) * == * PHPOutlineContentProvider * . * MODE_PHP * ) */)) { viewers[i].refresh(true); } else { viewers[i].refresh(node, true); } } else { if (DEBUG) { System.out.println(" !!! skipped refreshing disposed viewer: " + viewers[i]); //$NON-NLS-1$ } } } }); } /** * This method also synchronized because it accesses the fRequests queue and * fViewers list * * @return an array containing and array of the currently requested Nodes to * refresh and the viewers in which to refresh them */ private synchronized Object[] getRequests() { Node[] toRefresh = fRequests.toArray(new Node[fRequests.size()]); fRequests.clear(); StructuredViewer[] viewers = fViewers.toArray(new StructuredViewer[fViewers.size()]); fViewers.clear(); return new Object[] { toRefresh, viewers }; } /** * Invoke a refresh on the viewer on the given node. * * @param node */ public void refresh(StructuredViewer viewer, Node node) { if (node == null) { return; } addViewer(viewer); addRequest(node); schedule(UPDATE_DELAY); } @Override protected IStatus run(IProgressMonitor monitor) { IStatus status = Status.OK_STATUS; try { // Retrieve BOTH viewers and Nodes on one block Object[] requests = getRequests(); Node[] nodes = (Node[]) requests[0]; StructuredViewer[] viewers = (StructuredViewer[]) requests[1]; for (int i = 0; i < nodes.length; i++) { if (monitor.isCanceled()) { throw new OperationCanceledException(); } doRefresh(nodes[i], viewers); } } finally { monitor.done(); } return status; } }