Using a chart item in a report design
A Java program can open an existing BIRT report design file and alter the content of the report before displaying or saving the report. The chapter on programming BIRT describes how to open a report design file using the BIRT engine and model APIs. This section describes how to use the BIRT charting API to modify an existing chart element in the report design and to create a new chart element. The following sections contain code examples for each step in the process.
Accessing an existing chart item
To get a chart report item from a report design, first perform the following steps using the BIRT core and model APIs, as described earlier in this book:
*Start the platform using a configuration object, a design engine factory, and a design engine.
*Use the design engine to create a session handle object.
*Create a design handle object for a report design from the session handle.
*Use the design handle object to access the chart element in the design.
Next, retrieve a Chart object from the chart item. This Chart object supports accessing the BIRT Chart Engine classes and using BIRT’s charting API.
Listing 6‑15 illustrates the process of getting a chart report item. This code assumes that the chart is the first report item in a list and that the list is the first report item in the report.
Listing 6‑15 Getting a ReportDesignHandle object and a Chart object
DesignConfig dConfig = new DesignConfig( );
IDesignEngine dEngine = null;
ReportDesignHandle dHandle = null;
 
try {
Platform.startup( dConfig );
IDesignEngineFactory dFactory = ( IDesignEngineFactory )
Platform. createFactoryObject( IDesignEngineFactory.
EXTENSION_DESIGN_ENGINE_FACTORY );
dEngine = dFactory.createDesignEngine( dConfig );
SessionHandle sessionHandle =
dEngine.newSessionHandle( ULocale.ENGLISH );
dHandle = sessionHandle.openDesign( reportName );
} catch ( BirtException e ) {
e.printStackTrace( );
return;
}
ListHandle li = ( ListHandle ) dHandle.getBody( ).getContents( ).get( 0 );
ExtendedItemHandle eihChart1 = ( ExtendedItemHandle )
li.getSlot( 0 ).getContents( ).get( 0 );
Chart chart = ( Chart ) eihChart1.getProperty( "chart.instance" );
Creating a new chart item
Before creating a new chart item in a report design, a charting application performs all the steps to start up the platform and design engine, as described in the previous section. Next, the application creates the Chart object as described earlier in this chapter. Creating a new Chart object for use in a report design is identical to creating a chart for a stand-alone application. Finally, the application creates a chart element and sets up the properties required to link the Chart object to the element by performing the following tasks. These tasks relate to the appearance and behavior of a chart inside a report design. If the chart is not deployed in a report, the tasks in this section are not required.
*Getting an ElementFactory object
The ElementFactory object supports creating a new report element.
*Setting the chart type and creating sample data
The chart type and sample data provide a guide to the appearance of a chart element in a report design in BIRT Report Designer.
*Getting an ExtendedItemHandle object
The ExtendedItemHandle object is similar to a standard report item handle. The item handle associates the Chart object with the report item instance in the report design. The handle is also the object that binds to a data set.
*Setting the chart.instance property on the report item
The chart.instance property of the report item identifies the chart instance object and links the report item to the chart instance object.
*Getting a data set from the report design
A chart must bind to data in order to have meaning. The report design provides access to one or more data sets that the report developer defined. The program can create a data set and add it to the design.
*Binding a chart to the data set
To bind a chart to a data set, specify the data set as a property of the extended item handle object.
*Setting any other report item properties
*Adding the new chart to the report design
The last step is to add the chart to the report design by adding the extended item handle object to the report design.
*Optionally saving the report design
An application program that creates or modifies a BIRT report design can save the new or modified report design.
The following sections describe these tasks in more detail and provide code examples for every step.
Getting a design engine element factory object
Creating a chart item in a report design requires an ElementFactory object. To get an ElementFactory object, use the getElementFactory( ) method of the ReportDesignHandle object, as shown in the following line of code:
ElementFactory ef = dHandle.getElementFactory( );
The chapter on programming with the BIRT APIs provides more information about using the Design Engine APIs and how to place a new item in a report design.
Setting the chart type and subtype
A chart’s type and subtype determine the appearance of the chart in BIRT Report Designer. In conjunction with sample data, these properties provide a realistic rendering of the chart item in the report design’s layout window and in the chart wizard. To ensure that the appearance of this rendered chart is as accurate as possible, set the chart’s type so that it matches the series type set on the axis. Many types, such as bar, Gantt, line, and stock, are available for a chart with axes. Charts without axes can be dial, pie, or radar types only.
The bubble, dial, difference, Gantt, and pie classes each support only a single type. The other chart types have multiple subtypes. Set the chart type and subtype by using the Chart methods setType( ) and setSubType( ) respectively. These methods take a single String argument. Table 6-1 shows the available values for chart types and the valid subtypes for each chart type. Because cone, pyramid, and tube charts are merely different representations of a bar chart, they have the same subtypes as a bar chart.
Table 6-1 Chart type and subtype properties
Chart axes
Type
Subtype
With axes
Area Chart
Overlay
 
 
Percent Stacked
 
 
Stacked
 
Bar Chart
Percent Stacked
 
 
Side-by-side
 
 
Stacked
 
Bubble Chart
Standard Bubble Chart
 
Cone Chart
Percent Stacked
 
 
Side-by-side
 
 
Stacked
 
Difference Chart
Standard Difference Chart
 
Gantt Chart
Standard Gantt Chart
 
Line Chart
Overlay
 
 
Percent Stacked
 
 
Stacked
 
Pyramid Chart
Percent Stacked
 
 
Side-by-side
 
 
Stacked
 
Scatter Chart
Standard Scatter Chart
 
Stock Chart
Standard Stock Chart
 
 
Bar Stick Stock Chart
 
Tube Chart
Percent Stacked
 
 
Side-by-side
 
 
Stacked
Without axes
Meter Chart
Standard Meter Chart
 
 
Superimposed Meter Chart
 
Pie Chart
Standard Pie Chart
 
Radar
Radar.STANDARD_SUBTYPE_LITERAL
 
 
Radar.SPIDER_SUBTYPE_LITERAL
 
 
Radar.BULLSEYE_SUBTYPE_LITERAL
Listing 6‑16 shows how to set a chart type and subtype.
Listing 6‑16 Setting a chart’s type and subtype
ChartWithAxes cwaBar = ChartWithAxesImpl.create( );
cwaBar.setType( "Bar Chart" );
cwaBar.setSubType( "Side-by-side" );
Creating sample data
This section describes an optional step in the creation of a chart. Sample data provides visual information in BIRT Report Designer about a chart’s appearance. If you omit the code in Listing 6‑17, the chart renders correctly when the report generates, but the designer’s layout window does not display sample values.
Listing 6‑17 Adding sample data to a chart
SampleData sdt = DataFactory.eINSTANCE.createSampleData( );
BaseSampleData sdBase = DataFactory.eINSTANCE.createBaseSampleData( );
sdBase.setDataSetRepresentation( "A" );
sdt.getBaseSampleData( ).add( sdBase );
 
OrthogonalSampleData sdOrthogonal =
DataFactory.eINSTANCE.createOrthogonalSampleData( );
sdOrthogonal.setDataSetRepresentation( "1" );
sdOrthogonal.setSeriesDefinitionIndex( 0 );
sdt.getOrthogonalSampleData( ).add( sdOrthogonal );
newChart.setSampleData( sdt );
Getting an extended item handle object
A chart report item extends from the design engine’s ReportItemHandle class by further extending the ExtendedItemHandle class. Use the ElementFactory object to create this object by using the newExtendedItem( ) method of the ElementFactory object, as shown in the following line of code:
ExtendedItemHandle chartHandle =
ef.newExtendedItem( null, "Chart" );
Setting up the report item as a chart
Set the chart.instance property of the report item object to contain the chart instance object. Get the report item from the extended item handle object, as shown in Listing 6‑18.
Listing 6‑18 Associating a Chart object with an extended item handle
ExtendedItemHandle chartHandle =
ef.newExtendedItem( null, "Chart" );
 
try {
chartHandle.getReportItem( ).setProperty( "chart.instance", newChart );
} catch( ExtendedElementException e ) {
e.printStackTrace( );
}
Preparing a data set and data columns
The new chart item still does not have access to data from the report design. The chart item uses data rows from a data set and column bindings that define how to apply values from data set columns to the chart. The names of the column bindings must match the column names used in the chart’s series objects.
To access a data set, create a data set or get an existing data set from the report design. Next, set up the columns to bind to the chart by instantiating org.eclipse.birt.report.model.api.elements.structures.ComputedColumn objects. A ComputedColumn object contains an expression that accesses columns from a data set. The code in Listing 6‑19 gets the first data set in the report design.
Listing 6‑19 Accessing a data set and preparing columns
DataSetHandle dataSet =
( DataSetHandle ) dHandle.getDataSets( ).get( 0 );
ComputedColumn col1 = StructureFactory.createComputedColumn( );
col1.setName( "VALUE1" );
col1.setExpression( "dataSetRow[\"QUANTITYORDERED\"]") ;
col1.setDataType(
DesignChoiceConstants.COLUMN_DATA_TYPE_INTEGER );
ComputedColumn col2 = StructureFactory.createComputedColumn( );
col2.setName( "VALUE2" );
col2.setExpression( "dataSetRow[\"PRICEEACH\"]" );
col2.setDataType( DesignChoiceConstants.COLUMN_DATA_TYPE_FLOAT );
ComputedColumn col3 = StructureFactory.createComputedColumn( );
col3.setName( "CATEGORY" );
col3.setExpression( "dataSetRow[\"PRODUCTLINE\"]");
col3.setDataType( DesignChoiceConstants.COLUMN_DATA_TYPE_STRING );
Binding the chart to the data set
Use the extended item handle to bind the chart to the data set and data columns, as shown in Listing 6‑20.
Listing 6‑20 Binding a data set and columns to the chart item
try {
chartHandle.setDataSet( dataSet );
extendedItemHandle.addColumnBinding( col1, true );
extendedItemHandle.addColumnBinding( col2, true );
extendedItemHandle.addColumnBinding( col3, true );
}
catch ( SemanticException e ) {
e.printStackTrace( );
}
Setting any other report item properties
The extended report item supports all the properties provided by the org.eclipse.birt.report.model.api.ReportItemHandle class. These properties include a bookmark, dimensions, and a theme. For example, to set the dimensions of the chart report item, use code as shown in Listing 6‑21.
Listing 6‑21 Setting chart item properties
try {
extendedItemHandle.setHeight( "250pt" );
extendedItemHandle.setWidth( "400pt" );
} catch ( SemanticException e ) {
e.printStackTrace( );
}
Adding the new chart to the report design
After setting the properties of the chart and binding the chart to data, add the new chart to the report design. Listing 6‑22 adds the chart item to the footer of an existing list item.
Listing 6‑22 Adding the chart item to the report design
ListHandle li =
( ListHandle ) dHandle.getBody( ).getContents( ).get( 0 );
try {
li.getFooter( ).add( chartHandle );
}
catch ( ContentException e3 ) { e3.printStackTrace( ); }
catch ( NameException e3 ) { e3.printStackTrace( ); }
Saving the report design after adding the chart
The report design file on disk does not yet contain the chart report item. Typically, you save the modified report design with a new name in order not to overwrite the original report design file.
try {
dHandle.saveAs( "./Test_modified.rptdesign" );
}
catch ( IOException e ) { e.printStackTrace( ); }
dHandle.close( );
Putting it all together
The code in Listing 6‑23 uses many of the techniques illustrated in this chapter in a complete Java application that creates a chart report item.
Listing 6‑23 Adding a chart to the report design
import java.io.IOException;
 
import org.eclipse.birt.chart.model.ChartWithAxes;
 
import org.eclipse.birt.chart.model.attribute.Anchor;
import org.eclipse.birt.chart.model.attribute.AxisType;
import org.eclipse.birt.chart.model.attribute.DataType;
import org.eclipse.birt.chart.model.attribute.GroupingUnitType;
import org.eclipse.birt.chart.model.attribute.IntersectionType;
import org.eclipse.birt.chart.model.attribute.LineAttributes;
import org.eclipse.birt.chart.model.attribute.LineStyle;
import org.eclipse.birt.chart.model.attribute.MarkerType;
import org.eclipse.birt.chart.model.attribute.TickStyle;
 
import org.eclipse.birt.chart.model.attribute.impl.BoundsImpl;
import org.eclipse.birt.chart.model.attribute.impl
.ColorDefinitionImpl;
import org.eclipse.birt.chart.model.component.Axis;
import org.eclipse.birt.chart.model.component.Series;
 
import org.eclipse.birt.chart.model.component.impl.SeriesImpl;
 
import org.eclipse.birt.chart.model.data.BaseSampleData;
import org.eclipse.birt.chart.model.data.DataFactory;
import org.eclipse.birt.chart.model.data.OrthogonalSampleData;
import org.eclipse.birt.chart.model.data.Query;
import org.eclipse.birt.chart.model.data.SampleData;
import org.eclipse.birt.chart.model.data.SeriesDefinition;
import org.eclipse.birt.chart.model.data.SeriesGrouping;
 
import org.eclipse.birt.chart.model.data.impl
.NumberDataElementImpl;
import org.eclipse.birt.chart.model.data.impl.QueryImpl;
import org.eclipse.birt.chart.model.data.impl
.SeriesDefinitionImpl;
 
import org.eclipse.birt.chart.model.impl.ChartWithAxesImpl;
 
import org.eclipse.birt.chart.model.layout.Legend;
import org.eclipse.birt.chart.model.layout.Plot;
 
import org.eclipse.birt.chart.model.type.LineSeries;
 
import org.eclipse.birt.chart.model.type.impl.LineSeriesImpl;
 
import org.eclipse.birt.core.exception.BirtException;
 
import org.eclipse.birt.core.framework.Platform;
 
import org.eclipse.birt.report.engine.api.EngineConfig;
 
import org.eclipse.birt.report.model.api.DataSetHandle;
import org.eclipse.birt.report.model.api.DesignConfig;
import org.eclipse.birt.report.model.api.ElementFactory;
import org.eclipse.birt.report.model.api.ExtendedItemHandle;
import org.eclipse.birt.report.model.api.IDesignEngine;
import org.eclipse.birt.report.model.api.IDesignEngineFactory;
import org.eclipse.birt.report.model.api.ListHandle;
import org.eclipse.birt.report.model.api.PropertyHandle;
import org.eclipse.birt.report.model.api.ReportDesignHandle;
import org.eclipse.birt.report.model.api.SessionHandle;
import org.eclipse.birt.report.model.api.StructureFactory;
import org.eclipse.birt.report.model.api.activity
.SemanticException;
 
import org.eclipse.birt.report.model.api.command.ContentException;
import org.eclipse.birt.report.model.api.command.NameException;
 
import org.eclipse.birt.report.model.api.elements
.DesignChoiceConstants;
import org.eclipse.birt.report.model.api.elements.structures
.ComputedColumn;
import org.eclipse.birt.report.model.api.extension
.ExtendedElementException;
import org.eclipse.birt.report.model.api.extension.IReportItem;
 
import com.ibm.icu.util.ULocale;
 
/*************************************************************
* Read a BIRT report design file, add a chart and write a
* new report design file containing the added chart.
* Run this application with the following command line:
* java ChartReportApp origDesign modifiedDesign
************************************************************/
 
public class ChartReportApp
{
private static String reportName = "./test.rptdesign";
private static String newReportDesign = "./test_new.rptdesign";
 
/************************************************************
* Get the report design name and the name of the modified
* report design from the command line if the command line has
* any arguments.
* Create an instance of this class, create a new chart,
* write a new design file containing both the original and
* the new chart
*************************************************************/
 
public static void main( String[ ] args )
{
if( args.length > 0 ) {
reportName = args[0];
}
if( args.length > 1 ) {
newReportDesign = args[1];
}
 
ReportDesignHandle dHandle = createDesignHandle(reportName);
 
// create an instance of this class
ChartReportApp cra = new ChartReportApp( );
 
// Call the build method of this class.
cra.build( dHandle, newReportDesign );
}
 
/********************************************************
* The report design handle object is the entry point to
* the report.
 
* Create a report design handle object based on the
* original design file by performing the following steps:
 
* 1) Start the platform using the configuration object
* 2) Create a design engine factory object from the platform
* 3) Create a design engine using the factory object
* 4) Create a session handle object from the design engine
* 5) Create a design handle object from the session handle
 
* The resulting design handle object is the entry point
* to the report design and thus to the chart.
*********************************************************/
 
private static ReportDesignHandle createDesignHandle
( String reportName )
{
EngineConfig config = new EngineConfig( );
DesignConfig dConfig = new DesignConfig( );
IDesignEngine dEngine = null;
ReportDesignHandle dHandle = null;
try {
Platform.startup( config );
IDesignEngineFactory dFactory =
( IDesignEngineFactory ) Platform.createFactoryObject(
IDesignEngineFactory.EXTENSION_DESIGN_ENGINE_FACTORY );
dEngine = dFactory.createDesignEngine( dConfig );
SessionHandle sessionHandle =
dEngine.newSessionHandle( ULocale.ENGLISH );
dHandle = sessionHandle.openDesign( reportName );
}
catch(BirtException e) {
e.printStackTrace();
}
return dHandle;
}
 
/***********************************************************
* Build a chart
***********************************************************/
 
public void build
( ReportDesignHandle dHandle, String newDesignName )
{
// Create a new chart instance object
ChartWithAxes newChart = ChartWithAxesImpl.create( );
 
// Set the properties of the chart
newChart.setType( "Line Chart" );
newChart.setSubType( "Overlay" );
 
newChart.getBlock().setBackground(
ColorDefinitionImpl.WHITE() );
newChart.getBlock().setBounds(
BoundsImpl.create( 0, 0, 400, 250 ) );
 
Plot p = newChart.getPlot();
p.getClientArea().setBackground(
ColorDefinitionImpl.create( 255, 255, 225 ));
newChart.getTitle().getLabel().getCaption()
.setValue( "Europe" );
 
Legend lg = newChart.getLegend( );
LineAttributes lia = lg.getOutline( );
lg.getText().getFont().setSize( 16 );
lia.setStyle( LineStyle.SOLID_LITERAL );
lg.getInsets().set( 1, 1, 1, 1 );
lg.getOutline().setVisible( false );
lg.setAnchor( Anchor.NORTH_LITERAL );
 
Axis xAxisPrimary = newChart.getPrimaryBaseAxes( )[0];
xAxisPrimary.setType( AxisType.TEXT_LITERAL );
xAxisPrimary.getMajorGrid().setTickStyle(
TickStyle.BELOW_LITERAL );
xAxisPrimary.getOrigin().setType(
IntersectionType.VALUE_LITERAL );
xAxisPrimary.getTitle().setVisible( false );
 
Axis yAxisPrimary = newChart.getPrimaryOrthogonalAxis(
xAxisPrimary );
yAxisPrimary.getMajorGrid().setTickStyle(
TickStyle.LEFT_LITERAL );
yAxisPrimary.getScale().setMax(
NumberDataElementImpl.create( 160 ));
yAxisPrimary.getScale().setMin(NumberDataElementImpl
.create( -50 ));
yAxisPrimary.getTitle().getCaption()
.setValue( "Sales Growth" );
 
// Create sample data.
SampleData sdt =
DataFactory.eINSTANCE.createSampleData();
BaseSampleData sdBase =
DataFactory.eINSTANCE.createBaseSampleData();
sdBase.setDataSetRepresentation("A");
sdt.getBaseSampleData().add( sdBase );
 
OrthogonalSampleData sdOrthogonal =
DataFactory.eINSTANCE.createOrthogonalSampleData();
sdOrthogonal.setDataSetRepresentation( "1");
sdOrthogonal.setSeriesDefinitionIndex(0);
sdt.getOrthogonalSampleData().add( sdOrthogonal );
newChart.setSampleData(sdt);
 
// Create the category series.
Series seCategory = SeriesImpl.create();
 
// Set the data value for X-Series.
Query query = QueryImpl.create( "row[\"CATEGORY\"]" );
seCategory.getDataDefinition().add( query );
 
// Create the primary data set
LineSeries ls1 = ( LineSeries ) LineSeriesImpl.create();
ls1.setSeriesIdentifier( "Q1" );
 
// Set the data value for Y-Series 1.
Query query1 = QueryImpl.create( "row[\"VALUE1\"]/1000" );
ls1.getDataDefinition().add( query1 );
ls1.getLineAttributes().setColor(ColorDefinitionImpl.RED());
for ( int i = 0; i < ls1.getMarkers( ).size( ); i++ )
{
( ls1.getMarkers( ).get( i ) ).setType( MarkerType.TRIANGLE_LITERAL );
}
ls1.getLabel().setVisible( true );
 
LineSeries ls2 = (LineSeries) LineSeriesImpl.create( );
ls2.setSeriesIdentifier( "Q2" );
 
// Set the data value for Y-Series 2.
Query query2 = QueryImpl.create( "row[\"VALUE2\"]" );
ls2.getDataDefinition().add( query2 );
ls2.getLineAttributes().setColor(
ColorDefinitionImpl.YELLOW() );
for ( int i = 0; i < ls2.getMarkers( ).size( ); i++ )
{
( ls2.getMarkers( ).get( i ) ).setType(
MarkerType.CIRCLE_LITERAL );
}
ls2.getLabel().setVisible( true );
 
SeriesDefinition sdX = SeriesDefinitionImpl.create();
sdX.getSeriesPalette().shift( 0 );
 
// Set default grouping.
SeriesGrouping grouping = sdX.getGrouping( );
grouping.setEnabled( true );
grouping.setGroupType( DataType.TEXT_LITERAL );
grouping.setGroupingUnit( GroupingUnitType.STRING_LITERAL );
grouping.setGroupingInterval( 0 );
grouping.setAggregateExpression( "Sum" ); //$NON-NLS-1$
xAxisPrimary.getSeriesDefinitions().add( sdX );
sdX.getSeries().add( seCategory );
 
SeriesDefinition sdY1 = SeriesDefinitionImpl.create();
sdY1.getSeriesPalette().shift( 0 );
sdY1.getSeries().add( ls1 );
yAxisPrimary.getSeriesDefinitions().add( sdY1 );
 
SeriesDefinition sdY2 = SeriesDefinitionImpl.create();
sdY2.getSeriesPalette().shift( 0 );
sdY2.getSeries().add( ls2 );
yAxisPrimary.getSeriesDefinitions().add( sdY2 );
 
// Change the aggregation for Y-Series 2.
SeriesGrouping groupingY2 = sdY2.getGrouping( );
groupingY2.setEnabled( true );
groupingY2.setAggregateExpression( "Average" );//$NON-NLS-1$
 
// Get a chart implementation object and set its
// chart.instance property
ElementFactory ef = dHandle.getElementFactory( );
ExtendedItemHandle extendedItemHandle =
ef.newExtendedItem( null, "Chart" );
 
try{
IReportItem chartItem =
extendedItemHandle.getReportItem( );
chartItem.setProperty( "chart.instance", newChart );
} catch( ExtendedElementException e ) {
e.printStackTrace( );
}
 
// Get a data set and bind it to the chart.
DataSetHandle dataSet = ( DataSetHandle )
dHandle.getDataSets( ).get( 0 );
ComputedColumn col1 =
StructureFactory.createComputedColumn( );
col1.setName( "VALUE1" );
col1.setExpression( "dataSetRow[\"QUANTITYORDERED\"]") ;
col1.setDataType(
DesignChoiceConstants.COLUMN_DATA_TYPE_INTEGER );
ComputedColumn col2 =
StructureFactory.createComputedColumn( );
col2.setName( "VALUE2" );
col2.setExpression( "dataSetRow[\"PRICEEACH\"]" );
col2.setDataType(
DesignChoiceConstants.COLUMN_DATA_TYPE_FLOAT );
ComputedColumn col3 =
StructureFactory.createComputedColumn( );
col3.setName( "CATEGORY" );
col3.setExpression( "dataSetRow[\"PRODUCTLINE\"]");
col3.setDataType(
DesignChoiceConstants.COLUMN_DATA_TYPE_STRING );
 
try {
extendedItemHandle.setDataSet( dataSet );
extendedItemHandle.addColumnBinding(col1, true);
extendedItemHandle.addColumnBinding(col2, true);
extendedItemHandle.addColumnBinding(col3, true);
extendedItemHandle.setHeight( "250pt" );
extendedItemHandle.setWidth( "400pt" );
} catch ( SemanticException e ) {
e.printStackTrace( );
}
 
// Add the chart to the report design
ListHandle li = (ListHandle) dHandle.getBody( )
.getContents( ).get( 0 );
try {
li.getFooter( ).add( extendedItemHandle );
} catch ( ContentException e3 ) {
e3.printStackTrace( );
} catch ( NameException e3 ) { e3.printStackTrace( ); }
 
// Save the report design that now contains a chart
try {
dHandle.saveAs( newDesignName );
} catch ( IOException e ) { e.printStackTrace( ); }
dHandle.close( );
Platform.shutdown( );
System.out.println( "Finished" );
}
}