ar.com.fdvs.dj.core.layout.AbstractLayoutManager.java Source code

Java tutorial

Introduction

Here is the source code for ar.com.fdvs.dj.core.layout.AbstractLayoutManager.java

Source

/*
 * DynamicJasper: A library for creating reports dynamically by specifying
 * columns, groups, styles, etc. at runtime. It also saves a lot of development
 * time in many cases! (http://sourceforge.net/projects/dynamicjasper)
 *
 * Copyright (C) 2008  FDV Solutions (http://www.fdvsolutions.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 *
 * License as published by the Free Software Foundation; either
 *
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 *
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *
 */

package ar.com.fdvs.dj.core.layout;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.sf.jasperreports.charts.design.JRDesignBarPlot;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExpression;
import net.sf.jasperreports.engine.JRGroup;
import net.sf.jasperreports.engine.JRHyperlink;
import net.sf.jasperreports.engine.JRStyle;
import net.sf.jasperreports.engine.JRTextElement;
import net.sf.jasperreports.engine.JRTextField;
import net.sf.jasperreports.engine.base.JRBaseChartPlot;
import net.sf.jasperreports.engine.base.JRBaseVariable;
import net.sf.jasperreports.engine.design.JRDesignBand;
import net.sf.jasperreports.engine.design.JRDesignChart;
import net.sf.jasperreports.engine.design.JRDesignChartDataset;
import net.sf.jasperreports.engine.design.JRDesignConditionalStyle;
import net.sf.jasperreports.engine.design.JRDesignElement;
import net.sf.jasperreports.engine.design.JRDesignExpression;
import net.sf.jasperreports.engine.design.JRDesignGroup;
import net.sf.jasperreports.engine.design.JRDesignImage;
import net.sf.jasperreports.engine.design.JRDesignParameter;
import net.sf.jasperreports.engine.design.JRDesignStyle;
import net.sf.jasperreports.engine.design.JRDesignTextElement;
import net.sf.jasperreports.engine.design.JRDesignTextField;
import net.sf.jasperreports.engine.design.JRDesignVariable;
import net.sf.jasperreports.engine.design.JasperDesign;
import net.sf.jasperreports.engine.util.JRExpressionUtil;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MultiHashMap;
import org.apache.commons.collections.MultiMap;
import org.apache.commons.collections.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import ar.com.fdvs.dj.core.DJDefaultScriptlet;
import ar.com.fdvs.dj.core.DJException;
import ar.com.fdvs.dj.core.registration.EntitiesRegistrationException;
import ar.com.fdvs.dj.domain.CustomExpression;
import ar.com.fdvs.dj.domain.DJChart;
import ar.com.fdvs.dj.domain.DJChartOptions;
import ar.com.fdvs.dj.domain.DJHyperLink;
import ar.com.fdvs.dj.domain.DynamicJasperDesign;
import ar.com.fdvs.dj.domain.DynamicReport;
import ar.com.fdvs.dj.domain.StringExpression;
import ar.com.fdvs.dj.domain.Style;
import ar.com.fdvs.dj.domain.builders.DataSetFactory;
import ar.com.fdvs.dj.domain.constants.Transparency;
import ar.com.fdvs.dj.domain.entities.DJGroup;
import ar.com.fdvs.dj.domain.entities.columns.AbstractColumn;
import ar.com.fdvs.dj.domain.entities.columns.BarCodeColumn;
import ar.com.fdvs.dj.domain.entities.columns.ImageColumn;
import ar.com.fdvs.dj.domain.entities.columns.PercentageColumn;
import ar.com.fdvs.dj.domain.entities.columns.PropertyColumn;
import ar.com.fdvs.dj.domain.entities.conditionalStyle.ConditionStyleExpression;
import ar.com.fdvs.dj.domain.entities.conditionalStyle.ConditionalStyle;
import ar.com.fdvs.dj.util.ExpressionUtils;
import ar.com.fdvs.dj.util.HyperLinkUtil;
import ar.com.fdvs.dj.util.LayoutUtils;
import ar.com.fdvs.dj.util.Utils;

/**
 * Abstract Class used as base for the different Layout Managers.</br>
 * </br>
 * A Layout Manager is always invoked after the entities registration stage.</br>
 * A subclass should be created whenever we want to give the users the chance to </br>
 * easily apply global layout changes to their reports. Example: Ignore groups </br>
 * and styles for an Excel optimized report.
 */
public abstract class AbstractLayoutManager implements LayoutManager {

    static final Log log = LogFactory.getLog(AbstractLayoutManager.class);
    protected static final String EXPRESSION_TRUE_WHEN_ODD = "new java.lang.Boolean(((Number)$V{REPORT_COUNT}).doubleValue() % 2 == 0)";
    protected static final String EXPRESSION_TRUE_WHEN_EVEN = "new java.lang.Boolean(((Number)$V{REPORT_COUNT}).doubleValue() % 2 != 0)";

    JasperDesign design;
    private DynamicReport report;

    protected abstract void transformDetailBandTextField(AbstractColumn column, JRDesignTextField textField);

    private HashMap reportStyles = new HashMap();

    /**
     * Holds the original groups binded to a column.
     * Needed for later reference
     * List<JRDesignGroup>
     */
    protected List realGroups = new ArrayList();

    public HashMap getReportStyles() {
        return reportStyles;
    }

    public void setReportStyles(HashMap reportStyles) {
        this.reportStyles = reportStyles;
    }

    public void applyLayout(JasperDesign design, DynamicReport report) throws LayoutException {
        log.debug("Applying Layout...");
        try {
            setDesign(design);
            setReport(report);
            ensureDJStyles();
            startLayout();
            transformDetailBand();
            endLayout();
            setWhenNoDataBand(); //this goes at the end because it will copy objects from other bands
            registerRemainingStyles();
        } catch (RuntimeException e) {
            throw new LayoutException(e.getMessage(), e);
        }
    }

    /**
     * Creates the graphic element to be shown when the datasource is empty
     */
    protected void setWhenNoDataBand() {
        log.debug("setting up WHEN NO DATA band");
        String whenNoDataText = getReport().getWhenNoDataText();
        Style style = getReport().getWhenNoDataStyle();
        if (whenNoDataText == null || "".equals(whenNoDataText))
            return;
        JRDesignBand band = new JRDesignBand();
        getDesign().setNoData(band);

        JRDesignTextField text = new JRDesignTextField();
        JRDesignExpression expression = ExpressionUtils.createStringExpression("\"" + whenNoDataText + "\"");
        text.setExpression(expression);

        if (style == null) {
            style = getReport().getOptions().getDefaultDetailStyle();
        }

        if (getReport().isWhenNoDataShowTitle()) {
            LayoutUtils.copyBandElements(band, getDesign().getTitle());
            LayoutUtils.copyBandElements(band, getDesign().getPageHeader());
        }
        if (getReport().isWhenNoDataShowColumnHeader())
            LayoutUtils.copyBandElements(band, getDesign().getColumnHeader());

        int offset = LayoutUtils.findVerticalOffset(band);
        text.setY(offset);
        applyStyleToElement(style, text);
        text.setWidth(getReport().getOptions().getPrintableWidth());
        text.setHeight(50);
        band.addElement(text);
        setBandFinalHeight(band);
        log.debug("OK setting up WHEN NO DATA band");

    }

    protected void startLayout() {
        setColumnsFinalWidth();
        realGroups.addAll(getDesign().getGroupsList()); //Hold the original groups
    }

    protected void endLayout() {
        layoutCharts();
        setBandsFinalHeight();
    }

    protected void registerRemainingStyles() {
        //TODO: troll all elements in the JRDesing and for elements that has styles with null name
        //or not registered, register them in the design
    }

    /**
     * Sets a default style for every element that doesn't have one
     * @throws JRException
     */
    protected void ensureDJStyles() {
        //first of all, register all parent styles if any
        for (Iterator iterator = getReport().getStyles().values().iterator(); iterator.hasNext();) {
            Style style = (Style) iterator.next();
            addStyleToDesign(style);
        }

        Style defaultDetailStyle = getReport().getOptions().getDefaultDetailStyle();

        Style defaultHeaderStyle = getReport().getOptions().getDefaultHeaderStyle();
        for (Iterator iter = report.getColumns().iterator(); iter.hasNext();) {
            AbstractColumn column = (AbstractColumn) iter.next();
            if (column.getStyle() == null)
                column.setStyle(defaultDetailStyle);
            if (column.getHeaderStyle() == null)
                column.setHeaderStyle(defaultHeaderStyle);
        }
    }

    /**
     * @param baseStyle
     * @throws JRException
     */
    public void addStyleToDesign(Style style) {
        JRDesignStyle jrstyle = style.transform();
        try {
            if (jrstyle.getName() == null) {
                String name = createUniqueStyleName();
                jrstyle.setName(name);
                style.setName(name);
                getReportStyles().put(name, jrstyle);
                design.addStyle(jrstyle);
            }

            JRStyle old = (JRStyle) design.getStylesMap().get(jrstyle.getName());
            if (old != null && style.isOverridesExistingStyle()) {
                log.debug("Overriding style with name \"" + style.getName() + "\"");

                design.removeStyle(style.getName());
                design.addStyle(jrstyle);
            } else if (old == null) {
                log.debug("Registering new style with name \"" + style.getName() + "\"");
                design.addStyle(jrstyle);
            } else {
                if (style.getName() != null)
                    log.debug("Using existing style for style with name \"" + style.getName() + "\"");
            }
        } catch (JRException e) {
            log.debug("Duplicated style (it's ok): " + e.getMessage());
        }
    }

    protected String createUniqueStyleName() {
        synchronized (this) {
            int counter = getReportStyles().values().size() + 1;
            String tryName = "dj_style_" + counter;
            while (design.getStylesMap().get(tryName) != null) {
                counter++;
                tryName = "dj_style_" + counter;
            }
            return tryName;
        }
    }

    /**
     * For each column, puts the elements in the detail band
     */
    protected void transformDetailBand() {
        log.debug("transforming Detail Band...");
        JRDesignBand detail = (JRDesignBand) design.getDetail();
        if (detail == null) { //fixes issue 2747664
            detail = new JRDesignBand();
            design.setDetail(detail);
        }
        detail.setHeight(report.getOptions().getDetailHeight().intValue());

        for (Iterator iter = getVisibleColumns().iterator(); iter.hasNext();) {

            AbstractColumn column = (AbstractColumn) iter.next();

            /**
             * Barcode column
             */
            if (column instanceof BarCodeColumn) {
                BarCodeColumn barcodeColumn = (BarCodeColumn) column;
                JRDesignImage image = new JRDesignImage(new JRDesignStyle().getDefaultStyleProvider());
                JRDesignExpression imageExp = new JRDesignExpression();
                //            imageExp.setText("ar.com.fdvs.dj.core.BarcodeHelper.getBarcodeImage("+barcodeColumn.getBarcodeType() + ", "+ column.getTextForExpression()+ ", "+ barcodeColumn.isShowText() + ", " + barcodeColumn.isCheckSum() + ", " + barcodeColumn.getApplicationIdentifier() + ","+ column.getWidth() +", "+ report.getOptions().getDetailHeight().intValue() + " )" );

                //Do not pass column height and width mecause barbecue
                //generates the image with wierd dimensions. Pass 0 in both cases
                String applicationIdentifier = barcodeColumn.getApplicationIdentifier();
                if (applicationIdentifier != null && !"".equals(applicationIdentifier.trim())) {
                    applicationIdentifier = "$F{" + applicationIdentifier + "}";
                } else {
                    applicationIdentifier = "\"\"";
                }
                imageExp.setText(
                        "ar.com.fdvs.dj.core.BarcodeHelper.getBarcodeImage(" + barcodeColumn.getBarcodeType() + ", "
                                + column.getTextForExpression() + ", " + barcodeColumn.isShowText() + ", "
                                + barcodeColumn.isCheckSum() + ", " + applicationIdentifier + ",0,0 )");

                imageExp.setValueClass(java.awt.Image.class);
                image.setExpression(imageExp);
                image.setHeight(getReport().getOptions().getDetailHeight().intValue());
                image.setWidth(column.getWidth().intValue());
                image.setX(column.getPosX().intValue());
                image.setScaleImage(barcodeColumn.getScaleMode().getValue());

                image.setOnErrorType(JRDesignImage.ON_ERROR_TYPE_ICON); //FIXME should we provide control of this to the user?

                if (column.getLink() != null) {
                    String name = "column_" + getReport().getColumns().indexOf(column);
                    HyperLinkUtil.applyHyperLinkToElement((DynamicJasperDesign) getDesign(), column.getLink(),
                            image, name);
                }

                applyStyleToElement(column.getStyle(), image);

                detail.addElement(image);
            }
            /**
             * Image columns
             */
            else if (column instanceof ImageColumn) {
                ImageColumn imageColumn = (ImageColumn) column;
                JRDesignImage image = new JRDesignImage(new JRDesignStyle().getDefaultStyleProvider());
                JRDesignExpression imageExp = new JRDesignExpression();
                imageExp.setText(column.getTextForExpression());

                imageExp.setValueClassName(imageColumn.getColumnProperty().getValueClassName());
                image.setExpression(imageExp);
                image.setHeight(getReport().getOptions().getDetailHeight().intValue());
                image.setWidth(column.getWidth().intValue());
                image.setX(column.getPosX().intValue());
                image.setScaleImage(imageColumn.getScaleMode().getValue());

                applyStyleToElement(column.getStyle(), image);

                if (column.getLink() != null) {
                    String name = "column_" + getReport().getColumns().indexOf(column);
                    HyperLinkUtil.applyHyperLinkToElement((DynamicJasperDesign) getDesign(), column.getLink(),
                            image, name);
                }

                detail.addElement(image);
            }
            /**
             * Regular Column
             */
            else {
                if (getReport().getOptions().isShowDetailBand()) {
                    JRDesignTextField textField = generateTextFieldFromColumn(column,
                            getReport().getOptions().getDetailHeight().intValue(), null);

                    if (column.getLink() != null) {
                        String name = "column_" + getReport().getColumns().indexOf(column);
                        HyperLinkUtil.applyHyperLinkToElement((DynamicJasperDesign) getDesign(), column.getLink(),
                                textField, name);
                    }

                    transformDetailBandTextField(column, textField);

                    if (textField.getExpression() != null)
                        detail.addElement(textField);
                }

            }

        }
    }

    /**
     * Creates and returns the expression used to apply a conditional style.
     * @param String paramName
     * @param String textForExpression
     * @return JRExpression
     */
    protected JRDesignExpression getExpressionForConditionalStyle(ConditionalStyle condition,
            AbstractColumn column) {
        //String text = "(("+CustomExpression.class.getName()+")$P{"+paramName+"})."+CustomExpression.EVAL_METHOD_NAME+"("+textForExpression+")";
        String columExpression = column.getTextForExpression();
        //condition.getCondition().setFieldToEvaluate(exprParams)

        // PeS17 patch, 2008-11-29: put all fields to fields map, including "invisible" i.e. only registered ones

        String fieldsMap = "((" + DJDefaultScriptlet.class.getName() + ")$P{REPORT_SCRIPTLET}).getCurrentFiels()";
        String parametersMap = "((" + DJDefaultScriptlet.class.getName()
                + ")$P{REPORT_SCRIPTLET}).getCurrentParams()";
        String variablesMap = "((" + DJDefaultScriptlet.class.getName()
                + ")$P{REPORT_SCRIPTLET}).getCurrentVariables()";

        String evalMethodParams = fieldsMap + ", " + variablesMap + ", " + parametersMap + ", " + columExpression;

        String text = "((" + ConditionStyleExpression.class.getName() + ")$P{" + condition.getName() + "})."
                + CustomExpression.EVAL_METHOD_NAME + "(" + evalMethodParams + ")";
        JRDesignExpression expression = new JRDesignExpression();
        expression.setValueClass(Boolean.class);
        expression.setText(text);
        return expression;
    }

    protected void generateHeaderBand(JRDesignBand band) {
        log.debug("Adding column names in header band.");
        band.setHeight(report.getOptions().getHeaderHeight().intValue());

        for (Iterator iter = getVisibleColumns().iterator(); iter.hasNext();) {

            AbstractColumn col = (AbstractColumn) iter.next();
            if (col.getTitle() == null)
                continue;

            JRDesignExpression expression = new JRDesignExpression();
            JRDesignTextField textField = new JRDesignTextField();
            expression.setText("\"" + col.getTitle() + "\"");

            expression.setValueClass(String.class);

            textField.setKey("header_" + col.getTitle());
            textField.setExpression(expression);

            textField.setX(col.getPosX().intValue());
            textField.setY(col.getPosY().intValue());
            textField.setHeight(band.getHeight());
            textField.setWidth(col.getWidth().intValue());

            textField.setPrintWhenDetailOverflows(true);
            textField.setBlankWhenNull(true);

            Style headerStyle = col.getHeaderStyle();
            if (headerStyle == null)
                headerStyle = report.getOptions().getDefaultHeaderStyle();

            applyStyleToElement(headerStyle, textField);

            band.addElement(textField);
        }
    }

    /**
     * Given a dj-Style, it is applied to the jasper element.
     * If the style is being used by the first time, it is registered in the jasper-design,
     * if it is the second time, the one created before is used  (cached one)
     *
     *
     * @param style
     * @param designElemen
     */
    public void applyStyleToElement(Style style, JRDesignElement designElemen) {
        if (style == null) {
            //         log.warn("NULL style passed to object");
            JRDesignStyle style_ = new JRDesignStyle();
            style_.setName(createUniqueStyleName());
            designElemen.setStyle(style_);
            try {
                getDesign().addStyle(style_);
            } catch (JRException e) {
                //duplicated style, its ok
            }
            //         return null;
            return;
        }
        boolean existsInDesign = style.getName() != null && design.getStylesMap().get(style.getName()) != null;
        //      && !style.isOverridesExistingStyle();

        JRDesignStyle jrstyle = null;
        //Let's allways add a new JR style
        if (existsInDesign && !style.isOverridesExistingStyle()) {
            jrstyle = (JRDesignStyle) design.getStylesMap().get(style.getName());
        } else {
            addStyleToDesign(style); //Order maters. This line fist
            jrstyle = style.transform();
        }

        designElemen.setStyle(jrstyle);
        if (designElemen instanceof JRDesignTextElement) {
            JRDesignTextElement textField = (JRDesignTextElement) designElemen;
            if (style.getStreching() != null)
                textField.setStretchType(style.getStreching().getValue());
            textField.setPositionType(JRTextField.POSITION_TYPE_FLOAT);

        }
        if (designElemen instanceof JRDesignTextField) {
            JRDesignTextField textField = (JRDesignTextField) designElemen;
            textField.setStretchWithOverflow(style.isStretchWithOverflow());

            if (textField.isBlankWhenNull() == false && style.isBlankWhenNull()) //TODO Re check if this condition is ok 
                textField.setBlankWhenNull(true);
        }
        //      return jrstyle;
        return;
    }

    /**
     * Sets the columns width by reading some report options like the
     * printableArea and useFullPageWidth.
     * columns with fixedWidth property set in TRUE will not be modified
     */
    protected void setColumnsFinalWidth() {
        log.debug("Setting columns final width.");
        float factor = 1;
        int printableArea = report.getOptions().getColumnWidth();

        //Create a list with only the visible columns.
        List visibleColums = getVisibleColumns();

        if (report.getOptions().isUseFullPageWidth()) {
            int columnsWidth = 0;
            int notRezisableWidth = 0;

            //Store in a variable the total with of all visible columns
            for (Iterator iterator = visibleColums.iterator(); iterator.hasNext();) {
                AbstractColumn col = (AbstractColumn) iterator.next();
                columnsWidth += col.getWidth().intValue();
                if (col.getFixedWidth().booleanValue())
                    notRezisableWidth += col.getWidth().intValue();
            }

            factor = (float) (printableArea - notRezisableWidth) / (float) (columnsWidth - notRezisableWidth);

            log.debug("printableArea = " + printableArea + ", columnsWidth = " + columnsWidth + ", columnsWidth = "
                    + columnsWidth + ", notRezisableWidth = " + notRezisableWidth + ", factor = " + factor);

            int acumulated = 0;
            int colFinalWidth = 0;

            //Select the non-resizable columns
            Collection resizableColumns = CollectionUtils.select(visibleColums, new Predicate() {
                public boolean evaluate(Object arg0) {
                    return !((AbstractColumn) arg0).getFixedWidth().booleanValue();
                }

            });

            //Finally, set the new width to the resizable columns
            for (Iterator iter = resizableColumns.iterator(); iter.hasNext();) {
                AbstractColumn col = (AbstractColumn) iter.next();

                if (!iter.hasNext()) {
                    col.setWidth(new Integer(printableArea - notRezisableWidth - acumulated));
                } else {
                    colFinalWidth = (new Float(col.getWidth().intValue() * factor)).intValue();
                    acumulated += colFinalWidth;
                    col.setWidth(new Integer(colFinalWidth));
                }
            }
        }

        // If the columns width changed, the X position must be setted again.
        int posx = 0;
        for (Iterator iterator = visibleColums.iterator(); iterator.hasNext();) {
            AbstractColumn col = (AbstractColumn) iterator.next();
            col.setPosX(new Integer(posx));
            posx += col.getWidth().intValue();
        }
    }

    /**
     * @return
     */
    protected List getVisibleColumns() {
        List visibleColums = new ArrayList(report.getColumns());
        return visibleColums;
    }

    /**
     * Sets the necessary height for all bands in the report, to hold their children
     */
    protected void setBandsFinalHeight() {
        log.debug("Setting bands final height...");

        setBandFinalHeight((JRDesignBand) design.getPageHeader());
        setBandFinalHeight((JRDesignBand) design.getPageFooter());
        setBandFinalHeight((JRDesignBand) design.getColumnHeader());
        setBandFinalHeight((JRDesignBand) design.getColumnFooter());
        setBandFinalHeight((JRDesignBand) design.getSummary());
        setBandFinalHeight((JRDesignBand) design.getBackground());
        setBandFinalHeight((JRDesignBand) design.getDetail());
        setBandFinalHeight((JRDesignBand) design.getLastPageFooter());
        setBandFinalHeight((JRDesignBand) design.getTitle());
        setBandFinalHeight((JRDesignBand) design.getPageFooter());
        setBandFinalHeight((JRDesignBand) design.getNoData());

        for (Iterator iter = design.getGroupsList().iterator(); iter.hasNext();) {
            JRGroup jrgroup = (JRGroup) iter.next();
            DJGroup djGroup = (DJGroup) getReferencesMap().get(jrgroup.getName());
            if (djGroup != null) {
                setBandFinalHeight((JRDesignBand) jrgroup.getGroupHeader(), djGroup.getHeaderHeight().intValue(),
                        djGroup.isFitHeaderHeightToContent());
                setBandFinalHeight((JRDesignBand) jrgroup.getGroupFooter(), djGroup.getFooterHeight().intValue(),
                        djGroup.isFitFooterHeightToContent());
            } else {
                setBandFinalHeight((JRDesignBand) jrgroup.getGroupHeader());
                setBandFinalHeight((JRDesignBand) jrgroup.getGroupFooter());
            }
        }
    }

    /**
     * Removes empty space when "fitToContent" is true and real height of object is
     * taller than current bands height, otherwise, it is not modified
     * @param band
     * @param currHeigth
     * @param fitToContent
     */
    private void setBandFinalHeight(JRDesignBand band, int currHeigth, boolean fitToContent) {
        if (band != null) {
            int finalHeight = LayoutUtils.findVerticalOffset(band);
            if (finalHeight < currHeigth && !fitToContent) {
                //nothing
            } else {
                band.setHeight(finalHeight);
            }
        }

    }

    /**
     * Sets the band's height to hold all its children
     * @param band Band to be resized
     */
    protected void setBandFinalHeight(JRDesignBand band) {
        if (band != null) {
            int finalHeight = LayoutUtils.findVerticalOffset(band);
            band.setHeight(finalHeight);
        }
    }

    /**
     * Creates a JasperReport DesignTextField from a DynamicJasper AbstractColumn.
     * @param AbstractColumn col
     * @param int height
     * @param DJGroup group
     * @return JRDesignTextField
     */
    protected JRDesignTextField generateTextFieldFromColumn(AbstractColumn col, int height, DJGroup group) {
        JRDesignTextField textField = new JRDesignTextField();
        JRDesignExpression exp = new JRDesignExpression();

        if (col.getPattern() != null && "".equals(col.getPattern().trim())) {
            textField.setPattern(col.getPattern());
        }

        if (col.getTruncateSuffix() != null) {
            textField.getPropertiesMap().setProperty(JRTextElement.PROPERTY_TRUNCATE_SUFFIX,
                    col.getTruncateSuffix());
        }

        List columnsGroups = getReport().getColumnsGroups();
        if (col instanceof PercentageColumn) {
            PercentageColumn pcol = (PercentageColumn) col;

            if (group == null) { //we are in the detail band
                DJGroup innerMostGroup = (DJGroup) columnsGroups.get(columnsGroups.size() - 1);
                exp.setText(pcol.getTextForExpression(innerMostGroup));
            } else {
                exp.setText(pcol.getTextForExpression(group));
            }

            textField.setEvaluationTime(JRExpression.EVALUATION_TIME_AUTO);
        } else {
            exp.setText(col.getTextForExpression());

        }

        exp.setValueClassName(col.getValueClassNameForExpression());
        textField.setExpression(exp);
        textField.setWidth(col.getWidth().intValue());
        textField.setX(col.getPosX().intValue());
        textField.setY(col.getPosY().intValue());
        textField.setHeight(height);

        textField.setBlankWhenNull(col.getBlankWhenNull());

        textField.setPattern(col.getPattern());

        textField.setPrintRepeatedValues(col.getPrintRepeatedValues().booleanValue());

        textField.setPrintWhenDetailOverflows(true);

        Style columnStyle = col.getStyle();
        if (columnStyle == null)
            columnStyle = report.getOptions().getDefaultDetailStyle();

        applyStyleToElement(columnStyle, textField);
        JRDesignStyle jrstyle = (JRDesignStyle) textField.getStyle();

        if (group != null) {
            int index = columnsGroups.indexOf(group);
            //            JRDesignGroup previousGroup = (JRDesignGroup) getDesign().getGroupsList().get(index);
            JRDesignGroup previousGroup = getJRGroupFromDJGroup(group);
            textField.setPrintWhenGroupChanges(previousGroup);

            /**
             * Since a group column can share the style with non group columns, if oddRow coloring is enabled,
             * we modified this shared style to have a colored background on odd rows. We don't want that for group
             * columns, that's why we create our own style from the existing one, and remove proper odd-row conditional
             * style if present
             */
            JRDesignStyle groupStyle = Utils.cloneStyle(jrstyle);

            groupStyle.setName(groupStyle.getFontName() + "_for_group_" + index);
            textField.setStyle(groupStyle);
            try {
                design.addStyle(groupStyle);
            } catch (JRException e) {
                /**e.printStackTrace(); //Already there, nothing to do **/
            }

        } else {

            JRDesignStyle alternateStyle = Utils.cloneStyle(jrstyle);

            alternateStyle.setName(alternateStyle.getFontName() + "_for_column_" + col.getName());
            alternateStyle.getConditionalStyleList().clear();
            textField.setStyle(alternateStyle);
            try {
                design.addStyle(alternateStyle);
            } catch (JRException e) {
                /**e.printStackTrace(); //Already there, nothing to do **/
            }

            setUpConditionStyles(alternateStyle, col);
            /*
            if (getReport().getOptions().isPrintBackgroundOnOddRows() &&
             (jrstyle.getConditionalStyles() == null || jrstyle.getConditionalStyles().length == 0)) {
               // No group column so this is a detail text field
              JRDesignExpression expression = new JRDesignExpression();
              expression.setValueClass(Boolean.class);
              expression.setText(EXPRESSION_TRUE_WHEN_ODD);
                
              Style oddRowBackgroundStyle = getReport().getOptions().getOddRowBackgroundStyle();
                
              JRDesignConditionalStyle condStyle = new JRDesignConditionalStyle();
              condStyle.setBackcolor(oddRowBackgroundStyle.getBackgroundColor());
              condStyle.setMode(JRDesignElement.MODE_OPAQUE);
                
              condStyle.setConditionExpression(expression);
              jrstyle.addConditionalStyle(condStyle);
            }*/
        }
        return textField;
    }

    /**
     * set up properly the final JRStyle of the column element (for detail band) upon condition style and odd-background
     * @param jrstyle
     * @param column
     */
    private void setUpConditionStyles(JRDesignStyle jrstyle, AbstractColumn column) {

        if (getReport().getOptions().isPrintBackgroundOnOddRows() && Utils.isEmpty(column.getConditionalStyles())) {
            JRDesignExpression expression = new JRDesignExpression();
            expression.setValueClass(Boolean.class);
            expression.setText(EXPRESSION_TRUE_WHEN_ODD);

            Style oddRowBackgroundStyle = getReport().getOptions().getOddRowBackgroundStyle();

            JRDesignConditionalStyle condStyle = new JRDesignConditionalStyle();
            condStyle.setBackcolor(oddRowBackgroundStyle.getBackgroundColor());
            condStyle.setMode(JRDesignElement.MODE_OPAQUE);

            condStyle.setConditionExpression(expression);
            jrstyle.addConditionalStyle(condStyle);

            return;
        }

        if (Utils.isEmpty(column.getConditionalStyles()))
            return;

        for (Iterator iterator = column.getConditionalStyles().iterator(); iterator.hasNext();) {
            ConditionalStyle condition = (ConditionalStyle) iterator.next();

            if (getReport().getOptions().isPrintBackgroundOnOddRows()
                    && Transparency.TRANSPARENT == condition.getStyle().getTransparency()) { //condition style + odd row (only if conditional style's background is transparent)

                JRDesignExpression expressionForConditionalStyle = getExpressionForConditionalStyle(condition,
                        column);
                String expStr = JRExpressionUtil.getExpressionText(expressionForConditionalStyle);

                //ODD
                JRDesignExpression expressionOdd = new JRDesignExpression();
                expressionOdd.setValueClass(Boolean.class);
                expressionOdd.setText("new java.lang.Boolean(" + EXPRESSION_TRUE_WHEN_ODD
                        + ".booleanValue() && ((java.lang.Boolean)" + expStr + ").booleanValue() )");

                Style oddRowBackgroundStyle = getReport().getOptions().getOddRowBackgroundStyle();

                JRDesignConditionalStyle condStyleOdd = makeConditionalStyle(condition.getStyle());
                //            Utils.copyProperties(condStyleOdd, condition.getStyle().transform());
                condStyleOdd.setBackcolor(oddRowBackgroundStyle.getBackgroundColor());
                condStyleOdd.setMode(JRDesignElement.MODE_OPAQUE);
                condStyleOdd.setConditionExpression(expressionOdd);
                jrstyle.addConditionalStyle(condStyleOdd);

                //EVEN
                JRDesignExpression expressionEven = new JRDesignExpression();
                expressionEven.setValueClass(Boolean.class);
                expressionEven.setText("new java.lang.Boolean(" + EXPRESSION_TRUE_WHEN_EVEN
                        + ".booleanValue() && ((java.lang.Boolean)" + expStr + ").booleanValue() )");

                JRDesignConditionalStyle condStyleEven = makeConditionalStyle(condition.getStyle());
                condStyleEven.setConditionExpression(expressionEven);
                jrstyle.addConditionalStyle(condStyleEven);

            } else { //No odd row, just the conditional style
                JRDesignExpression expression = getExpressionForConditionalStyle(condition, column);
                JRDesignConditionalStyle condStyle = makeConditionalStyle(condition.getStyle());
                condStyle.setConditionExpression(expression);
                jrstyle.addConditionalStyle(condStyle);
            }
        }

        //The last condition is the basic one
        //ODD
        if (getReport().getOptions().isPrintBackgroundOnOddRows()) {

            JRDesignExpression expressionOdd = new JRDesignExpression();
            expressionOdd.setValueClass(Boolean.class);
            expressionOdd.setText(EXPRESSION_TRUE_WHEN_ODD);

            Style oddRowBackgroundStyle = getReport().getOptions().getOddRowBackgroundStyle();

            JRDesignConditionalStyle condStyleOdd = new JRDesignConditionalStyle();
            condStyleOdd.setBackcolor(oddRowBackgroundStyle.getBackgroundColor());
            condStyleOdd.setMode(JRDesignElement.MODE_OPAQUE);
            condStyleOdd.setConditionExpression(expressionOdd);

            jrstyle.addConditionalStyle(condStyleOdd);

            //EVEN
            JRDesignExpression expressionEven = new JRDesignExpression();
            expressionEven.setValueClass(Boolean.class);
            expressionEven.setText(EXPRESSION_TRUE_WHEN_EVEN);

            JRDesignConditionalStyle condStyleEven = new JRDesignConditionalStyle();
            condStyleEven.setBackcolor(jrstyle.getBackcolor());
            condStyleEven.setMode(jrstyle.getMode());
            condStyleEven.setConditionExpression(expressionEven);

            jrstyle.addConditionalStyle(condStyleEven);
        }
    }

    protected JRDesignConditionalStyle makeConditionalStyle(Style style) {
        JRDesignConditionalStyle condStyle = style.transformAsConditinalStyle();
        return condStyle;
    }

    /*
     * Takes all the report's charts and inserts them in their corresponding bands
     */
    protected void layoutCharts() {
        //Pre-sort charts by group column
        MultiMap mmap = new MultiHashMap();
        for (Iterator iter = getReport().getCharts().iterator(); iter.hasNext();) {
            DJChart djChart = (DJChart) iter.next();
            mmap.put(djChart.getColumnsGroup(), djChart);
        }

        for (Iterator iterator = mmap.keySet().iterator(); iterator.hasNext();) {
            Object key = iterator.next();
            Collection charts = (Collection) mmap.get(key);
            ArrayList l = new ArrayList(charts);
            //Reverse iteration of the charts to meet insertion order
            for (int i = l.size(); i > 0; i--) {
                DJChart djChart = (DJChart) l.get(i - 1);
                JRDesignChart chart = createChart(djChart);

                //Charts has their own band, so they are added in the band at Y=0
                JRDesignBand band = createGroupForChartAndGetBand(djChart);
                band.addElement(chart);
            }
        }

        //Pre-sort charts by group column
        mmap = new MultiHashMap();
        for (Iterator iter = getReport().getNewCharts().iterator(); iter.hasNext();) {
            ar.com.fdvs.dj.domain.chart.DJChart djChart = (ar.com.fdvs.dj.domain.chart.DJChart) iter.next();
            mmap.put(djChart.getDataset().getColumnsGroup(), djChart);
        }

        for (Iterator iterator = mmap.keySet().iterator(); iterator.hasNext();) {
            Object key = iterator.next();
            Collection charts = (Collection) mmap.get(key);
            ArrayList l = new ArrayList(charts);
            //Reverse iteration of the charts to meet insertion order
            for (int i = l.size(); i > 0; i--) {
                ar.com.fdvs.dj.domain.chart.DJChart djChart = (ar.com.fdvs.dj.domain.chart.DJChart) l.get(i - 1);
                String name = "chart_" + (i - 1);
                JRDesignChart chart = createChart(djChart, name);

                if (djChart.getLink() != null)
                    HyperLinkUtil.applyHyperLinkToElement((DynamicJasperDesign) getDesign(), djChart.getLink(),
                            chart, name + "_hyperlink");

                //Charts has their own band, so they are added in the band at Y=0
                JRDesignBand band = createGroupForChartAndGetBand(djChart);
                band.addElement(chart);
            }
        }
    }

    protected JRDesignBand createGroupForChartAndGetBand(DJChart djChart) {
        JRDesignGroup jrGroup = getJRGroupFromDJGroup(djChart.getColumnsGroup());
        JRDesignGroup parentGroup = getParent(jrGroup);
        JRDesignGroup jrGroupChart = null;
        try {
            jrGroupChart = (JRDesignGroup) BeanUtils.cloneBean(parentGroup);
            jrGroupChart.setGroupFooter(new JRDesignBand());
            jrGroupChart.setGroupHeader(new JRDesignBand());
            jrGroupChart.setName(jrGroupChart.getName() + "_Chart" + getReport().getCharts().indexOf(djChart));
        } catch (Exception e) {
            throw new DJException("Problem creating band for chart: " + e.getMessage(), e);
        }

        //Charts should be added in its own band (to ensure page break, etc)
        //To achieve that, we create a group and insert it right before to the criteria group.
        //I need to find parent group of the criteria group, clone and insert after.
        //The only precaution is that if parent == child (only one group in the report) the we insert before
        if (jrGroup.equals(parentGroup)) {
            jrGroupChart.setExpression(ExpressionUtils.createStringExpression("\"dummy_for_chart\""));
            getDesign().getGroupsList().add(getDesign().getGroupsList().indexOf(jrGroup), jrGroupChart);
        } else {
            int index = getDesign().getGroupsList().indexOf(parentGroup);
            getDesign().getGroupsList().add(index, jrGroupChart);
        }

        JRDesignBand band = null;
        switch (djChart.getOptions().getPosition()) {
        case DJChartOptions.POSITION_HEADER:
            band = (JRDesignBand) jrGroupChart.getGroupHeader();
            break;
        case DJChartOptions.POSITION_FOOTER:
            band = (JRDesignBand) jrGroupChart.getGroupFooter();
        }
        return band;
    }

    /**
     * Creates the JRDesignChart from the DJChart. To do so it also creates needed variables and data-set
     * @param djChart
     * @return
     */
    protected JRDesignChart createChart(DJChart djChart) {
        JRDesignGroup jrGroupChart = getJRGroupFromDJGroup(djChart.getColumnsGroup());

        JRDesignChart chart = new JRDesignChart(new JRDesignStyle().getDefaultStyleProvider(), djChart.getType());
        JRDesignGroup parentGroup = getParent(jrGroupChart);
        List chartVariables = registerChartVariable(djChart);
        JRDesignChartDataset chartDataset = DataSetFactory.getDataset(djChart, jrGroupChart, parentGroup,
                chartVariables);
        chart.setDataset(chartDataset);
        interpeterOptions(djChart, chart);

        chart.setEvaluationTime(JRExpression.EVALUATION_TIME_GROUP);
        chart.setEvaluationGroup(jrGroupChart);
        return chart;
    }

    protected void interpeterOptions(DJChart djChart, JRDesignChart chart) {
        DJChartOptions options = djChart.getOptions();

        //size
        if (options.isCentered())
            chart.setWidth(getReport().getOptions().getPrintableWidth());
        else
            chart.setWidth(options.getWidth());

        chart.setHeight(options.getHeight());

        //position
        chart.setX(options.getX());
        chart.setPadding(10);
        chart.setY(options.getY());

        //options
        chart.setShowLegend(options.isShowLegend());
        chart.setBackcolor(options.getBackColor());
        chart.setBorder(options.getBorder());

        //colors
        if (options.getColors() != null) {
            int i = 1;
            for (Iterator iter = options.getColors().iterator(); iter.hasNext(); i++) {
                Color color = (Color) iter.next();
                chart.getPlot().getSeriesColors().add(new JRBaseChartPlot.JRBaseSeriesColor(i, color));
            }
        }
        //Chart-dependent options
        if (djChart.getType() == DJChart.BAR_CHART)
            ((JRDesignBarPlot) chart.getPlot()).setShowTickLabels(options.isShowLabels());
    }

    /**
     * Creates and registers a variable to be used by the Chart
     * @param chart Chart that needs a variable to be generated
     * @return the generated variables
     */
    protected List registerChartVariable(DJChart chart) {
        //FIXME aca hay que iterar por cada columna. Cambiar DJChart para que tome muchas
        JRDesignGroup group = getJRGroupFromDJGroup(chart.getColumnsGroup());
        List vars = new ArrayList();

        int serieNum = 0;
        for (Iterator iterator = chart.getColumns().iterator(); iterator.hasNext();) {
            AbstractColumn col = (AbstractColumn) iterator.next();

            Class clazz = null;
            try {
                clazz = Class.forName(((PropertyColumn) col).getColumnProperty().getValueClassName());
            } catch (ClassNotFoundException e) {
                throw new DJException("Exeption creating chart variable: " + e.getMessage(), e);
            }

            JRDesignExpression expression = new JRDesignExpression();
            //FIXME Only PropertyColumn allowed?
            expression.setText("$F{" + ((PropertyColumn) col).getColumnProperty().getProperty() + "}");
            expression.setValueClass(clazz);

            JRDesignVariable var = new JRDesignVariable();
            var.setValueClass(clazz);
            var.setExpression(expression);
            var.setCalculation(chart.getOperation());
            var.setResetGroup(group);
            var.setResetType(JRBaseVariable.RESET_TYPE_GROUP);

            //use the index as part of the name just because I may want 2
            //different types of chart from the very same column (with the same operation also) making the variables name to be duplicated
            int chartIndex = getReport().getCharts().indexOf(chart);
            var.setName("CHART_[" + chartIndex + "_s" + serieNum + "+]_" + group.getName() + "_" + col.getTitle()
                    + "_" + chart.getOperation());

            try {
                getDesign().addVariable(var);
                vars.add(var);
            } catch (JRException e) {
                throw new LayoutException(e.getMessage(), e);
            }
            serieNum++;
        }
        return vars;
    }

    protected JRDesignGroup getChartColumnsGroup(ar.com.fdvs.dj.domain.chart.DJChart djChart) {
        PropertyColumn columnsGroup = djChart.getDataset().getColumnsGroup();
        for (Iterator iterator = getReport().getColumnsGroups().iterator(); iterator.hasNext();) {
            DJGroup djGroup = (DJGroup) iterator.next();
            if (djGroup.getColumnToGroupBy() == columnsGroup)
                return getJRGroupFromDJGroup(djGroup);
        }
        return null;
    }

    protected JRDesignBand createGroupForChartAndGetBand(ar.com.fdvs.dj.domain.chart.DJChart djChart) {
        JRDesignGroup jrGroup = getChartColumnsGroup(djChart);
        JRDesignGroup parentGroup = getParent(jrGroup);
        JRDesignGroup jrGroupChart = null;
        try {
            jrGroupChart = (JRDesignGroup) BeanUtils.cloneBean(parentGroup);
            jrGroupChart.setGroupFooter(new JRDesignBand());
            jrGroupChart.setGroupHeader(new JRDesignBand());
            jrGroupChart.setName(jrGroupChart.getName() + "_Chart" + getReport().getCharts().indexOf(djChart));
        } catch (Exception e) {
            throw new DJException("Problem creating band for chart: " + e.getMessage(), e);
        }

        //Charts should be added in its own band (to ensure page break, etc)
        //To achieve that, we create a group and insert it right before to the criteria group.
        //I need to find parent group of the criteria group, clone and insert after.
        //The only precaution is that if parent == child (only one group in the report) the we insert before
        if (jrGroup.equals(parentGroup)) {
            jrGroupChart.setExpression(ExpressionUtils.createStringExpression("\"dummy_for_chart\""));
            getDesign().getGroupsList().add(getDesign().getGroupsList().indexOf(jrGroup), jrGroupChart);
        } else {
            int index = getDesign().getGroupsList().indexOf(parentGroup);
            getDesign().getGroupsList().add(index, jrGroupChart);
        }

        JRDesignBand band = null;
        switch (djChart.getOptions().getPosition()) {
        case DJChartOptions.POSITION_HEADER:
            band = (JRDesignBand) jrGroupChart.getGroupHeader();
            break;
        case DJChartOptions.POSITION_FOOTER:
            band = (JRDesignBand) jrGroupChart.getGroupFooter();
        }
        return band;
    }

    /**
     * Creates the JRDesignChart from the DJChart. To do so it also creates needed variables and data-set
     * @param djChart
     * @return
     */
    protected JRDesignChart createChart(ar.com.fdvs.dj.domain.chart.DJChart djChart, String name) {
        JRDesignGroup jrGroupChart = getChartColumnsGroup(djChart);
        JRDesignGroup parentGroup = getParent(jrGroupChart);
        Map chartVariables = registerChartVariable(djChart);
        return djChart.transform((DynamicJasperDesign) getDesign(), name, jrGroupChart, parentGroup, chartVariables,
                getReport().getOptions().getPrintableWidth());
    }

    /**
     * Creates and registers a variable to be used by the Chart
     * @param chart Chart that needs a variable to be generated
     * @return the generated variables
     */
    protected Map registerChartVariable(ar.com.fdvs.dj.domain.chart.DJChart chart) {
        //FIXME aca hay que iterar por cada columna. Cambiar DJChart para que tome muchas
        JRDesignGroup group = getChartColumnsGroup(chart);
        Map vars = new HashMap();

        int serieNum = 0;
        for (Iterator iterator = chart.getDataset().getColumns().iterator(); iterator.hasNext();) {
            AbstractColumn col = (AbstractColumn) iterator.next();

            Class clazz = null;
            try {
                clazz = Class.forName(col.getValueClassNameForExpression());
            } catch (ClassNotFoundException e) {
                throw new DJException("Exeption creating chart variable: " + e.getMessage(), e);
            }

            JRDesignExpression expression = new JRDesignExpression();
            //FIXME Only PropertyColumn allowed?
            expression.setText("$F{" + ((PropertyColumn) col).getColumnProperty().getProperty() + "}");
            expression.setValueClass(clazz);

            JRDesignVariable var = new JRDesignVariable();
            var.setValueClass(clazz);
            var.setExpression(expression);
            var.setCalculation(chart.getOperation());
            var.setResetGroup(group);
            var.setResetType(JRBaseVariable.RESET_TYPE_GROUP);

            //use the index as part of the name just because I may want 2
            //different types of chart from the very same column (with the same operation also) making the variables name to be duplicated
            int chartIndex = getReport().getNewCharts().indexOf(chart);
            var.setName("CHART_[" + chartIndex + "_s" + serieNum + "+]_" + group.getName() + "_" + col.getTitle()
                    + "_" + chart.getOperation());

            try {
                getDesign().addVariable(var);
                vars.put(col, var);
            } catch (JRException e) {
                throw new LayoutException(e.getMessage(), e);
            }
            serieNum++;
        }
        return vars;
    }

    /**
     * Finds the parent group of the given one and returns it
     * @param group Group for which the parent is needed
     * @return The parent group of the given one. If the given one is the first one, it returns the same group
     */
    protected JRDesignGroup getParent(JRDesignGroup group) {
        int index = realGroups.indexOf(group);
        JRDesignGroup parentGroup = (index > 0) ? (JRDesignGroup) realGroups.get(index - 1) : group;
        return parentGroup;
    }

    /***
     * Finds JRDesignGroup associated to a DJGroup
     * @param group
     * @return
     */
    protected JRDesignGroup getJRGroupFromDJGroup(DJGroup group) {
        int index = getReport().getColumnsGroups().indexOf(group);
        return (JRDesignGroup) realGroups.get(index);
    }

    protected DJGroup getDJGroup(AbstractColumn col) {
        Iterator it = getReport().getColumnsGroups().iterator();
        while (it.hasNext()) {
            DJGroup group = (DJGroup) it.next();
            if (group.getColumnToGroupBy().equals(col))
                return group;
        }
        return null;
    }

    /**
     * Returns true if at least one group is configured to show the column name in its header
     * @param groups
     * @return
     */
    protected boolean existsGroupWithColumnNames() {
        Iterator it = getReport().getColumnsGroups().iterator();
        while (it.hasNext()) {
            DJGroup group = (DJGroup) it.next();
            if (group.getLayout().isShowColumnName())
                return true;
        }
        return false;
    }

    protected JasperDesign getDesign() {
        return design;
    }

    protected void setDesign(JasperDesign design) {
        this.design = design;
    }

    protected DynamicReport getReport() {
        return report;
    }

    protected void setReport(DynamicReport report) {
        this.report = report;
    }

}