// Copyright 2012 Google Inc. All Rights Reserved.
//
// 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.google.collide.shared.util;
import com.google.collide.json.shared.JsonArray;
import com.google.collide.shared.document.util.LineUtils;
import com.google.collide.shared.util.SortedList.Comparator;
import com.google.collide.shared.util.SortedList.OneWayComparator;
/**
* A map with a key of (line number, column) and an arbitrary value.
*
*/
public class SortedPositionMap<T> {
private class Entry {
private final int lineNumber;
private final int column;
private final T value;
private Entry(int lineNumber, int column, T value) {
this.lineNumber = lineNumber;
this.column = column;
this.value = value;
}
}
/**
* A class to be used as a cached one-way comparator instance.
*
* Methods on this class are NOT re-entrant (though I can't imagine a scenario
* where the execution would lead to re-entrancy.)
*/
private class Finder implements OneWayComparator<Entry> {
private int lineNumber;
private int column;
@Override
public int compareTo(Entry o) {
return LineUtils.comparePositions(lineNumber, column, o.lineNumber, o.column);
}
private Entry findEntry(int lineNumber, int column) {
this.lineNumber = lineNumber;
this.column = column;
return list.find(this);
}
private int findInsertionIndex(int lineNumber, int column) {
this.lineNumber = lineNumber;
this.column = column;
return list.findInsertionIndex(this);
}
}
private final Comparator<Entry> comparator = new Comparator<Entry>() {
@Override
public int compare(Entry a, Entry b) {
return LineUtils.comparePositions(a.lineNumber, a.column, b.lineNumber, b.column);
}
};
private final Finder finder = new Finder();
private final SortedList<Entry> list;
public SortedPositionMap() {
list = new SortedList<Entry>(comparator);
}
public T get(int lineNumber, int column) {
Entry entry = finder.findEntry(lineNumber, column);
return entry != null ? entry.value : null;
}
/**
* Puts the value at the given position, replacing any existing value (which
* will be returned).
*/
public T put(int lineNumber, int column, T value) {
Entry existingEntry = finder.findEntry(lineNumber, column);
if (existingEntry != null) {
list.remove(existingEntry);
}
list.add(new Entry(lineNumber, column, value));
return existingEntry != null ? existingEntry.value : null;
}
public void putAll(SortedPositionMap<T> positionToToken) {
for (int i = 0, n = positionToToken.list.size(); i < n; i++) {
Entry entry = positionToToken.list.get(i);
put(entry.lineNumber, entry.column, entry.value);
}
}
/**
* Removes the values in the given range (begin is inclusive, end is
* exclusive).
*/
public void removeRange(int beginLineNumber, int beginColumn, int endLineNumber, int endColumn) {
int index = finder.findInsertionIndex(beginLineNumber, beginColumn);
while (index < list.size()) {
Entry entry = list.get(index);
if (LineUtils.comparePositions(entry.lineNumber, entry.column, endLineNumber, endColumn)
>= 0) {
// This item is past the end, we're done!
return;
}
list.remove(index);
// No need to increment index since we just removed an item
}
}
public int size() {
return list.size();
}
public JsonArray<T> values() {
JsonArray<T> values = JsonCollections.createArray();
for (int i = 0, n = list.size(); i < n; i++) {
values.add(list.get(i).value);
}
return values;
}
}