blob: 11114cec93dd967fa23edaeb2c770f20b20d9eaa [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 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
*
*******************************************************************************/
package org.eclipse.wst.css.ui.internal.contentoutline;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import org.eclipse.wst.css.core.internal.provisional.document.ICSSDocument;
import org.eclipse.wst.css.core.internal.provisional.document.ICSSNode;
import org.eclipse.wst.xml.ui.internal.XMLUIMessages;
public class RefreshStructureJob extends Job {
/** debug flag */
static final boolean DEBUG;
private static final long UPDATE_DELAY = 300;
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 fRefreshes;
/** List of update requests (Nodes) */
private final List fUpdates;
/** List of update requests (Nodes) */
private final List fUpdateProperties;
/** the structured viewers */
Set fRefreshViewers = new HashSet(3);
Set fUpdateViewers = new HashSet(3);
public RefreshStructureJob() {
super(XMLUIMessages.refreshoutline_0);
setPriority(Job.LONG);
setSystem(true);
fRefreshes = new ArrayList(5);
fUpdates = new ArrayList(5);
fUpdateProperties = new ArrayList(5);
}
private synchronized void addUpdateRequest(ICSSNode newNodeRequest, String[] updateProperties) {
/*
* If we get to here, either from existing request list being zero
* length, or no exisitng requests "matched" new request, then add the
* new request.
*/
fUpdates.add(newNodeRequest);
fUpdateProperties.add(updateProperties);
}
private synchronized void addUpdateViewer(StructuredViewer viewer) {
fUpdateViewers.add(viewer);
}
private synchronized void addRefreshRequest(ICSSNode newNodeRequest) {
/*
* note: the caller must NOT pass in null node request (which, since
* private method, we do not need to gaurd against here, as long as we
* gaurd against it in calling method.
*/
int size = fRefreshes.size();
for (int i = 0; i < size; i++) {
ICSSNode existingNodeRequest = (ICSSNode) fRefreshes.get(i);
/*
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=157427 If we
* already have a request which equals the new request, discard
* the new request
*/
if (existingNodeRequest.equals(newNodeRequest)) {
return;
}
/*
* If we already have a request which contains the new request,
* discard the new request
*/
if (contains(existingNodeRequest, newNodeRequest)) {
return;
}
/*
* If new request contains any existing requests, replace it with
* new request. ISSUE: technically, we should replace ALL
* contained, existing requests (such as if many siblings already
* que'd up when their common parent is then requested, but, I'm
* not sure if that occurs much, in practice, or if there's an
* algorithm to quickly find them all. Actually, I guess we could
* just go through the _rest_ of the list (i+1 to size) and remove
* any that are contained by new request ... in future :) .
*/
if (contains(newNodeRequest, existingNodeRequest)) {
fRefreshes.set(i, newNodeRequest);
return;
}
}
/*
* If we get to here, either from existing request list being zero
* length, or no exisitng requests "matched" new request, then add the
* new request.
*/
fRefreshes.add(newNodeRequest);
}
private synchronized void addRefreshViewer(StructuredViewer viewer) {
fRefreshViewers.add(viewer);
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.jobs.Job#canceling()
*/
protected void canceling() {
fUpdates.clear();
fUpdateViewers.clear();
super.canceling();
}
/**
* Simple hierarchical containment relationship. Note, this method returns
* "false" if the two nodes are equal!
*
* @param root
* @param possible
* @return if the root is parent of possible, return true, otherwise
* return false
*/
private boolean contains(ICSSNode root, ICSSNode possible) {
if (DEBUG) {
System.out.println("=============================================================================================================="); //$NON-NLS-1$
System.out.println("recursive call w/ root: " + root + " 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 child 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 ICSSDocument) {
if (DEBUG) {
System.out.println("returning false: possible is Document node"); //$NON-NLS-1$
}
return false;
}
// document contains everything
if (root instanceof ICSSDocument) {
if (DEBUG) {
System.out.println("returning true: root is Document node"); //$NON-NLS-1$
}
return true;
}
// check parentage
ICSSNode current = possible;
// loop parents
while ((current != null) && (current.getNodeType() != ICSSNode.STYLESHEET_NODE || current.getNodeType() != ICSSNode.STYLEDECLARATION_NODE)) {
// found it
if (root.equals(current)) {
if (DEBUG) {
System.out.println(" !!! found: " + possible + " in subelement of: " + root); //$NON-NLS-1$ //$NON-NLS-2$
}
return true;
}
current = current.getParentNode();
}
// never found it
return false;
}
/**
* Refresh must be on UI thread because it's on a SWT widget.
*
* @param node
*/
private void doRefresh(final ICSSNode node, final StructuredViewer[] viewers) {
final Display display = PlatformUI.getWorkbench().getDisplay();
display.asyncExec(new Runnable() {
public void run() {
if (DEBUG) {
System.out.println("refresh on: [" + node + "]"); //$NON-NLS-1$ //$NON-NLS-2$
}
for (int i = 0; i < viewers.length; i++) {
if (!viewers[i].getControl().isDisposed()) {
if (node.getNodeType() == ICSSNode.STYLESHEET_NODE || node.getNodeType() == ICSSNode.STYLEDECLARATION_NODE) {
viewers[i].refresh(false);
}
else {
viewers[i].refresh(node, false);
}
}
else {
if (DEBUG) {
System.out.println(" !!! skipped refreshing disposed viewer: " + viewers[i]); //$NON-NLS-1$
}
}
}
}
});
}
/**
* Update must be on UI thread because it's on a SWT widget.
*
* @param node
*/
private void doUpdate(final StructuredViewer[] viewers, final ICSSNode node, final String[] properties) {
final Display display = PlatformUI.getWorkbench().getDisplay();
display.asyncExec(new Runnable() {
public void run() {
if (DEBUG) {
System.out.println("refresh on: [" + node + "]"); //$NON-NLS-1$ //$NON-NLS-2$
}
for (int i = 0; i < viewers.length; i++) {
if (!viewers[i].getControl().isDisposed()) {
viewers[i].update(node, properties);
}
else {
if (DEBUG) {
System.out.println(" !!! skipped refreshing disposed viewer: " + viewers[i]); //$NON-NLS-1$
}
}
}
}
});
}
/**
* This method also synchronized because it accesses the fRefreshes queue
* and fRefreshViewers 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[] getRefreshRequests() {
ICSSNode[] toRefresh = (ICSSNode[]) fRefreshes.toArray(new ICSSNode[fRefreshes.size()]);
fRefreshes.clear();
StructuredViewer[] viewers = (StructuredViewer[]) fRefreshViewers.toArray(new StructuredViewer[fRefreshViewers.size()]);
fRefreshViewers.clear();
return new Object[]{toRefresh, viewers};
}
/**
* This method also synchronized because it accesses the fUpdates queue
* and fUpdateViewers 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[] getUpdateRequests() {
ICSSNode[] toUpdate = (ICSSNode[]) fUpdates.toArray(new ICSSNode[fUpdates.size()]);
fUpdates.clear();
StructuredViewer[] viewers = (StructuredViewer[]) fUpdateViewers.toArray(new StructuredViewer[fUpdateViewers.size()]);
fUpdateViewers.clear();
String[][] properties = (String[][]) fUpdateProperties.toArray(new String[fUpdateProperties.size()][]);
fUpdateProperties.clear();
return new Object[]{toUpdate, viewers, properties};
}
/**
* Invoke a refresh on the viewer on the given node.
*
* @param node
*/
public void refresh(StructuredViewer viewer, ICSSNode node) {
if (node == null) {
return;
}
addRefreshViewer(viewer);
addRefreshRequest(node);
schedule(UPDATE_DELAY);
}
/**
* Invoke a refresh on the viewer on the given node.
*
* @param node
*/
public void update(StructuredViewer viewer, ICSSNode node, String[] properties) {
if (node == null) {
return;
}
addUpdateViewer(viewer);
addUpdateRequest(node, properties);
schedule(UPDATE_DELAY);
}
protected IStatus run(IProgressMonitor monitor) {
IStatus status = Status.OK_STATUS;
try {
performUpdates();
performRefreshes(monitor);
}
finally {
monitor.done();
}
return status;
}
private void performRefreshes(IProgressMonitor monitor) {
// Retrieve BOTH viewers and Nodes on one block
Object[] requests = getRefreshRequests();
ICSSNode[] nodes = (ICSSNode[]) 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);
}
}
private void performUpdates() {
// Retrieve BOTH viewers and Nodes on one block
Object[] requests = getUpdateRequests();
ICSSNode[] nodes = (ICSSNode[]) requests[0];
StructuredViewer[] viewers = (StructuredViewer[]) requests[1];
String[][] properties = (String[][]) requests[2];
for (int i = 0; i < nodes.length; i++) {
doUpdate(viewers, nodes[i], properties[i]);
}
}
}