001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2014, by Object Refinery Limited and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022 * USA.
023 *
024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ----------------
028 * BarRenderer.java
029 * ----------------
030 * (C) Copyright 2002-2014, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   Christian W. Zuckschwerdt;
034 *                   Peter Kolb (patches 2497611, 2791407);
035 *
036 * Changes
037 * -------
038 * 14-Mar-2002 : Version 1 (DG);
039 * 23-May-2002 : Added tooltip generator to renderer (DG);
040 * 29-May-2002 : Moved tooltip generator to abstract super-class (DG);
041 * 25-Jun-2002 : Changed constructor to protected and removed redundant
042 *               code (DG);
043 * 26-Jun-2002 : Added axis to initialise method, and record upper and lower
044 *               clip values (DG);
045 * 24-Sep-2002 : Added getLegendItem() method (DG);
046 * 09-Oct-2002 : Modified constructor to include URL generator (DG);
047 * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
048 * 10-Jan-2003 : Moved get/setItemMargin() method up from subclasses (DG);
049 * 17-Jan-2003 : Moved plot classes into a separate package (DG);
050 * 25-Mar-2003 : Implemented Serializable (DG);
051 * 01-May-2003 : Modified clipping to allow for dual axes and datasets (DG);
052 * 12-May-2003 : Merged horizontal and vertical bar renderers (DG);
053 * 12-Jun-2003 : Updates for item labels (DG);
054 * 30-Jul-2003 : Modified entity constructor (CZ);
055 * 02-Sep-2003 : Changed initialise method to fix bug 790407 (DG);
056 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
057 * 07-Oct-2003 : Added renderer state (DG);
058 * 27-Oct-2003 : Merged drawHorizontalItem() and drawVerticalItem()
059 *               methods (DG);
060 * 28-Oct-2003 : Added support for gradient paint on bars (DG);
061 * 14-Nov-2003 : Added 'maxBarWidth' attribute (DG);
062 * 10-Feb-2004 : Small changes inside drawItem() method to ease cut-and-paste
063 *               overriding (DG);
064 * 19-Mar-2004 : Fixed bug introduced with separation of tool tip and item
065 *               label generators.  Fixed equals() method (DG);
066 * 11-May-2004 : Fix for null pointer exception (bug id 951127) (DG);
067 * 05-Nov-2004 : Modified drawItem() signature (DG);
068 * 26-Jan-2005 : Provided override for getLegendItem() method (DG);
069 * 20-Apr-2005 : Generate legend labels, tooltips and URLs (DG);
070 * 18-May-2005 : Added configurable base value (DG);
071 * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
072 * 01-Dec-2005 : Update legend item to use/not use outline (DG);
073 * ------------: JFreeChart 1.0.x ---------------------------------------------
074 * 06-Dec-2005 : Fixed bug 1374222 (JDK 1.4 specific code) (DG);
075 * 11-Jan-2006 : Fixed bug 1401856 (bad rendering for non-zero base) (DG);
076 * 04-Aug-2006 : Fixed bug 1467706 (missing item labels for zero value
077 *               bars) (DG);
078 * 04-Dec-2006 : Fixed bug in rendering to non-primary axis (DG);
079 * 13-Dec-2006 : Add support for GradientPaint display in legend items (DG);
080 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
081 * 11-May-2007 : Check for visibility in getLegendItem() (DG);
082 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
083 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
084 * 07-May-2008 : If minimumBarLength is > 0.0, extend the non-base end of the
085 *               bar (DG);
086 * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG);
087 * 24-Jun-2008 : Added barPainter mechanism (DG);
088 * 26-Jun-2008 : Added crosshair support (DG);
089 * 13-Aug-2008 : Added shadowPaint attribute (DG);
090 * 14-Jan-2009 : Added support for seriesVisible flags (PK);
091 * 03-Feb-2009 : Added defaultShadowsVisible flag - see patch 2511330 (PK);
092 * 03-Jul-2013 : Use ParamChecks (DG);
093 *
094 */
095
096package org.jfree.chart.renderer.category;
097
098import java.awt.BasicStroke;
099import java.awt.Color;
100import java.awt.Font;
101import java.awt.Graphics2D;
102import java.awt.Paint;
103import java.awt.Shape;
104import java.awt.Stroke;
105import java.awt.geom.Line2D;
106import java.awt.geom.Point2D;
107import java.awt.geom.Rectangle2D;
108import java.io.IOException;
109import java.io.ObjectInputStream;
110import java.io.ObjectOutputStream;
111import java.io.Serializable;
112
113import org.jfree.chart.LegendItem;
114import org.jfree.chart.axis.CategoryAxis;
115import org.jfree.chart.axis.ValueAxis;
116import org.jfree.chart.entity.EntityCollection;
117import org.jfree.chart.event.RendererChangeEvent;
118import org.jfree.chart.labels.CategoryItemLabelGenerator;
119import org.jfree.chart.labels.ItemLabelAnchor;
120import org.jfree.chart.labels.ItemLabelPosition;
121import org.jfree.chart.plot.CategoryPlot;
122import org.jfree.chart.plot.PlotOrientation;
123import org.jfree.chart.plot.PlotRenderingInfo;
124import org.jfree.chart.util.ParamChecks;
125import org.jfree.data.Range;
126import org.jfree.data.category.CategoryDataset;
127import org.jfree.io.SerialUtilities;
128import org.jfree.text.TextUtilities;
129import org.jfree.ui.GradientPaintTransformer;
130import org.jfree.ui.RectangleEdge;
131import org.jfree.ui.StandardGradientPaintTransformer;
132import org.jfree.util.ObjectUtilities;
133import org.jfree.util.PaintUtilities;
134import org.jfree.util.PublicCloneable;
135
136/**
137 * A {@link CategoryItemRenderer} that draws individual data items as bars.
138 * The example shown here is generated by the <code>BarChartDemo1.java</code>
139 * program included in the JFreeChart Demo Collection:
140 * <br><br>
141 * <img src="../../../../../images/BarRendererSample.png"
142 * alt="BarRendererSample.png">
143 */
144public class BarRenderer extends AbstractCategoryItemRenderer
145        implements Cloneable, PublicCloneable, Serializable {
146
147    /** For serialization. */
148    private static final long serialVersionUID = 6000649414965887481L;
149
150    /** The default item margin percentage. */
151    public static final double DEFAULT_ITEM_MARGIN = 0.20;
152
153    /**
154     * Constant that controls the minimum width before a bar has an outline
155     * drawn.
156     */
157    public static final double BAR_OUTLINE_WIDTH_THRESHOLD = 3.0;
158
159    /**
160     * The default bar painter assigned to each new instance of this renderer.
161     *
162     * @since 1.0.11
163     */
164    private static BarPainter defaultBarPainter = new GradientBarPainter();
165
166    /**
167     * Returns the default bar painter.
168     *
169     * @return The default bar painter.
170     *
171     * @since 1.0.11
172     */
173    public static BarPainter getDefaultBarPainter() {
174        return BarRenderer.defaultBarPainter;
175    }
176
177    /**
178     * Sets the default bar painter.
179     *
180     * @param painter  the painter (<code>null</code> not permitted).
181     *
182     * @since 1.0.11
183     */
184    public static void setDefaultBarPainter(BarPainter painter) {
185        ParamChecks.nullNotPermitted(painter, "painter");
186        BarRenderer.defaultBarPainter = painter;
187    }
188
189    /**
190     * The default value for the initialisation of the shadowsVisible flag.
191     */
192    private static boolean defaultShadowsVisible = true;
193
194    /**
195     * Returns the default value for the <code>shadowsVisible</code> flag.
196     *
197     * @return A boolean.
198     *
199     * @see #setDefaultShadowsVisible(boolean)
200     *
201     * @since 1.0.13
202     */
203    public static boolean getDefaultShadowsVisible() {
204        return BarRenderer.defaultShadowsVisible;
205    }
206
207    /**
208     * Sets the default value for the shadows visible flag.
209     *
210     * @param visible  the new value for the default.
211     *
212     * @see #getDefaultShadowsVisible()
213     *
214     * @since 1.0.13
215     */
216    public static void setDefaultShadowsVisible(boolean visible) {
217        BarRenderer.defaultShadowsVisible = visible;
218    }
219
220    /** The margin between items (bars) within a category. */
221    private double itemMargin;
222
223    /** A flag that controls whether or not bar outlines are drawn. */
224    private boolean drawBarOutline;
225
226    /** The maximum bar width as a percentage of the available space. */
227    private double maximumBarWidth;
228
229    /** The minimum bar length (in Java2D units). */
230    private double minimumBarLength;
231
232    /**
233     * An optional class used to transform gradient paint objects to fit each
234     * bar.
235     */
236    private GradientPaintTransformer gradientPaintTransformer;
237
238    /**
239     * The fallback position if a positive item label doesn't fit inside the
240     * bar.
241     */
242    private ItemLabelPosition positiveItemLabelPositionFallback;
243
244    /**
245     * The fallback position if a negative item label doesn't fit inside the
246     * bar.
247     */
248    private ItemLabelPosition negativeItemLabelPositionFallback;
249
250    /** The upper clip (axis) value for the axis. */
251    private double upperClip;
252    // TODO:  this needs to move into the renderer state
253
254    /** The lower clip (axis) value for the axis. */
255    private double lowerClip;
256    // TODO:  this needs to move into the renderer state
257
258    /** The base value for the bars (defaults to 0.0). */
259    private double base;
260
261    /**
262     * A flag that controls whether the base value is included in the range
263     * returned by the findRangeBounds() method.
264     */
265    private boolean includeBaseInRange;
266
267    /**
268     * The bar painter (never <code>null</code>).
269     *
270     * @since 1.0.11
271     */
272    private BarPainter barPainter;
273
274    /**
275     * The flag that controls whether or not shadows are drawn for the bars.
276     *
277     * @since 1.0.11
278     */
279    private boolean shadowsVisible;
280
281    /**
282     * The shadow paint.
283     *
284     * @since 1.0.11
285     */
286    private transient Paint shadowPaint;
287
288    /**
289     * The x-offset for the shadow effect.
290     *
291     * @since 1.0.11
292     */
293    private double shadowXOffset;
294
295    /**
296     * The y-offset for the shadow effect.
297     *
298     * @since 1.0.11
299     */
300    private double shadowYOffset;
301
302    /**
303     * Creates a new bar renderer with default settings.
304     */
305    public BarRenderer() {
306        super();
307        this.base = 0.0;
308        this.includeBaseInRange = true;
309        this.itemMargin = DEFAULT_ITEM_MARGIN;
310        this.drawBarOutline = false;
311        this.maximumBarWidth = 1.0;
312            // 100 percent, so it will not apply unless changed
313        this.positiveItemLabelPositionFallback = null;
314        this.negativeItemLabelPositionFallback = null;
315        this.gradientPaintTransformer = new StandardGradientPaintTransformer();
316        this.minimumBarLength = 0.0;
317        setBaseLegendShape(new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0));
318        this.barPainter = getDefaultBarPainter();
319        this.shadowsVisible = getDefaultShadowsVisible();
320        this.shadowPaint = Color.gray;
321        this.shadowXOffset = 4.0;
322        this.shadowYOffset = 4.0;
323    }
324
325    /**
326     * Returns the base value for the bars.  The default value is
327     * <code>0.0</code>.
328     *
329     * @return The base value for the bars.
330     *
331     * @see #setBase(double)
332     */
333    public double getBase() {
334        return this.base;
335    }
336
337    /**
338     * Sets the base value for the bars and sends a {@link RendererChangeEvent}
339     * to all registered listeners.
340     *
341     * @param base  the new base value.
342     *
343     * @see #getBase()
344     */
345    public void setBase(double base) {
346        this.base = base;
347        fireChangeEvent();
348    }
349
350    /**
351     * Returns the item margin as a percentage of the available space for all
352     * bars.
353     *
354     * @return The margin percentage (where 0.10 is ten percent).
355     *
356     * @see #setItemMargin(double)
357     */
358    public double getItemMargin() {
359        return this.itemMargin;
360    }
361
362    /**
363     * Sets the item margin and sends a {@link RendererChangeEvent} to all
364     * registered listeners.  The value is expressed as a percentage of the
365     * available width for plotting all the bars, with the resulting amount to
366     * be distributed between all the bars evenly.
367     *
368     * @param percent  the margin (where 0.10 is ten percent).
369     *
370     * @see #getItemMargin()
371     */
372    public void setItemMargin(double percent) {
373        this.itemMargin = percent;
374        fireChangeEvent();
375    }
376
377    /**
378     * Returns a flag that controls whether or not bar outlines are drawn.
379     *
380     * @return A boolean.
381     *
382     * @see #setDrawBarOutline(boolean)
383     */
384    public boolean isDrawBarOutline() {
385        return this.drawBarOutline;
386    }
387
388    /**
389     * Sets the flag that controls whether or not bar outlines are drawn and
390     * sends a {@link RendererChangeEvent} to all registered listeners.
391     *
392     * @param draw  the flag.
393     *
394     * @see #isDrawBarOutline()
395     */
396    public void setDrawBarOutline(boolean draw) {
397        this.drawBarOutline = draw;
398        fireChangeEvent();
399    }
400
401    /**
402     * Returns the maximum bar width, as a percentage of the available drawing
403     * space.
404     *
405     * @return The maximum bar width.
406     *
407     * @see #setMaximumBarWidth(double)
408     */
409    public double getMaximumBarWidth() {
410        return this.maximumBarWidth;
411    }
412
413    /**
414     * Sets the maximum bar width, which is specified as a percentage of the
415     * available space for all bars, and sends a {@link RendererChangeEvent} to
416     * all registered listeners.
417     *
418     * @param percent  the percent (where 0.05 is five percent).
419     *
420     * @see #getMaximumBarWidth()
421     */
422    public void setMaximumBarWidth(double percent) {
423        this.maximumBarWidth = percent;
424        fireChangeEvent();
425    }
426
427    /**
428     * Returns the minimum bar length (in Java2D units).  The default value is
429     * 0.0.
430     *
431     * @return The minimum bar length.
432     *
433     * @see #setMinimumBarLength(double)
434     */
435    public double getMinimumBarLength() {
436        return this.minimumBarLength;
437    }
438
439    /**
440     * Sets the minimum bar length and sends a {@link RendererChangeEvent} to
441     * all registered listeners.  The minimum bar length is specified in Java2D
442     * units, and can be used to prevent bars that represent very small data
443     * values from disappearing when drawn on the screen.  Typically you would
444     * set this to (say) 0.5 or 1.0 Java 2D units.  Use this attribute with
445     * caution, however, because setting it to a non-zero value will
446     * artificially increase the length of bars representing small values,
447     * which may misrepresent your data.
448     *
449     * @param min  the minimum bar length (in Java2D units, must be &gt;= 0.0).
450     *
451     * @see #getMinimumBarLength()
452     */
453    public void setMinimumBarLength(double min) {
454        if (min < 0.0) {
455            throw new IllegalArgumentException("Requires 'min' >= 0.0");
456        }
457        this.minimumBarLength = min;
458        fireChangeEvent();
459    }
460
461    /**
462     * Returns the gradient paint transformer (an object used to transform
463     * gradient paint objects to fit each bar).
464     *
465     * @return A transformer (<code>null</code> possible).
466     *
467     * @see #setGradientPaintTransformer(GradientPaintTransformer)
468     */
469    public GradientPaintTransformer getGradientPaintTransformer() {
470        return this.gradientPaintTransformer;
471    }
472
473    /**
474     * Sets the gradient paint transformer and sends a
475     * {@link RendererChangeEvent} to all registered listeners.
476     *
477     * @param transformer  the transformer (<code>null</code> permitted).
478     *
479     * @see #getGradientPaintTransformer()
480     */
481    public void setGradientPaintTransformer(
482            GradientPaintTransformer transformer) {
483        this.gradientPaintTransformer = transformer;
484        fireChangeEvent();
485    }
486
487    /**
488     * Returns the fallback position for positive item labels that don't fit
489     * within a bar.
490     *
491     * @return The fallback position (<code>null</code> possible).
492     *
493     * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
494     */
495    public ItemLabelPosition getPositiveItemLabelPositionFallback() {
496        return this.positiveItemLabelPositionFallback;
497    }
498
499    /**
500     * Sets the fallback position for positive item labels that don't fit
501     * within a bar, and sends a {@link RendererChangeEvent} to all registered
502     * listeners.
503     *
504     * @param position  the position (<code>null</code> permitted).
505     *
506     * @see #getPositiveItemLabelPositionFallback()
507     */
508    public void setPositiveItemLabelPositionFallback(
509            ItemLabelPosition position) {
510        this.positiveItemLabelPositionFallback = position;
511        fireChangeEvent();
512    }
513
514    /**
515     * Returns the fallback position for negative item labels that don't fit
516     * within a bar.
517     *
518     * @return The fallback position (<code>null</code> possible).
519     *
520     * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
521     */
522    public ItemLabelPosition getNegativeItemLabelPositionFallback() {
523        return this.negativeItemLabelPositionFallback;
524    }
525
526    /**
527     * Sets the fallback position for negative item labels that don't fit
528     * within a bar, and sends a {@link RendererChangeEvent} to all registered
529     * listeners.
530     *
531     * @param position  the position (<code>null</code> permitted).
532     *
533     * @see #getNegativeItemLabelPositionFallback()
534     */
535    public void setNegativeItemLabelPositionFallback(
536            ItemLabelPosition position) {
537        this.negativeItemLabelPositionFallback = position;
538        fireChangeEvent();
539    }
540
541    /**
542     * Returns the flag that controls whether or not the base value for the
543     * bars is included in the range calculated by
544     * {@link #findRangeBounds(CategoryDataset)}.
545     *
546     * @return <code>true</code> if the base is included in the range, and
547     *         <code>false</code> otherwise.
548     *
549     * @since 1.0.1
550     *
551     * @see #setIncludeBaseInRange(boolean)
552     */
553    public boolean getIncludeBaseInRange() {
554        return this.includeBaseInRange;
555    }
556
557    /**
558     * Sets the flag that controls whether or not the base value for the bars
559     * is included in the range calculated by
560     * {@link #findRangeBounds(CategoryDataset)}.  If the flag is changed,
561     * a {@link RendererChangeEvent} is sent to all registered listeners.
562     *
563     * @param include  the new value for the flag.
564     *
565     * @since 1.0.1
566     *
567     * @see #getIncludeBaseInRange()
568     */
569    public void setIncludeBaseInRange(boolean include) {
570        if (this.includeBaseInRange != include) {
571            this.includeBaseInRange = include;
572            fireChangeEvent();
573        }
574    }
575
576    /**
577     * Returns the bar painter.
578     *
579     * @return The bar painter (never <code>null</code>).
580     *
581     * @see #setBarPainter(BarPainter)
582     *
583     * @since 1.0.11
584     */
585    public BarPainter getBarPainter() {
586        return this.barPainter;
587    }
588
589    /**
590     * Sets the bar painter for this renderer and sends a
591     * {@link RendererChangeEvent} to all registered listeners.
592     *
593     * @param painter  the painter (<code>null</code> not permitted).
594     *
595     * @see #getBarPainter()
596     *
597     * @since 1.0.11
598     */
599    public void setBarPainter(BarPainter painter) {
600        ParamChecks.nullNotPermitted(painter, "painter");
601        this.barPainter = painter;
602        fireChangeEvent();
603    }
604
605    /**
606     * Returns the flag that controls whether or not shadows are drawn for
607     * the bars.
608     *
609     * @return A boolean.
610     *
611     * @since 1.0.11
612     */
613    public boolean getShadowsVisible() {
614        return this.shadowsVisible;
615    }
616
617    /**
618     * Sets the flag that controls whether or not shadows are
619     * drawn by the renderer.
620     *
621     * @param visible  the new flag value.
622     *
623     * @since 1.0.11
624     */
625    public void setShadowVisible(boolean visible) {
626        this.shadowsVisible = visible;
627        fireChangeEvent();
628    }
629
630    /**
631     * Returns the shadow paint.
632     *
633     * @return The shadow paint.
634     *
635     * @see #setShadowPaint(Paint)
636     *
637     * @since 1.0.11
638     */
639    public Paint getShadowPaint() {
640        return this.shadowPaint;
641    }
642
643    /**
644     * Sets the shadow paint and sends a {@link RendererChangeEvent} to all
645     * registered listeners.
646     *
647     * @param paint  the paint (<code>null</code> not permitted).
648     *
649     * @see #getShadowPaint()
650     *
651     * @since 1.0.11
652     */
653    public void setShadowPaint(Paint paint) {
654        ParamChecks.nullNotPermitted(paint, "paint");
655        this.shadowPaint = paint;
656        fireChangeEvent();
657    }
658
659    /**
660     * Returns the shadow x-offset.
661     *
662     * @return The shadow x-offset.
663     *
664     * @since 1.0.11
665     */
666    public double getShadowXOffset() {
667        return this.shadowXOffset;
668    }
669
670    /**
671     * Sets the x-offset for the bar shadow and sends a
672     * {@link RendererChangeEvent} to all registered listeners.
673     *
674     * @param offset  the offset.
675     *
676     * @since 1.0.11
677     */
678    public void setShadowXOffset(double offset) {
679        this.shadowXOffset = offset;
680        fireChangeEvent();
681    }
682
683    /**
684     * Returns the shadow y-offset.
685     *
686     * @return The shadow y-offset.
687     *
688     * @since 1.0.11
689     */
690    public double getShadowYOffset() {
691        return this.shadowYOffset;
692    }
693
694    /**
695     * Sets the y-offset for the bar shadow and sends a
696     * {@link RendererChangeEvent} to all registered listeners.
697     *
698     * @param offset  the offset.
699     *
700     * @since 1.0.11
701     */
702    public void setShadowYOffset(double offset) {
703        this.shadowYOffset = offset;
704        fireChangeEvent();
705    }
706
707    /**
708     * Returns the lower clip value.  This value is recalculated in the
709     * initialise() method.
710     *
711     * @return The value.
712     */
713    public double getLowerClip() {
714        // TODO:  this attribute should be transferred to the renderer state.
715        return this.lowerClip;
716    }
717
718    /**
719     * Returns the upper clip value.  This value is recalculated in the
720     * initialise() method.
721     *
722     * @return The value.
723     */
724    public double getUpperClip() {
725        // TODO:  this attribute should be transferred to the renderer state.
726        return this.upperClip;
727    }
728
729    /**
730     * Initialises the renderer and returns a state object that will be passed
731     * to subsequent calls to the drawItem method.  This method gets called
732     * once at the start of the process of drawing a chart.
733     *
734     * @param g2  the graphics device.
735     * @param dataArea  the area in which the data is to be plotted.
736     * @param plot  the plot.
737     * @param rendererIndex  the renderer index.
738     * @param info  collects chart rendering information for return to caller.
739     *
740     * @return The renderer state.
741     */
742    @Override
743    public CategoryItemRendererState initialise(Graphics2D g2, 
744            Rectangle2D dataArea, CategoryPlot plot, int rendererIndex,
745            PlotRenderingInfo info) {
746
747        CategoryItemRendererState state = super.initialise(g2, dataArea, plot,
748                rendererIndex, info);
749
750        // get the clipping values...
751        ValueAxis rangeAxis = plot.getRangeAxisForDataset(rendererIndex);
752        this.lowerClip = rangeAxis.getRange().getLowerBound();
753        this.upperClip = rangeAxis.getRange().getUpperBound();
754
755        // calculate the bar width
756        calculateBarWidth(plot, dataArea, rendererIndex, state);
757
758        return state;
759
760    }
761
762    /**
763     * Calculates the bar width and stores it in the renderer state.
764     *
765     * @param plot  the plot.
766     * @param dataArea  the data area.
767     * @param rendererIndex  the renderer index.
768     * @param state  the renderer state.
769     */
770    protected void calculateBarWidth(CategoryPlot plot,
771                                     Rectangle2D dataArea,
772                                     int rendererIndex,
773                                     CategoryItemRendererState state) {
774
775        CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
776        CategoryDataset dataset = plot.getDataset(rendererIndex);
777        if (dataset != null) {
778            int columns = dataset.getColumnCount();
779            int rows = state.getVisibleSeriesCount() >= 0
780                    ? state.getVisibleSeriesCount() : dataset.getRowCount();
781            double space = 0.0;
782            PlotOrientation orientation = plot.getOrientation();
783            if (orientation == PlotOrientation.HORIZONTAL) {
784                space = dataArea.getHeight();
785            }
786            else if (orientation == PlotOrientation.VERTICAL) {
787                space = dataArea.getWidth();
788            }
789            double maxWidth = space * getMaximumBarWidth();
790            double categoryMargin = 0.0;
791            double currentItemMargin = 0.0;
792            if (columns > 1) {
793                categoryMargin = domainAxis.getCategoryMargin();
794            }
795            if (rows > 1) {
796                currentItemMargin = getItemMargin();
797            }
798            double used = space * (1 - domainAxis.getLowerMargin()
799                                     - domainAxis.getUpperMargin()
800                                     - categoryMargin - currentItemMargin);
801            if ((rows * columns) > 0) {
802                state.setBarWidth(Math.min(used / (rows * columns), maxWidth));
803            }
804            else {
805                state.setBarWidth(Math.min(used, maxWidth));
806            }
807        }
808    }
809
810    /**
811     * Calculates the coordinate of the first "side" of a bar.  This will be
812     * the minimum x-coordinate for a vertical bar, and the minimum
813     * y-coordinate for a horizontal bar.
814     *
815     * @param plot  the plot.
816     * @param orientation  the plot orientation.
817     * @param dataArea  the data area.
818     * @param domainAxis  the domain axis.
819     * @param state  the renderer state (has the bar width precalculated).
820     * @param row  the row index.
821     * @param column  the column index.
822     *
823     * @return The coordinate.
824     */
825    protected double calculateBarW0(CategoryPlot plot, 
826            PlotOrientation orientation, Rectangle2D dataArea, 
827            CategoryAxis domainAxis, CategoryItemRendererState state,
828            int row, int column) {
829        // calculate bar width...
830        double space;
831        if (orientation == PlotOrientation.HORIZONTAL) {
832            space = dataArea.getHeight();
833        }
834        else {
835            space = dataArea.getWidth();
836        }
837        double barW0 = domainAxis.getCategoryStart(column, getColumnCount(),
838                dataArea, plot.getDomainAxisEdge());
839        int seriesCount = state.getVisibleSeriesCount() >= 0
840                ? state.getVisibleSeriesCount() : getRowCount();
841        int categoryCount = getColumnCount();
842        if (seriesCount > 1) {
843            double seriesGap = space * getItemMargin()
844                               / (categoryCount * (seriesCount - 1));
845            double seriesW = calculateSeriesWidth(space, domainAxis,
846                    categoryCount, seriesCount);
847            barW0 = barW0 + row * (seriesW + seriesGap)
848                          + (seriesW / 2.0) - (state.getBarWidth() / 2.0);
849        }
850        else {
851            barW0 = domainAxis.getCategoryMiddle(column, getColumnCount(),
852                    dataArea, plot.getDomainAxisEdge()) - state.getBarWidth()
853                    / 2.0;
854        }
855        return barW0;
856    }
857
858    /**
859     * Calculates the coordinates for the length of a single bar.
860     *
861     * @param value  the value represented by the bar.
862     *
863     * @return The coordinates for each end of the bar (or <code>null</code> if
864     *         the bar is not visible for the current axis range).
865     */
866    protected double[] calculateBarL0L1(double value) {
867        double lclip = getLowerClip();
868        double uclip = getUpperClip();
869        double barLow = Math.min(this.base, value);
870        double barHigh = Math.max(this.base, value);
871        if (barHigh < lclip) {  // bar is not visible
872            return null;
873        }
874        if (barLow > uclip) {   // bar is not visible
875            return null;
876        }
877        barLow = Math.max(barLow, lclip);
878        barHigh = Math.min(barHigh, uclip);
879        return new double[] {barLow, barHigh};
880    }
881
882    /**
883     * Returns the range of values the renderer requires to display all the
884     * items from the specified dataset.  This takes into account the range
885     * of values in the dataset, plus the flag that determines whether or not
886     * the base value for the bars should be included in the range.
887     *
888     * @param dataset  the dataset (<code>null</code> permitted).
889     * @param includeInterval  include the interval if the dataset has one?
890     *
891     * @return The range (or <code>null</code> if the dataset is
892     *         <code>null</code> or empty).
893     */
894    @Override
895    public Range findRangeBounds(CategoryDataset dataset,
896            boolean includeInterval) {
897        if (dataset == null) {
898            return null;
899        }
900        Range result = super.findRangeBounds(dataset, includeInterval);
901        if (result != null) {
902            if (this.includeBaseInRange) {
903                result = Range.expandToInclude(result, this.base);
904            }
905        }
906        return result;
907    }
908
909    /**
910     * Returns a legend item for a series.
911     *
912     * @param datasetIndex  the dataset index (zero-based).
913     * @param series  the series index (zero-based).
914     *
915     * @return The legend item (possibly <code>null</code>).
916     */
917    @Override
918    public LegendItem getLegendItem(int datasetIndex, int series) {
919
920        CategoryPlot cp = getPlot();
921        if (cp == null) {
922            return null;
923        }
924
925        // check that a legend item needs to be displayed...
926        if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
927            return null;
928        }
929
930        CategoryDataset dataset = cp.getDataset(datasetIndex);
931        String label = getLegendItemLabelGenerator().generateLabel(dataset,
932                series);
933        String description = label;
934        String toolTipText = null;
935        if (getLegendItemToolTipGenerator() != null) {
936            toolTipText = getLegendItemToolTipGenerator().generateLabel(
937                    dataset, series);
938        }
939        String urlText = null;
940        if (getLegendItemURLGenerator() != null) {
941            urlText = getLegendItemURLGenerator().generateLabel(dataset,
942                    series);
943        }
944        Shape shape = lookupLegendShape(series);
945        Paint paint = lookupSeriesPaint(series);
946        Paint outlinePaint = lookupSeriesOutlinePaint(series);
947        Stroke outlineStroke = lookupSeriesOutlineStroke(series);
948
949        LegendItem result = new LegendItem(label, description, toolTipText,
950                urlText, true, shape, true, paint, isDrawBarOutline(),
951                outlinePaint, outlineStroke, false, new Line2D.Float(),
952                new BasicStroke(1.0f), Color.black);
953        result.setLabelFont(lookupLegendTextFont(series));
954        Paint labelPaint = lookupLegendTextPaint(series);
955        if (labelPaint != null) {
956            result.setLabelPaint(labelPaint);
957        }
958        result.setDataset(dataset);
959        result.setDatasetIndex(datasetIndex);
960        result.setSeriesKey(dataset.getRowKey(series));
961        result.setSeriesIndex(series);
962        if (this.gradientPaintTransformer != null) {
963            result.setFillPaintTransformer(this.gradientPaintTransformer);
964        }
965        return result;
966    }
967
968    /**
969     * Draws the bar for a single (series, category) data item.
970     *
971     * @param g2  the graphics device.
972     * @param state  the renderer state.
973     * @param dataArea  the data area.
974     * @param plot  the plot.
975     * @param domainAxis  the domain axis.
976     * @param rangeAxis  the range axis.
977     * @param dataset  the dataset.
978     * @param row  the row index (zero-based).
979     * @param column  the column index (zero-based).
980     * @param pass  the pass index.
981     */
982    @Override
983    public void drawItem(Graphics2D g2, CategoryItemRendererState state,
984            Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
985            ValueAxis rangeAxis, CategoryDataset dataset, int row,
986            int column, int pass) {
987
988        // nothing is drawn if the row index is not included in the list with
989        // the indices of the visible rows...
990        int visibleRow = state.getVisibleSeriesIndex(row);
991        if (visibleRow < 0) {
992            return;
993        }
994        // nothing is drawn for null values...
995        Number dataValue = dataset.getValue(row, column);
996        if (dataValue == null) {
997            return;
998        }
999
1000        final double value = dataValue.doubleValue();
1001        PlotOrientation orientation = plot.getOrientation();
1002        double barW0 = calculateBarW0(plot, orientation, dataArea, domainAxis,
1003                state, visibleRow, column);
1004        double[] barL0L1 = calculateBarL0L1(value);
1005        if (barL0L1 == null) {
1006            return;  // the bar is not visible
1007        }
1008
1009        RectangleEdge edge = plot.getRangeAxisEdge();
1010        double transL0 = rangeAxis.valueToJava2D(barL0L1[0], dataArea, edge);
1011        double transL1 = rangeAxis.valueToJava2D(barL0L1[1], dataArea, edge);
1012
1013        // in the following code, barL0 is (in Java2D coordinates) the LEFT
1014        // end of the bar for a horizontal bar chart, and the TOP end of the
1015        // bar for a vertical bar chart.  Whether this is the BASE of the bar
1016        // or not depends also on (a) whether the data value is 'negative'
1017        // relative to the base value and (b) whether or not the range axis is
1018        // inverted.  This only matters if/when we apply the minimumBarLength
1019        // attribute, because we should extend the non-base end of the bar
1020        boolean positive = (value >= this.base);
1021        boolean inverted = rangeAxis.isInverted();
1022        double barL0 = Math.min(transL0, transL1);
1023        double barLength = Math.abs(transL1 - transL0);
1024        double barLengthAdj = 0.0;
1025        if (barLength > 0.0 && barLength < getMinimumBarLength()) {
1026            barLengthAdj = getMinimumBarLength() - barLength;
1027        }
1028        double barL0Adj = 0.0;
1029        RectangleEdge barBase;
1030        if (orientation == PlotOrientation.HORIZONTAL) {
1031            if (positive && inverted || !positive && !inverted) {
1032                barL0Adj = barLengthAdj;
1033                barBase = RectangleEdge.RIGHT;
1034            }
1035            else {
1036                barBase = RectangleEdge.LEFT;
1037            }
1038        }
1039        else {
1040            if (positive && !inverted || !positive && inverted) {
1041                barL0Adj = barLengthAdj;
1042                barBase = RectangleEdge.BOTTOM;
1043            }
1044            else {
1045                barBase = RectangleEdge.TOP;
1046            }
1047        }
1048
1049        // draw the bar...
1050        Rectangle2D bar;
1051        if (orientation == PlotOrientation.HORIZONTAL) {
1052            bar = new Rectangle2D.Double(barL0 - barL0Adj, barW0,
1053                    barLength + barLengthAdj, state.getBarWidth());
1054        }
1055        else {
1056            bar = new Rectangle2D.Double(barW0, barL0 - barL0Adj,
1057                    state.getBarWidth(), barLength + barLengthAdj);
1058        }
1059        if (getShadowsVisible()) {
1060            this.barPainter.paintBarShadow(g2, this, row, column, bar, barBase,
1061                true);
1062        }
1063        this.barPainter.paintBar(g2, this, row, column, bar, barBase);
1064
1065        CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
1066                column);
1067        if (generator != null && isItemLabelVisible(row, column)) {
1068            drawItemLabel(g2, dataset, row, column, plot, generator, bar,
1069                    (value < 0.0));
1070        }
1071
1072        // submit the current data point as a crosshair candidate
1073        int datasetIndex = plot.indexOf(dataset);
1074        updateCrosshairValues(state.getCrosshairState(),
1075                dataset.getRowKey(row), dataset.getColumnKey(column), value,
1076                datasetIndex, barW0, barL0, orientation);
1077
1078        // add an item entity, if this information is being collected
1079        EntityCollection entities = state.getEntityCollection();
1080        if (entities != null) {
1081            addItemEntity(entities, dataset, row, column, bar);
1082        }
1083
1084    }
1085
1086    /**
1087     * Calculates the available space for each series.
1088     *
1089     * @param space  the space along the entire axis (in Java2D units).
1090     * @param axis  the category axis.
1091     * @param categories  the number of categories.
1092     * @param series  the number of series.
1093     *
1094     * @return The width of one series.
1095     */
1096    protected double calculateSeriesWidth(double space, CategoryAxis axis,
1097                                          int categories, int series) {
1098        double factor = 1.0 - getItemMargin() - axis.getLowerMargin()
1099                            - axis.getUpperMargin();
1100        if (categories > 1) {
1101            factor = factor - axis.getCategoryMargin();
1102        }
1103        return (space * factor) / (categories * series);
1104    }
1105
1106    /**
1107     * Draws an item label.  This method is overridden so that the bar can be
1108     * used to calculate the label anchor point.
1109     *
1110     * @param g2  the graphics device.
1111     * @param data  the dataset.
1112     * @param row  the row.
1113     * @param column  the column.
1114     * @param plot  the plot.
1115     * @param generator  the label generator.
1116     * @param bar  the bar.
1117     * @param negative  a flag indicating a negative value.
1118     */
1119    protected void drawItemLabel(Graphics2D g2,
1120                                 CategoryDataset data,
1121                                 int row,
1122                                 int column,
1123                                 CategoryPlot plot,
1124                                 CategoryItemLabelGenerator generator,
1125                                 Rectangle2D bar,
1126                                 boolean negative) {
1127
1128        String label = generator.generateLabel(data, row, column);
1129        if (label == null) {
1130            return;  // nothing to do
1131        }
1132
1133        Font labelFont = getItemLabelFont(row, column);
1134        g2.setFont(labelFont);
1135        Paint paint = getItemLabelPaint(row, column);
1136        g2.setPaint(paint);
1137
1138        // find out where to place the label...
1139        ItemLabelPosition position;
1140        if (!negative) {
1141            position = getPositiveItemLabelPosition(row, column);
1142        }
1143        else {
1144            position = getNegativeItemLabelPosition(row, column);
1145        }
1146
1147        // work out the label anchor point...
1148        Point2D anchorPoint = calculateLabelAnchorPoint(
1149                position.getItemLabelAnchor(), bar, plot.getOrientation());
1150
1151        if (isInternalAnchor(position.getItemLabelAnchor())) {
1152            Shape bounds = TextUtilities.calculateRotatedStringBounds(label,
1153                    g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(),
1154                    position.getTextAnchor(), position.getAngle(),
1155                    position.getRotationAnchor());
1156
1157            if (bounds != null) {
1158                if (!bar.contains(bounds.getBounds2D())) {
1159                    if (!negative) {
1160                        position = getPositiveItemLabelPositionFallback();
1161                    }
1162                    else {
1163                        position = getNegativeItemLabelPositionFallback();
1164                    }
1165                    if (position != null) {
1166                        anchorPoint = calculateLabelAnchorPoint(
1167                                position.getItemLabelAnchor(), bar,
1168                                plot.getOrientation());
1169                    }
1170                }
1171            }
1172
1173        }
1174
1175        if (position != null) {
1176            TextUtilities.drawRotatedString(label, g2,
1177                    (float) anchorPoint.getX(), (float) anchorPoint.getY(),
1178                    position.getTextAnchor(), position.getAngle(),
1179                    position.getRotationAnchor());
1180        }
1181    }
1182
1183    /**
1184     * Calculates the item label anchor point.
1185     *
1186     * @param anchor  the anchor.
1187     * @param bar  the bar.
1188     * @param orientation  the plot orientation.
1189     *
1190     * @return The anchor point.
1191     */
1192    private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor,
1193                                              Rectangle2D bar,
1194                                              PlotOrientation orientation) {
1195
1196        Point2D result = null;
1197        double offset = getItemLabelAnchorOffset();
1198        double x0 = bar.getX() - offset;
1199        double x1 = bar.getX();
1200        double x2 = bar.getX() + offset;
1201        double x3 = bar.getCenterX();
1202        double x4 = bar.getMaxX() - offset;
1203        double x5 = bar.getMaxX();
1204        double x6 = bar.getMaxX() + offset;
1205
1206        double y0 = bar.getMaxY() + offset;
1207        double y1 = bar.getMaxY();
1208        double y2 = bar.getMaxY() - offset;
1209        double y3 = bar.getCenterY();
1210        double y4 = bar.getMinY() + offset;
1211        double y5 = bar.getMinY();
1212        double y6 = bar.getMinY() - offset;
1213
1214        if (anchor == ItemLabelAnchor.CENTER) {
1215            result = new Point2D.Double(x3, y3);
1216        }
1217        else if (anchor == ItemLabelAnchor.INSIDE1) {
1218            result = new Point2D.Double(x4, y4);
1219        }
1220        else if (anchor == ItemLabelAnchor.INSIDE2) {
1221            result = new Point2D.Double(x4, y4);
1222        }
1223        else if (anchor == ItemLabelAnchor.INSIDE3) {
1224            result = new Point2D.Double(x4, y3);
1225        }
1226        else if (anchor == ItemLabelAnchor.INSIDE4) {
1227            result = new Point2D.Double(x4, y2);
1228        }
1229        else if (anchor == ItemLabelAnchor.INSIDE5) {
1230            result = new Point2D.Double(x4, y2);
1231        }
1232        else if (anchor == ItemLabelAnchor.INSIDE6) {
1233            result = new Point2D.Double(x3, y2);
1234        }
1235        else if (anchor == ItemLabelAnchor.INSIDE7) {
1236            result = new Point2D.Double(x2, y2);
1237        }
1238        else if (anchor == ItemLabelAnchor.INSIDE8) {
1239            result = new Point2D.Double(x2, y2);
1240        }
1241        else if (anchor == ItemLabelAnchor.INSIDE9) {
1242            result = new Point2D.Double(x2, y3);
1243        }
1244        else if (anchor == ItemLabelAnchor.INSIDE10) {
1245            result = new Point2D.Double(x2, y4);
1246        }
1247        else if (anchor == ItemLabelAnchor.INSIDE11) {
1248            result = new Point2D.Double(x2, y4);
1249        }
1250        else if (anchor == ItemLabelAnchor.INSIDE12) {
1251            result = new Point2D.Double(x3, y4);
1252        }
1253        else if (anchor == ItemLabelAnchor.OUTSIDE1) {
1254            result = new Point2D.Double(x5, y6);
1255        }
1256        else if (anchor == ItemLabelAnchor.OUTSIDE2) {
1257            result = new Point2D.Double(x6, y5);
1258        }
1259        else if (anchor == ItemLabelAnchor.OUTSIDE3) {
1260            result = new Point2D.Double(x6, y3);
1261        }
1262        else if (anchor == ItemLabelAnchor.OUTSIDE4) {
1263            result = new Point2D.Double(x6, y1);
1264        }
1265        else if (anchor == ItemLabelAnchor.OUTSIDE5) {
1266            result = new Point2D.Double(x5, y0);
1267        }
1268        else if (anchor == ItemLabelAnchor.OUTSIDE6) {
1269            result = new Point2D.Double(x3, y0);
1270        }
1271        else if (anchor == ItemLabelAnchor.OUTSIDE7) {
1272            result = new Point2D.Double(x1, y0);
1273        }
1274        else if (anchor == ItemLabelAnchor.OUTSIDE8) {
1275            result = new Point2D.Double(x0, y1);
1276        }
1277        else if (anchor == ItemLabelAnchor.OUTSIDE9) {
1278            result = new Point2D.Double(x0, y3);
1279        }
1280        else if (anchor == ItemLabelAnchor.OUTSIDE10) {
1281            result = new Point2D.Double(x0, y5);
1282        }
1283        else if (anchor == ItemLabelAnchor.OUTSIDE11) {
1284            result = new Point2D.Double(x1, y6);
1285        }
1286        else if (anchor == ItemLabelAnchor.OUTSIDE12) {
1287            result = new Point2D.Double(x3, y6);
1288        }
1289
1290        return result;
1291
1292    }
1293
1294    /**
1295     * Returns <code>true</code> if the specified anchor point is inside a bar.
1296     *
1297     * @param anchor  the anchor point.
1298     *
1299     * @return A boolean.
1300     */
1301    private boolean isInternalAnchor(ItemLabelAnchor anchor) {
1302        return anchor == ItemLabelAnchor.CENTER
1303               || anchor == ItemLabelAnchor.INSIDE1
1304               || anchor == ItemLabelAnchor.INSIDE2
1305               || anchor == ItemLabelAnchor.INSIDE3
1306               || anchor == ItemLabelAnchor.INSIDE4
1307               || anchor == ItemLabelAnchor.INSIDE5
1308               || anchor == ItemLabelAnchor.INSIDE6
1309               || anchor == ItemLabelAnchor.INSIDE7
1310               || anchor == ItemLabelAnchor.INSIDE8
1311               || anchor == ItemLabelAnchor.INSIDE9
1312               || anchor == ItemLabelAnchor.INSIDE10
1313               || anchor == ItemLabelAnchor.INSIDE11
1314               || anchor == ItemLabelAnchor.INSIDE12;
1315    }
1316
1317    /**
1318     * Tests this instance for equality with an arbitrary object.
1319     *
1320     * @param obj  the object (<code>null</code> permitted).
1321     *
1322     * @return A boolean.
1323     */
1324    @Override
1325    public boolean equals(Object obj) {
1326        if (obj == this) {
1327            return true;
1328        }
1329        if (!(obj instanceof BarRenderer)) {
1330            return false;
1331        }
1332        BarRenderer that = (BarRenderer) obj;
1333        if (this.base != that.base) {
1334            return false;
1335        }
1336        if (this.itemMargin != that.itemMargin) {
1337            return false;
1338        }
1339        if (this.drawBarOutline != that.drawBarOutline) {
1340            return false;
1341        }
1342        if (this.maximumBarWidth != that.maximumBarWidth) {
1343            return false;
1344        }
1345        if (this.minimumBarLength != that.minimumBarLength) {
1346            return false;
1347        }
1348        if (!ObjectUtilities.equal(this.gradientPaintTransformer,
1349                that.gradientPaintTransformer)) {
1350            return false;
1351        }
1352        if (!ObjectUtilities.equal(this.positiveItemLabelPositionFallback,
1353            that.positiveItemLabelPositionFallback)) {
1354            return false;
1355        }
1356        if (!ObjectUtilities.equal(this.negativeItemLabelPositionFallback,
1357            that.negativeItemLabelPositionFallback)) {
1358            return false;
1359        }
1360        if (!this.barPainter.equals(that.barPainter)) {
1361            return false;
1362        }
1363        if (this.shadowsVisible != that.shadowsVisible) {
1364            return false;
1365        }
1366        if (!PaintUtilities.equal(this.shadowPaint, that.shadowPaint)) {
1367            return false;
1368        }
1369        if (this.shadowXOffset != that.shadowXOffset) {
1370            return false;
1371        }
1372        if (this.shadowYOffset != that.shadowYOffset) {
1373            return false;
1374        }
1375        return super.equals(obj);
1376    }
1377
1378    /**
1379     * Provides serialization support.
1380     *
1381     * @param stream  the output stream.
1382     *
1383     * @throws IOException  if there is an I/O error.
1384     */
1385    private void writeObject(ObjectOutputStream stream) throws IOException {
1386        stream.defaultWriteObject();
1387        SerialUtilities.writePaint(this.shadowPaint, stream);
1388    }
1389
1390    /**
1391     * Provides serialization support.
1392     *
1393     * @param stream  the input stream.
1394     *
1395     * @throws IOException  if there is an I/O error.
1396     * @throws ClassNotFoundException  if there is a classpath problem.
1397     */
1398    private void readObject(ObjectInputStream stream)
1399            throws IOException, ClassNotFoundException {
1400        stream.defaultReadObject();
1401        this.shadowPaint = SerialUtilities.readPaint(stream);
1402    }
1403
1404}