/* * 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 - 2013 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()] ); } }