blob: 828cf78e6175ab0b6b1c61bdcd1267c6ef08cefe [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.openide.util.lookup;
import org.openide.util.Lookup;
import java.util.*;
import java.util.logging.Level;
import org.openide.util.lookup.AbstractLookup.Pair;
/** ArrayStorage of Pairs from AbstractLookup.
* @author Jaroslav Tulach
*/
final class ArrayStorage extends Object
implements AbstractLookup.Storage<ArrayStorage.Transaction> {
/** list of items */
private Object content;
/** linked list of refernces to results */
private transient AbstractLookup.ReferenceToResult<?> results;
/** Constructor
*/
public ArrayStorage() {
this(/*default threshold*/11);
}
/** Constructs new ArrayStorage */
public ArrayStorage(Integer treshhold) {
this.content = treshhold;
}
/** Adds an item into the tree.
* @param item to add
* @return true if the Item has been added for the first time or false if some other
* item equal to this one already existed in the lookup
*/
public boolean add(AbstractLookup.Pair<?> item, Transaction changed) {
Object[] arr = changed.current;
if (changed.arr == null) {
// just simple add of one item
for (int i = 0; i < arr.length; i++) {
if (arr[i] == null) {
arr[i] = item;
changed.add(item);
return true;
}
if (arr[i].equals(item)) {
// reassign the item number
item.setIndex(null, ((AbstractLookup.Pair) arr[i]).getIndex());
// already there, but update it
arr[i] = item;
return false;
}
}
// cannot happen as the beginTransaction ensured we can finish
// correctly
throw new IllegalStateException(
"current objects: " + Arrays.toString(changed.current) +
"\nnew objects: " + Arrays.toString(changed.arr) +
"\ncnt: " + changed.cnt
);
} else {
// doing remainAll after that, let Transaction hold the new array
int newIndex = changed.addPair(item);
for (int i = 0; i < arr.length; i++) {
if (arr[i] == null) {
changed.add(item);
return true;
}
if (arr[i].equals(item)) {
// already there
if (i != newIndex) {
// change in index
changed.add(item);
return false;
} else {
// no change
return false;
}
}
}
// if not found in the original array
changed.add(item);
return true;
}
}
/** Removes an item.
*/
public void remove(AbstractLookup.Pair item, Transaction changed) {
Object[] arr = changed.current;
if (arr == null) {
return;
}
int found = -1;
for (int i = 0; i < arr.length;) {
if (arr[i] == null) {
// end of task
return;
}
if ((found == -1) && arr[i].equals(item)) {
// already there
Pair<?> p = (Pair<?>)arr[i];
p.setIndex(null, -1);
changed.add(p);
found = i;
}
i++;
if (found != -1) {
if (i < arr.length && !(arr[i] instanceof Integer)) {
// moving the array
arr[i - 1] = arr[i];
} else {
arr[i - 1] = null;
}
}
}
}
/** Removes all items that are not present in the provided collection.
* @param retain Pair -> AbstractLookup.Info map
* @param notify set of Classes that has possibly changed
*/
public void retainAll(Map retain, Transaction changed) {
Object[] arr = changed.current;
for (int from = 0; from < arr.length; from++) {
if (!(arr[from] instanceof AbstractLookup.Pair)) {
// end of content
break;
}
AbstractLookup.Pair p = (AbstractLookup.Pair) arr[from];
AbstractLookup.Info info = (AbstractLookup.Info) retain.get(p);
if (info == null) {
// was removed
/*
if (info != null) {
if (info.index < arr.length) {
newArr[info.index] = p;
}
if (p.getIndex() != info.index) {
p.setIndex (null, info.index);
changed.add (p);
}
} else {
// removed
*/
changed.add(p);
}
}
}
/** Queries for instances of given class.
* @param clazz the class to check
* @return enumeration of Item
* @see #unsorted
*/
public <T> Enumeration<Pair<T>> lookup(final Class<T> clazz) {
if (content instanceof Object[]) {
final Enumeration<Object> all = InheritanceTree.arrayEn((Object[]) content);
class JustPairs implements Enumeration<Pair<T>> {
private Pair<T> next;
@SuppressWarnings("unchecked")
private Pair<T> findNext() {
for (;;) {
if (next != null) {
return next;
}
if (!all.hasMoreElements()) {
return null;
}
Object o = all.nextElement();
boolean ok;
if (o instanceof AbstractLookup.Pair) {
ok = (clazz == null) || ((AbstractLookup.Pair<?>) o).instanceOf(clazz);
} else {
ok = false;
}
next = ok ? (Pair<T>) o : null;
}
}
public boolean hasMoreElements() {
return findNext() != null;
}
public Pair<T> nextElement() {
Pair<T> r = findNext();
if (r == null) {
throw new NoSuchElementException();
}
next = null;
return r;
}
} // end of JustPairs
return new JustPairs();
} else {
return InheritanceTree.emptyEn();
}
}
@Override
public <T> Lookup.Result<T> findResult(Lookup.Template<T> t) {
AbstractLookup.ReferenceIterator it = new AbstractLookup.ReferenceIterator(this.results);
while (it.next()) {
if (it.current().template.equals(t)) {
return (Lookup.Result<T>) it.current().getResult();
}
}
return null;
}
/** Associates another result with this storage.
*/
public AbstractLookup.ReferenceToResult registerReferenceToResult(AbstractLookup.ReferenceToResult<?> newRef) {
AbstractLookup.ReferenceToResult prev = this.results;
this.results = newRef;
return prev;
}
/** Cleanup the references
*/
@Override
public AbstractLookup.ReferenceToResult cleanUpResult(Lookup.Template<?> templ) {
long now = System.currentTimeMillis();
AbstractLookup.ReferenceIterator it = new AbstractLookup.ReferenceIterator(this.results);
int cnt = 0;
while (it.next()) {
cnt++;
}
long took = System.currentTimeMillis() - now;
if (took > 500 && AbstractLookup.LOG != null) {
AbstractLookup.LOG.log(Level.WARNING,
"Too long ({0} ms and {1} references) cleanUpResult for {2}",
new Object[]{took, cnt, templ != null ? templ.getType() : null}
);
}
return this.results = it.first();
}
/** We use a hash set of all modified Pair to handle the transaction */
public Transaction beginTransaction(int ensure) {
return new Transaction(ensure, content);
}
/** Extract all results.
*/
public void endTransaction(Transaction changed, Set<AbstractLookup.R> modified) {
AbstractLookup.ReferenceIterator it = new AbstractLookup.ReferenceIterator(this.results);
if (changed.arr == null) {
// either add or remove, only check the content of check HashSet
while (it.next()) {
AbstractLookup.ReferenceToResult ref = it.current();
Iterator<Pair<?>> pairs = changed.iterator();
while (pairs.hasNext()) {
AbstractLookup.Pair p = (AbstractLookup.Pair) pairs.next();
if (AbstractLookup.matches(ref.template, p, true)) {
modified.add(ref.getResult());
}
}
}
} else {
// do full check of changes
while (it.next()) {
AbstractLookup.ReferenceToResult ref = it.current();
int oldIndex = -1;
int newIndex = -1;
for (;;) {
oldIndex = findMatching(ref.template, changed.current, oldIndex);
newIndex = findMatching(ref.template, changed.arr, newIndex);
if ((oldIndex == -1) && (newIndex == -1)) {
break;
}
if (
(oldIndex == -1) || (newIndex == -1) ||
!changed.current[oldIndex].equals(changed.arr[newIndex])
) {
modified.add(ref.getResult());
break;
}
}
}
}
this.results = it.first();
this.content = changed.newContent(this.content);
}
private static int findMatching(Lookup.Template t, Object[] arr, int from) {
while (++from < arr.length) {
if (arr[from] instanceof AbstractLookup.Pair) {
if (AbstractLookup.matches(t, (AbstractLookup.Pair) arr[from], true)) {
return from;
}
}
}
return -1;
}
/** HashSet with additional field for new array which is callocated
* in case we are doing replace to hold all new items.
*/
static final class Transaction extends HashSet<Pair<?>> {
/** array with current objects */
public final Object[] current;
/** array with new objects */
public final Object[] arr;
/** number of objects in the array */
private int cnt;
public Transaction(int ensure, Object currentContent) {
Integer trashold;
Object[] _arr;
if (currentContent instanceof Integer) {
trashold = (Integer) currentContent;
_arr = null;
} else {
_arr = (Object[]) currentContent;
if (_arr[_arr.length - 1] instanceof Integer) {
trashold = (Integer) _arr[_arr.length - 1];
} else {
// nowhere to grow we have reached the limit
trashold = null;
}
}
int maxSize = (trashold == null) ? _arr.length : trashold.intValue();
if (ensure > maxSize) {
throw new UnsupportedOperationException();
}
if (ensure == -1) {
// remove => it is ok
this.current = currentContent instanceof Integer ? null : (Object[]) currentContent;
this.arr = null;
return;
}
if (ensure == -2) {
// adding one
if (_arr == null) {
// first time add, let's allocate the array
_arr = new Object[2];
_arr[1] = trashold;
} else {
if (_arr[_arr.length - 1] instanceof AbstractLookup.Pair) {
// we are full
throw new UnsupportedOperationException();
} else {
// ensure we have allocated enough space
if (_arr.length < 2 || _arr[_arr.length - 2] != null) {
// double the array
int newSize = (_arr.length - 1) * 2;
if (newSize <= 1) {
newSize = 2;
}
if (newSize > maxSize) {
newSize = maxSize;
if (newSize <= _arr.length) {
// no space to get in
throw new UnsupportedOperationException();
}
_arr = new Object[newSize];
} else {
// still a lot of space
_arr = new Object[newSize + 1];
_arr[newSize] = trashold;
}
// copy content of original array without the last Integer into
// the new one
System.arraycopy(currentContent, 0, _arr, 0, ((Object[]) currentContent).length - 1);
}
}
}
this.current = _arr;
this.arr = null;
} else {
// allocate array for complete replacement
if (ensure == maxSize) {
this.arr = new Object[ensure];
} else {
this.arr = new Object[ensure + 1];
this.arr[ensure] = trashold;
}
this.current = (currentContent instanceof Object[]) ? (Object[]) currentContent : new Object[0];
}
}
public int addPair(AbstractLookup.Pair<?> p) {
p.setIndex(null, cnt);
arr[cnt++] = p;
return p.getIndex();
}
public Object newContent(Object prev) {
if (arr == null) {
if (current == null) {
return prev;
} else {
return current;
}
} else {
return arr;
}
}
}
// end of Transaction
}