/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2013 by Pentaho : http://www.pentaho.com
*
*******************************************************************************
*
* 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 org.pentaho.di.trans.steps.olapinput.olap4jhelper;
import java.text.DecimalFormat;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.olap4j.Cell;
import org.olap4j.CellSet;
import org.olap4j.CellSetAxis;
import org.olap4j.Position;
import org.olap4j.impl.CoordinateIterator;
import org.olap4j.impl.Olap4jUtil;
import org.olap4j.metadata.Member;
public class CellSetFormatter {
/**
* Description of an axis.
*/
private static class AxisInfo {
final List<AxisOrdinalInfo> ordinalInfos;
/**
* Creates an AxisInfo.
*
* @param ordinalCount
* Number of hierarchies on this axis
*/
AxisInfo( final int ordinalCount ) {
ordinalInfos = new ArrayList<AxisOrdinalInfo>( ordinalCount );
for ( int i = 0; i < ordinalCount; i++ ) {
ordinalInfos.add( new AxisOrdinalInfo() );
}
}
/**
* Returns the number of matrix columns required by this axis. The sum of the width of the hierarchies on this axis.
*
* @return Width of axis
*/
public int getWidth() {
int width = 0;
for ( final AxisOrdinalInfo info : ordinalInfos ) {
width += info.getWidth();
}
return width;
}
}
/**
* Description of a particular hierarchy mapped to an axis.
*/
private static class AxisOrdinalInfo {
private int minDepth = 1;
private int maxDepth = 0;
/**
* Returns the number of matrix columns required to display this hierarchy.
*/
public int getWidth() {
return maxDepth - minDepth + 1;
}
}
/**
* @param formattedValue
* @return values
*/
public static String getValueString( final String formattedValue ) {
final String[] values = formattedValue.split( "\\|" );
if ( values.length > 1 ) {
return values[1];
}
return values[0];
}
/**
* Returns an iterator over cells in a result.
*/
private static Iterable<Cell> cellIter( final int[] pageCoords, final CellSet cellSet ) {
return new Iterable<Cell>() {
public Iterator<Cell> iterator() {
final int[] axisDimensions = new int[cellSet.getAxes().size() - pageCoords.length];
assert pageCoords.length <= axisDimensions.length;
for ( int i = 0; i < axisDimensions.length; i++ ) {
final CellSetAxis axis = cellSet.getAxes().get( i );
axisDimensions[i] = axis.getPositions().size();
}
final CoordinateIterator coordIter = new CoordinateIterator( axisDimensions, true );
return new Iterator<Cell>() {
public boolean hasNext() {
return coordIter.hasNext();
}
public Cell next() {
final int[] ints = coordIter.next();
final AbstractList<Integer> intList = new AbstractList<Integer>() {
@Override
public Integer get( final int index ) {
return index < ints.length ? ints[index] : pageCoords[index - ints.length];
}
@Override
public int size() {
return pageCoords.length + ints.length;
}
};
return cellSet.getCell( intList );
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
private Matrix matrix;
public Matrix format( final CellSet cellSet ) {
// Compute how many rows are required to display the columns axis.
final CellSetAxis columnsAxis;
if ( cellSet.getAxes().size() > 0 ) {
columnsAxis = cellSet.getAxes().get( 0 );
} else {
columnsAxis = null;
}
final AxisInfo columnsAxisInfo = computeAxisInfo( columnsAxis );
// Compute how many columns are required to display the rows axis.
final CellSetAxis rowsAxis;
if ( cellSet.getAxes().size() > 1 ) {
rowsAxis = cellSet.getAxes().get( 1 );
} else {
rowsAxis = null;
}
final AxisInfo rowsAxisInfo = computeAxisInfo( rowsAxis );
if ( cellSet.getAxes().size() > 2 ) {
final int[] dimensions = new int[cellSet.getAxes().size() - 2];
for ( int i = 2; i < cellSet.getAxes().size(); i++ ) {
final CellSetAxis cellSetAxis = cellSet.getAxes().get( i );
dimensions[i - 2] = cellSetAxis.getPositions().size();
}
for ( final int[] pageCoords : CoordinateIterator.iterate( dimensions ) ) {
matrix = formatPage( cellSet, pageCoords, columnsAxis, columnsAxisInfo, rowsAxis, rowsAxisInfo );
}
} else {
matrix = formatPage( cellSet, new int[] {}, columnsAxis, columnsAxisInfo, rowsAxis, rowsAxisInfo );
}
return matrix;
}
/**
* Computes a description of an axis.
*
* @param axis
* Axis
* @return Description of axis
*/
private AxisInfo computeAxisInfo( final CellSetAxis axis ) {
if ( axis == null ) {
return new AxisInfo( 0 );
}
final AxisInfo axisInfo = new AxisInfo( axis.getAxisMetaData().getHierarchies().size() );
int p = -1;
for ( final Position position : axis.getPositions() ) {
++p;
int k = -1;
for ( final Member member : position.getMembers() ) {
++k;
final AxisOrdinalInfo axisOrdinalInfo = axisInfo.ordinalInfos.get( k );
final int topDepth = member.isAll() ? member.getDepth() : member.getHierarchy().hasAll() ? 1 : 0;
if ( axisOrdinalInfo.minDepth > topDepth || p == 0 ) {
axisOrdinalInfo.minDepth = topDepth;
}
axisOrdinalInfo.maxDepth = Math.max( axisOrdinalInfo.maxDepth, member.getDepth() );
}
}
return axisInfo;
}
/**
* Formats a two-dimensional page.
*
* @param cellSet
* Cell set
* @param pw
* Print writer
* @param pageCoords
* Coordinates of page [page, chapter, section, ...]
* @param columnsAxis
* Columns axis
* @param columnsAxisInfo
* Description of columns axis
* @param rowsAxis
* Rows axis
* @param rowsAxisInfo
* Description of rows axis
*/
private Matrix formatPage( final CellSet cellSet, final int[] pageCoords, final CellSetAxis columnsAxis,
final AxisInfo columnsAxisInfo, final CellSetAxis rowsAxis, final AxisInfo rowsAxisInfo ) {
// Figure out the dimensions of the blank rectangle in the top left
// corner.
final int yOffset = columnsAxisInfo.getWidth();
final int xOffsset = rowsAxisInfo.getWidth();
// Populate a string matrix
final Matrix matrix =
new Matrix( xOffsset + ( columnsAxis == null ? 1 : columnsAxis.getPositions().size() ), yOffset
+ ( rowsAxis == null ? 1 : rowsAxis.getPositions().size() ) );
// Populate corner
for ( int x = 0; x < xOffsset; x++ ) {
for ( int y = 0; y < yOffset; y++ ) {
final MemberCell memberInfo = new MemberCell( false, x > 0 );
matrix.set( x, y, memberInfo );
}
}
// Populate matrix with cells representing axes
// noinspection SuspiciousNameCombination
populateAxis( matrix, columnsAxis, columnsAxisInfo, true, xOffsset );
populateAxis( matrix, rowsAxis, rowsAxisInfo, false, yOffset );
// Populate cell values
for ( final Cell cell : cellIter( pageCoords, cellSet ) ) {
final List<Integer> coordList = cell.getCoordinateList();
int x = xOffsset;
if ( coordList.size() > 0 ) {
x += coordList.get( 0 );
}
int y = yOffset;
if ( coordList.size() > 1 ) {
y += coordList.get( 1 );
}
final DataCell cellInfo = new DataCell( true, false );
for ( int z = 0; z < matrix.getMatrixHeight(); z++ ) {
final AbstractBaseCell headerCell = matrix.get( x, z );
if ( !( headerCell instanceof MemberCell && ( (MemberCell) headerCell ).getUniqueName() != null ) ) {
cellInfo.setParentColMember( (MemberCell) matrix.get( x, z - 1 ) );
break;
}
}
for ( int z = 0; z < matrix.getMatrixWidth(); z++ ) {
final AbstractBaseCell headerCell = matrix.get( z, y );
if ( !( headerCell instanceof MemberCell && ( (MemberCell) headerCell ).getUniqueName() != null ) ) {
cellInfo.setParentRowMember( (MemberCell) matrix.get( z - 1, y ) );
break;
}
}
if ( cell.getValue() != null ) {
if ( cell.getValue() instanceof Number ) {
cellInfo.setRawNumber( (Number) cell.getValue() );
}
}
String cellValue = cell.getFormattedValue(); // First try to get a
// formatted value
if ( cellValue == null || cellValue.equals( "null" ) ) {
cellValue = "";
}
if ( cellValue.length() < 1 ) {
final Object value = cell.getValue();
if ( value == null || value.equals( "null" ) ) {
cellValue = "";
} else {
try {
DecimalFormat myFormatter = new DecimalFormat( "#,###.###" );
String output = myFormatter.format( cell.getValue() );
cellValue = output;
} catch ( Exception e ) {
// TODO: handle exception
}
}
// the raw value
}
cellInfo.setFormattedValue( getValueString( cellValue ) );
matrix.set( x, y, cellInfo );
}
return matrix;
}
/**
* Populates cells in the matrix corresponding to a particular axis.
*
* @param matrix
* Matrix to populate
* @param axis
* Axis
* @param axisInfo
* Description of axis
* @param isColumns
* True if columns, false if rows
* @param offset
* Ordinal of first cell to populate in matrix
*/
private void populateAxis( final Matrix matrix, final CellSetAxis axis, final AxisInfo axisInfo,
final boolean isColumns, final int offset ) {
if ( axis == null ) {
return;
}
final Member[] prevMembers = new Member[axisInfo.getWidth()];
final MemberCell[] prevMemberInfo = new MemberCell[axisInfo.getWidth()];
final Member[] members = new Member[axisInfo.getWidth()];
for ( int i = 0; i < axis.getPositions().size(); i++ ) {
final int x = offset + i;
final Position position = axis.getPositions().get( i );
int yOffset = 0;
final List<Member> memberList = position.getMembers();
for ( int j = 0; j < memberList.size(); j++ ) {
Member member = memberList.get( j );
final AxisOrdinalInfo ordinalInfo = axisInfo.ordinalInfos.get( j );
while ( member != null ) {
if ( member.getDepth() < ordinalInfo.minDepth ) {
break;
}
final int y = yOffset + member.getDepth() - ordinalInfo.minDepth;
members[y] = member;
member = member.getParentMember();
}
yOffset += ordinalInfo.getWidth();
}
boolean same = true;
for ( int y = 0; y < members.length; y++ ) {
final MemberCell memberInfo = new MemberCell();
final Member member = members[y];
final List<String> memberPath = new ArrayList<String>();
for ( int z = 0; z <= position.getMembers().size() - 1; z++ ) {
if ( i < axis.getPositions().size() - 1 ) {
if ( axis.getPositions().get( i + 1 ).getMembers().get( z ).getParentMember() != null
&& axis.getPositions().get( i + 1 ).getMembers().get( z ).getParentMember().equals( member ) ) {
if ( member == null
|| position.getMembers().get( z ).getUniqueName().equals( member.getUniqueName() ) ) {
break;
}
}
}
memberPath.add( position.getMembers().get( z ).getUniqueName() );
}
if ( member != null ) {
memberPath.add( member.getUniqueName() );
}
memberInfo.setMemberPath( memberPath );
same = same && i > 0 && Olap4jUtil.equal( prevMembers[y], member );
if ( member != null ) {
if ( x - 1 == offset ) {
matrix.setOffset( offset );
}
memberInfo.setRawValue( member.getCaption() );
memberInfo.setFormattedValue( member.getCaption() ); // First try to get a formatted value
memberInfo.setParentDimension( member.getDimension().getName() );
memberInfo.setUniqueName( member.getUniqueName() );
if ( y > 0 && prevMembers[y - 1] != null ) {
memberInfo.setRightOf( prevMemberInfo[y - 1] );
}
if ( y > 0 && prevMembers[y - 1] != null ) {
memberInfo.setRightOfDimension( prevMembers[y - 1].getDimension().getName() );
}
if ( member.getParentMember() != null ) {
memberInfo.setParentMember( member.getParentMember().getUniqueName() );
}
} else {
memberInfo.setRawValue( null );
memberInfo.setFormattedValue( null );
memberInfo.setParentDimension( null );
}
if ( isColumns ) {
memberInfo.setRight( false );
memberInfo.setSameAsPrev( same );
if ( member != null ) {
memberInfo.setParentDimension( member.getDimension().getName() );
}
matrix.set( x, y, memberInfo );
} else {
if ( same ) {
memberInfo.setFormattedValue( null );
memberInfo.setRawValue( null );
memberInfo.setParentDimension( null );
}
memberInfo.setRight( false );
memberInfo.setSameAsPrev( false );
matrix.set( y, x, memberInfo );
}
prevMembers[y] = member;
prevMemberInfo[y] = memberInfo;
members[y] = null;
}
}
}
}