/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * 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: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.ide.api.editor.annotation; import org.eclipse.che.ide.api.editor.document.Document; import org.eclipse.che.ide.api.editor.text.BadLocationException; import org.eclipse.che.ide.api.editor.text.BadPositionCategoryException; import org.eclipse.che.ide.api.editor.text.Position; import org.eclipse.che.ide.runtime.Assert; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * */ final class PositionHolder { private final Document document; private List<Position> positions = new ArrayList<>(); public PositionHolder(Document document) { this.document = document; } public void addPosition(Position position) throws BadLocationException { if ((0 > position.offset) || (0 > position.length) || (position.offset + position.length > document.getContentsCharCount())) throw new BadLocationException(); positions.add(computeIndexInPositionList(positions, position.offset, true), position); } /** * Computes the index in the list of positions at which a position with the given * position would be inserted. The position to insert is supposed to become the first * in this list of all positions with the same position. * * @param positions the list in which the index is computed * @param offset the offset for which the index is computed * @param orderedByOffset <code>true</code> if ordered by offset, false if ordered by end position * @return the computed index * @since 3.4 */ protected int computeIndexInPositionList(List<? extends Position> positions, int offset, boolean orderedByOffset) { if (positions.size() == 0) return 0; int left = 0; int right = positions.size() - 1; int mid = 0; Position p = null; while (left < right) { mid = (left + right) / 2; p = positions.get(mid); int pOffset = getOffset(orderedByOffset, p); if (offset < pOffset) { if (left == mid) right = left; else right = mid - 1; } else if (offset > pOffset) { if (right == mid) left = right; else left = mid + 1; } else if (offset == pOffset) { left = right = mid; } } int pos = left; p = positions.get(pos); int pPosition = getOffset(orderedByOffset, p); if (offset > pPosition) { // append to the end pos++; } else { // entry will become the first of all entries with the same offset do { --pos; if (pos < 0) break; p = positions.get(pos); pPosition = getOffset(orderedByOffset, p); } while (offset == pPosition); ++pos; } Assert.isTrue(0 <= pos && pos <= positions.size()); return pos; } private int getOffset(boolean orderedByOffset, Position position) { if (orderedByOffset || position.getLength() == 0) return position.getOffset(); return position.getOffset() + position.getLength() - 1; } public List<Position> getPositions(int offset, int length, boolean canStartBefore, boolean canEndAfter) throws BadPositionCategoryException { if (canStartBefore && canEndAfter || (!canStartBefore && !canEndAfter)) { List<Position> documentPositions; if (canStartBefore && canEndAfter) { if (offset < getLength() / 2) { documentPositions = getStartingPositions(0, offset + length); } else { documentPositions = getEndingPositions(offset, getLength() - offset + 1); } } else { documentPositions = getStartingPositions(offset, length); } ArrayList<Position> list = new ArrayList<>(documentPositions.size()); Position region = new Position(offset, length); for (Iterator<Position> iterator = documentPositions.iterator(); iterator.hasNext(); ) { Position position = iterator.next(); if (isWithinRegion(region, position, canStartBefore, canEndAfter)) { list.add(position); } } return list; } else if (canStartBefore) { List<Position> list = getEndingPositions(offset, length); return list; } else { Assert.isLegal(canEndAfter && !canStartBefore); List<Position> list = getStartingPositions(offset, length); return list; } } private boolean isWithinRegion(Position region, Position position, boolean canStartBefore, boolean canEndAfter) { if (canStartBefore && canEndAfter) { return region.overlapsWith(position.getOffset(), position.getLength()); } else if (canStartBefore) { return region.includes(position.getOffset() + position.getLength() - 1); } else if (canEndAfter) { return region.includes(position.getOffset()); } else { int start = position.getOffset(); return region.includes(start) && region.includes(start + position.getLength() - 1); } } private List<Position> getStartingPositions(int offset, int length) throws BadPositionCategoryException { int indexStart = computeIndexInPositionList(positions, offset, true); int indexEnd = computeIndexInPositionList(positions, offset + length, true); return positions.subList(indexStart, indexEnd); } private List<Position> getEndingPositions(int offset, int length) throws BadPositionCategoryException { int indexStart = computeIndexInPositionList(positions, offset, false); int indexEnd = computeIndexInPositionList(positions, offset + length, false); return positions.subList(indexStart, indexEnd); } private int getLength() { return document.getContentsCharCount(); } }