| /******************************************************************************* |
| * Copyright (c) 2015 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 API and implementation |
| ******************************************************************************/ |
| |
| package org.eclipse.ui.internal.views.markers; |
| |
| import java.util.Collection; |
| import java.util.LinkedList; |
| |
| import org.eclipse.core.resources.IMarker; |
| import org.eclipse.core.resources.IMarkerDelta; |
| import org.eclipse.core.resources.IResourceChangeEvent; |
| import org.eclipse.core.resources.IResourceChangeListener; |
| import org.eclipse.core.resources.IResourceDelta; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.jobs.Job; |
| |
| /** |
| * The MarkersChangeListener is IResourceChangeListener that waits for any |
| * change in the markers in workspace that are of the view's interest. Schedules |
| * an update if we have a change that affects the view. |
| * |
| * @since 3.6 |
| */ |
| class MarkersChangeListener implements IResourceChangeListener { |
| |
| private ExtendedMarkersView view; |
| private CachedMarkerBuilder builder; |
| |
| private String[] listeningTypes; |
| private boolean receiving; |
| |
| //private static final int UPDATE_TEST_CHECK_LIMIT = 1500; |
| |
| // The time the build started. A -1 indicates no build in progress. |
| private long preBuildTime; |
| |
| /** |
| * |
| * @param view |
| * the marker view the listener is listening for |
| * @param builder |
| * the builder for the view |
| */ |
| MarkersChangeListener(ExtendedMarkersView view, CachedMarkerBuilder builder) { |
| this.view = view; |
| this.builder = builder; |
| listeningTypes = new String[0]; |
| } |
| |
| /** |
| * Start listening for changes. |
| */ |
| synchronized void start() { |
| ResourcesPlugin.getWorkspace().addResourceChangeListener( |
| this, |
| IResourceChangeEvent.POST_CHANGE |
| | IResourceChangeEvent.PRE_BUILD |
| | IResourceChangeEvent.POST_BUILD); |
| } |
| |
| /** |
| * Stop listening for changes. |
| */ |
| synchronized void stop() { |
| if (listeningTypes != null) { |
| listeningTypes = new String[0]; |
| } |
| ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); |
| } |
| |
| /** |
| * Checks if the workspace is building |
| * |
| */ |
| boolean workspaceBuilding() { |
| return preBuildTime > 0; |
| } |
| |
| /** |
| * Tells the listener to become responsive to changes for the specified |
| * types of markers. |
| * |
| * @param typeIds |
| * the ids of the IMarker types |
| * @param includeSubTypes |
| * true to include the sub-marker-types |
| */ |
| void listenToTypes(String[] typeIds, boolean includeSubTypes) { |
| try { |
| // register marker types being gathering |
| if (includeSubTypes) { |
| listeningTypes = MarkerResourceUtil.getAllSubTypesIds(typeIds); |
| } else { |
| // register marker types being gathering |
| listeningTypes = typeIds; |
| } |
| } catch (Exception e) { |
| MarkerSupportInternalUtilities.logViewError(e); |
| } |
| } |
| |
| @Override |
| public synchronized void resourceChanged(IResourceChangeEvent event) { |
| /* We can now consider removing synchronized for |
| * this method.Only the start and stop need to be |
| * synchronize on the listener |
| */ |
| setReceivingChange(true); |
| try { |
| if (event.getType() == IResourceChangeEvent.PRE_BUILD) { |
| preBuild(); |
| return; |
| } |
| if (event.getType() == IResourceChangeEvent.POST_BUILD) { |
| postBuild(); |
| // clear any pending updates |
| builder.getUpdateScheduler().speedUpPendingUpdates(); |
| return; |
| } |
| if(!hasApplicableTypes(event)){ |
| return; |
| } |
| // if (!needsUpdate(event)) { |
| // return; |
| // } |
| |
| if (!builder.isIncremental()) { |
| handleMarkerChange(event); |
| return; |
| } |
| handleIncrementalChange(event); |
| } finally { |
| setReceivingChange(false); |
| } |
| } |
| |
| /** |
| * @return the receiving |
| */ |
| boolean isReceivingChange() { |
| return receiving; |
| } |
| |
| /** |
| * @param receiving |
| * the receiving to set |
| */ |
| void setReceivingChange(boolean receiving) { |
| this.receiving = receiving; |
| } |
| |
| /** |
| * Handle marker change event |
| * @param event |
| */ |
| private void handleMarkerChange(IResourceChangeEvent event) { |
| builder.getUpdateScheduler().scheduleUpdate(); |
| } |
| |
| /** |
| * Markers have not changed |
| */ |
| private void handleNoMarkerChange() { |
| //view.indicateUpdating(null, true, false); |
| } |
| |
| /** |
| * Handle changes incrementally. |
| * The following performs incremental updation |
| * of the markers that were gathered initially, and keeps them synched at |
| * any point with the markers of interest in Workspace. Unfortunately marker |
| * operations cannot be locked so locking between gathering of markers and |
| * marker deltas is not possible. |
| * |
| * Note : this method of updating is NOT used and tested yet and has holes |
| * but left out SOLELY for further investigation(*). |
| * |
| * @param event |
| */ |
| private void handleIncrementalChange(IResourceChangeEvent event) { |
| IMarkerDelta[] markerDeltas = event.findMarkerDeltas(null, true); |
| if (markerDeltas.length == 0) { |
| return; |
| } |
| Collection<MarkerEntry> removed = new LinkedList<MarkerEntry>(), added = new LinkedList<MarkerEntry>(), changed = new LinkedList<>(); |
| String[] types = listeningTypes; |
| for (int i = 0; i < markerDeltas.length; i++) { |
| try { |
| String typeId = markerDeltas[i].getType(); |
| if (!isApplicableType(types, typeId)) { |
| continue; |
| } |
| IMarker marker = markerDeltas[i].getMarker(); |
| MarkerEntry markerEntry = new MarkerEntry(marker); |
| switch (markerDeltas[i].getKind()) { |
| case IResourceDelta.REMOVED: { |
| removed.add(markerEntry); |
| break; |
| } |
| case IResourceDelta.ADDED: { |
| added.add(markerEntry); |
| break; |
| } |
| case IResourceDelta.CHANGED: { |
| changed.add(markerEntry); |
| break; |
| } |
| default:{ |
| break; |
| } |
| } |
| } catch (Exception e) { |
| // log exception |
| MarkerSupportInternalUtilities.logViewError(e); |
| } |
| } |
| if (removed.size() > 0 || added.size() > 0 || changed.size() > 0) { |
| MarkerUpdate update = new MarkerUpdate(added, removed, changed); |
| builder.incrementalUpdate(update); |
| builder.getUpdateScheduler().scheduleUpdate(); |
| } else { |
| handleNoMarkerChange(); |
| } |
| return; |
| } |
| |
| /** |
| * @param event |
| * @return true if the marker delta has a change in an applicable marker |
| * type else false. |
| */ |
| private boolean hasApplicableTypes(IResourceChangeEvent event) { |
| IMarkerDelta[] markerDeltas = event.findMarkerDeltas(null, true); |
| String[] types = listeningTypes; |
| if (types.length == 0) { |
| return false; |
| } |
| for (int i = 0; i < markerDeltas.length; i++) { |
| if (isApplicableType(types, markerDeltas[i].getType())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Helper to {@link #hasApplicableTypes(IResourceChangeEvent)} |
| * |
| * @param types |
| * @param typeId |
| */ |
| private boolean isApplicableType(String[] types, String typeId) { |
| for (int i = 0; i < types.length; i++) { |
| if (types[i].equals(typeId)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // /** |
| // * Note: This has been left commented out for further |
| // * investigation(*),instead we we use the above for just checking types. |
| // * This may invoke contributed code; as a field filter can be contributed. |
| // * But again in such a case, the view would be a contributed as well.And, it |
| // * is the responsibility of the field filter code to ensure the select |
| // * method of filter remains fast. |
| // * |
| // */ |
| // private boolean needsUpdate(IResourceChangeEvent event) { |
| // IMarkerDelta[] markerDeltas = event.findMarkerDeltas(null, true); |
| // MarkerEntry[] presentEntries = builder.getClonedMarkers().getClone() |
| // .getMarkerEntryArray(); |
| // int deltaCount = markerDeltas.length; |
| // if (deltaCount == 0) { |
| // return false; |
| // } |
| // String[] types = listeningTypes; |
| // if (hasMarkerRemoval(presentEntries, null)) { |
| // return true; |
| // } |
| // int maxTestCount = deltaCount > UPDATE_TEST_CHECK_LIMIT ? UPDATE_TEST_CHECK_LIMIT |
| // : deltaCount; |
| // |
| // for (int i = 0; i < markerDeltas.length; i++) { |
| // String typeId = markerDeltas[i].getType(); |
| // if (!hasApplicableTypes(types, typeId)) { |
| // continue; |
| // } |
| // if (presentEntries == null || presentEntries.length == 0) { |
| // return true; |
| // } |
| // int kind = markerDeltas[i].getKind(); |
| // IMarker marker = markerDeltas[i].getMarker(); |
| // MarkerEntry markerEntry = new MarkerEntry(marker); |
| // if (affectsCurrentState(presentEntries, markerEntry, kind)) { |
| // return true; |
| // } |
| // if (i >= maxTestCount - 1) { |
| // return true; |
| // } |
| // } |
| // return false; |
| // } |
| // |
| // /** |
| // * Check if a marker change, removal, or addition is of interest to the |
| // * view. |
| // * |
| // * <ul> |
| // * <li>Set the MarkerEntry to be stale, if discovered at any point of time |
| // * of its use.This will greatly speed up lot of parts of the view.</li> |
| // * <li>Instead of testing all marker changes, test only upto a maximum limit |
| // * beyond which we schedule an update anyway.</li> |
| // * |
| // * </ul> |
| // * |
| // * @param marker |
| // * @param kind |
| // */ |
| // private boolean affectsCurrentState(MarkerEntry[] presentEntries, |
| // MarkerEntry marker, int kind) { |
| // switch (kind) { |
| // case IResourceDelta.REMOVED: { |
| // return hasMarkerRemoval(presentEntries, marker); |
| // } |
| // case IResourceDelta.ADDED: { |
| // return hasMarkerAdditionsofInterest(presentEntries, marker); |
| // } |
| // case IResourceDelta.CHANGED: { |
| // return hasMarkerChanges(presentEntries, marker); |
| // } |
| // default: { |
| // return false; |
| // } |
| // } |
| // } |
| // |
| // /** |
| // * Returns whether or not the given marker addition is of interest to the |
| // * view. |
| // * |
| // * @param presentEntries |
| // * current marker entries |
| // * @param marker |
| // * the marker entry |
| // * @return <code>true</code> if build is needed <code>false</code> if no |
| // * update needed |
| // */ |
| // private boolean hasMarkerAdditionsofInterest(MarkerEntry[] presentEntries, |
| // MarkerEntry marker) { |
| // MarkerContentGenerator generator = builder.getGenerator(); |
| // if (generator.select(marker)) { |
| // return true; |
| // } |
| // return false; |
| // } |
| // |
| // /** |
| // * Returns whether or not markers were removed from the view. |
| // * |
| // * @param presentEntriest |
| // * current marker entries |
| // * @param marker |
| // * the marker entry |
| // * @return <code>true</code> if build is needed <code>false</code> if no |
| // * update needed |
| // */ |
| // private boolean hasMarkerRemoval(MarkerEntry[] presentEntriest, |
| // MarkerEntry marker) { |
| // for (int i = 0; i < presentEntriest.length; i++) { |
| // if (presentEntriest[i].getStaleState() |
| // || presentEntriest[i].getMarker() == null) { |
| // return true; |
| // } |
| // if (marker != null) { |
| // if (presentEntriest[i].getMarker().equals(marker.getMarker())) { |
| // return false; |
| // } |
| // } |
| // } |
| // return false; |
| // } |
| // |
| // /** |
| // * Returns whether or not markers were removed from the view. |
| // * |
| // * @param presentEntriest |
| // * current marker entries |
| // * @param marker |
| // * the marker entry |
| // * @return <code>true</code> if build is needed <code>false</code> if no |
| // * update needed |
| // */ |
| // private boolean hasMarkerChanges(MarkerEntry[] presentEntriest, |
| // MarkerEntry marker) { |
| // MarkerContentGenerator generator = builder.getGenerator(); |
| // if (generator.select(marker)) { |
| // return true; |
| // } |
| // for (int i = 0; i < presentEntriest.length; i++) { |
| // if (presentEntriest[i].getMarker().equals(marker.getMarker())) { |
| // return true; |
| // } |
| // |
| // } |
| // return false; |
| // } |
| |
| /** |
| * We are in a pre-build state. |
| */ |
| private void preBuild() { |
| preBuildTime = System.currentTimeMillis(); |
| } |
| |
| /** |
| * Post-build has happened. |
| */ |
| private void postBuild() { |
| preBuildTime = -1; |
| } |
| |
| /** |
| * @return Returns the view. |
| */ |
| ExtendedMarkersView getView() { |
| return view; |
| } |
| |
| /** |
| * @return Returns the builder. |
| */ |
| CachedMarkerBuilder getBuilder() { |
| return builder; |
| } |
| } |
| |
| ///////////helpers///////////// |
| |
| /** |
| * For Incremental updating |
| * @since 3.6 |
| */ |
| class MarkerUpdate { |
| Collection<MarkerEntry> added; |
| Collection<MarkerEntry> removed; |
| Collection<MarkerEntry> changed; |
| |
| MarkerUpdate(Collection<MarkerEntry> added, Collection<MarkerEntry> removed, Collection<MarkerEntry> changed) { |
| this.added = added; |
| this.removed = removed; |
| this.changed = changed; |
| } |
| } |
| |
| /** |
| * Manages scheduling of marker updates and the view ,also various other methods |
| * related to scheduling updates.This class should be used for update |
| * scheduling to avoid confusion. |
| * |
| * Note: the reason for keeping this class is because the update scheduling is |
| * so closely related to Marker change events. |
| * |
| * @since 3.6 |
| */ |
| class MarkerUpdateScheduler { |
| |
| static final int SHORT_DELAY = 150; |
| static final int LONG_DELAY = 10000; |
| static final long TIME_OUT = 30000; |
| |
| private CachedMarkerBuilder builder; |
| private ExtendedMarkersView view; |
| |
| private MarkerUpdateJob updateJob; |
| private UIUpdateJob uiUpdateJob; |
| |
| private final Object schedulingLock; |
| |
| private MarkerUpdateTimer updateTimer; |
| |
| /** |
| * @param view |
| * @param builder |
| */ |
| public MarkerUpdateScheduler(ExtendedMarkersView view, CachedMarkerBuilder builder) { |
| this.view = view; |
| this.builder = builder; |
| schedulingLock = new Object(); |
| updateTimer = new MarkerUpdateTimer(); |
| } |
| |
| /** |
| * Always use this to schedule update job |
| * @return Returns the schedulingLock. |
| */ |
| Object getSchedulingLock() { |
| return schedulingLock; |
| } |
| |
| /** |
| * Schedule marker update. |
| */ |
| void scheduleUpdate(long delay, boolean cancelPrevious, |
| boolean[] changeFlags) { |
| //we do not need to make this atomic (?) |
| builder.setBuilding(true); |
| if (cancelPrevious) { |
| cancelQueuedUIUpdates(); |
| cancelUpdate(); |
| } |
| // indicateStatus(MarkerMessages.MarkerView_queueing_updates, true); |
| updateJob = builder.scheduleUpdateJob(delay, true, changeFlags); |
| // updateTimer.reset(); |
| } |
| |
| /** |
| * Schedule marker update. |
| */ |
| void scheduleUpdate(long delay, boolean cancelPrevious) { |
| //we do not need to make this atomic (?) |
| builder.setBuilding(true); |
| if (cancelPrevious) { |
| cancelQueuedUIUpdates(); |
| cancelUpdate(); |
| } |
| // indicateStatus(MarkerMessages.MarkerView_queueing_updates, true); |
| updateJob = builder.scheduleUpdateJob(delay, true); |
| // updateTimer.reset(); |
| } |
| |
| /** |
| * Schedule marker update. |
| */ |
| void scheduleUpdate(long delay, boolean[] changeFlags) { |
| scheduleUpdate(delay, true, changeFlags); |
| } |
| |
| /** |
| * Schedule marker update. |
| */ |
| void scheduleUpdate(boolean[] changeFlags) { |
| synchronized (updateTimer) { |
| builder.updateChangeFlags(changeFlags); |
| updateTimer.update(); |
| } |
| } |
| |
| /** |
| * Schedule marker update. |
| */ |
| |
| void scheduleUpdate() { |
| synchronized (updateTimer) { |
| updateTimer.update(); |
| } |
| } |
| |
| /** |
| * Schedule pending updates to happen quickly. |
| */ |
| void speedUpPendingUpdates() { |
| synchronized (updateTimer) { |
| updateTimer.speedUpPendingUpdates(); |
| } |
| } |
| |
| /** |
| * Returns true if updates have been scheduled and not finished,else false. |
| */ |
| boolean updatesPending() { |
| synchronized (updateTimer) { |
| if (builder.isBuilding()) { |
| return true; |
| } |
| boolean pending = false; |
| if (updateJob != null) { |
| pending = updateJob.getState() != Job.NONE; |
| } |
| if (!pending) { |
| if (uiUpdateJob != null) { |
| pending = uiUpdateJob.getState() != Job.NONE; |
| } |
| } |
| if (!pending) { |
| // No need to come till here |
| pending = updateTimer.updatesPending(); |
| } |
| return pending; |
| } |
| } |
| |
| /** |
| * Schedule only an UI update |
| * |
| * @param delay |
| * |
| */ |
| void scheduleUIUpdate(long delay) { |
| uiUpdateJob = view.scheduleUpdate(delay); |
| } |
| |
| /** |
| * Cancel any marker update if pending. |
| * |
| */ |
| void cancelUpdate() { |
| builder.cancelUpdate(); |
| } |
| |
| /** |
| * Cancel any UI update if pending. |
| * |
| */ |
| void cancelQueuedUIUpdates() { |
| view.cancelQueuedUpdates(); |
| } |
| |
| ///** |
| // * Indicate the status message on UI. |
| // * |
| // * @param messsage |
| // * the status to display |
| // */ |
| //void indicateStatus(String messsage) { |
| // indicateStatus(messsage, false); |
| //} |
| ////See Bug 294303 |
| ///** |
| // * Indicate the status message on UI. |
| // * |
| // * @param messsage |
| // * the status to display |
| // * @param updateUI |
| // * <code>true</code> update label to show changing status |
| // */ |
| //void indicateStatus(String messsage, boolean updateUI) { |
| // //See Bug 294303 |
| // view.indicateUpdating(messsage != null ? messsage |
| // : MarkerMessages.MarkerView_queueing_updates, updateUI); |
| //} |
| |
| |
| /** |
| * //Fix for Bug 294959.There is another patch(more exhaustive in terms |
| * of possibilities to cover) on the bug in which we keep scheduling |
| * updates with CANCEL_MARGIN_DELAY after a Post-Build event until we |
| * have actually finished an update. In case the current way has |
| * problems on a machine It would be worth looking at that.An |
| * optimization to ensure we do not update too often, yet be responsive |
| * and not miss any change. |
| * |
| * Note that we re-schedule the update every time.This is to ensure we |
| * do not miss out an update even if another update was externally(UI) |
| * scheduled, and finished much earlier(The changes before that have |
| * been taken care of by the that update).Also we mandate updating once |
| * in TIME-OUT.To change behaviour, changes in the DELAY parameters will |
| * suffice. For example, setting TIME_OUT much larger value, and so on. |
| * |
| * @since 3.6 |
| */ |
| class MarkerUpdateTimer { |
| |
| /** |
| * This is to allow batching together any changes that may arrive in |
| * after a post-build, in a short interval.This controls how we |
| * update when we are receiving post-build events and change-events |
| * continuously over a short gap of time. |
| */ |
| private final long CANCEL_MARGIN_DELAY = (SHORT_DELAY * 3); |
| private final long NO_CANCEL_TIME_OUT = (LONG_DELAY * 3); |
| //this to account for an ordinary change that may come in |
| //after post build |
| private static final long AFTER_MARGIN = 2; |
| |
| private long timeB4Update; |
| |
| private long timerValidStart; |
| |
| void update() { |
| long startTime = view.getLastUIRefreshTime(); |
| long currentTime = System.currentTimeMillis(); |
| long updateTimeGap = currentTime - startTime; |
| // check if we can cancel a scheduled or a running update |
| boolean cancelable = !(updateTimeGap > TIME_OUT); |
| updateTimeGap = updateTimeGap % TIME_OUT; |
| if (!cancelable) { |
| cancelable = !isValidTimeOut(startTime, currentTime, TIME_OUT); |
| if (timeB4Update != -1 && cancelable) { |
| if (updateTimeGap < CANCEL_MARGIN_DELAY) { |
| updateTimeGap = CANCEL_MARGIN_DELAY; |
| } |
| } |
| } |
| |
| if (timeB4Update == -1) { |
| /* |
| * This is an optimization and may be removed.But, it is |
| * desirable that we schedule soon after a post-build. |
| */ |
| // a Special Update request |
| go(CANCEL_MARGIN_DELAY, cancelable); |
| return; |
| } |
| |
| long delay = TIME_OUT - updateTimeGap; |
| if ((delay + updateTimeGap) > NO_CANCEL_TIME_OUT) { |
| if (delay > NO_CANCEL_TIME_OUT) { |
| // rectify the delay |
| delay = LONG_DELAY; |
| } |
| if (isValidTimeOut(startTime, currentTime, NO_CANCEL_TIME_OUT)) { |
| cancelable = false; |
| } |
| } |
| if (!builder.getMarkerListener().workspaceBuilding()) { |
| if (updateTimeGap + LONG_DELAY > TIME_OUT) { |
| if (updateTimeGap + (CANCEL_MARGIN_DELAY) >= TIME_OUT) { |
| go(delay, false); |
| } else { |
| go(delay, cancelable); |
| } |
| } else { |
| //long diff =timeB4Update-currentTime; |
| //if (diff <= AFTER_MARGIN && diff >= 0) { |
| // go(0L, false); |
| //} else { |
| // go(LONG_DELAY, cancelable); |
| //} |
| go(LONG_DELAY, cancelable); |
| } |
| } else { |
| // we are in build again |
| go(delay, cancelable); |
| } |
| } |
| |
| /** |
| * Schedules quickly if any update is pending, Or prepares for quick |
| * scheduling on next change |
| */ |
| void speedUpPendingUpdates() { |
| /* |
| * if we have a distant pending update schedule it with |
| * CANCEL_MARGIN_DELAY |
| */ |
| if (updatesPending()) { |
| timeB4Update = -1; |
| update(); |
| } |
| /* |
| * Else wait for next change(Post-Change?), it will be scheduled |
| * with CANCEL_MARGIN_DELAY |
| */ |
| timeB4Update = -1; |
| } |
| |
| /** |
| * Checks if we have a pending update |
| */ |
| boolean updatesPending() { |
| long diff = timeB4Update - System.currentTimeMillis(); |
| return diff > CANCEL_MARGIN_DELAY; |
| } |
| |
| /** |
| * Checks if a time-out is valid,or if its just a period of |
| * inactivity. NOTE:This is PURELY an optimization and can be |
| * omitted. |
| */ |
| private boolean isValidTimeOut(long startTime, long currentTime, long timeOut) { |
| // long updateTimeGap = currentTime - startTime; |
| if (timeB4Update != -1 && startTime > timeB4Update) { |
| /* |
| * The last scheduled update finished.This is not an actual |
| * TIME_OUT.Possible that we have not updated for a long |
| * interval.Lets make this update cancelable anyway.Reset |
| * timer. |
| */ |
| timerValidStart = currentTime; |
| return false; |
| } else if ((currentTime - timerValidStart) < timeOut ) { |
| return false; |
| } else { |
| /* |
| * Do not update internal value we only use this for |
| * checking valid TIME_OUTs |
| */ |
| return true; |
| } |
| } |
| |
| private void go(long delay, boolean cancelPrevious) { |
| timeB4Update = System.currentTimeMillis() + delay; |
| scheduleUpdate(delay + AFTER_MARGIN, cancelPrevious); |
| } |
| } |
| } |