/******************************************************************************* * Copyright 2005-2007, CHISEL Group, University of Victoria, Victoria, BC, Canada * and IBM Corporation. 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: * The Chisel Group, University of Victoria *******************************************************************************/ package net.sourceforge.tagsea.resources.synchronize; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; 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.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubProgressMonitor; import org.xml.sax.SAXException; import com.sun.org.apache.xerces.internal.xs.datatypes.ObjectList; import net.sourceforge.tagsea.TagSEAPlugin; import net.sourceforge.tagsea.core.ITagSEAOperationStateListener; import net.sourceforge.tagsea.core.IWaypoint; import net.sourceforge.tagsea.core.TagSEAOperation; import net.sourceforge.tagsea.resources.ResourceWaypointPlugin; import net.sourceforge.tagsea.resources.ResourceWaypointPreferences; import net.sourceforge.tagsea.resources.ResourceWaypointUtils; import net.sourceforge.tagsea.resources.waypoints.IResourceWaypointDescriptor; import net.sourceforge.tagsea.resources.waypoints.ResourceWaypointProxyDescriptor; import net.sourceforge.tagsea.resources.waypoints.operations.LoadProjectWaypointsOperation; import net.sourceforge.tagsea.resources.waypoints.operations.UpdateStampOperation; import net.sourceforge.tagsea.resources.waypoints.operations.WaypointProjectXMLWriteOperation; import net.sourceforge.tagsea.resources.waypoints.xml.WaypointXMLUtilities; /** * An object that loads, reads, and watches for changes in the shared resource waypoints, and publishes change * events. * @author Del Myers * */ @Deprecated public class WaypointSynchronizerHelp { public static final WaypointSynchronizerHelp INSTANCE = new WaypointSynchronizerHelp(); private ListenerList listeners; /** * project being saved by the internal project saver. */ IProject projectBeingSaved; private class ProjectSaveListener implements ITagSEAOperationStateListener { /* (non-Javadoc) * @see net.sourceforge.tagsea.core.ITagSEAOperationStateListener#stateChanged(net.sourceforge.tagsea.core.TagSEAOperation) */ public void stateChanged(TagSEAOperation operation) { if (operation instanceof WaypointProjectXMLWriteOperation) { switch (operation.getState()) { case RUNNING: projectBeingSaved = ((WaypointProjectXMLWriteOperation)operation).getProject(); break; case DONE: projectBeingSaved = null; } } } } /** * * Listens for when resources are opened/closed, deleted, etc. This will gather information about resources * that are no longer available so that the waypoints can be removed. It will also look for when the * .resourceWaypoints file is added, so that the waypoints can be loaded again. * @author Del Myers * */ private class ResourceGainLostListener implements IResourceChangeListener { private class ResourceDeltaVisitor implements IResourceDeltaVisitor { private List<IProject> newProjects = new LinkedList<IProject>(); private List<IWaypoint> removedWaypoints = new LinkedList<IWaypoint>(); private List<IProject> changedProjects = new LinkedList<IProject>(); /* (non-Javadoc) * @see org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta) */ public boolean visit(IResourceDelta delta) throws CoreException { if (delta.getKind() == IResourceDelta.ADDED) { if (delta.getResource() instanceof IFile) { IFile file = (IFile) delta.getResource(); if (file.getProjectRelativePath().equals(new Path(WaypointXMLUtilities.fileName))) { if (checkProjectForSynchronization(file.getProject())) { newProjects.add(file.getProject()); } return false; } } } else if (delta.getKind() == IResourceDelta.REMOVED) { if (delta.getResource() instanceof IFile) { if (((IFile)delta.getResource()).getProjectRelativePath().equals(new Path(WaypointXMLUtilities.fileName))) newProjects.remove(delta.getResource().getProject()); } IWaypoint[] wps = ResourceWaypointUtils.getWaypointsForResource(delta.getResource(), false); if (wps.length > 0) { removedWaypoints.addAll(Arrays.asList(wps)); } } else if (delta.getKind() == IResourceDelta.CHANGED) { if (delta.getResource() instanceof IFile) { IFile file = (IFile) delta.getResource(); if (file.getProjectRelativePath().equals(new Path(WaypointXMLUtilities.fileName))) { if (checkProjectForSynchronization(file.getProject())) { changedProjects.add(file.getProject()); } return false; } } } return true; } /** * Checks to see if the given project is sharing waypoints. If it is, a check is done to see * whether the the project is currently saving waypoints (i.e. the resource change was caused * internally). If the first condition is true, and the second is false, the waypoints have to * be synchronized, and this method will return true. * @param project the project to check. * @return true iff the project needs to be synchronized. */ private boolean checkProjectForSynchronization(IProject project) { return (!project.equals(projectBeingSaved) && ResourceWaypointPreferences.doesShareWaypoints(project)); } } /* (non-Javadoc) * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent) */ public void resourceChanged(IResourceChangeEvent event) { final ResourceDeltaVisitor visitor = new ResourceDeltaVisitor(); try { if (event.getDelta() == null) return; event.getDelta().accept(visitor); } catch (CoreException e) { return; } if (visitor.newProjects.size() > 0) { for (IProject project : visitor.newProjects) { TagSEAPlugin.run(new LoadProjectWaypointsOperation(project), false); } } if (visitor.removedWaypoints.size() > 0) { TagSEAPlugin.run(new TagSEAOperation("Deleting Resource Waypoints..."){ @Override public IStatus run(IProgressMonitor monitor) throws InvocationTargetException { monitor.beginTask("Deleting waypoints...", visitor.removedWaypoints.size()); for (IWaypoint waypoint : visitor.removedWaypoints) { waypoint.delete(); monitor.worked(1); } monitor.done(); return Status.OK_STATUS; } }, false); } if (visitor.changedProjects.size() > 0) { //copy all of the project waypoint files to .synchronize files in the //workspace data area so that we can do synchronization. for (IProject project : visitor.changedProjects) { IFile projectFile = project.getFile(WaypointXMLUtilities.fileName); if (projectFile != null && projectFile.exists()) { //copy the contents. try { File synchronizeFile = getSynchronizeFile(project); InputStream in = projectFile.getContents(); FileOutputStream out = new FileOutputStream(synchronizeFile); byte[] buff = new byte[1024]; int i = 0; while ((i = in.read(buff)) != -1) { out.write(buff, 0, i); } in.close(); out.close(); } catch (IOException e) { ResourceWaypointPlugin.getDefault().log(e); continue; } catch (CoreException e) { ResourceWaypointPlugin.getDefault().getLog().log(e.getStatus()); } } fireSynchronizeChanged(project); } } } } private static class DescriptorComparator implements Comparator<IResourceWaypointDescriptor> { public int compare(IResourceWaypointDescriptor o1, IResourceWaypointDescriptor o2) { return (o1.getResource()+o1.getStamp()).compareTo(o2.getResource()+o2.getStamp()); } } //make it so that only the static instance can be created; private WaypointSynchronizerHelp() { listeners = new ListenerList(); } /** * Installs all hooks and starts listening to the workspace. * */ public void start() { TagSEAPlugin.addOperationStateListener(new ProjectSaveListener()); ResourcesPlugin.getWorkspace().addResourceChangeListener(new ResourceGainLostListener()); } /* (non-Javadoc) * @see org.eclipse.jface.operation.IRunnableWithProgress#run(org.eclipse.core.runtime.IProgressMonitor) */ public List<WaypointSynchronizeObject> getSynchronizeInfo(IProject project, IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { List<WaypointSynchronizeObject> synchronizers = new ArrayList<WaypointSynchronizeObject>(0); monitor.beginTask("Collecting synchronization data...", 300); monitor.subTask("Updating synchronization information..."); long lastRevision = ResourceWaypointPreferences.getRevision(project); TagSEAPlugin.run(new UpdateStampOperation(), true); monitor.worked(100); IPath stateLocation = ResourceWaypointPlugin.getDefault().getStateLocation(); IPath synchronizePath = stateLocation.append(project.getName() + "/.synchronize"); File synchronizeFile = synchronizePath.toFile(); if (!synchronizeFile.exists()) { monitor.done(); return synchronizers; } monitor.subTask("Getting difference data..."); IWaypoint[] localWaypoints = ResourceWaypointUtils.getWaypointsForProject(project); IResourceWaypointDescriptor[] localDescriptors = new IResourceWaypointDescriptor[localWaypoints.length]; for (int i = 0; i < localWaypoints.length; i++) { localDescriptors[i] = new ResourceWaypointProxyDescriptor(localWaypoints[i]); } monitor.worked(50); IResourceWaypointDescriptor[] remoteDescriptors = null; try { remoteDescriptors = WaypointXMLUtilities.loadFile(synchronizeFile); } catch (IOException e) { ResourceWaypointPlugin.getDefault().log(e); monitor.done(); return synchronizers; } catch (SAXException e) { ResourceWaypointPlugin.getDefault().log(e); monitor.done(); return synchronizers; } monitor.worked(50); monitor.subTask("Organizing differences..."); synchronizers = organizeDescriptors(project, lastRevision, localDescriptors, remoteDescriptors); monitor.done(); return synchronizers; } /** * Saves the given descriptors to the synchronization file for the given project. * @param project * @param descriptors * @throws SAXException * @throws IOException * @throws FileNotFoundException */ public void saveSynchronizeInfo(IProject project, List<IResourceWaypointDescriptor> descriptors, IProgressMonitor monitor) throws IOException, SAXException { File file = getSynchronizeFile(project); if (file == null) return; //make sure that all of the descriptors are actually for the project. List<IResourceWaypointDescriptor> writeDescriptors = new ArrayList<IResourceWaypointDescriptor>(descriptors.size()); for (IResourceWaypointDescriptor d : descriptors) { if (project.getFullPath().isPrefixOf(new Path(d.getResource()))) { writeDescriptors.add(d); } } WaypointXMLUtilities.writeDescriptors(writeDescriptors, new FileOutputStream(file), monitor); fireSynchronizeChanged(project); } /** * Updates the current set of descriptors that represent the synchronization data for the updated * set of descriptors. If a descriptor exists with the same stamp as one of the descriptors in the * given list, the current one is replaced. If there doesn't exist a descriptor with the given stamp, * than the new descriptor is added. If the resources for the two descriptors don't match, the new one * is assumed to be in error. * @param project * @param descriptors * @param monitor * @throws IOException * @throws SAXException */ public void updateSynchronizeInfo(IProject project, List<IResourceWaypointDescriptor> descriptors, IProgressMonitor monitor) throws IOException, SAXException { monitor.beginTask("Updating Synchronization Info", 10); File file = getSynchronizeFile(project); if (file == null) return; IResourceWaypointDescriptor[] oldArray = WaypointXMLUtilities.loadFile(file); List<IResourceWaypointDescriptor> writeDescriptors = new ArrayList<IResourceWaypointDescriptor>(); Comparator<IResourceWaypointDescriptor> sorter = new Comparator<IResourceWaypointDescriptor>() { public int compare(IResourceWaypointDescriptor o1, IResourceWaypointDescriptor o2) { return o1.getStamp().compareTo(o2.getStamp()); } }; IResourceWaypointDescriptor[] newArray = descriptors.toArray(new IResourceWaypointDescriptor[descriptors.size()]); Arrays.sort(oldArray, sorter); Arrays.sort(newArray, sorter); int j = 0; int i = 0; for (i = 0; i < newArray.length; i++) { for (;j < oldArray.length; j++) { if (i >= newArray.length) { writeDescriptors.add(oldArray[j]); continue; } int diff = sorter.compare(newArray[i], oldArray[j]); if (diff < 0) { writeDescriptors.add(newArray[i]); break; } else if (diff == 0) { if (newArray[i].getResource().equals(oldArray[j].getResource())) { writeDescriptors.add(newArray[i]); } else { writeDescriptors.add(oldArray[j]); } j++; break; } else { writeDescriptors.add(oldArray[j]); } } if (i >= oldArray.length) { if (writeDescriptors.add(newArray[i])); } } for (; i < newArray.length; i++) { writeDescriptors.add(newArray[i]); } for (; j < oldArray.length; j++) { writeDescriptors.add(oldArray[i]); } saveSynchronizeInfo(project, writeDescriptors, new SubProgressMonitor(monitor, 5)); } /** * Sorts and organizes the local and remote descriptors to group waypoint changes. * * @param localDescriptors * @param remoteDescriptors */ private List<WaypointSynchronizeObject> organizeDescriptors(IProject project, long workspaceRevision, IResourceWaypointDescriptor[] localDescriptors, IResourceWaypointDescriptor[] remoteDescriptors) { Arrays.sort(localDescriptors, new DescriptorComparator()); Arrays.sort(remoteDescriptors, new DescriptorComparator()); int li = 0; int ri = 0; boolean done = false; List<WaypointSynchronizeObject> synchronizeObjects = new ArrayList<WaypointSynchronizeObject>(); while (!done) { if (li >= localDescriptors.length) { done = true; } if (ri >= remoteDescriptors.length) { done = true; } if (!done) { String lstamp = localDescriptors[li].getStamp(); String rstamp = remoteDescriptors[ri].getStamp(); int diff = lstamp.compareTo(rstamp); if (diff == 0) { //same waypoint create a synchronize object that contains both. WaypointSynchronizeObject so = new WaypointSynchronizeObject(project, workspaceRevision, localDescriptors[li], remoteDescriptors[ri]); synchronizeObjects.add(so); li++; ri++; } else if (diff < 0) { //the local waypoint does not exist in the remote repository... add it. synchronizeObjects.add( new WaypointSynchronizeObject(project, workspaceRevision, localDescriptors[li], null) ); li++; } else if (diff > 0) { // the remote waypoint does not exist locally... add it. synchronizeObjects.add( new WaypointSynchronizeObject(project, workspaceRevision, null, remoteDescriptors[ri]) ); ri++; } } } while (li < localDescriptors.length) { synchronizeObjects.add( new WaypointSynchronizeObject(project, workspaceRevision, localDescriptors[li], null) ); li++; } while (ri < remoteDescriptors.length) { synchronizeObjects.add( new WaypointSynchronizeObject(project, workspaceRevision, null, remoteDescriptors[ri]) ); ri++; } return synchronizeObjects; } /** * Returns the file used to synchronize changes to the resource waypoints, or null if the project is * not being shared. If the file does not exist, it is created. * @param project the project to get the file for. * @return the file used to synchronize changes to the resource waypoints. * @throws IOException */ public File getSynchronizeFile(IProject project) throws IOException { if (!ResourceWaypointPreferences.doesShareWaypoints(project)) return null; IPath state = ResourceWaypointPlugin.getDefault().getStateLocation(); File stateFile = state.toFile(); File synchronizeProjectLocation = new File(stateFile, project.getName()); if (!synchronizeProjectLocation.exists()) { synchronizeProjectLocation.mkdir(); } File synchronizeFile = new File(synchronizeProjectLocation, ".synchronize"); if (!synchronizeFile.exists()) { synchronizeFile.createNewFile(); } return synchronizeFile; } /** * Adds the given listener to the list of listeners if it doesn't already exist. * @param listener the listener to add. */ public void addSynchronizeListener(IWaypointSynchronizeListener listener) { listeners.add(listener); } public void removeSynchronizeListener(IWaypointSynchronizeListener listener) { listeners.remove(listener); } private void fireSynchronizeChanged(IProject project) { for (Object listener : listeners.getListeners()) { ((IWaypointSynchronizeListener)listener).synchronizationChanged(project); } } }