package org.eclipse.core.internal.resources.refresh.win32;
import java.io.Closeable;
import java.io.File;
import java.util.*;
import org.eclipse.core.internal.utils.Messages;
import org.eclipse.core.internal.utils.Policy;
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;
class Win32Monitor extends Job implements IRefreshMonitor {
private static final long RESCHEDULE_DELAY = 3000;
private static final int WAIT_FOR_MULTIPLE_OBJECTS_TIMEOUT = 1000;
private static final String DEBUG_PREFIX = "Win32RefreshMonitor: ";
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;
}
@Override
public boolean exists() {
return file.exists();
}
@Override
public void handleNotification() {
if (!isOpen())
return;
ChainedHandle next = getNext();
if (next != null) {
if (next.isOpen()) {
if (!next.exists()) {
next.close();
if (next instanceof LinkedResourceHandle) {
LinkedResourceHandle linkedResourceHandle = (LinkedResourceHandle) next;
linkedResourceHandle.postRefreshRequest();
}
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();
}
@Override
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();
}
}
}
}
@Override
public String toString() {
return file.toString();
}
}
protected abstract class Handle implements Closeable {
protected long handleValue;
public Handle() {
handleValue = Win32Natives.INVALID_HANDLE_VALUE;
}
@Override
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 (Policy.DEBUG_AUTO_REFRESH)
Policy.debug(DEBUG_PREFIX + "removed handle: " + handleValue);
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) {
IPath location = resource.getLocation();
if (location != null) {
openHandleOn(location.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(getHandleValue(), this);
setHandleValueArrays(createHandleArrays());
} else {
close();
}
}
protected void postRefreshRequest(IResource resource) {
if (!resource.isSynchronized(IResource.DEPTH_INFINITE))
refreshResult.refresh(resource);
}
public void setHandleValue(long handleValue) {
this.handleValue = handleValue;
}
}
protected class LinkedResourceHandle extends ChainedHandle {
private List<FileHandle> fileHandleChain;
private IResource 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 = fileHandleChain.get(i);
handle.setPrevious((i > 0) ? fileHandleChain.get(i - 1) : null);
handle.setNext((i + 1 < size) ? fileHandleChain.get(i + 1) : this);
}
setPrevious((size > 0) ? fileHandleChain.get(size - 1) : null);
}
@Override
public void destroy() {
super.destroy();
for (FileHandle fileHandle : fileHandleChain) {
Handle handle = fileHandle;
handle.destroy();
}
}
@Override
public boolean exists() {
IPath location = resource.getLocation();
return location == null ? false : location.toFile().exists();
}
@Override
public void handleNotification() {
if (isOpen()) {
postRefreshRequest(resource);
findNextChange();
}
}
@Override
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);
}
@Override
public String toString() {
return resource.toString();
}
}
protected class ResourceHandle extends Handle {
private IResource resource;
public ResourceHandle(IResource resource) {
super();
this.resource = resource;
}
public IResource getResource() {
return resource;
}
@Override
public void handleNotification() {
if (isOpen()) {
postRefreshRequest(resource);
findNextChange();
}
}
@Override
public void open() {
if (!isOpen()) {
openHandleOn(resource);
}
}
@Override
public String toString() {
return resource.toString();
}
}
protected MultiStatus errors;
protected long[][] fHandleValueArrays;
protected Map<Long, Handle> fHandleValueToHandle;
protected IRefreshResult refreshResult;
public Win32Monitor(IRefreshResult result) {
super(Messages.WM_jobName);
this.refreshResult = result;
setPriority(Job.DECORATE);
setSystem(true);
fHandleValueToHandle = new HashMap<>(1);
setHandleValueArrays(createHandleArrays());
}
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));
}
private long[][] balancedSplit(final long[] array, final int max) {
int elementCount = array.length;
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);
}
protected long[][] createHandleArrays() {
long[] handles;
synchronized (fHandleValueToHandle) {
Set<Long> keys = fHandleValueToHandle.keySet();
int size = keys.size();
if (size == 0) {
return new long[0][0];
}
handles = new long[size];
int count = 0;
for (Long long2 : keys) {
handles[count++] = long2.longValue();
}
}
return balancedSplit(handles, Win32Natives.MAXIMUM_WAIT_OBJECTS);
}
private Handle getHandle(IResource resource) {
if (resource == null) {
return null;
}
synchronized (fHandleValueToHandle) {
for (Handle handle : fHandleValueToHandle.values()) {
if (handle instanceof ResourceHandle) {
ResourceHandle resourceHandle = (ResourceHandle) handle;
if (resourceHandle.getResource().equals(resource)) {
return handle;
}
}
}
}
return null;
}
private long[][] getHandleValueArrays() {
return fHandleValueArrays;
}
public boolean monitor(IResource resource) {
IPath location = resource.getLocation();
if (location == null) {
return false;
}
Handle handle = createHandle(resource);
synchronized (this) {
handle.open();
}
if (!handle.isOpen()) {
errors = null;
return false;
}
schedule(RESCHEDULE_DELAY);
if (Policy.DEBUG_AUTO_REFRESH)
Policy.debug(DEBUG_PREFIX + " added monitor for: " + resource);
return true;
}
protected void removeHandle(Handle handle) {
List<Handle> handles = new ArrayList<>(1);
handles.add(handle);
removeHandles(handles);
}
private void removeHandles(Collection<Handle> handles) {
synchronized (this) {
for (Handle handle : handles) {
fHandleValueToHandle.remove(handle.getHandleValue());
handle.destroy();
}
setHandleValueArrays(createHandleArrays());
}
}
@Override
protected IStatus run(IProgressMonitor monitor) {
long start = -System.currentTimeMillis();
if (Policy.DEBUG_AUTO_REFRESH)
Policy.debug(DEBUG_PREFIX + "job started.");
try {
long[][] handleArrays = getHandleValueArrays();
monitor.beginTask(Messages.WM_beginTask, handleArrays.length);
for (long[] handleArray : handleArrays) {
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
waitForNotification(handleArray);
monitor.worked(1);
}
} finally {
monitor.done();
start += System.currentTimeMillis();
if (Policy.DEBUG_AUTO_REFRESH)
Policy.debug(DEBUG_PREFIX + "job finished in: " + start + "ms");
}
long delay = Math.max(RESCHEDULE_DELAY, start);
if (Policy.DEBUG_AUTO_REFRESH)
Policy.debug(DEBUG_PREFIX + "rescheduling in: " + delay / 1000 + " seconds");
final Bundle bundle = Platform.getBundle(ResourcesPlugin.PI_RESOURCES);
if (bundle == null)
return Status.OK_STATUS;
if (bundle.getState() == Bundle.ACTIVE)
schedule(delay);
MultiStatus result = errors;
errors = null;
if (result != null && !result.isOK())
ResourcesPlugin.getPlugin().getLog().log(result);
return Status.OK_STATUS;
}
protected void setHandleValueArrays(long[][] arrays) {
fHandleValueArrays = arrays;
}
@Override
public boolean shouldRun() {
return !fHandleValueToHandle.isEmpty();
}
@Override
public void unmonitor(IResource resource) {
if (resource == null) {
synchronized (fHandleValueToHandle) {
removeHandles(new ArrayList<>(fHandleValueToHandle.values()));
}
} else {
Handle handle = getHandle(resource);
if (handle != null)
removeHandle(handle);
}
if (fHandleValueToHandle.isEmpty())
cancel();
}
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) {
return;
}
if (index == Win32Natives.WAIT_FAILED) {
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;
}
if (index >= Win32Natives.WAIT_ABANDONED_0) {
index -= Win32Natives.WAIT_ABANDONED_0;
Handle handle = fHandleValueToHandle.get(handleValues[index]);
addException(NLS.bind(Messages.WM_mutexAbandoned, handle));
refreshResult.monitorFailed(this, null);
return;
}
index -= Win32Natives.WAIT_OBJECT_0;
Handle handle = fHandleValueToHandle.get(handleValues[index]);
if (handle != null)
handle.handleNotification();
}
}