| //------------------------------------------------------------------------------ |
| // Copyright (c) 2005, 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 Corporation - initial implementation |
| //------------------------------------------------------------------------------ |
| package org.eclipse.epf.library.persistence.util; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceChangeEvent; |
| import org.eclipse.core.resources.IResourceChangeListener; |
| import org.eclipse.core.resources.IResourceDelta; |
| import org.eclipse.core.resources.IResourceDeltaVisitor; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.ILog; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.epf.persistence.PersistencePlugin; |
| import org.osgi.framework.Bundle; |
| |
| /** |
| * Synchronizes the document with external resource changes. |
| * |
| * @author Phong Nguyen Le |
| * |
| * @since 1.2 |
| */ |
| public class FileSynchronizer implements IResourceChangeListener { |
| |
| /** |
| * Bundle of all required information to allow files as underlying document resources. |
| */ |
| public static class FileInfo //extends StorageInfo |
| { |
| /** The time stamp at which this provider changed the file. */ |
| public long fModificationStamp= IResource.NULL_STAMP; |
| public boolean fCanBeSaved; |
| public IFile fFile; |
| |
| /** |
| * Creates and returns a new file info. |
| * |
| * @param document the document |
| * @param model the annotation model |
| * @param fileSynchronizer the file synchronizer |
| */ |
| public FileInfo(IFile file) { |
| fFile = file; |
| } |
| } |
| |
| /** |
| * A flag indicating whether this synchronizer is installed or not. |
| * |
| * |
| */ |
| protected boolean fIsInstalled= false; |
| |
| /** Element information of all connected elements */ |
| private Map<Object, FileInfo> fFileInfoMap= new HashMap<Object, FileInfo>(); |
| |
| |
| /** |
| * Creates a new file synchronizer. Is not yet installed on a resource. |
| */ |
| public FileSynchronizer() { |
| install(); |
| } |
| |
| public boolean accept(IFile file) { |
| return false; |
| } |
| |
| public boolean isInstalled() { |
| return fIsInstalled; |
| } |
| |
| public void updateModificationStamp(IFile file) { |
| FileInfo info = getFileInfo(file); |
| if(info != null) { |
| info.fModificationStamp = computeModificationStamp(file); |
| } |
| } |
| |
| /** |
| * Returns the element info object for the given element. |
| * |
| * @param element the element |
| * @return the element info object, or <code>null</code> if none |
| */ |
| public FileInfo getFileInfo(Object element) { |
| return (FileInfo) fFileInfoMap.get(element); |
| } |
| |
| /** |
| * Installs the synchronizer on the input's file. |
| */ |
| public void install() { |
| ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE); |
| fIsInstalled= true; |
| } |
| |
| /** |
| * Uninstalls the synchronizer from the input's file. |
| */ |
| public void uninstall() { |
| ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); |
| fIsInstalled= false; |
| } |
| |
| /* |
| * @see IResourceChangeListener#resourceChanged(IResourceChangeEvent) |
| */ |
| public void resourceChanged(IResourceChangeEvent e) { |
| IResourceDelta delta= e.getDelta(); |
| try { |
| if (delta != null && fIsInstalled) { |
| ResourceDeltaVisistor visitor = new ResourceDeltaVisistor(); |
| delta.accept(visitor); |
| Runnable runnable = visitor.getRunnable(); |
| if(runnable != null) { |
| update(runnable); |
| } |
| } |
| } catch (CoreException x) { |
| handleCoreException(x, "Error handling resource change event."); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Defines the standard procedure to handle <code>CoreExceptions</code>. Exceptions |
| * are written to the plug-in log. |
| * |
| * @param exception the exception to be logged |
| * @param message the message to be logged |
| * |
| */ |
| protected void handleCoreException(CoreException exception, String message) { |
| |
| Bundle bundle = Platform.getBundle(PersistencePlugin.getDefault().getId()); |
| ILog log= Platform.getLog(bundle); |
| |
| if (message != null) |
| log.log(new Status(IStatus.ERROR, PersistencePlugin.getDefault().getId(), 0, message, exception)); |
| else |
| log.log(exception.getStatus()); |
| } |
| |
| /** |
| * Computes the initial modification stamp for the given resource. |
| * |
| * @param resource the resource |
| * @return the modification stamp |
| */ |
| public static long computeModificationStamp(IResource resource) { |
| if(resource == null) { |
| System.err.println("FATAL ERROR: resource is null."); //$NON-NLS-1$ |
| return 0; |
| } |
| long modificationStamp= resource.getModificationStamp(); |
| |
| IPath path= resource.getLocation(); |
| if (path == null) |
| return modificationStamp; |
| |
| modificationStamp= path.toFile().lastModified(); |
| return modificationStamp; |
| } |
| |
| private class ResourceDeltaVisistor implements IResourceDeltaVisitor { |
| private Collection<IFile> changedFiles = new ArrayList<IFile>(); |
| private Map<IFile, IPath> movedFileToNewPathMap = new HashMap<IFile, IPath>(); |
| private Collection<IFile> deletedFiles = new ArrayList<IFile>(); |
| private Collection<IFile> addedFiles = new ArrayList<IFile>(); |
| |
| /* |
| * @see IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta) |
| */ |
| public boolean visit(IResourceDelta delta) throws CoreException { |
| if (delta == null) |
| return false; |
| if (delta.getResource().getType() == IResource.FILE) { |
| switch (delta.getKind()) { |
| case IResourceDelta.CHANGED: |
| FileInfo info = (FileInfo) getFileInfo(delta.getResource()); |
| if (info == null || info.fCanBeSaved) |
| break; |
| IFile file = (IFile) delta.getResource(); |
| boolean isSynchronized = computeModificationStamp(file) == info.fModificationStamp; |
| if (((IResourceDelta.ENCODING & delta.getFlags()) != 0 && isSynchronized) |
| || ((IResourceDelta.CONTENT & delta.getFlags()) != 0 && !isSynchronized)) { |
| changedFiles.add(file); |
| } |
| break; |
| |
| case IResourceDelta.REMOVED: |
| if ((IResourceDelta.MOVED_TO & delta.getFlags()) != 0) { |
| info = (FileInfo) getFileInfo(delta.getResource()); |
| if (info == null) |
| break; |
| final IPath path = delta.getMovedToPath(); |
| movedFileToNewPathMap.put((IFile) delta.getResource(), |
| path); |
| } else { |
| info = (FileInfo) getFileInfo(delta.getResource()); |
| if (info != null && !info.fCanBeSaved) { |
| deletedFiles.add((IFile) delta.getResource()); |
| } |
| } |
| break; |
| case IResourceDelta.ADDED: |
| file = (IFile) delta.getResource(); |
| if(accept(file)) { |
| addedFiles.add(file); |
| } |
| |
| } |
| } |
| return true; |
| } |
| |
| public Runnable getRunnable() { |
| // consider the file is newly added if it is accepted and not a moved file |
| // |
| if (!addedFiles.isEmpty() && !movedFileToNewPathMap.isEmpty()) { |
| for (Iterator<IFile> iter = addedFiles.iterator(); iter |
| .hasNext();) { |
| IFile file = iter.next(); |
| if (movedFileToNewPathMap.containsValue(file)) { |
| iter.remove(); |
| } |
| } |
| } |
| if(changedFiles.isEmpty() && movedFileToNewPathMap.isEmpty() && deletedFiles.isEmpty() && addedFiles.isEmpty()) { |
| return null; |
| } |
| |
| return new Runnable() { |
| |
| public void run() { |
| handleChanged(ResourceDeltaVisistor.this); |
| } |
| |
| }; |
| } |
| } |
| |
| /** |
| * Posts the update code "behind" the running operation. |
| * |
| * @param runnable the update code |
| */ |
| protected void update(Runnable runnable) { |
| runnable.run(); |
| } |
| |
| public void monitor(IFile file) { |
| FileInfo info= (FileInfo) fFileInfoMap.get(file); |
| if (info == null) { |
| info = new FileInfo(file); |
| info.fModificationStamp = computeModificationStamp(file); |
| fFileInfoMap.put(file, info); |
| } |
| } |
| |
| public void unmonitor(IFile file) { |
| fFileInfoMap.remove(file); |
| } |
| |
| public void unmonitorAll() { |
| fFileInfoMap.clear(); |
| } |
| |
| protected void handleChanged(ResourceDeltaVisistor visitor) { |
| if(!visitor.changedFiles.isEmpty()) { |
| handleChangedFiles(visitor.changedFiles); |
| } |
| if(!visitor.movedFileToNewPathMap.isEmpty()) { |
| handleMovedFiles(visitor.movedFileToNewPathMap); |
| } |
| if(!visitor.deletedFiles.isEmpty()) { |
| handleDeletedFiles(visitor.deletedFiles); |
| } |
| if(!visitor.addedFiles.isEmpty()) { |
| handleAddedFiles(visitor.addedFiles); |
| } |
| } |
| |
| protected Collection<IFile> handleDeletedFiles(Collection<IFile> deletedFiles) { |
| return Collections.emptyList(); |
| } |
| |
| protected Collection<IFile> handleMovedFiles(Map<IFile, IPath> movedFileToNewPathMap) { |
| return Collections.emptyList(); |
| } |
| |
| protected Collection<IFile> handleChangedFiles(Collection<IFile> changedFiles) { |
| return Collections.emptyList(); |
| } |
| |
| protected Collection<IFile> handleAddedFiles(Collection<IFile> addedFiles) { |
| return Collections.emptyList(); |
| } |
| } |
| |