Copyright (c) 2018 IBM Corporation and others. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ SPDX-License-Identifier: EPL-2.0 Contributors: IBM Corporation - initial API and implementation Red Hat Inc. - copied from SemanticHighlightingPresenter and modified
/******************************************************************************* * Copyright (c) 2018 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation * Red Hat Inc. - copied from SemanticHighlightingPresenter and modified *******************************************************************************/
package org.eclipse.jdt.internal.ui.javaeditor; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPositionCategoryException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IPositionUpdater; import org.eclipse.jface.text.Position; import org.eclipse.jdt.internal.core.manipulation.JavaManipulationPlugin;
Semantic highlighting presenter - UI thread implementation.
Since:1.11
/** * Semantic highlighting presenter - UI thread implementation. * * @since 1.11 */
public class SemanticHighlightingPresenterCore {
Semantic highlighting position updater.
/** * Semantic highlighting position updater. */
protected class HighlightingPositionUpdater implements IPositionUpdater {
The position category.
/** The position category. */
private final String fCategory;
Creates a new updater for the given category.
Params:
  • category – the new category.
/** * Creates a new updater for the given <code>category</code>. * * @param category the new category. */
public HighlightingPositionUpdater(String category) { fCategory= category; } /* * @see org.eclipse.jface.text.IPositionUpdater#update(org.eclipse.jface.text.DocumentEvent) */ @Override public void update(DocumentEvent event) { int eventOffset= event.getOffset(); int eventOldLength= event.getLength(); int eventEnd= eventOffset + eventOldLength; try { Position[] positions= event.getDocument().getPositions(fCategory); for (int i= 0; i != positions.length; i++) { HighlightedPositionCore position= (HighlightedPositionCore) positions[i]; // Also update deleted positions because they get deleted by the background thread and removed/invalidated only in the UI runnable // if (position.isDeleted()) // continue; int offset= position.getOffset(); int length= position.getLength(); int end= offset + length; if (offset > eventEnd) updateWithPrecedingEvent(position, event); else if (end < eventOffset) updateWithSucceedingEvent(position, event); else if (offset <= eventOffset && end >= eventEnd) updateWithIncludedEvent(position, event); else if (offset <= eventOffset) updateWithOverEndEvent(position, event); else if (end >= eventEnd) updateWithOverStartEvent(position, event); else updateWithIncludingEvent(position, event); } } catch (BadPositionCategoryException e) { // ignore and return } }
Update the given position with the given event. The event precedes the position.
Params:
  • position – The position
  • event – The event
/** * Update the given position with the given event. The event precedes the position. * * @param position The position * @param event The event */
private void updateWithPrecedingEvent(HighlightedPositionCore position, DocumentEvent event) { String newText= event.getText(); int eventNewLength= newText != null ? newText.length() : 0; int deltaLength= eventNewLength - event.getLength(); position.setOffset(position.getOffset() + deltaLength); }
Update the given position with the given event. The event succeeds the position.
Params:
  • position – The position
  • event – The event
/** * Update the given position with the given event. The event succeeds the position. * * @param position The position * @param event The event */
private void updateWithSucceedingEvent(HighlightedPositionCore position, DocumentEvent event) { }
Update the given position with the given event. The event is included by the position.
Params:
  • position – The position
  • event – The event
/** * Update the given position with the given event. The event is included by the position. * * @param position The position * @param event The event */
private void updateWithIncludedEvent(HighlightedPositionCore position, DocumentEvent event) { int eventOffset= event.getOffset(); String newText= event.getText(); if (newText == null) newText= ""; //$NON-NLS-1$ int eventNewLength= newText.length(); int deltaLength= eventNewLength - event.getLength(); int offset= position.getOffset(); int length= position.getLength(); int end= offset + length; int includedLength= 0; while (includedLength < eventNewLength && Character.isJavaIdentifierPart(newText.charAt(includedLength))) includedLength++; if (includedLength == eventNewLength) position.setLength(length + deltaLength); else { int newLeftLength= eventOffset - offset + includedLength; int excludedLength= eventNewLength; while (excludedLength > 0 && Character.isJavaIdentifierPart(newText.charAt(excludedLength - 1))) excludedLength--; int newRightOffset= eventOffset + excludedLength; int newRightLength= end + deltaLength - newRightOffset; if (newRightLength == 0) { position.setLength(newLeftLength); } else { if (newLeftLength == 0) { position.update(newRightOffset, newRightLength); } else { position.setLength(newLeftLength); addPositionForEvent(event, fCategory, newRightOffset, newRightLength, position.getHighlighting()); } } } }
Update the given position with the given event. The event overlaps with the end of the position.
Params:
  • position – The position
  • event – The event
/** * Update the given position with the given event. The event overlaps with the end of the position. * * @param position The position * @param event The event */
private void updateWithOverEndEvent(HighlightedPositionCore position, DocumentEvent event) { String newText= event.getText(); if (newText == null) newText= ""; //$NON-NLS-1$ int eventNewLength= newText.length(); int includedLength= 0; while (includedLength < eventNewLength && Character.isJavaIdentifierPart(newText.charAt(includedLength))) includedLength++; position.setLength(event.getOffset() - position.getOffset() + includedLength); }
Update the given position with the given event. The event overlaps with the start of the position.
Params:
  • position – The position
  • event – The event
/** * Update the given position with the given event. The event overlaps with the start of the position. * * @param position The position * @param event The event */
private void updateWithOverStartEvent(HighlightedPositionCore position, DocumentEvent event) { int eventOffset= event.getOffset(); int eventEnd= eventOffset + event.getLength(); String newText= event.getText(); if (newText == null) newText= ""; //$NON-NLS-1$ int eventNewLength= newText.length(); int excludedLength= eventNewLength; while (excludedLength > 0 && Character.isJavaIdentifierPart(newText.charAt(excludedLength - 1))) excludedLength--; int deleted= eventEnd - position.getOffset(); int inserted= eventNewLength - excludedLength; position.update(eventOffset + excludedLength, position.getLength() - deleted + inserted); }
Update the given position with the given event. The event includes the position.
Params:
  • position – The position
  • event – The event
/** * Update the given position with the given event. The event includes the position. * * @param position The position * @param event The event */
private void updateWithIncludingEvent(HighlightedPositionCore position, DocumentEvent event) { position.delete(); position.update(event.getOffset(), 0); } }
Position updater
/** Position updater */
protected IPositionUpdater fPositionUpdater= new HighlightingPositionUpdater(getPositionCategory());
UI's current highlighted positions - can contain null elements
/** UI's current highlighted positions - can contain <code>null</code> elements */
protected List<Position> fPositions= new ArrayList<>();
UI position lock
/** UI position lock */
protected Object fPositionLock= new Object();
true iff the current reconcile is canceled.
/** <code>true</code> iff the current reconcile is canceled. */
protected boolean fIsCanceled= false;
Creates and returns a new highlighted position with the given offset, length and highlighting.

NOTE: Also called from background thread.

Params:
  • offset – The offset
  • length – The length
  • highlighting – The highlighting
Returns:The new highlighted position
/** * Creates and returns a new highlighted position with the given offset, length and highlighting. * <p> * NOTE: Also called from background thread. * </p> * * @param offset The offset * @param length The length * @param highlighting The highlighting * @return The new highlighted position */
public HighlightedPositionCore createHighlightedPositionCore(int offset, int length, Object highlighting) { // TODO: reuse deleted positions return new HighlightedPositionCore(offset, length, highlighting, fPositionUpdater); }
Add a position with the given range and highlighting unconditionally, only from UI thread. The position will also be registered on the document. The text presentation is not invalidated.
Params:
  • event – The document event
  • category – The category
  • offset – The range offset
  • length – The range length
  • highlighting – the highlighting
/** * Add a position with the given range and highlighting unconditionally, only from UI thread. * The position will also be registered on the document. The text presentation is not * invalidated. * * @param event The document event * @param category The category * @param offset The range offset * @param length The range length * @param highlighting the highlighting */
protected void addPositionForEvent(DocumentEvent event, String category, int offset, int length, Object highlighting) { HighlightedPositionCore highlightedPosition = new HighlightedPositionCore(offset, length, highlighting, new Object()); try { event.fDocument.addPosition(category, highlightedPosition); } catch (BadLocationException | BadPositionCategoryException e) { JavaManipulationPlugin.logException("Error when adding new highlighting position to the document", e); //$NON-NLS-1$ } }
Adds all current positions to the given list.

NOTE: Called from background thread.

Params:
  • list – The list
/** * Adds all current positions to the given list. * <p> * NOTE: Called from background thread. * </p> * * @param list The list */
public void addAllPositions(List<Position> list) { synchronized (fPositionLock) { list.addAll(fPositions); } }
Create a runnable for updating the presentation.

NOTE: Called from background thread.

Params:
  • document – the document
  • addedPositions – the added positions
  • removedPositions – the removed positions
Returns:the runnable or null, if reconciliation should be canceled
/** * Create a runnable for updating the presentation. * <p> * NOTE: Called from background thread. * </p> * @param document the document * @param addedPositions the added positions * @param removedPositions the removed positions * @return the runnable or <code>null</code>, if reconciliation should be canceled */
public Runnable createUpdateRunnableCore(IDocument document, List<Position> addedPositions, List<Position> removedPositions) { // TODO: do clustering of positions and post multiple fast runnables final HighlightedPositionCore[] added= new HighlightedPositionCore[addedPositions.size()]; addedPositions.toArray(added); final HighlightedPositionCore[] removed= new HighlightedPositionCore[removedPositions.size()]; removedPositions.toArray(removed); if (isCanceled()) return null; Runnable runnable= new Runnable() { @Override public void run() { updatePresentationCore(document, added, removed); } }; return runnable; }
Invalidate the presentation of the positions based on the given added positions and the existing deleted positions. Also unregisters the deleted positions from the document and patches the positions of this presenter.

NOTE: Indirectly called from background thread by UI runnable.

Params:
  • document – the document or null, if the presentation should computed in the UI thread
  • addedPositions – the added positions
  • removedPositions – the removed positions
/** * Invalidate the presentation of the positions based on the given added positions and the existing deleted positions. * Also unregisters the deleted positions from the document and patches the positions of this presenter. * <p> * NOTE: Indirectly called from background thread by UI runnable. * </p> * @param document the document or <code>null</code>, if the presentation should computed in the UI thread * @param addedPositions the added positions * @param removedPositions the removed positions */
public void updatePresentationCore(IDocument document, HighlightedPositionCore[] addedPositions, HighlightedPositionCore[] removedPositions) { // TODO: double-check consistency with document.getPositions(...) // TODO: reuse removed positions if (isCanceled()) return; if (document == null) return; String positionCategory= getPositionCategory(); List<HighlightedPositionCore> removedPositionsList= Arrays.asList(removedPositions); try { synchronized (fPositionLock) { List<Position> oldPositions= fPositions; int newSize= Math.max(fPositions.size() + addedPositions.length - removedPositions.length, 10); /* * The following loop is a kind of merge sort: it merges two List<Position>, each * sorted by position.offset, into one new list. The first of the two is the * previous list of positions (oldPositions), from which any deleted positions get * removed on the fly. The second of two is the list of added positions. The result * is stored in newPositions. */ List<Position> newPositions= new ArrayList<>(newSize); Position position= null; Position addedPosition= null; for (int i= 0, j= 0, n= oldPositions.size(), m= addedPositions.length; i < n || position != null || j < m || addedPosition != null;) { // loop variant: i + j < old(i + j) // a) find the next non-deleted Position from the old list while (position == null && i < n) { position= oldPositions.get(i++); if (position.isDeleted() || contain(removedPositionsList, position)) { document.removePosition(positionCategory, position); position= null; } } // b) find the next Position from the added list if (addedPosition == null && j < m) { addedPosition= addedPositions[j++]; document.addPosition(positionCategory, addedPosition); } // c) merge: add the next of position/addedPosition with the lower offset if (position != null) { if (addedPosition != null) if (position.getOffset() <= addedPosition.getOffset()) { newPositions.add(position); position= null; } else { newPositions.add(addedPosition); addedPosition= null; } else { newPositions.add(position); position= null; } } else if (addedPosition != null) { newPositions.add(addedPosition); addedPosition= null; } } fPositions= newPositions; } } catch (BadPositionCategoryException e) { // Should not happen JavaManipulationPlugin.log(e); } catch (BadLocationException e) { // Should not happen JavaManipulationPlugin.log(e); } }
Returns true iff the positions contain the position.
Params:
  • positions – the positions, must be ordered by offset but may overlap
  • position – the position
Returns:true iff the positions contain the position
/** * Returns <code>true</code> iff the positions contain the position. * @param positions the positions, must be ordered by offset but may overlap * @param position the position * @return <code>true</code> iff the positions contain the position */
protected boolean contain(List<? extends Position> positions, Position position) { return indexOf(positions, position) != -1; }
Returns index of the position in the positions, -1 if not found.
Params:
  • positions – the positions, must be ordered by offset but may overlap
  • position – the position
Returns:the index
/** * Returns index of the position in the positions, <code>-1</code> if not found. * @param positions the positions, must be ordered by offset but may overlap * @param position the position * @return the index */
protected int indexOf(List<? extends Position> positions, Position position) { int index= computeIndexAtOffset(positions, position.getOffset()); int size= positions.size(); while (index < size) { if (positions.get(index) == position) return index; index++; } return -1; }
Returns the index of the first position with an offset equal or greater than the given offset.
Params:
  • positions – the positions, must be ordered by offset and must not overlap
  • offset – the offset
Returns:the index of the last position with an offset equal or greater than the given offset
/** * Returns the index of the first position with an offset equal or greater than the given offset. * * @param positions the positions, must be ordered by offset and must not overlap * @param offset the offset * @return the index of the last position with an offset equal or greater than the given offset */
protected int computeIndexAtOffset(List<? extends Position> positions, int offset) { int i= -1; int j= positions.size(); while (j - i > 1) { int k= (i + j) >> 1; Position position= positions.get(k); if (position.getOffset() >= offset) j= k; else i= k; } return j; }
Returns:Returns true iff the current reconcile is canceled.

NOTE: Also called from background thread.

/** * @return Returns <code>true</code> iff the current reconcile is canceled. * <p> * NOTE: Also called from background thread. * </p> */
public boolean isCanceled() { return fIsCanceled; }
Set whether or not the current reconcile is canceled.
Params:
  • isCanceled – true iff the current reconcile is canceled
/** * Set whether or not the current reconcile is canceled. * * @param isCanceled <code>true</code> iff the current reconcile is canceled */
public void setCanceled(boolean isCanceled) { fIsCanceled = isCanceled; }
Returns:The semantic reconciler position's category.
/** * @return The semantic reconciler position's category. */
public String getPositionCategory() { return toString(); } }