/**
* This file Copyright (c) 2005-2008 Aptana, Inc. This program is
* dual-licensed under both the Aptana Public License and the GNU General
* Public license. You may elect to use one or the other of these licenses.
*
* This program is distributed in the hope that it will be useful, but
* AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
* NONINFRINGEMENT. Redistribution, except as permitted by whichever of
* the GPL or APL you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or modify this
* program under the terms of the GNU General Public License,
* Version 3, as published by the Free Software Foundation. You should
* have received a copy of the GNU General Public License, Version 3 along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Aptana provides a special exception to allow redistribution of this file
* with certain other free and open source software ("FOSS") code and certain additional terms
* pursuant to Section 7 of the GPL. You may view the exception and these
* terms on the web at http://www.aptana.com/legal/gpl/.
*
* 2. For the Aptana Public License (APL), this program and the
* accompanying materials are made available under the terms of the APL
* v1.0 which accompanies this distribution, and is available at
* http://www.aptana.com/legal/apl/.
*
* You may view the GPL, Aptana's exception and additional terms, and the
* APL in the file titled license.html at the root of the corresponding
* plugin containing this source file.
*
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.ide.lexer;
/**
* @author Kevin Lindsey
*/
public class LexemeList
{
private static final Lexeme[] NO_LEXEMES = new Lexeme[0];
private transient Lexeme[] _lexemes;
private int _size;
private Range _affectedRegion;
/**
* Create a new instance of LexemeList
*/
public LexemeList()
{
this._lexemes = new Lexeme[128];
this._affectedRegion = new Range();
}
/**
* Create a new instance of LexemeList
*/
public LexemeList(Lexeme[] lexemes)
{
this.setContents(lexemes);
}
/**
* add
*
* @param index
* @param lexeme
*/
private void add(int index, Lexeme lexeme)
{
// NOTE: not range checking nor checking for nulls since
// we can only call this method internally. We assume we
// know what we're doing
int currentLength = this._lexemes.length;
int size = this._size + 1;
// see if the size we need is within our current buffer size
if (size > currentLength)
{
// it's not, add about 50% to our current buffer size
int newLength = (currentLength * 3) / 2 + 1;
// create a new empty list
Lexeme[] newList = new Lexeme[newLength];
// move the current contents to our new list
System.arraycopy(this._lexemes, 0, newList, 0, this._size);
// set out current list to the new list
this._lexemes = newList;
}
// shift the contents over by one leaving a hole where the new lexeme will go
System.arraycopy(this._lexemes, index, this._lexemes, index + 1, this._size - index);
// place the lexeme into the hole
this._lexemes[index] = lexeme;
// update the current size
this._size++;
}
/**
* Add a lexeme to this list. If a lexeme already exists within the range
* of the lexeme being added, then it is assume the lexeme is the same and
* it will not be added to the list.
*
* When the lexeme is added to the list, items following the newly added
* lexeme are removed if they overlap the new lexeme's range.
*
* @param lexeme
* The lexeme to add to the end of this list
*/
synchronized public void add(Lexeme lexeme)
{
if (lexeme == null || lexeme.offset < 0)
{
throw new IllegalArgumentException(Messages.LexemeList_Lexeme_Must_Be_Defined);
}
int lexemeIndex = this.getLexemeIndex(lexeme.offset);
// only process if we don't have the specified lexeme in our list already
if (lexemeIndex < 0)
{
int insertIndex = -(lexemeIndex + 1);
// insert into our list
this.add(insertIndex, lexeme);
// update our affected region
this._affectedRegion.includeInRange(lexeme);
// clear out any following lexemes that have been incorporated into this new lexeme
int followingIndex = insertIndex + 1;
while (followingIndex < this._size)
{
if (this._lexemes[followingIndex].isOverlapping(lexeme))
{
// include removed lexeme in affected region
this._affectedRegion.includeInRange(this._lexemes[followingIndex]);
// remove lexeme
this.remove(followingIndex);
}
else
{
break;
}
}
}
}
/**
* Clear this lexeme list
*/
synchronized public void clear()
{
// clear our lexeme array list
for (int i = 0; i < this._size; i++)
{
this._lexemes[i] = null;
}
// reset our size
this._size = 0;
// clear affected range
this._affectedRegion.clear();
}
/**
* Clone a range of lexemes from this list and return an array of those elements.
* Note that if a lexeme throws a CloneNotSupportedException, then that array
* element will have a reference to the lexeme as if a copyRange had been called.
*
* @param startingIndex
* The starting offset of the range to copy
* @param endingIndex
* The ending offset of the range to copy. The item at this index is included in the result
* @return Returns an array of the specified elements. An empty array is returned if the range is invalid for this
* list
*/
synchronized public Lexeme[] cloneRange(int startingIndex, int endingIndex)
{
Lexeme[] result = NO_LEXEMES;
if
(
0 <= startingIndex && startingIndex < this._size
&& 0 <= endingIndex && endingIndex < this._size
&& startingIndex <= endingIndex
)
{
int size = endingIndex - startingIndex + 1;
result = new Lexeme[size];
for (int i = startingIndex; i <= endingIndex; i++)
{
Lexeme lexeme = this._lexemes[i];
try
{
result[i - startingIndex] = (Lexeme) lexeme.clone();
}
catch (Exception e)
{
result[i - startingIndex] = lexeme;
}
}
}
return result;
}
/**
* Copy a range of lexemes from this list and return an array of those elements.
*
*
* @param startingIndex
* The starting offset of the range to copy
* @param endingIndex
* The ending offset of the range to copy. The item at this index is included in the result
* @return Returns an array of the specified elements. An empty array is returned if the range is invalid for this
* list
*/
synchronized public Lexeme[] copyRange(int startingIndex, int endingIndex)
{
Lexeme[] result = NO_LEXEMES;
if
(
0 <= startingIndex && startingIndex < this._size
&& 0 <= endingIndex && endingIndex < this._size
&& startingIndex <= endingIndex
)
{
int size = endingIndex - startingIndex + 1;
result = new Lexeme[size];
System.arraycopy(this._lexemes, startingIndex, result, 0, size);
}
return result;
}
/**
* Get a lexeme at the specified index. This method will return null if the index
* is not within the range of this lexeme list
*
* @param index
* The index to retrieve
* @return The lexeme at the specified index
*/
synchronized public Lexeme get(int index)
{
Lexeme result = null;
if (0 <= index && index < this._size)
{
result = this._lexemes[index];
}
return result;
}
/**
* Get the range of offsets that have been affected in this lexeme list
*
* @return Returns the offsets that have been affected in this list
*/
synchronized public Range getAffectedRegion()
{
return this._affectedRegion;
}
/**
* Gets the lexeme at the specified offset. If it is a whitespace character it will return the next (higher) lexeme
* if one exists. If not found it will return null.
*
* @param offset
* @return Returns the lexeme at the given offset or the lexeme immediately following the offset if none exists at
* the given offset
*/
synchronized public Lexeme getCeilingLexeme(int offset)
{
int index = this.getLexemeCeilingIndex(offset);
Lexeme result = null;
if (index >= 0)
{
result = this._lexemes[index];
}
return result;
}
/**
* Gets the lexeme at the specified offset. If it is a whitespace character it will return the previous (lower)
* lexeme if one exists. If not found it will return null.
*
* @param offset
* @return Returns the lexeme at the given offset or the lexeme immediately preceding the offset if none exists at
* the given offset
*/
synchronized public Lexeme getFloorLexeme(int offset)
{
int index = this.getLexemeFloorIndex(offset);
Lexeme result = null;
if (index >= 0)
{
result = this._lexemes[index];
}
return result;
}
/**
* Get the index of the lexeme at the specified offset. If it is a whitespace character it will return the next
* (higher) lexeme if one exists. If not found it will return -1.
*
* @param offset
* @return Returns the lexeme at the given offset or the lexeme immediately following the offset if none exists at
* the given offset
*/
synchronized public int getLexemeCeilingIndex(int offset)
{
int length = this._size;
int result = -1;
if (length > 0)
{
// find index in our collection
result = this.getLexemeIndex(offset);
// see if we're in between lexemes
if (result < 0)
{
// we are in between lexemes, so find the lexeme index to our right
result = -(result + 1);
// make sure we're in a valid range
if (result >= length)
{
// we're past the end of our list, so return -1
result = -1;
}
}
}
return result;
}
/**
* Get the index of the lexeme at the specified offset. If it is a whitespace character it will return the previous
* (lower) lexeme if one exists. If not found it will return -1.
*
* @param offset
* @return Returns the lexeme at the given offset or the lexeme immediately preceding the offset if none exists at
* the given offset
*/
synchronized public int getLexemeFloorIndex(int offset)
{
int result = -1;
if (this._size > 0)
{
// find index in our collection
result = this.getLexemeIndex(offset);
// see if we're in between lexemes
if (result < 0)
{
// we are in between lexemes, so find the lexeme index to our left
result = -(result + 1) - 1;
// make sure we're in a valid range
if (result < 0)
{
// we're before the start of our list, so return -1
result = -1;
}
}
}
return result;
}
/**
* Get the index of the lexeme at the specified offset
*
* @param offset
* @return Returns the lexeme at the given offset. Returns null if no lexeme is at the given offset.
*/
synchronized public Lexeme getLexemeFromOffset(int offset)
{
int index = this.getLexemeIndex(offset);
Lexeme result = null;
if ( 0 <= index && index < this._size)
{
result = this._lexemes[index];
}
return result;
}
/**
* Get the index of the lexeme at the specified offset
*
* @param offset
* @return Returns the index of the lexeme at the given offset. A negative value will be returned if there is no
* lexeme at the given offset
*/
synchronized public int getLexemeIndex(int offset)
{
int low = 0;
int high = this._size - 1;
while (low <= high)
{
int mid = (low + high) >>> 1;
Lexeme candidate = this._lexemes[mid];
if (offset < candidate.offset)
{
high = mid - 1;
}
else if (candidate.offset + candidate.length <= offset)
{
low = mid + 1;
}
else
{
return mid;
}
}
return -(low + 1);
}
/**
* Get the index of the specified lexeme. Returns -1 if the lexeme list is not
* in this list
*
* @param lexeme
* @return Returns the index of the specified lexeme
*/
synchronized public int getLexemeIndex(Lexeme lexeme)
{
int result = -1;
if (lexeme != null)
{
int candidate = this.getLexemeIndex(lexeme.offset);
if (candidate >= 0 && this._lexemes[candidate] == lexeme)
{
result = candidate;
}
}
return result;
}
/**
* Remove the specified index from our list of lexemes
*
* @param index
* The index to remove from this list
*/
synchronized public void remove(int index)
{
if (0 <= index && index < this._size)
{
int remainder = this._size - index - 1;
// if we have content after the index being remove, then shift that over one slot
if (remainder > 0)
{
System.arraycopy(this._lexemes, index + 1, this._lexemes, index, remainder);
}
// reduce our buffer size
this._size--;
// free up the last reference that is no longer part of the active region
this._lexemes[this._size] = null;
}
}
/**
* Remove the specified range of lexemes from our list
*
* @param startingIndex
* The starting index to remove
* @param endingIndex
* The ending index to remove
*/
synchronized public void remove(int startingIndex, int endingIndex)
{
if
(
0 <= startingIndex && startingIndex < this._size
&& 0 <= endingIndex && endingIndex < this._size
&& startingIndex <= endingIndex
)
{
for (int i = startingIndex; i <= endingIndex; i++)
{
// NOTE: Always remove the current starting index since lexemes
// shift left after each remove
this.remove(startingIndex);
}
}
}
/**
* Remove the specified lexeme from our list
*
* @param lexeme
* The lexeme to remove from this list
*/
synchronized public void remove(Lexeme lexeme)
{
if (lexeme != null)
{
int index = this.getLexemeIndex(lexeme.offset);
if (index >= 0 && this._lexemes[index] == lexeme)
{
this.remove(index);
}
}
}
/**
* Remove the specified range of lexemes from our list
*
* @param startingLexeme
* The starting lexeme of the range to remove from this list
* @param endingLexeme
* The ending lexeme of the range to remove form this list
*/
synchronized public void remove(Lexeme startingLexeme, Lexeme endingLexeme)
{
if (startingLexeme != null && endingLexeme != null)
{
int startingIndex = this.getLexemeIndex(startingLexeme.offset);
int endingIndex = this.getLexemeIndex(endingLexeme.offset);
if
( startingIndex >= 0
&& endingIndex >= 0
&& startingIndex <= endingIndex
&& startingLexeme == this._lexemes[startingIndex]
&& endingLexeme == this._lexemes[endingIndex]
)
{
// NOTE: Always remove the current starting index since lexemes
// shift left after each remove
for (int i = startingIndex; i <= endingIndex; i++)
{
this.remove(startingIndex);
}
}
}
}
/**
* Shift the offset of all lexemes beginning with the specified index
*
* @param startingIndex
* The beginning lexeme to shift
* @param offsetDelta
* The amount by which to shift each lexeme's offset
*/
synchronized public void shiftLexemeOffsets(int startingIndex, int offsetDelta)
{
if (0 <= startingIndex)
{
for (int i = startingIndex; i < this._size; i++)
{
this._lexemes[i].adjustOffset(offsetDelta);
}
}
}
/**
* Return the size of this list
*
* @return The list size
*/
synchronized public int size()
{
return this._size;
}
/**
* converts lexeme list to plain array
* @return array
*/
synchronized public Lexeme[] toArray()
{
Lexeme[] result = NO_LEXEMES;
if (this._size > 0)
{
result = this.copyRange(0, this._size - 1);
}
return result;
}
/**
* Allows to initialize lexeme list from array of lexemes
* @param array
*/
synchronized public void setContents(Lexeme[] lexemes)
{
if (lexemes == null)
{
throw new IllegalArgumentException(Messages.LexemeList_Lexeme_Must_Be_Defined);
}
for (Lexeme lexeme : lexemes)
{
if (lexeme == null)
{
throw new IllegalArgumentException(Messages.LexemeList_Lexeme_Must_Be_Defined);
}
}
this._lexemes = lexemes;
this._size = lexemes.length;
this._affectedRegion = new Range();
}
}