/* * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.openapi.editor.ex.util; import com.intellij.openapi.diagnostic.Logger; import org.jetbrains.annotations.NotNull; /** * This class is a data structure specialized for working with the indexed segments, i.e. it holds numerous mappings like * {@code 'index <-> (start; end)'} and provides convenient way for working with them, e.g. find index by particular offset that * belongs to target {@code (start; end)} segment etc. * <p/> * Not thread-safe. */ public class SegmentArray { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.ex.util.SegmentArray"); protected int[] myStarts; protected int[] myEnds; protected int mySegmentCount; protected static final int INITIAL_SIZE = 64; protected SegmentArray() { myStarts = new int[INITIAL_SIZE]; myEnds = new int[INITIAL_SIZE]; } protected void setElementAt(int i, int startOffset, int endOffset) { if (startOffset < 0) { LOG.error("Invalid startOffset:" + startOffset); } if (endOffset < 0) { LOG.error("Invalid endOffset:" + endOffset); } if (i >= mySegmentCount) { mySegmentCount = i + 1; } myStarts = reallocateArray(myStarts, i); myStarts[i] = startOffset; myEnds = reallocateArray(myEnds, i); myEnds[i] = endOffset; } protected void replace(int startOffset, @NotNull SegmentArray data, int len) { System.arraycopy(data.myStarts, 0, myStarts, startOffset, len); System.arraycopy(data.myEnds, 0, myEnds, startOffset, len); } static int calcCapacity(int currentArraySize, int index) { if (currentArraySize == 0) { currentArraySize = 16; } else { currentArraySize += currentArraySize / 5; // avoid overflow } if (index >= currentArraySize) { currentArraySize = index + index / 5; // avoid overflow } return currentArraySize; } @NotNull private static int[] reallocateArray(@NotNull int[] array, int index) { if (index < array.length) return array; int[] newArray = new int[calcCapacity(array.length, index)]; System.arraycopy(array, 0, newArray, 0, array.length); return newArray; } protected int noSegmentsAvailable(int offset) { throw new IllegalStateException("no segments available. offset = " + offset); } protected int offsetOutOfRange(int offset, int lastValidOffset) { throw new IndexOutOfBoundsException("Wrong offset: " + offset + ". Should be in range: [0, " + lastValidOffset + "]"); } /** * @throws IllegalStateException if a gap between segments is detected, or if there are no segments and an index for a positive offset is * requested */ public final int findSegmentIndex(int offset) { if (mySegmentCount <= 0) { return offset == 0 ? 0 : noSegmentsAvailable(offset); } final int lastValidOffset = getLastValidOffset(); if (offset > lastValidOffset || offset < 0) { return offsetOutOfRange(offset, lastValidOffset); } int end = mySegmentCount - 1; if (offset == lastValidOffset) { return end; } int start = 0; while (start <= end) { int i = (start + end) >>> 1; if (offset < myStarts[i]) { end = i - 1; } else if (offset >= myEnds[i]) { start = i + 1; } else { return i; } } return segmentNotFound(offset, start); } protected int segmentNotFound(int offset, int start) { // This means that there is a gap at given offset if (offset < myStarts[start] || offset >= myEnds[start]) { throw new IllegalStateException("Gap at offset " + offset + " near segment " + start); } return start; } public int getLastValidOffset() { return mySegmentCount == 0 ? 0 : myEnds[mySegmentCount - 1]; } public final void changeSegmentLength(int startIndex, int change) { if (startIndex >= 0 && startIndex < mySegmentCount) { myEnds[startIndex] += change; } shiftSegments(startIndex + 1, change); } public final void shiftSegments(int startIndex, int shift) { for (int i = startIndex; i < mySegmentCount; i++) { myStarts[i] += shift; myEnds[i] += shift; if (myStarts[i] < 0 || myEnds[i] < 0) { LOG.error("Error shifting segments: myStarts[" + i + "] = " + myStarts[i] + ", myEnds[" + i + "] = " + myEnds[i]); } } } public void removeAll() { mySegmentCount = 0; } public void remove(int startIndex, int endIndex) { myStarts = remove(myStarts, startIndex, endIndex); myEnds = remove(myEnds, startIndex, endIndex); mySegmentCount -= endIndex - startIndex; } @NotNull protected int[] remove(@NotNull int[] array, int startIndex, int endIndex) { if (endIndex < mySegmentCount) { System.arraycopy(array, endIndex, array, startIndex, mySegmentCount - endIndex); } return array; } protected void insert(@NotNull SegmentArray segmentArray, int startIndex) { myStarts = insert(myStarts, segmentArray.myStarts, startIndex, segmentArray.getSegmentCount()); myEnds = insert(myEnds, segmentArray.myEnds, startIndex, segmentArray.getSegmentCount()); mySegmentCount += segmentArray.getSegmentCount(); } @NotNull protected int[] insert(@NotNull int[] array, @NotNull int[] insertArray, int startIndex, int insertLength) { int[] newArray = reallocateArray(array, mySegmentCount + insertLength); if (startIndex < mySegmentCount) { System.arraycopy(newArray, startIndex, newArray, startIndex + insertLength, mySegmentCount - startIndex); } System.arraycopy(insertArray, 0, newArray, startIndex, insertLength); return newArray; } public int getSegmentStart(int index) { if (index < 0 || index >= mySegmentCount) { throw new IndexOutOfBoundsException("Wrong line: " + index + ". Available lines count: " + mySegmentCount); } return myStarts[index]; } public int getSegmentEnd(int index) { if (index < 0 || index >= mySegmentCount) { throw new IndexOutOfBoundsException("Wrong line: " + index + ". Available lines count: " + mySegmentCount); } return myEnds[index]; } public int getSegmentCount() { return mySegmentCount; } }