All pastes #636987 Raw Edit

Win32Monitor

public java v1 · immutable
#636987 ·published 2007-07-28 12:33 UTC
rendered paste body
/******************************************************************************* * Copyright (c) 2002, 2006 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 - Initial API and implementation *******************************************************************************/package org.eclipse.core.internal.resources.refresh.win32;import java.io.File;import java.util.*;import org.eclipse.core.internal.refresh.RefreshManager;import org.eclipse.core.internal.utils.Messages;import org.eclipse.core.resources.IResource;import org.eclipse.core.resources.ResourcesPlugin;import org.eclipse.core.resources.refresh.IRefreshMonitor;import org.eclipse.core.resources.refresh.IRefreshResult;import org.eclipse.core.runtime.*;import org.eclipse.core.runtime.jobs.Job;import org.eclipse.osgi.util.NLS;import org.osgi.framework.Bundle;/** * A monitor that works on Win32 platforms. Provides simple notification of * entire trees by reporting that the root of the tree has changed to depth * DEPTH_INFINITE. */class Win32Monitor extends Job implements IRefreshMonitor {	private static final long RESCHEDULE_DELAY = 1000;	/**	 * A ChainedHandle is a linked list of handles.	 */	protected abstract class ChainedHandle extends Handle {		private ChainedHandle next;		private ChainedHandle previous;		public abstract boolean exists();		public ChainedHandle getNext() {			return next;		}		public ChainedHandle getPrevious() {			return previous;		}		public void setNext(ChainedHandle next) {			this.next = next;		}		public void setPrevious(ChainedHandle previous) {			this.previous = previous;		}	}	protected class FileHandle extends ChainedHandle {		private File file;		public FileHandle(File file) {			this.file = file;		}		public boolean exists() {			return file.exists();		}		public void handleNotification() {			if (!isOpen())				return;			ChainedHandle next = getNext();			if (next != null) {				if (next.isOpen()) {					if (!next.exists()) {						if (next instanceof LinkedResourceHandle) {							next.close();							LinkedResourceHandle linkedResourceHandle = (LinkedResourceHandle) next;							linkedResourceHandle.postRefreshRequest();						} else {							next.close();						}						ChainedHandle previous = getPrevious();						if (previous != null)							previous.open();					}				} else {					next.open();					if (next.isOpen()) {						Handle previous = getPrevious();						previous.close();						if (next instanceof LinkedResourceHandle)							((LinkedResourceHandle) next).postRefreshRequest();					}				}			}			findNextChange();		}		public void open() {			if (!isOpen()) {				Handle next = getNext();				if (next != null && next.isOpen()) {					openHandleOn(file);				} else {					if (exists()) {						openHandleOn(file);					}					Handle previous = getPrevious();					if (previous != null) {						previous.open();					}				}			}		}	}	protected abstract class Handle {		protected long handleValue;		public Handle() {			handleValue = Win32Natives.INVALID_HANDLE_VALUE;		}		public void close() {			if (isOpen()) {				if (!Win32Natives.FindCloseChangeNotification(handleValue)) {					int error = Win32Natives.GetLastError();					if (error != Win32Natives.ERROR_INVALID_HANDLE)						addException(NLS.bind(Messages.WM_errCloseHandle, Integer.toString(error)));				}				if (RefreshManager.DEBUG)					System.out.println(DEBUG_PREFIX + "removed handle: " + handleValue); //$NON-NLS-1$				handleValue = Win32Natives.INVALID_HANDLE_VALUE;			}		}		private long createHandleValue(String path, boolean monitorSubtree, int flags) {			long handle = Win32Natives.FindFirstChangeNotification(path, monitorSubtree, flags);			if (handle == Win32Natives.INVALID_HANDLE_VALUE) {				int error = Win32Natives.GetLastError();				addException(NLS.bind(Messages.WM_errCreateHandle, path, Integer.toString(error)));			}			return handle;		}		public void destroy() {			close();		}		protected void findNextChange() {			if (!Win32Natives.FindNextChangeNotification(handleValue)) {				int error = Win32Natives.GetLastError();				if (error != Win32Natives.ERROR_INVALID_HANDLE && error != Win32Natives.ERROR_SUCCESS) {					addException(NLS.bind(Messages.WM_errFindChange, Integer.toString(error)));				}				removeHandle(this);			}		}		public long getHandleValue() {			return handleValue;		}		public abstract void handleNotification();		public boolean isOpen() {			return handleValue != Win32Natives.INVALID_HANDLE_VALUE;		}		public abstract void open();		protected void openHandleOn(File file) {			openHandleOn(file.getAbsolutePath(), false);		}		protected void openHandleOn(IResource resource) {			openHandleOn(resource.getLocation().toOSString(), true);		}		private void openHandleOn(String path, boolean subtree) {			setHandleValue(createHandleValue(path, subtree, Win32Natives.FILE_NOTIFY_CHANGE_FILE_NAME | Win32Natives.FILE_NOTIFY_CHANGE_DIR_NAME | Win32Natives.FILE_NOTIFY_CHANGE_LAST_WRITE | Win32Natives.FILE_NOTIFY_CHANGE_SIZE));			if (isOpen()) {				fHandleValueToHandle.put(new Long(getHandleValue()), this);				setHandleValueArrays(createHandleArrays());			} else {				close();			}		}		protected void postRefreshRequest(IResource resource) {			//native callback occurs even if resource was changed within workspace			if (!resource.isSynchronized(IResource.DEPTH_INFINITE))				refreshResult.refresh(resource);		}		public void setHandleValue(long handleValue) {			this.handleValue = handleValue;		}	}	protected class LinkedResourceHandle extends ChainedHandle {		private List fileHandleChain;		private IResource resource;		/**		 * @param resource		 */		public LinkedResourceHandle(IResource resource) {			this.resource = resource;			createFileHandleChain();		}		protected void createFileHandleChain() {			fileHandleChain = new ArrayList(1);			File file = new File(resource.getLocation().toOSString());			file = file.getParentFile();			while (file != null) {				fileHandleChain.add(0, new FileHandle(file));				file = file.getParentFile();			}			int size = fileHandleChain.size();			for (int i = 0; i < size; i++) {				ChainedHandle handle = (ChainedHandle) fileHandleChain.get(i);				handle.setPrevious((i > 0) ? (ChainedHandle) fileHandleChain.get(i - 1) : null);				handle.setNext((i + 1 < size) ? (ChainedHandle) fileHandleChain.get(i + 1) : this);			}			setPrevious((size > 0) ? (ChainedHandle) fileHandleChain.get(size - 1) : null);		}		public void destroy() {			super.destroy();			for (Iterator i = fileHandleChain.iterator(); i.hasNext();) {				Handle handle = (Handle) i.next();				handle.destroy();			}		}		public boolean exists() {			IPath location = resource.getLocation();			return location == null ? false : location.toFile().exists();		}		public void handleNotification() {			if (isOpen()) {				postRefreshRequest(resource);				findNextChange();			}		}		public void open() {			if (!isOpen()) {				if (exists()) {					openHandleOn(resource);				}				FileHandle handle = (FileHandle) getPrevious();				if (handle != null && !handle.isOpen()) {					handle.open();				}			}		}		public void postRefreshRequest() {			postRefreshRequest(resource);		}	}	protected class ResourceHandle extends Handle {		private IResource resource;		public ResourceHandle(IResource resource) {			super();			this.resource = resource;		}		public IResource getResource() {			return resource;		}		public void handleNotification() {			if (isOpen()) {				postRefreshRequest(resource);				findNextChange();			}		}		public void open() {			if (!isOpen()) {				openHandleOn(resource);			}		}	}	private static final String DEBUG_PREFIX = "Win32RefreshMonitor: "; //$NON-NLS-1$	private static final int WAIT_FOR_MULTIPLE_OBJECTS_TIMEOUT = 300;	/**	 * Any errors that have occurred	 */	protected MultiStatus errors;	/**	 * Arrays of handles, split evenly when the number of handles is larger	 * than Win32Natives.MAXIMUM_WAIT_OBJECTS	 */	protected long[][] fHandleValueArrays;	/**	 * Mapping of handles (java.lang.Long) to absolute paths	 * (java.lang.String).	 */	protected Map fHandleValueToHandle;	protected IRefreshResult refreshResult;	/*	 * Creates a new monitor. @param result A result that will recieve refresh	 * callbacks and error notifications	 */	public Win32Monitor(IRefreshResult result) {		super(Messages.WM_jobName);		this.refreshResult = result;		setPriority(Job.DECORATE);		setSystem(true);		fHandleValueToHandle = new HashMap(1);		setHandleValueArrays(createHandleArrays());	}	/**	 * Logs an exception	 */	protected synchronized void addException(String message) {		if (errors == null) {			String msg = Messages.WM_errors;			errors = new MultiStatus(ResourcesPlugin.PI_RESOURCES, 1, msg, null);		}		errors.add(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, message, null));	}	/*	 * Splits the given array into arrays of length no greater than <code> max	 * </code> . The lengths of the sub arrays differ in size by no more than	 * one element. <p> Examples: <ul><li> If an array of size 11 is split	 * with a max of 4, the resulting arrays are of size 4, 4, and 3. </li>	 * <li> If an array of size 18 is split with a max of 5, the resulting	 * arrays are of size 5, 5, 4, and 4. </li></ul>	 */	private long[][] balancedSplit(final long[] array, final int max) {		int elementCount = array.length;		// want to handle [1, max] rather than [0, max)		int subArrayCount = ((elementCount - 1) / max) + 1;		int subArrayBaseLength = elementCount / subArrayCount;		int overflow = elementCount % subArrayCount;		long[][] result = new long[subArrayCount][];		int count = 0;		for (int i = 0; i < subArrayCount; i++) {			int subArrayLength = subArrayBaseLength + (overflow-- > 0 ? 1 : 0);			long[] subArray = new long[subArrayLength];			for (int j = 0; j < subArrayLength; j++) {				subArray[j] = array[count++];			}			result[i] = subArray;		}		return result;	}	private Handle createHandle(IResource resource) {		if (resource.isLinked())			return new LinkedResourceHandle(resource);		return new ResourceHandle(resource);	}	/*	 * Since the Win32Natives.WaitForMultipleObjects(...) method cannot accept	 * more than a certain number of objects, we are forced to split the array	 * of objects to monitor and monitor each one individually. <p> This method	 * splits the list of handles into arrays no larger than	 * Win32Natives.MAXIMUM_WAIT_OBJECTS. The arrays are balenced so that they	 * differ in size by no more than one element.	 */	protected long[][] createHandleArrays() {		long[] handles;		// synchronized: in order to protect the map during iteration		synchronized (fHandleValueToHandle) {			Set keys = fHandleValueToHandle.keySet();			int size = keys.size();			if (size == 0) {				return new long[0][0];			}			handles = new long[size];			int count = 0;			for (Iterator i = keys.iterator(); i.hasNext();) {				handles[count++] = ((Long) i.next()).longValue();			}		}		return balancedSplit(handles, Win32Natives.MAXIMUM_WAIT_OBJECTS);	}	private Handle getHandle(IResource resource) {		if (resource == null) {			return null;		}		// synchronized: in order to protect the map during iteration		synchronized (fHandleValueToHandle) {			for (Iterator i = fHandleValueToHandle.values().iterator(); i.hasNext();) {				Handle handle = (Handle) i.next();				if (handle instanceof ResourceHandle) {					ResourceHandle resourceHandle = (ResourceHandle) handle;					if (resourceHandle.getResource().equals(resource)) {						return handle;					}				}			}		}		return null;	}	/*	 * Answers arrays of handles. The handles are split evenly when the number	 * of handles becomes larger than Win32Natives.MAXIMUM_WAIT_OBJECTS.	 * @return long[][]	 */	private long[][] getHandleValueArrays() {		return fHandleValueArrays;	}	/**	 * Adds a resource to be monitored by this native monitor	 */	public boolean monitor(IResource resource) {		IPath location = resource.getLocation();		if (location == null) {			// cannot monitor remotely managed containers			return false;		}		Handle handle = createHandle(resource);		// synchronized: handle creation must be atomic		synchronized (this) {			handle.open();		}		if (!handle.isOpen()) {			//ignore errors if we can't even create a handle on the resource			//it will fall back to polling anyway			errors = null;			return false;		}		//make sure the job is running		schedule(RESCHEDULE_DELAY);		if (RefreshManager.DEBUG)			System.out.println(DEBUG_PREFIX + " added monitor for: " + resource); //$NON-NLS-1$		return true;	}	/**	 * Removes the handle from the <code>fHandleValueToHandle</code> map.	 * 	 * @param handle	 *                  a handle, not <code>null</code>	 */	protected void removeHandle(Handle handle) {		List handles = new ArrayList(1);		handles.add(handle);		removeHandles(handles);	}	/**	 * Removes all of the handles in the given collection from the <code>fHandleValueToHandle</code>	 * map. If collections from the <code>fHandleValueToHandle</code> map are	 * used, copy them before passing them in as this method modifies the	 * <code>fHandleValueToHandle</code> map.	 * 	 * @param handles	 *                  a collection of handles, not <code>null</code>	 */	private void removeHandles(Collection handles) {		// synchronized: protect the array, removal must be atomic		synchronized (this) {			for (Iterator i = handles.iterator(); i.hasNext();) {				Handle handle = (Handle) i.next();				fHandleValueToHandle.remove(new Long(handle.getHandleValue()));				handle.destroy();			}			setHandleValueArrays(createHandleArrays());		}	}	/*	 * @see java.lang.Runnable#run()	 */	protected IStatus run(IProgressMonitor monitor) {		long start = -System.currentTimeMillis();		if (RefreshManager.DEBUG)			System.out.println(DEBUG_PREFIX + "job started."); //$NON-NLS-1$		try {			long[][] handleArrays = getHandleValueArrays();			monitor.beginTask(Messages.WM_beginTask, handleArrays.length);			// If changes occur to the list of handles,			// ignore them until the next time through the loop.			for (int i = 0, length = handleArrays.length; i < length; i++) {				if (monitor.isCanceled())					return Status.CANCEL_STATUS;				waitForNotification(handleArrays[i]);				monitor.worked(1);			}		} finally {			monitor.done();			start += System.currentTimeMillis();			if (RefreshManager.DEBUG)				System.out.println(DEBUG_PREFIX + "job finished in: " + start + "ms"); //$NON-NLS-1$ //$NON-NLS-2$		}		//always reschedule the job - so it will come back after errors or cancelation		//make sure it doesn't hog more that 5% of CPU		long delay = Math.max(RESCHEDULE_DELAY, start * 30);		if (RefreshManager.DEBUG)			System.out.println(DEBUG_PREFIX + "rescheduling in: " + delay / 1000 + " seconds"); //$NON-NLS-1$ //$NON-NLS-2$		final Bundle bundle = Platform.getBundle(ResourcesPlugin.PI_RESOURCES);		//if the bundle is null then the framework has shutdown - just bail out completely (bug 98219)		if (bundle == null)			return Status.OK_STATUS;		//don't reschedule the job if the resources plugin has been shut down		if (bundle.getState() == Bundle.ACTIVE)			schedule(delay);		MultiStatus result = errors;		errors = null;		//just log native refresh failures		if (result != null && !result.isOK())			ResourcesPlugin.getPlugin().getLog().log(result);		return Status.OK_STATUS;	}	protected void setHandleValueArrays(long[][] arrays) {		fHandleValueArrays = arrays;	}	/* (non-Javadoc)	 * @see org.eclipse.core.runtime.jobs.Job#shouldRun()	 */	public boolean shouldRun() {		return !fHandleValueToHandle.isEmpty();	}	/*	 * @see org.eclipse.core.resources.refresh.IRefreshMonitor#unmonitor(IContainer)	 */	public void unmonitor(IResource resource) {		if (resource == null) {			// resource == null means stop monitoring all resources			synchronized (fHandleValueToHandle) {				removeHandles(new ArrayList(fHandleValueToHandle.values()));			}		} else {			Handle handle = getHandle(resource);			if (handle != null)				removeHandle(handle);		}		//stop the job if there are no more handles		if (fHandleValueToHandle.isEmpty())			cancel();	}	/**	 * Performs the native call to wait for notification on one of the given	 * handles.	 * 	 * @param handleValues	 *                  an array of handles, it must contain no duplicates.	 */	private void waitForNotification(long[] handleValues) {		int handleCount = handleValues.length;		int index = Win32Natives.WaitForMultipleObjects(handleCount, handleValues, false, WAIT_FOR_MULTIPLE_OBJECTS_TIMEOUT);		if (index == Win32Natives.WAIT_TIMEOUT) {			// nothing happened.			return;		}		if (index == Win32Natives.WAIT_FAILED) {			// we ran into a problem			int error = Win32Natives.GetLastError();			if (error != Win32Natives.ERROR_INVALID_HANDLE && error != Win32Natives.ERROR_SUCCESS) {				addException(NLS.bind(Messages.WM_nativeErr, Integer.toString(error)));				refreshResult.monitorFailed(this, null);			}			return;		}		// a change occurred		// WaitForMultipleObjects returns WAIT_OBJECT_0 + index		index -= Win32Natives.WAIT_OBJECT_0;		Handle handle = (Handle) fHandleValueToHandle.get(new Long(handleValues[index]));		if (handle != null)			handle.handleNotification();	}}