/* * Copyright 2008, 2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package org.visage.runtime.sequence; import org.visage.runtime.TypeInfo; /** * Sequence implementation class that stores sequence elements in an array. * * The {@code ArraySequence} can be in one of two modes: * <ul> * <li>In unshared/writabable mode there is only a single "owner" of the * sequence, so modifying the sequence in-place is OK. * For example inserting into a sequence variable can be implemented * by changing the sequence object itself. * <li>In shared/read-only mode there is (or at least may be) multiple * references to this sequence, so modifications are not allowed. * For example inserting into a sequence variable has to be implemented * by assigning a modified sequence. * </ul> * * An {@code ArraySequence} starts out unshared, and can be set to shared. * The compiler inserts calls to {@code incrementShared} when "reading" a sequence * variable in a way that may cause it to be shared. Getting a single * item from the sequence does not require {@code shared} to be set. * We never go back from shared to unshared. * * We use a <a href="http://en.wikipedia.org/wiki/Buffer_gap">gap buffer</a> * as in the Emacs text editor and {@code javax.swing.text.GapContent}. * This provies efficient memory usage and locality. It should perform * well in most uses. The exception is if there are lots of insertions and * deletions at widely separated offsets, since that requires "moving" the * gap to the insertion or deletion point. * * @author Brian Goetz * @author Per Bothner */ public abstract class ArraySequence<T> extends AbstractSequence<T> { int gapStart, gapEnd; protected final static int DEFAULT_SIZE = 16; /* A simple (conservative) reference count. * If sharing==1, there are no "other" references, so operations like * insertion and concatenation can re-use this object, modifying the * object in-place. This leads to major performance improvements - but * the compiler and library must be careful to set sharing > 1 whenever * sharing happens or can happen. This is done with {@code #incrementSharing}. * The count is "sticky" once it wraps around. Of course that's not going * to happen as long as we use an int, but in future if it saves space * we might reduce the number of bits used for {@code sharing}. */ private int sharing; protected ArraySequence(TypeInfo<T> ti) { super(ti); } @Override public void incrementSharing() { int sh = sharing + 1; if (sh >= 0) sharing = sh; } @Override public void decrementSharing() { int sh = sharing; if (sh > 0) sharing = sh - 1; } public void setMaxShared() { sharing = (-1) >>> 1; } public boolean isShared() { return sharing > 1; } protected abstract Object getRawArray(); protected abstract Object newRawArray(int size); protected abstract void setRawArray(Object array); protected abstract int getRawArrayLength(); protected abstract T getRawArrayElementAsObject(int index); public ArraySequence<T> makeNew(int initializeSize) { return new ObjectArraySequence(initializeSize, getElementType()); } /** Used by triggers to copy the "old value" of a sequence that got replace. * Assumes we're preserving the replaced elements in the buffer gap. * @param startPos * @param endPos * @return */ protected abstract ArraySequence extractOldValue(int startPos, int endPos); public abstract void add(Sequence<? extends T> elements); public void addFromArray(Object data, int loIndex, int hiIndex) { int length = hiIndex - loIndex; int size = size(); gapReserve(size, length); System.arraycopy(data, loIndex, getRawArray(), size, length); gapStart += length; } protected void shiftGap(int newGapStart) { int delta = newGapStart - gapStart; Object array = getRawArray(); if (delta > 0) System.arraycopy(array, gapEnd, array, gapStart, delta); else if (delta < 0) System.arraycopy(array, newGapStart, array, gapEnd + delta, - delta); gapEnd += delta; gapStart = newGapStart; /* * When shifting gap, we need to clear unused portions of the "new" gap * with nulls. Without that ObjectArraySequences will leak objects. * See VSGC-3337 (memory leak in bound for). * * For large gaps, we don't want to null every slot in new gap. We can * ignore slots overlapping with "old" gap. In the line diagrams below, * NGS is New Gap Start, NGE is New Gap End, OGS is Old Gap Start and * OGE is Old Gap End. To clear with null slots, we re-use "clearOldValues" * that is used to clean the preserved old elements for triggers. */ int gapSize = gapEnd - gapStart; if (delta > 0) { if (gapSize < delta) { // // |<--gapSize-->|--------|<--gapSize-->| // OGS OGE NGS NGE // |<------ delta ------->| // // need to clear the entire new gap clearOldValues(gapSize); } else { // // |<--delta-->|--------|<--delta-->| // OGS NGS OGE NGE // |<------ gapSize---->| // // need to clear only the "delta" between NGE and OGE clearOldValues(delta); } } else { // negative delta if (gapSize < -delta) { // // |<--gapSize-->|--------|<--gapSize-->| // NGS NGE OGS OGE // |<------ delta ------->| // // need to clear the entire new gap clearOldValues(gapSize); } else { // // |<--delta-->|--------|<--delta-->| // NGS OGS NGE OGE // |<------ gapSize---->| // // need to clear only the "delta" between NGS and OGS // do it by temporarily moving NGE to where OGS is // and clear "delta" values from there. int tmpGapEnd = gapEnd; gapEnd = gapStart - delta; clearOldValues(-delta); gapEnd = tmpGapEnd; } } } /** Make sure gap is at least 'needed' elements long. */ protected void gapReserve(int where, int needed) { if (needed > gapEnd - gapStart) { int oldLength = getRawArrayLength(); int newLength = oldLength < 16 ? 16 : 2 * oldLength; int minLength = oldLength - (gapEnd - gapStart) + needed; if (newLength < minLength) newLength = minLength; Object newArray = newRawArray(newLength); int oldGapSize = gapEnd - gapStart; int size = oldLength-oldGapSize; int newGapEnd = newLength - size + where; int gapDelta = gapStart - where; int startLength; Object oldArray = getRawArray(); if (gapDelta >= 0) { startLength = where; int endLength = oldLength - gapEnd; System.arraycopy(oldArray, gapEnd, newArray, newLength - endLength, endLength); if (gapDelta > 0) System.arraycopy(oldArray, where, newArray, newGapEnd, gapDelta); } else { startLength = gapStart; int endLength = newLength - newGapEnd; System.arraycopy(oldArray, oldLength-endLength, newArray, newGapEnd, endLength); System.arraycopy(oldArray, gapEnd, newArray, gapStart, -gapDelta); } System.arraycopy(oldArray, 0, newArray, 0, startLength); setRawArray(newArray); gapStart = where; gapEnd = newGapEnd; } else if (where != gapStart) shiftGap(where); } @Override public int size() { return getRawArrayLength() - (gapEnd - gapStart); } @Override public T get(int position) { if (position >= gapStart) position += gapEnd - gapStart; if (position < 0 || position >= getRawArrayLength()) return getDefaultValue(); else return getRawArrayElementAsObject(position); } /** Replace a slice of this array. * @param startPos Starting position of the slice, inclusive, may be 0..size. * @param endPos Ending position of the slice, exclusive, may be startPos..size. * @param value The replacement source * @param sourceStart Starting position in source. * @param sourceEnd Ending position in source. * @param hasTrigger If true, keep removed source in the gap. */ public void replace(int startPos, int endPos, Sequence<? extends T> source, int sourceStart, int sourceEnd, boolean hasTrigger) { int size = size(); int nsize = sourceEnd-sourceStart; int delta = nsize - (endPos-startPos); gapReserve(startPos, hasTrigger ? nsize : delta >= 0 ? delta : 0); if (nsize != 0) replaceRaw(source, sourceStart, nsize, startPos); startPos += nsize; gapStart=startPos; gapEnd = startPos + (getRawArrayLength() - size - delta); } protected abstract void replaceRaw(Sequence<? extends T> values, int sourceOffset, int length, int startPos); public void clearOldValues (int oldLength) { } /* DEBUGGING code: int id=++counter; static int counter; public String toXString() { StringBuilder sbuf = new StringBuilder(); int alen = getRawArrayLength(); int sz = size(); sbuf.append("[#"+id+"(sharing:"+sharing+" gap:"+gapStart+"..<"+gapEnd+" alen:"+alen+" size:"+sz+")"); if (false) { for (int i = 0; i < sz; i++) sbuf.append(" "+get(i)); } else { for (int i = 0; ; i++) { if (gapStart == i) sbuf.append(" [gap: "); if (gapEnd == i) sbuf.append(" ] "); if (i >= alen) break; sbuf.append(" "+getRawArrayElementAsObject(i)); } } sbuf.append(']'); return sbuf.toString(); } public String toString() { return toXString(); } */ }