/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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 Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2009 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.util;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
/**
* A list that associates a level (instance of <code>Integer</code>) with each element in the list.
*
* @author Thomas Morgner
*/
public class LevelList implements Cloneable
{
/**
* A static object array of size zero.
*/
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
/**
* A static object array of size zero.
*/
private static final Integer[] EMPTY_INTEGER_ARRAY = new Integer[0];
/**
* Constant for level zero.
*/
private static final Integer ZERO = new Integer(0);
/**
* A treeset to build the iterator.
*/
private transient TreeSet iteratorSetAsc;
/**
* A treeset to build the iterator.
*/
private transient TreeSet iteratorSetDesc;
/**
* A treeset to cache the level iterator.
*/
private HashMap iteratorCache;
/**
* A comparator for levels in descending order.
*/
private static final class DescendingComparator implements Comparator, Serializable
{
/**
* Default constructor.
*/
private DescendingComparator()
{
}
/**
* Compares its two arguments for order. Returns a negative integer, zero, or a positive integer as the first
* argument is less than, equal to, or greater than the second.<p>
*
* @param o1 the first object to be compared.
* @param o2 the second object to be compared.
* @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater
* than the second.
* @throws ClassCastException if the arguments' types prevent them from being compared by this Comparator.
*/
public int compare(final Object o1, final Object o2)
{
if ((o1 instanceof Comparable) == false)
{
throw new ClassCastException("Need comparable Elements");
}
if ((o2 instanceof Comparable) == false)
{
throw new ClassCastException("Need comparable Elements");
}
final Comparable c1 = (Comparable) o1;
final Comparable c2 = (Comparable) o2;
return -1 * c1.compareTo(c2);
}
}
/**
* An list that caches all elements for a certain level.
*/
private static final class ElementLevelList
{
/**
* The level list.
*/
private Object[] data;
/**
* Creates an iterator that provides access to all the elements in a list at the specified level.
*
* @param list the list (null not permitted).
* @param level the level.
*/
private ElementLevelList(final LevelList list, final int level)
{
if (list == null)
{
throw new NullPointerException();
}
final Object[] rawElements = list.getRawElements();
final Integer[] rawLevels = list.getRawLevels();
final ArrayList datalist = new ArrayList(rawElements.length);
for (int i = 0; i < rawElements.length; i++)
{
final Object iNext = rawElements[i];
final Integer iLevel = rawLevels[i];
if (iLevel.intValue() == level)
{
datalist.add(iNext);
}
}
data = datalist.toArray();
}
/**
* Returns the data for this level as object array.
*
* @return the data for this level as object array.
*/
protected Object[] getData()
{
return (Object[]) data.clone();
}
/**
* Returns the data for this level as object array. Behaves like ArrayList.toArray(..)
*
* @param target object array that should receive the contents
* @return the data for this level as object array.
*/
protected Object[] getData(Object[] target)
{
if (target == null)
{
target = new Object[data.length];
}
else if (target.length < data.length)
{
target = (Object[]) Array.newInstance(target.getClass().getComponentType(), data.length);
}
System.arraycopy(data, 0, target, 0, data.length);
if (target.length > data.length)
{
target[data.length] = null;
}
return target;
}
/**
* Returns the size if the list.
*
* @return the size.
*/
protected int size()
{
return data.length;
}
}
/**
* The elements.
*/
private ArrayList elements;
/**
* The levels.
*/
private ArrayList levels;
/**
* Creates a new list (initially empty).
*/
public LevelList()
{
this.elements = new ArrayList();
this.levels = new ArrayList();
this.iteratorCache = new HashMap();
}
/**
* Returns the number of elements in the list.
*
* @return the element count.
*/
public int size()
{
return elements.size();
}
/**
* Returns an iterator that iterates through the levels in ascending order.
*
* @return an iterator.
*/
public Iterator getLevelsAscending()
{
if (iteratorSetAsc == null)
{
iteratorSetAsc = new TreeSet();
final Integer[] ilevels = (Integer[]) levels.toArray(new Integer[levels.size()]);
for (int i = 0; i < ilevels.length; i++)
{
if (iteratorSetAsc.contains(ilevels[i]) == false)
{
iteratorSetAsc.add(ilevels[i]);
}
}
}
return iteratorSetAsc.iterator();
}
/**
* Returns the levels of the elements in the list in descending order.
*
* @return the levels in descending order.
*/
public Integer[] getLevelsDescendingArray()
{
if (levels.isEmpty())
{
return LevelList.EMPTY_INTEGER_ARRAY;
}
if (iteratorSetDesc == null)
{
final Integer[] ilevels = (Integer[]) levels.toArray(new Integer[levels.size()]);
iteratorSetDesc = new TreeSet(new DescendingComparator());
for (int i = 0; i < ilevels.length; i++)
{
if (iteratorSetDesc.contains(ilevels[i]) == false)
{
iteratorSetDesc.add(ilevels[i]);
}
}
}
return (Integer[]) iteratorSetDesc.toArray(new Integer[iteratorSetDesc.size()]);
}
/**
* Returns an iterator that iterates through the levels in descending order.
*
* @return an iterator.
*/
public Iterator getLevelsDescending()
{
if (iteratorSetDesc == null)
{
iteratorSetDesc = new TreeSet(new DescendingComparator());
final Integer[] ilevels = (Integer[]) levels.toArray(new Integer[levels.size()]);
for (int i = 0; i < ilevels.length; i++)
{
if (iteratorSetDesc.contains(ilevels[i]) == false)
{
iteratorSetDesc.add(ilevels[i]);
}
}
}
return iteratorSetDesc.iterator();
}
/**
* Returns the elements as an array.
*
* @return the array.
*/
public Object[] toArray()
{
return elements.toArray();
}
/**
* Returns an iterator for all the elements at a given level.
*
* @param level the level.
* @param target the target array that should receive the contentes
* @return the data for the level as object array.
*/
public Object[] getElementArrayForLevel(final int level, final Object[] target)
{
ElementLevelList it = (ElementLevelList)
iteratorCache.get(IntegerCache.getInteger(level));
if (it == null)
{
it = new ElementLevelList(this, level);
iteratorCache.put(IntegerCache.getInteger(level), it);
}
if (target == null)
{
return it.getData();
}
else
{
return it.getData(target);
}
}
/**
* Returns an iterator for all the elements at a given level.
*
* @param level the level.
* @return the data for the level as object array.
*/
public Object[] getElementArrayForLevel(final int level)
{
return getElementArrayForLevel(level, null);
}
/**
* Returns the numer of elements registered for an certain level.
*
* @param level the level that should be queried
* @return the numer of elements in that level
*/
public int getElementCountForLevel(final int level)
{
ElementLevelList it = (ElementLevelList) iteratorCache.get
(IntegerCache.getInteger(level));
if (it == null)
{
it = new ElementLevelList(this, level);
iteratorCache.put(IntegerCache.getInteger(level), it);
}
return it.size();
}
/**
* Creates an iterator for the elements in the list at the given level.
*
* @param level the level.
* @return An iterator.
* @deprecated use the array methods for best performance.
*/
protected Iterator getElementsForLevel(final int level)
{
final List list = Arrays.asList(getElementArrayForLevel(level));
return Collections.unmodifiableList(list).iterator();
}
/**
* Returns the element with the given index.
*
* @param index the index.
* @return the element.
*/
public Object get(final int index)
{
return elements.get(index);
}
/**
* Adds an element at level zero.
*
* @param o the element.
*/
public void add(final Object o)
{
elements.add(o);
levels.add(LevelList.ZERO);
iteratorSetAsc = null;
iteratorSetDesc = null;
iteratorCache.remove(LevelList.ZERO);
}
/**
* Adds an element at a given level.
*
* @param o the element.
* @param level the level.
*/
public void add(final Object o, final int level)
{
elements.add(o);
final Integer i = IntegerCache.getInteger(level);
levels.add(i);
iteratorCache.remove(i);
iteratorSetAsc = null;
iteratorSetDesc = null;
}
/**
* Sets the level for an element.
*
* @param index the element index.
* @param level the level.
*/
public void setLevel(final int index, final int level)
{
levels.set(index, IntegerCache.getInteger(level));
}
/**
* Returns the level for an element.
*
* @param index the element index.
* @return the level.
*/
public int getLevel(final int index)
{
return ((Integer) levels.get(index)).intValue();
}
/**
* Returns the index of an element.
*
* @param o the element.
* @return the index.
*/
public int indexOf(final Object o)
{
return elements.indexOf(o);
}
/**
* Returns the level of an element.
*
* @param o the element.
* @return the level.
*/
public int getLevel(final Object o)
{
return getLevel(indexOf(o));
}
/**
* Sets the level of an element.
*
* @param o the element.
* @param level the level.
*/
public void setLevel(final Object o, final int level)
{
setLevel(indexOf(o), level);
}
/**
* Clones the list.
*
* @return the clone.
* @throws CloneNotSupportedException should never happen.
*/
public Object clone()
throws CloneNotSupportedException
{
final LevelList l = (LevelList) super.clone();
l.elements = (ArrayList) elements.clone();
l.levels = (ArrayList) levels.clone();
l.iteratorCache = (HashMap) iteratorCache.clone();
return l;
}
/**
* Clears the list.
*/
public void clear()
{
elements.clear();
levels.clear();
iteratorCache.clear();
iteratorSetAsc = null;
iteratorSetDesc = null;
}
/**
* Returns all stored objects as object array.
*
* @return all elements as object array.
*/
protected Object[] getRawElements()
{
if (elements.isEmpty())
{
return LevelList.EMPTY_OBJECT_ARRAY;
}
return elements.toArray(new Object[elements.size()]);
}
/**
* Returns all active levels as java.lang.Integer array.
*
* @return all levels as Integer array.
*/
protected Integer[] getRawLevels()
{
if (levels.isEmpty())
{
return LevelList.EMPTY_INTEGER_ARRAY;
}
return (Integer[]) levels.toArray(new Integer[levels.size()]);
}
}