/*! * 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) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.reporting.engine.classic.core.states.crosstab; import org.pentaho.reporting.engine.classic.core.InvalidReportStateException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; public class DimensionNode implements Comparable<DimensionNode> { private Object[] data; private HashSet<DimensionNode> previousNodes; private HashSet<DimensionNode> nextNodes; private int distanceFromRoot; private int rowCount; private boolean valid; public DimensionNode( final Object[] data, final int rowCount ) { this.rowCount = rowCount; this.data = data.clone(); this.previousNodes = new HashSet<DimensionNode>(); this.nextNodes = new HashSet<DimensionNode>(); } public boolean equals( final Object o ) { if ( this == o ) { return true; } if ( o == null || getClass() != o.getClass() ) { return false; } final DimensionNode that = (DimensionNode) o; // Probably incorrect - comparing Object[] arrays with Arrays.equals if ( !Arrays.equals( data, that.data ) ) { return false; } return true; } public int hashCode() { return Arrays.hashCode( data ); } public boolean addParent( final DimensionNode node ) { node.checkLoop( this ); if ( this.previousNodes.add( node ) ) { node.nextNodes.add( this ); invalidate(); return true; } return false; } private void removeParent( final DimensionNode previousNode ) { this.previousNodes.remove( previousNode ); previousNode.nextNodes.remove( this ); // no need to invalidate that node. Removing a child does not change the // distance of the parent node to the root of the tree. } private void checkLoop( final DimensionNode node ) { if ( node.equals( this ) ) { throw new InvalidReportStateException( "Looping nodes. This data-model cannot be normalized." ); } for ( final DimensionNode previousNode : previousNodes ) { previousNode.checkLoop( node ); } } private void invalidate() { if ( valid == false ) { return; } valid = false; for ( final DimensionNode dimensionNode : nextNodes ) { dimensionNode.invalidate(); } } public void rebalance() { if ( valid ) { return; } if ( previousNodes.isEmpty() ) { distanceFromRoot = 0; valid = true; return; } int maxDepth = 0; for ( final DimensionNode previousNode : previousNodes ) { final int depth = previousNode.getDistanceFromRoot(); if ( depth > maxDepth ) { maxDepth = depth; } } final ArrayList<DimensionNode> nodesForRemoval = new ArrayList<DimensionNode>(); for ( final DimensionNode previousNode : previousNodes ) { final int depth = previousNode.getDistanceFromRoot(); if ( depth < maxDepth ) { nodesForRemoval.add( previousNode ); } } for ( int i = 0; i < nodesForRemoval.size(); i++ ) { removeParent( nodesForRemoval.get( i ) ); } distanceFromRoot = maxDepth + 1; valid = true; } private int getDistanceFromRoot() { if ( !valid ) { rebalance(); } return distanceFromRoot; } public String toString() { final StringBuilder sb = new StringBuilder(); sb.append( "DimensionNode" ); sb.append( "{data=" ).append( Arrays.asList( data ).toString() ); sb.append( ", nextNodes=" ).append( nextNodes ); sb.append( ", rowCount=" ).append( rowCount ); sb.append( '}' ); return sb.toString(); } public String printFormatted() { return printFormatted( 0 ); } private String printFormatted( final int indent ) { final StringBuilder b = new StringBuilder(); for ( int i = 0; i < indent; i += 1 ) { b.append( " " ); } b.append( "[" ); b.append( Arrays.asList( data ).toString() ); b.append( "] distance=" ); b.append( getDistanceFromRoot() ); b.append( " sort-order=" ); b.append( rowCount ); for ( final DimensionNode nextNode : nextNodes ) { b.append( "\n" ); b.append( nextNode.printFormatted( indent + 1 ) ); } return b.toString(); } public Object[] getData() { return data; } public int compareTo( final DimensionNode o ) { final int distance = getDistanceFromRoot(); final int otherDistance = o.getDistanceFromRoot(); if ( distance < otherDistance ) { return -1; } if ( distance > otherDistance ) { return +1; } final int dataComparison = CrosstabKeyComparator.INSTANCE.compare( data, o.data ); if ( dataComparison != 0 ) { return dataComparison; } // fallback to the row-count. if ( rowCount < o.rowCount ) { return -1; } if ( o.rowCount < rowCount ) { return +1; } return 0; } }