/******************************************************************************* * Copyright (c) 2002, 2007 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(); } }