package org.codehaus.mojo.pomtools.console.toolkit.widgets;
/*
* Copyright 2005-2006 The Apache Software Foundation.
*
* 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.
*/
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.codehaus.mojo.pomtools.console.toolkit.ConsoleUtils;
import org.codehaus.mojo.pomtools.console.toolkit.terminal.Terminal;
import org.codehaus.mojo.pomtools.helpers.LocalStringUtils;
import org.codehaus.plexus.util.StringUtils;
/**
*
* @author <a href="mailto:dhawkins@codehaus.org">David Hawkins</a>
* @version $Id$
*/
public class TableLayout
{
private static final String NEWLINE = "\n";
private static final String RPAD = " ";
private TableColumn[] columns;
private int[] maxLengths;
private final Terminal term;
private int tableWidth = -1;
private List rowList = new ArrayList();
private static final String ELLIPSES = "...";
public TableLayout( Terminal term )
{
this.term = term;
}
public TableLayout( Terminal term, int width )
{
this.term = term;
this.tableWidth = width;
}
public TableLayout( Terminal term, TableColumn[] columns )
{
this.term = term;
this.columns = columns;
this.maxLengths = new int[columns.length];
}
public TableLayout( Terminal term, TableColumn[] columns, int width )
{
this.term = term;
this.columns = columns;
this.tableWidth = width;
this.maxLengths = new int[columns.length];
int sumColumnWidths = 0;
for ( int i = 0; i < columns.length; i++ )
{
if ( columns[i].getFixedWidth() >= 0 )
{
sumColumnWidths += columns[i].getFixedWidth();
}
else
{
sumColumnWidths++; // minimum of 1 char for the column
}
}
if ( width > 0 && sumColumnWidths > width )
{
throw new IllegalArgumentException( "You may not specify a width that is less than the "
+ "sum of the column widths" );
}
else if ( width > 0 && columns.length > width )
{
throw new IllegalArgumentException( "You may not specify a width that is less than the "
+ "number of columns" );
}
}
public void addEmptyRow()
{
if ( columns == null )
{
throw new IllegalArgumentException( "Columns must be initialized before adding an empty row" );
}
this.add( new String[columns.length] );
}
public void add( String[] row )
{
if ( columns == null )
{
createDefaultColumns( row.length );
}
else if ( row.length != columns.length )
{
throw new IllegalArgumentException( "Row data must have same number of columns as construtor" );
}
for ( int nColumn = 0; nColumn < row.length; nColumn++ )
{
if ( row[nColumn] != null )
{
String[] cellLines = StringUtils.split( row[nColumn], NEWLINE );
for ( int nLine = 0; nLine < cellLines.length; nLine++ )
{
maxLengths[nColumn] = Math.max( maxLengths[nColumn], term.length( cellLines[nLine] ) );
}
}
}
rowList.add( row );
}
/** Simple helper function that adds n columns of data.
* Easier than passing new String[] { cell1 .. celln }
*/
public void add( String cell1 )
{
add( new String[] { cell1 } );
}
/** Simple helper function that adds n columns of data.
* Easier than passing new String[] { cell1 .. celln }
*/
public void add( String cell1, String cell2 )
{
add( new String[] { cell1, cell2 } );
}
/** Simple helper function that adds n columns of data.
* Easier than passing new String[] { cell1 .. celln }
*/
public void add( String cell1, String cell2, String cell3 )
{
add( new String[] { cell1, cell2, cell3 } );
}
private void createDefaultColumns( int nCols )
{
columns = new TableColumn[nCols];
maxLengths = new int[nCols];
for ( int i = 0; i < columns.length; i++ )
{
columns[i] = new TableColumn();
}
}
private int sum( int[] iarr )
{
int result = 0;
for ( int i = 0; i < iarr.length; i++ )
{
result += iarr[i];
}
return result;
}
private int[] calculateColumnWidths()
{
int ncols = columns.length;
int[] widths = new int[ncols];
int curWidth = 0;
for ( int i = 0; i < ncols; i++ )
{
if ( columns[i].getFixedWidth() > 0 )
{
widths[i] = columns[i].getFixedWidth();
}
else
{
widths[i] = maxLengths[i];
if ( columns[i].getMaxWidth() > 0 )
{
widths[i] = Math.min( widths[i], columns[i].getMaxWidth() );
}
}
curWidth += widths[i];
}
if ( this.tableWidth > 0 && curWidth > this.tableWidth )
{
int[] fixedColumns = new int[ncols];
int[] varColumns = new int[ncols];
// we need to reduce the length of columns
for ( int i = 0; i < ncols; i++ )
{
if ( columns[i].getFixedWidth() < 0 )
{
varColumns[i] = widths[i]; // variable sized column
}
else
{
fixedColumns[i] = widths[i]; // fixed size column
}
}
int diff = curWidth - sum( fixedColumns ) - this.tableWidth;
double sumVarCols = sum( varColumns );
for ( int i = 0; i < ncols; i++ )
{
if ( varColumns[i] > 0 )
{
widths[i] = (int) Math.round( (double) varColumns[i] / sumVarCols * diff );
}
}
}
return widths;
}
protected TableColumn getColumn( int index )
{
return columns[index];
}
public String getOutput()
{
StringBuffer sb = new StringBuffer();
int[] columnWidths = calculateColumnWidths();
for ( Iterator iter = rowList.iterator(); iter.hasNext(); )
{
String[] row = (String[]) iter.next();
RowOutput output = new RowOutput( columnWidths );
for ( int columnIndex = 0; columnIndex < row.length; columnIndex++ )
{
TableColumn col = columns[columnIndex];
int columnWidth = columnWidths[columnIndex];
String cellData = StringUtils.defaultString( row[columnIndex] );
if ( !col.isWrap() && term.length( cellData ) > columnWidth )
{
int encodingLen = term.encodingLength( cellData );
int lastPos = columnWidth - ELLIPSES.length();
if ( lastPos - encodingLen <= 0 )
{
throw new IllegalArgumentException( "Can't truncate string in a column that is too "
+ "short for ellipses" );
}
// TODO: this could chop in the middle of an escape
cellData = cellData.substring( 0, lastPos + encodingLen ) + ELLIPSES;
}
if ( term.length( cellData ) > columnWidth )
{
// If any single line of the data is longer than the column width, just wordwrap the whole value
String[] cellLines = LocalStringUtils.splitPreserveAllTokens( cellData, NEWLINE );
for ( int i = 0; i < cellLines.length; i++ )
{
if ( term.length( cellLines[i] ) > columnWidth )
{
// TODO: Make wordWrap understand escape sequences
cellData = ConsoleUtils.wordWrap( cellData, columnWidth );
break;
}
}
}
output.setColumnData( columnIndex, cellData );
}
sb.append( output.getOutput() );
}
return sb.toString();
}
protected int getWidthBeforeColumn( int colIndex )
{
int width = 0;
for ( int i = 0; i < colIndex; i++ )
{
width += maxLengths[i];
}
return width;
}
private class RowOutput
{
private final int[] columnWidths;
private final List[] columnLists;
public RowOutput( int[] columnWidths )
{
this.columnWidths = columnWidths;
columnLists = new List[columnWidths.length];
for ( int i = 0; i < columnLists.length; i++ )
{
columnLists[i] = new ArrayList();
}
}
public RowOutput setColumnData( int colIndex, String s )
{
TableColumn column = getColumn( colIndex );
String[] strLines = LocalStringUtils.splitPreserveAllTokens( s, NEWLINE );
if ( strLines.length == 0 )
{
columnLists[colIndex].add( s );
}
else
{
for ( int i = 0; i < strLines.length; i++ )
{
String line = strLines[i];
if ( term.length( line ) > columnWidths[colIndex] )
{
// This shouldn't happen
throw new IllegalArgumentException( "Passed column data that was longer than the max width" );
}
if ( column.getAlignment() == TableColumn.ALIGN_LEFT )
{
line = StringUtils.rightPad( line, columnWidths[colIndex] + term.encodingLength( line ) );
}
else
{
line = StringUtils.leftPad( line, columnWidths[colIndex] + term.encodingLength( line ) );
}
columnLists[colIndex].add( line );
}
}
return this;
}
private String getFormattedData( int columnIndex, String data )
{
TableColumn col = getColumn( columnIndex );
if ( col.getTextStyle() != null )
{
if ( col.getTextStyle() == TableColumn.BOLD )
{
return term.bold( data );
}
else if ( col.getTextStyle() == TableColumn.UNDERLINE )
{
return term.underline( data );
}
}
return data;
}
public String getOutput()
{
StringBuffer sb = new StringBuffer();
int maxLinesInRow = 0;
for ( int i = 0; i < columnLists.length; i++ )
{
maxLinesInRow = Math.max( maxLinesInRow, columnLists[i].size() );
}
for ( int nLine = 0; nLine < maxLinesInRow; nLine++ )
{
for ( int nColumn = 0; nColumn < columnLists.length; nColumn++ )
{
// Does this column have data for this line?
if ( columnLists[nColumn].size() > nLine )
{
sb.append( getFormattedData( nColumn, (String) columnLists[nColumn].get( nLine ) ) );
}
else
{
// no data for line so just pad it out so the next column prints in the right place
sb.append( StringUtils.repeat( " ", columnWidths[nColumn] ) );
}
if ( nColumn < columnLists.length - 1 )
{
sb.append( RPAD );
}
}
sb.append( NEWLINE );
}
return sb.toString();
}
}
}