/******************************************************************************* * 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.parsed.core.internal.resources; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import net.sourceforge.tagsea.TagSEAPlugin; import net.sourceforge.tagsea.core.IWaypoint; import net.sourceforge.tagsea.parsed.IParsedWaypointAttributes; import net.sourceforge.tagsea.parsed.ParsedWaypointPlugin; import net.sourceforge.tagsea.parsed.core.IParsedWaypointDefinition; import net.sourceforge.tagsea.parsed.core.ParsedWaypointUtils; import net.sourceforge.tagsea.parsed.core.internal.WaypointParsingTools; import net.sourceforge.tagsea.parsed.core.internal.WaypointParsingTools.RegionGraph; import net.sourceforge.tagsea.parsed.core.internal.WaypointParsingTools.WaypointDescriptorMatch; import net.sourceforge.tagsea.parsed.core.internal.operations.AbstractWaypointUpdateOperation; import net.sourceforge.tagsea.parsed.core.internal.operations.CleanFilesOperation; import net.sourceforge.tagsea.parsed.core.internal.operations.WaypointOverlapProblem; import net.sourceforge.tagsea.parsed.parser.IParsedWaypointDescriptor; import net.sourceforge.tagsea.parsed.parser.IReconcilingWaypointParser; import net.sourceforge.tagsea.parsed.parser.IWaypointParseProblemCollector; import net.sourceforge.tagsea.parsed.parser.IWaypointParser; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentListener; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Region; /** * Document listener that actually does all of the updating for changes in the documents. * @author Del Myers * */ public class DocumentWaypointUpdater implements IDocumentListener { private DocumentRegistry registry; private FullParseTimer timer; private class FullParseTimer { private HashMap<IFile, TimerTask> timerMap; private Timer timer; /** * */ public FullParseTimer() { timerMap = new HashMap<IFile, TimerTask>(); timer = new Timer(); } public void schedule(final IFile file) { synchronized (timerMap) { TimerTask task = timerMap.get(file); if (task != null) { task.cancel(); } task = new TimerTask() { @Override public void run() { synchronized (timerMap) { ArrayList<IFile> list = new ArrayList<IFile>(1); list.add(file); TagSEAPlugin.run(new CleanFilesOperation(list), false); timerMap.remove(file); } } }; timerMap.put(file, task); timer.schedule(task, 1000); } } } /** * @param fileBufferRegistry */ public DocumentWaypointUpdater(DocumentRegistry fileBufferRegistry) { this.registry = fileBufferRegistry; this.timer = new FullParseTimer(); } public void documentAboutToBeChanged(DocumentEvent event) { } public void documentChanged(final DocumentEvent event) { final IFile file = registry.getFileForDocument(event.getDocument()); if (file == null) return; if (TagSEAPlugin.isBlocked()) { //just schedule a full parse and return. timer.schedule(file); return; } final long time = System.currentTimeMillis(); TagSEAPlugin.run(new AbstractWaypointUpdateOperation("Repairing Waypoints"){ @Override public IStatus run(IProgressMonitor monitor) throws InvocationTargetException { if (System.currentTimeMillis() - time > 1000) { //don't update if the full parse has already run. return Status.OK_STATUS; } IParsedWaypointDefinition[] defs = ParsedWaypointPlugin.getDefault().getParsedWaypointRegistry().getMatchingDefinitions(file); MultiStatus status = new MultiStatus(ParsedWaypointPlugin.PLUGIN_ID, 0, "Synchronizing document change to waypoints.", null); List<IWaypoint> generated = new ArrayList<IWaypoint>(); for (IParsedWaypointDefinition def : defs) { IWaypointParser parser = def.getParser(); IRegion[] regionsToConsider = new IRegion[] {new Region(0, event.getDocument().getLength())}; if (parser instanceof IReconcilingWaypointParser) { regionsToConsider = ((IReconcilingWaypointParser)parser).calculateDirtyRegions(event); } int minStart = -1; int maxEnd = -1; boolean doParse = true; //calculate the full spanning region so that we can get all of the waypoints in that region which //will need replacing. if (regionsToConsider.length == 0) { doParse = false; } else { for (IRegion region : regionsToConsider) { if (minStart == -1 || region.getOffset() < minStart) { minStart = region.getOffset(); } if (maxEnd == -1 || maxEnd < region.getOffset() + region.getLength()) { maxEnd = region.getOffset() + region.getLength(); } } } int textLength = (event.getText() == null) ? 0 : event.getText().length(); //adjust the region to fit the state of the document _before_ the document change if (doParse && (event.getOffset() < minStart || event.getOffset() > maxEnd || event.getOffset() + textLength > maxEnd)) { //the returned region was outside of the actual document change try to gracefully //reparse the whole document. minStart = 0; maxEnd = event.getDocument().getLength(); regionsToConsider = new IRegion[] {new Region(0, event.getDocument().getLength())}; } int textDiff = event.getLength()- textLength; int textAdjustment = -textDiff; maxEnd += textDiff; //find the old waypoints and adjust their locations to fit the document change //so that they can be properly synchronized. IWaypoint[] oldArray = ParsedWaypointPlugin.getDefault().getParsedWaypointRegistry().getWaypointsForFile(file, def.getKind()); LinkedList<IWaypoint> waypointsToConsider = new LinkedList<IWaypoint>(); for (IWaypoint wp : oldArray) { int wpStart = wp.getIntValue(IParsedWaypointAttributes.ATTR_CHAR_START, -1); int wpEnd = wp.getIntValue(IParsedWaypointAttributes.ATTR_CHAR_END, -1); if (doParse && ((wpStart >= minStart && wpStart <= maxEnd) || (wpEnd >= minStart && wpEnd <= maxEnd))) { //at least one end is in the region: add it to the list to consider. waypointsToConsider.add(wp); } if (wpStart > event.getOffset() + event.getLength() && wpEnd > event.getOffset()) { //if it comes after the changed region, update both of it's indexes wp.setIntValue(IParsedWaypointAttributes.ATTR_CHAR_START, wpStart + textAdjustment); wp.setIntValue(IParsedWaypointAttributes.ATTR_CHAR_END, wpEnd + textAdjustment); } else if (wpStart <= event.getOffset() && wpEnd >= event.getOffset()+event.getLength()) { //if it is inside, adjust the end of the waypoint only int newEnd = wpEnd+textAdjustment; if (newEnd == wpStart) { //0-lengthed waypoints must be deleted. waypointsToConsider.add(wp); } else { wp.setIntValue(IParsedWaypointAttributes.ATTR_CHAR_END, newEnd); if (!doParse) { Status s = new Status(Status.WARNING, ParsedWaypointPlugin.PLUGIN_ID, "Unsynchronized edit in " + def.getName() + "waypoint"); status.merge(s); } } } else { //check to make sure that no error has occurred in the gathering of dirty areas. //if it has, log an error. if (!doParse) { if ((wpStart >= event.getOffset() && wpStart <= event.getOffset() + event.getLength()) || (wpEnd >= event.getOffset() && wpEnd <= event.getOffset() + event.getLength())) { //if we aren't parsing, but it intersects with a dead region, asume that //it should be deleted. waypointsToConsider.add(wp); } } //otherwise, do nothing: it will either be before the change in which case it doesn't matter, //or it will intersect with the change in which case it has to be replaced. } } //clear the document in the given regions for (IRegion r : regionsToConsider) { registry.clearProblems(event.getDocument(), r); } IParsedWaypointDescriptor[] descriptors = parser.parse(event.getDocument(), regionsToConsider, registry); List<WaypointDescriptorMatch> matchings =WaypointParsingTools.organizeWaypoints( file, waypointsToConsider.toArray(new IWaypoint[waypointsToConsider.size()]), descriptors, def.getKind() ); generated.addAll(WaypointParsingTools.generateWaypoints(matchings, file, def.getKind())); } removeOverlaps(generated, registry, event.getDocument()); return status; } /** * Removes the overlaps for the given waypoints. * @param generated * @param collector * @param document */ private void removeOverlaps(List<IWaypoint> generated, IWaypointParseProblemCollector collector, IDocument document) { RegionGraph<IWaypoint> overlaps = WaypointParsingTools.calculateOverlappingWaypoints(generated); for (IWaypoint wp : generated) { if (overlaps.getOverlapCount(wp) > 0) { int start = ParsedWaypointUtils.getCharStart(wp); int end = ParsedWaypointUtils.getCharEnd(wp); WaypointParsingTools.deleteWaypoint(wp); collector.accept(new WaypointOverlapProblem(start, end-start, document)); } } } }, false); //schedule a full clean timer.schedule(file); } }