001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2007, 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     * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
025     * in the United States and other countries.]
026     *
027     * --------------------
028     * ThermometerPlot.java
029     * --------------------
030     *
031     * (C) Copyright 2000-2007, by Bryan Scott and Contributors.
032     *
033     * Original Author:  Bryan Scott (based on MeterPlot by Hari).
034     * Contributor(s):   David Gilbert (for Object Refinery Limited).
035     *                   Arnaud Lelievre;
036     *
037     * Changes
038     * -------
039     * 11-Apr-2002 : Version 1, contributed by Bryan Scott;
040     * 15-Apr-2002 : Changed to implement VerticalValuePlot;
041     * 29-Apr-2002 : Added getVerticalValueAxis() method (DG);
042     * 25-Jun-2002 : Removed redundant imports (DG);
043     * 17-Sep-2002 : Reviewed with Checkstyle utility (DG);
044     * 18-Sep-2002 : Extensive changes made to API, to iron out bugs and 
045     *               inconsistencies (DG);
046     * 13-Oct-2002 : Corrected error datasetChanged which would generate exceptions
047     *               when value set to null (BRS).
048     * 23-Jan-2003 : Removed one constructor (DG);
049     * 26-Mar-2003 : Implemented Serializable (DG);
050     * 02-Jun-2003 : Removed test for compatible range axis (DG);
051     * 01-Jul-2003 : Added additional check in draw method to ensure value not 
052     *               null (BRS);
053     * 08-Sep-2003 : Added internationalization via use of properties 
054     *               resourceBundle (RFE 690236) (AL);
055     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
056     * 29-Sep-2003 : Updated draw to set value of cursor to non-zero and allow 
057     *               painting of axis.  An incomplete fix and needs to be set for 
058     *               left or right drawing (BRS);
059     * 19-Nov-2003 : Added support for value labels to be displayed left of the 
060     *               thermometer
061     * 19-Nov-2003 : Improved axis drawing (now default axis does not draw axis line
062     *               and is closer to the bulb).  Added support for the positioning
063     *               of the axis to the left or right of the bulb. (BRS);
064     * 03-Dec-2003 : Directly mapped deprecated setData()/getData() method to 
065     *               get/setDataset() (TM);
066     * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
067     * 07-Apr-2004 : Changed string width calculation (DG);
068     * 12-Nov-2004 : Implemented the new Zoomable interface (DG);
069     * 06-Jan-2004 : Added getOrientation() method (DG);
070     * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
071     * 29-Mar-2005 : Fixed equals() method (DG);
072     * 05-May-2005 : Updated draw() method parameters (DG);
073     * 09-Jun-2005 : Fixed more bugs in equals() method (DG);
074     * 10-Jun-2005 : Fixed minor bug in setDisplayRange() method (DG);
075     * ------------- JFREECHART 1.0.x ---------------------------------------------
076     * 14-Nov-2006 : Fixed margin when drawing (DG);
077     * 03-May-2007 : Fixed datasetChanged() to handle null dataset, added null 
078     *               argument check and event notification to setRangeAxis(), 
079     *               added null argument check to setPadding(), setValueFont(),
080     *               setValuePaint(), setValueFormat() and setMercuryPaint(), 
081     *               deprecated get/setShowValueLines(), deprecated 
082     *               getMinimum/MaximumVerticalDataValue(), and fixed serialization 
083     *               bug (DG);
084     * 
085     */
086    
087    package org.jfree.chart.plot;
088    
089    import java.awt.BasicStroke;
090    import java.awt.Color;
091    import java.awt.Font;
092    import java.awt.FontMetrics;
093    import java.awt.Graphics2D;
094    import java.awt.Paint;
095    import java.awt.Stroke;
096    import java.awt.geom.Area;
097    import java.awt.geom.Ellipse2D;
098    import java.awt.geom.Line2D;
099    import java.awt.geom.Point2D;
100    import java.awt.geom.Rectangle2D;
101    import java.awt.geom.RoundRectangle2D;
102    import java.io.IOException;
103    import java.io.ObjectInputStream;
104    import java.io.ObjectOutputStream;
105    import java.io.Serializable;
106    import java.text.DecimalFormat;
107    import java.text.NumberFormat;
108    import java.util.Arrays;
109    import java.util.ResourceBundle;
110    
111    import org.jfree.chart.LegendItemCollection;
112    import org.jfree.chart.axis.NumberAxis;
113    import org.jfree.chart.axis.ValueAxis;
114    import org.jfree.chart.event.PlotChangeEvent;
115    import org.jfree.data.Range;
116    import org.jfree.data.general.DatasetChangeEvent;
117    import org.jfree.data.general.DefaultValueDataset;
118    import org.jfree.data.general.ValueDataset;
119    import org.jfree.io.SerialUtilities;
120    import org.jfree.ui.RectangleEdge;
121    import org.jfree.ui.RectangleInsets;
122    import org.jfree.util.ObjectUtilities;
123    import org.jfree.util.PaintUtilities;
124    import org.jfree.util.UnitType;
125    
126    /**
127     * A plot that displays a single value (from a {@link ValueDataset}) in a 
128     * thermometer type display.
129     * <p>
130     * This plot supports a number of options:
131     * <ol>
132     * <li>three sub-ranges which could be viewed as 'Normal', 'Warning' 
133     *   and 'Critical' ranges.</li>
134     * <li>the thermometer can be run in two modes:
135     *      <ul>
136     *      <li>fixed range, or</li>
137     *      <li>range adjusts to current sub-range.</li>
138     *      </ul>
139     * </li>
140     * <li>settable units to be displayed.</li>
141     * <li>settable display location for the value text.</li>
142     * </ol>
143     */
144    public class ThermometerPlot extends Plot implements ValueAxisPlot,
145            Zoomable, Cloneable, Serializable {
146    
147        /** For serialization. */
148        private static final long serialVersionUID = 4087093313147984390L;
149        
150        /** A constant for unit type 'None'. */
151        public static final int UNITS_NONE = 0;
152    
153        /** A constant for unit type 'Fahrenheit'. */
154        public static final int UNITS_FAHRENHEIT = 1;
155    
156        /** A constant for unit type 'Celcius'. */
157        public static final int UNITS_CELCIUS = 2;
158    
159        /** A constant for unit type 'Kelvin'. */
160        public static final int UNITS_KELVIN = 3;
161    
162        /** A constant for the value label position (no label). */
163        public static final int NONE = 0;
164    
165        /** A constant for the value label position (right of the thermometer). */
166        public static final int RIGHT = 1;
167    
168        /** A constant for the value label position (left of the thermometer). */
169        public static final int LEFT = 2;
170    
171        /** A constant for the value label position (in the thermometer bulb). */
172        public static final int BULB = 3;
173    
174        /** A constant for the 'normal' range. */
175        public static final int NORMAL = 0;
176    
177        /** A constant for the 'warning' range. */
178        public static final int WARNING = 1;
179    
180        /** A constant for the 'critical' range. */
181        public static final int CRITICAL = 2;
182    
183        /** The bulb radius. */
184        protected static final int BULB_RADIUS = 40;
185    
186        /** The bulb diameter. */
187        protected static final int BULB_DIAMETER = BULB_RADIUS * 2;
188    
189        /** The column radius. */
190        protected static final int COLUMN_RADIUS = 20;
191    
192        /** The column diameter.*/
193        protected static final int COLUMN_DIAMETER = COLUMN_RADIUS * 2;
194    
195        /** The gap radius. */
196        protected static final int GAP_RADIUS = 5;
197    
198        /** The gap diameter. */
199        protected static final int GAP_DIAMETER = GAP_RADIUS * 2;
200    
201        /** The axis gap. */
202        protected static final int AXIS_GAP = 10;
203    
204        /** The unit strings. */
205        protected static final String[] UNITS = {"", "\u00B0F", "\u00B0C", 
206                "\u00B0K"};
207    
208        /** Index for low value in subrangeInfo matrix. */
209        protected static final int RANGE_LOW = 0;
210    
211        /** Index for high value in subrangeInfo matrix. */
212        protected static final int RANGE_HIGH = 1;
213    
214        /** Index for display low value in subrangeInfo matrix. */
215        protected static final int DISPLAY_LOW = 2;
216    
217        /** Index for display high value in subrangeInfo matrix. */
218        protected static final int DISPLAY_HIGH = 3;
219    
220        /** The default lower bound. */
221        protected static final double DEFAULT_LOWER_BOUND = 0.0;
222    
223        /** The default upper bound. */
224        protected static final double DEFAULT_UPPER_BOUND = 100.0;
225    
226        /** The dataset for the plot. */
227        private ValueDataset dataset;
228    
229        /** The range axis. */
230        private ValueAxis rangeAxis;
231    
232        /** The lower bound for the thermometer. */
233        private double lowerBound = DEFAULT_LOWER_BOUND;
234    
235        /** The upper bound for the thermometer. */
236        private double upperBound = DEFAULT_UPPER_BOUND;
237    
238        /** 
239         * Blank space inside the plot area around the outside of the thermometer. 
240         */
241        private RectangleInsets padding;
242    
243        /** Stroke for drawing the thermometer */
244        private transient Stroke thermometerStroke = new BasicStroke(1.0f);
245    
246        /** Paint for drawing the thermometer */
247        private transient Paint thermometerPaint = Color.black;
248    
249        /** The display units */
250        private int units = UNITS_CELCIUS;
251    
252        /** The value label position. */
253        private int valueLocation = BULB;
254    
255        /** The position of the axis **/
256        private int axisLocation = LEFT;
257    
258        /** The font to write the value in */
259        private Font valueFont = new Font("SansSerif", Font.BOLD, 16);
260    
261        /** Colour that the value is written in */
262        private transient Paint valuePaint = Color.white;
263    
264        /** Number format for the value */
265        private NumberFormat valueFormat = new DecimalFormat();
266    
267        /** The default paint for the mercury in the thermometer. */
268        private transient Paint mercuryPaint = Color.lightGray;
269    
270        /** A flag that controls whether value lines are drawn. */
271        private boolean showValueLines = false;
272    
273        /** The display sub-range. */
274        private int subrange = -1;
275    
276        /** The start and end values for the subranges. */
277        private double[][] subrangeInfo = {
278            {0.0, 50.0, 0.0, 50.0}, 
279            {50.0, 75.0, 50.0, 75.0}, 
280            {75.0, 100.0, 75.0, 100.0}
281        };
282    
283        /** 
284         * A flag that controls whether or not the axis range adjusts to the 
285         * sub-ranges. 
286         */
287        private boolean followDataInSubranges = false;
288    
289        /** 
290         * A flag that controls whether or not the mercury paint changes with 
291         * the subranges. 
292         */
293        private boolean useSubrangePaint = true;
294    
295        /** Paint for each range */
296        private transient Paint[] subrangePaint = {Color.green, Color.orange, 
297                Color.red};
298    
299        /** A flag that controls whether the sub-range indicators are visible. */
300        private boolean subrangeIndicatorsVisible = true;
301    
302        /** The stroke for the sub-range indicators. */
303        private transient Stroke subrangeIndicatorStroke = new BasicStroke(2.0f);
304    
305        /** The range indicator stroke. */
306        private transient Stroke rangeIndicatorStroke = new BasicStroke(3.0f);
307    
308        /** The resourceBundle for the localization. */
309        protected static ResourceBundle localizationResources =
310            ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
311    
312        /**
313         * Creates a new thermometer plot.
314         */
315        public ThermometerPlot() {
316            this(new DefaultValueDataset());
317        }
318    
319        /**
320         * Creates a new thermometer plot, using default attributes where necessary.
321         *
322         * @param dataset  the data set.
323         */
324        public ThermometerPlot(ValueDataset dataset) {
325    
326            super();
327    
328            this.padding = new RectangleInsets(UnitType.RELATIVE, 0.05, 0.05, 0.05, 
329                    0.05);
330            this.dataset = dataset;
331            if (dataset != null) {
332                dataset.addChangeListener(this);
333            }
334            NumberAxis axis = new NumberAxis(null);
335            axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
336            axis.setAxisLineVisible(false);
337            axis.setPlot(this);
338            axis.addChangeListener(this);
339            this.rangeAxis = axis;
340            setAxisRange();
341        }
342    
343        /**
344         * Returns the dataset for the plot.
345         *
346         * @return The dataset (possibly <code>null</code>).
347         * 
348         * @see #setDataset(ValueDataset)
349         */
350        public ValueDataset getDataset() {
351            return this.dataset;
352        }
353    
354        /**
355         * Sets the dataset for the plot, replacing the existing dataset if there 
356         * is one, and sends a {@link PlotChangeEvent} to all registered listeners.
357         *
358         * @param dataset  the dataset (<code>null</code> permitted).
359         * 
360         * @see #getDataset()
361         */
362        public void setDataset(ValueDataset dataset) {
363    
364            // if there is an existing dataset, remove the plot from the list 
365            // of change listeners...
366            ValueDataset existing = this.dataset;
367            if (existing != null) {
368                existing.removeChangeListener(this);
369            }
370    
371            // set the new dataset, and register the chart as a change listener...
372            this.dataset = dataset;
373            if (dataset != null) {
374                setDatasetGroup(dataset.getGroup());
375                dataset.addChangeListener(this);
376            }
377    
378            // send a dataset change event to self...
379            DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
380            datasetChanged(event);
381    
382        }
383    
384        /**
385         * Returns the range axis.
386         *
387         * @return The range axis (never <code>null</code>).
388         * 
389         * @see #setRangeAxis(ValueAxis)
390         */
391        public ValueAxis getRangeAxis() {
392            return this.rangeAxis;
393        }
394    
395        /**
396         * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to 
397         * all registered listeners.
398         *
399         * @param axis  the new axis (<code>null</code> not permitted).
400         * 
401         * @see #getRangeAxis()
402         */
403        public void setRangeAxis(ValueAxis axis) {
404            if (axis == null) {
405                throw new IllegalArgumentException("Null 'axis' argument.");
406            }
407            // plot is registered as a listener with the existing axis...
408            this.rangeAxis.removeChangeListener(this);
409    
410            axis.setPlot(this);
411            axis.addChangeListener(this);
412            this.rangeAxis = axis;
413            notifyListeners(new PlotChangeEvent(this));
414    
415        }
416    
417        /**
418         * Returns the lower bound for the thermometer.  The data value can be set 
419         * lower than this, but it will not be shown in the thermometer.
420         *
421         * @return The lower bound.
422         * 
423         * @see #setLowerBound(double)
424         */
425        public double getLowerBound() {
426            return this.lowerBound;
427        }
428    
429        /**
430         * Sets the lower bound for the thermometer.
431         *
432         * @param lower the lower bound.
433         * 
434         * @see #getLowerBound()
435         */
436        public void setLowerBound(double lower) {
437            this.lowerBound = lower;
438            setAxisRange();
439        }
440    
441        /**
442         * Returns the upper bound for the thermometer.  The data value can be set 
443         * higher than this, but it will not be shown in the thermometer.
444         *
445         * @return The upper bound.
446         * 
447         * @see #setUpperBound(double)
448         */
449        public double getUpperBound() {
450            return this.upperBound;
451        }
452    
453        /**
454         * Sets the upper bound for the thermometer.
455         *
456         * @param upper the upper bound.
457         * 
458         * @see #getUpperBound()
459         */
460        public void setUpperBound(double upper) {
461            this.upperBound = upper;
462            setAxisRange();
463        }
464    
465        /**
466         * Sets the lower and upper bounds for the thermometer.
467         *
468         * @param lower  the lower bound.
469         * @param upper  the upper bound.
470         */
471        public void setRange(double lower, double upper) {
472            this.lowerBound = lower;
473            this.upperBound = upper;
474            setAxisRange();
475        }
476    
477        /**
478         * Returns the padding for the thermometer.  This is the space inside the 
479         * plot area.
480         *
481         * @return The padding (never <code>null</code>).
482         * 
483         * @see #setPadding(RectangleInsets)
484         */
485        public RectangleInsets getPadding() {
486            return this.padding;
487        }
488    
489        /**
490         * Sets the padding for the thermometer and sends a {@link PlotChangeEvent} 
491         * to all registered listeners.
492         *
493         * @param padding  the padding (<code>null</code> not permitted).
494         * 
495         * @see #getPadding()
496         */
497        public void setPadding(RectangleInsets padding) {
498            if (padding == null) {
499                throw new IllegalArgumentException("Null 'padding' argument.");
500            }
501            this.padding = padding;
502            notifyListeners(new PlotChangeEvent(this));
503        }
504    
505        /**
506         * Returns the stroke used to draw the thermometer outline.
507         *
508         * @return The stroke (never <code>null</code>).
509         * 
510         * @see #setThermometerStroke(Stroke)
511         * @see #getThermometerPaint()
512         */
513        public Stroke getThermometerStroke() {
514            return this.thermometerStroke;
515        }
516    
517        /**
518         * Sets the stroke used to draw the thermometer outline and sends a 
519         * {@link PlotChangeEvent} to all registered listeners.
520         *
521         * @param s  the new stroke (<code>null</code> ignored).
522         * 
523         * @see #getThermometerStroke()
524         */
525        public void setThermometerStroke(Stroke s) {
526            if (s != null) {
527                this.thermometerStroke = s;
528                notifyListeners(new PlotChangeEvent(this));
529            }
530        }
531    
532        /**
533         * Returns the paint used to draw the thermometer outline.
534         *
535         * @return The paint (never <code>null</code>).
536         * 
537         * @see #setThermometerPaint(Paint)
538         * @see #getThermometerStroke()
539         */
540        public Paint getThermometerPaint() {
541            return this.thermometerPaint;
542        }
543    
544        /**
545         * Sets the paint used to draw the thermometer outline and sends a 
546         * {@link PlotChangeEvent} to all registered listeners.
547         *
548         * @param paint  the new paint (<code>null</code> ignored).
549         * 
550         * @see #getThermometerPaint()
551         */
552        public void setThermometerPaint(Paint paint) {
553            if (paint != null) {
554                this.thermometerPaint = paint;
555                notifyListeners(new PlotChangeEvent(this));
556            }
557        }
558    
559        /**
560         * Returns a code indicating the unit display type.  This is one of
561         * {@link #UNITS_NONE}, {@link #UNITS_FAHRENHEIT}, {@link #UNITS_CELCIUS} 
562         * and {@link #UNITS_KELVIN}.
563         *
564         * @return The units type.
565         * 
566         * @see #setUnits(int)
567         */
568        public int getUnits() {
569            return this.units;
570        }
571    
572        /**
573         * Sets the units to be displayed in the thermometer. Use one of the 
574         * following constants:
575         *
576         * <ul>
577         * <li>UNITS_NONE : no units displayed.</li>
578         * <li>UNITS_FAHRENHEIT : units displayed in Fahrenheit.</li>
579         * <li>UNITS_CELCIUS : units displayed in Celcius.</li>
580         * <li>UNITS_KELVIN : units displayed in Kelvin.</li>
581         * </ul>
582         *
583         * @param u  the new unit type.
584         * 
585         * @see #getUnits()
586         */
587        public void setUnits(int u) {
588            if ((u >= 0) && (u < UNITS.length)) {
589                if (this.units != u) {
590                    this.units = u;
591                    notifyListeners(new PlotChangeEvent(this));
592                }
593            }
594        }
595    
596        /**
597         * Sets the unit type.
598         *
599         * @param u  the unit type (<code>null</code> ignored).
600         * 
601         * @deprecated Use setUnits(int) instead.  Deprecated as of version 1.0.6,
602         *     because this method is a little obscure and redundant anyway.
603         */
604        public void setUnits(String u) {
605            if (u == null) {
606                return;
607            }
608    
609            u = u.toUpperCase().trim();
610            for (int i = 0; i < UNITS.length; ++i) {
611                if (u.equals(UNITS[i].toUpperCase().trim())) {
612                    setUnits(i);
613                    i = UNITS.length;
614                }
615            }
616        }
617    
618        /**
619         * Returns a code indicating the location at which the value label is
620         * displayed.
621         *
622         * @return The location (one of {@link #NONE}, {@link #RIGHT}, 
623         *         {@link #LEFT} and {@link #BULB}.).
624         */
625        public int getValueLocation() {
626            return this.valueLocation;
627        }
628    
629        /**
630         * Sets the location at which the current value is displayed and sends a
631         * {@link PlotChangeEvent} to all registered listeners.
632         * <P>
633         * The location can be one of the constants:
634         * <code>NONE</code>,
635         * <code>RIGHT</code>
636         * <code>LEFT</code> and
637         * <code>BULB</code>.
638         *
639         * @param location  the location.
640         */
641        public void setValueLocation(int location) {
642            if ((location >= 0) && (location < 4)) {
643                this.valueLocation = location;
644                notifyListeners(new PlotChangeEvent(this));
645            }
646            else {
647                throw new IllegalArgumentException("Location not recognised.");
648            }
649        }
650    
651        /**
652         * Returns the axis location.
653         *
654         * @return The location (one of {@link #NONE}, {@link #LEFT} and 
655         *         {@link #RIGHT}).
656         *         
657         * @see #setAxisLocation(int)
658         */
659        public int getAxisLocation() {
660            return this.axisLocation;
661        }
662    
663        /**
664         * Sets the location at which the axis is displayed relative to the 
665         * thermometer, and sends a {@link PlotChangeEvent} to all registered
666         * listeners.
667         *
668         * @param location  the location (one of {@link #NONE}, {@link #LEFT} and 
669         *         {@link #RIGHT}).
670         * 
671         * @see #getAxisLocation()
672         */
673        public void setAxisLocation(int location) {
674            if ((location >= 0) && (location < 3)) {
675                this.axisLocation = location;
676                notifyListeners(new PlotChangeEvent(this));
677            }
678            else {
679                throw new IllegalArgumentException("Location not recognised.");
680            }
681        }
682    
683        /**
684         * Gets the font used to display the current value.
685         *
686         * @return The font.
687         * 
688         * @see #setValueFont(Font)
689         */
690        public Font getValueFont() {
691            return this.valueFont;
692        }
693    
694        /**
695         * Sets the font used to display the current value.
696         *
697         * @param f  the new font (<code>null</code> not permitted).
698         * 
699         * @see #getValueFont()
700         */
701        public void setValueFont(Font f) {
702            if (f == null) {
703                throw new IllegalArgumentException("Null 'font' argument.");
704            }
705            if (!this.valueFont.equals(f)) {
706                this.valueFont = f;
707                notifyListeners(new PlotChangeEvent(this));
708            }
709        }
710    
711        /**
712         * Gets the paint used to display the current value.
713        *
714         * @return The paint.
715         * 
716         * @see #setValuePaint(Paint)
717         */
718        public Paint getValuePaint() {
719            return this.valuePaint;
720        }
721    
722        /**
723         * Sets the paint used to display the current value and sends a 
724         * {@link PlotChangeEvent} to all registered listeners.
725         *
726         * @param paint  the new paint (<code>null</code> not permitted).
727         * 
728         * @see #getValuePaint()
729         */
730        public void setValuePaint(Paint paint) {
731            if (paint == null) {
732                throw new IllegalArgumentException("Null 'paint' argument.");
733            }
734            if (!this.valuePaint.equals(paint)) {
735                this.valuePaint = paint;
736                notifyListeners(new PlotChangeEvent(this));
737            }
738        }
739    
740        // FIXME: No getValueFormat() method?
741        
742        /**
743         * Sets the formatter for the value label and sends a 
744         * {@link PlotChangeEvent} to all registered listeners.
745         *
746         * @param formatter  the new formatter (<code>null</code> not permitted).
747         */
748        public void setValueFormat(NumberFormat formatter) {
749            if (formatter == null) {
750                throw new IllegalArgumentException("Null 'formatter' argument.");
751            }
752            this.valueFormat = formatter;
753            notifyListeners(new PlotChangeEvent(this));
754        }
755    
756        /**
757         * Returns the default mercury paint.
758         *
759         * @return The paint (never <code>null</code>).
760         * 
761         * @see #setMercuryPaint(Paint)
762         */
763        public Paint getMercuryPaint() {
764            return this.mercuryPaint;
765        }
766    
767        /**
768         * Sets the default mercury paint and sends a {@link PlotChangeEvent} to 
769         * all registered listeners.
770         *
771         * @param paint  the new paint (<code>null</code> not permitted).
772         * 
773         * @see #getMercuryPaint()
774         */
775        public void setMercuryPaint(Paint paint) {
776            if (paint == null) {
777                throw new IllegalArgumentException("Null 'paint' argument.");
778            }
779            this.mercuryPaint = paint;
780            notifyListeners(new PlotChangeEvent(this));
781        }
782    
783        /**
784         * Returns the flag that controls whether not value lines are displayed.
785         *
786         * @return The flag.
787         * 
788         * @see #setShowValueLines(boolean)
789         * 
790         * @deprecated This flag doesn't do anything useful/visible.  Deprecated 
791         *     as of version 1.0.6.
792         */
793        public boolean getShowValueLines() {
794            return this.showValueLines;
795        }
796    
797        /**
798         * Sets the display as to whether to show value lines in the output.
799         *
800         * @param b Whether to show value lines in the thermometer
801         * 
802         * @see #getShowValueLines()
803         * 
804         * @deprecated This flag doesn't do anything useful/visible.  Deprecated 
805         *     as of version 1.0.6.
806         */
807        public void setShowValueLines(boolean b) {
808            this.showValueLines = b;
809            notifyListeners(new PlotChangeEvent(this));
810        }
811    
812        /**
813         * Sets information for a particular range.
814         *
815         * @param range  the range to specify information about.
816         * @param low  the low value for the range
817         * @param hi  the high value for the range
818         */
819        public void setSubrangeInfo(int range, double low, double hi) {
820            setSubrangeInfo(range, low, hi, low, hi);
821        }
822    
823        /**
824         * Sets the subrangeInfo attribute of the ThermometerPlot object
825         *
826         * @param range  the new rangeInfo value.
827         * @param rangeLow  the new rangeInfo value
828         * @param rangeHigh  the new rangeInfo value
829         * @param displayLow  the new rangeInfo value
830         * @param displayHigh  the new rangeInfo value
831         */
832        public void setSubrangeInfo(int range,
833                                    double rangeLow, double rangeHigh,
834                                    double displayLow, double displayHigh) {
835    
836            if ((range >= 0) && (range < 3)) {
837                setSubrange(range, rangeLow, rangeHigh);
838                setDisplayRange(range, displayLow, displayHigh);
839                setAxisRange();
840                notifyListeners(new PlotChangeEvent(this));
841            }
842    
843        }
844    
845        /**
846         * Sets the bounds for a subrange.
847         *
848         * @param range  the range type.
849         * @param low  the low value.
850         * @param high  the high value.
851         */
852        public void setSubrange(int range, double low, double high) {
853            if ((range >= 0) && (range < 3)) {
854                this.subrangeInfo[range][RANGE_HIGH] = high;
855                this.subrangeInfo[range][RANGE_LOW] = low;
856            }
857        }
858    
859        /**
860         * Sets the displayed bounds for a sub range.
861         *
862         * @param range  the range type.
863         * @param low  the low value.
864         * @param high  the high value.
865         */
866        public void setDisplayRange(int range, double low, double high) {
867    
868            if ((range >= 0) && (range < this.subrangeInfo.length)
869                && isValidNumber(high) && isValidNumber(low)) {
870     
871                if (high > low) {
872                    this.subrangeInfo[range][DISPLAY_HIGH] = high;
873                    this.subrangeInfo[range][DISPLAY_LOW] = low;
874                }
875                else {
876                    this.subrangeInfo[range][DISPLAY_HIGH] = low;
877                    this.subrangeInfo[range][DISPLAY_LOW] = high;
878                }
879    
880            }
881    
882        }
883    
884        /**
885         * Gets the paint used for a particular subrange.
886         *
887         * @param range  the range (.
888         *
889         * @return The paint.
890         * 
891         * @see #setSubrangePaint(int, Paint)
892         */
893        public Paint getSubrangePaint(int range) {
894            if ((range >= 0) && (range < this.subrangePaint.length)) {
895                return this.subrangePaint[range];
896            }
897            else {
898                return this.mercuryPaint;
899            }
900        }
901    
902        /**
903         * Sets the paint to be used for a subrange and sends a 
904         * {@link PlotChangeEvent} to all registered listeners.
905         *
906         * @param range  the range (0, 1 or 2).
907         * @param paint  the paint to be applied (<code>null</code> not permitted).
908         * 
909         * @see #getSubrangePaint(int)
910         */
911        public void setSubrangePaint(int range, Paint paint) {
912            if ((range >= 0) 
913                    && (range < this.subrangePaint.length) && (paint != null)) {
914                this.subrangePaint[range] = paint;
915                notifyListeners(new PlotChangeEvent(this));
916            }
917        }
918    
919        /**
920         * Returns a flag that controls whether or not the thermometer axis zooms 
921         * to display the subrange within which the data value falls.
922         *
923         * @return The flag.
924         */
925        public boolean getFollowDataInSubranges() {
926            return this.followDataInSubranges;
927        }
928    
929        /**
930         * Sets the flag that controls whether or not the thermometer axis zooms 
931         * to display the subrange within which the data value falls.
932         *
933         * @param flag  the flag.
934         */
935        public void setFollowDataInSubranges(boolean flag) {
936            this.followDataInSubranges = flag;
937            notifyListeners(new PlotChangeEvent(this));
938        }
939    
940        /**
941         * Returns a flag that controls whether or not the mercury color changes 
942         * for each subrange.
943         *
944         * @return The flag.
945         * 
946         * @see #setUseSubrangePaint(boolean)
947         */
948        public boolean getUseSubrangePaint() {
949            return this.useSubrangePaint;
950        }
951    
952        /**
953         * Sets the range colour change option.
954         *
955         * @param flag the new range colour change option
956         * 
957         * @see #getUseSubrangePaint()
958         */
959        public void setUseSubrangePaint(boolean flag) {
960            this.useSubrangePaint = flag;
961            notifyListeners(new PlotChangeEvent(this));
962        }
963    
964        /**
965         * Draws the plot on a Java 2D graphics device (such as the screen or a 
966         * printer).
967         *
968         * @param g2  the graphics device.
969         * @param area  the area within which the plot should be drawn.
970         * @param anchor  the anchor point (<code>null</code> permitted).
971         * @param parentState  the state from the parent plot, if there is one.
972         * @param info  collects info about the drawing.
973         */
974        public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
975                         PlotState parentState,
976                         PlotRenderingInfo info) {
977    
978            RoundRectangle2D outerStem = new RoundRectangle2D.Double();
979            RoundRectangle2D innerStem = new RoundRectangle2D.Double();
980            RoundRectangle2D mercuryStem = new RoundRectangle2D.Double();
981            Ellipse2D outerBulb = new Ellipse2D.Double();
982            Ellipse2D innerBulb = new Ellipse2D.Double();
983            String temp = null;
984            FontMetrics metrics = null;
985            if (info != null) {
986                info.setPlotArea(area);
987            }
988    
989            // adjust for insets...
990            RectangleInsets insets = getInsets();
991            insets.trim(area);
992            drawBackground(g2, area);
993    
994            // adjust for padding...
995            Rectangle2D interior = (Rectangle2D) area.clone();
996            this.padding.trim(interior);
997            int midX = (int) (interior.getX() + (interior.getWidth() / 2));
998            int midY = (int) (interior.getY() + (interior.getHeight() / 2));
999            int stemTop = (int) (interior.getMinY() + BULB_RADIUS);
1000            int stemBottom = (int) (interior.getMaxY() - BULB_DIAMETER);
1001            Rectangle2D dataArea = new Rectangle2D.Double(midX - COLUMN_RADIUS, 
1002                    stemTop, COLUMN_RADIUS, stemBottom - stemTop);
1003    
1004            outerBulb.setFrame(midX - BULB_RADIUS, stemBottom, BULB_DIAMETER, 
1005                    BULB_DIAMETER);
1006    
1007            outerStem.setRoundRect(midX - COLUMN_RADIUS, interior.getMinY(), 
1008                    COLUMN_DIAMETER, stemBottom + BULB_DIAMETER - stemTop, 
1009                    COLUMN_DIAMETER, COLUMN_DIAMETER);
1010    
1011            Area outerThermometer = new Area(outerBulb);
1012            Area tempArea = new Area(outerStem);
1013            outerThermometer.add(tempArea);
1014    
1015            innerBulb.setFrame(midX - BULB_RADIUS + GAP_RADIUS, 
1016                    stemBottom + GAP_RADIUS, BULB_DIAMETER - GAP_DIAMETER, 
1017                    BULB_DIAMETER - GAP_DIAMETER);
1018    
1019            innerStem.setRoundRect(midX - COLUMN_RADIUS + GAP_RADIUS, 
1020                    interior.getMinY() + GAP_RADIUS, COLUMN_DIAMETER - GAP_DIAMETER, 
1021                    stemBottom + BULB_DIAMETER - GAP_DIAMETER - stemTop,
1022                    COLUMN_DIAMETER - GAP_DIAMETER, COLUMN_DIAMETER - GAP_DIAMETER);
1023    
1024            Area innerThermometer = new Area(innerBulb);
1025            tempArea = new Area(innerStem);
1026            innerThermometer.add(tempArea);
1027       
1028            if ((this.dataset != null) && (this.dataset.getValue() != null)) {
1029                double current = this.dataset.getValue().doubleValue();
1030                double ds = this.rangeAxis.valueToJava2D(current, dataArea, 
1031                        RectangleEdge.LEFT);
1032    
1033                int i = COLUMN_DIAMETER - GAP_DIAMETER; // already calculated
1034                int j = COLUMN_RADIUS - GAP_RADIUS; // already calculated
1035                int l = (i / 2);
1036                int k = (int) Math.round(ds);
1037                if (k < (GAP_RADIUS + interior.getMinY())) {
1038                    k = (int) (GAP_RADIUS + interior.getMinY());
1039                    l = BULB_RADIUS;
1040                }
1041    
1042                Area mercury = new Area(innerBulb);
1043    
1044                if (k < (stemBottom + BULB_RADIUS)) {
1045                    mercuryStem.setRoundRect(midX - j, k, i, 
1046                            (stemBottom + BULB_RADIUS) - k, l, l);
1047                    tempArea = new Area(mercuryStem);
1048                    mercury.add(tempArea);
1049                }
1050    
1051                g2.setPaint(getCurrentPaint());
1052                g2.fill(mercury);
1053    
1054                // draw range indicators...
1055                if (this.subrangeIndicatorsVisible) {
1056                    g2.setStroke(this.subrangeIndicatorStroke);
1057                    Range range = this.rangeAxis.getRange();
1058    
1059                    // draw start of normal range
1060                    double value = this.subrangeInfo[NORMAL][RANGE_LOW];
1061                    if (range.contains(value)) {
1062                        double x = midX + COLUMN_RADIUS + 2;
1063                        double y = this.rangeAxis.valueToJava2D(value, dataArea, 
1064                                RectangleEdge.LEFT);
1065                        Line2D line = new Line2D.Double(x, y, x + 10, y);
1066                        g2.setPaint(this.subrangePaint[NORMAL]);
1067                        g2.draw(line);
1068                    }
1069    
1070                    // draw start of warning range
1071                    value = this.subrangeInfo[WARNING][RANGE_LOW];
1072                    if (range.contains(value)) {
1073                        double x = midX + COLUMN_RADIUS + 2;
1074                        double y = this.rangeAxis.valueToJava2D(value, dataArea, 
1075                                RectangleEdge.LEFT);
1076                        Line2D line = new Line2D.Double(x, y, x + 10, y);
1077                        g2.setPaint(this.subrangePaint[WARNING]);
1078                        g2.draw(line);
1079                    }
1080    
1081                    // draw start of critical range
1082                    value = this.subrangeInfo[CRITICAL][RANGE_LOW];
1083                    if (range.contains(value)) {
1084                        double x = midX + COLUMN_RADIUS + 2;
1085                        double y = this.rangeAxis.valueToJava2D(value, dataArea, 
1086                                RectangleEdge.LEFT);
1087                        Line2D line = new Line2D.Double(x, y, x + 10, y);
1088                        g2.setPaint(this.subrangePaint[CRITICAL]);
1089                        g2.draw(line);
1090                    }
1091                }
1092    
1093                // draw the axis...
1094                if ((this.rangeAxis != null) && (this.axisLocation != NONE)) {
1095                    int drawWidth = AXIS_GAP;
1096                    if (this.showValueLines) {
1097                        drawWidth += COLUMN_DIAMETER;
1098                    }
1099                    Rectangle2D drawArea;
1100                    double cursor = 0;
1101    
1102                    switch (this.axisLocation) {
1103                        case RIGHT:
1104                            cursor = midX + COLUMN_RADIUS;
1105                            drawArea = new Rectangle2D.Double(cursor,
1106                                    stemTop, drawWidth, (stemBottom - stemTop + 1));
1107                            this.rangeAxis.draw(g2, cursor, area, drawArea, 
1108                                    RectangleEdge.RIGHT, null);
1109                            break;
1110    
1111                        case LEFT:
1112                        default:
1113                            //cursor = midX - COLUMN_RADIUS - AXIS_GAP;
1114                            cursor = midX - COLUMN_RADIUS;
1115                            drawArea = new Rectangle2D.Double(cursor, stemTop,
1116                                    drawWidth, (stemBottom - stemTop + 1));
1117                            this.rangeAxis.draw(g2, cursor, area, drawArea, 
1118                                    RectangleEdge.LEFT, null);
1119                            break;
1120                    }
1121                       
1122                }
1123    
1124                // draw text value on screen
1125                g2.setFont(this.valueFont);
1126                g2.setPaint(this.valuePaint);
1127                metrics = g2.getFontMetrics();
1128                switch (this.valueLocation) {
1129                    case RIGHT:
1130                        g2.drawString(this.valueFormat.format(current), 
1131                                midX + COLUMN_RADIUS + GAP_RADIUS, midY);
1132                        break;
1133                    case LEFT:
1134                        String valueString = this.valueFormat.format(current);
1135                        int stringWidth = metrics.stringWidth(valueString);
1136                        g2.drawString(valueString, midX - COLUMN_RADIUS 
1137                                - GAP_RADIUS - stringWidth, midY);
1138                        break;
1139                    case BULB:
1140                        temp = this.valueFormat.format(current);
1141                        i = metrics.stringWidth(temp) / 2;
1142                        g2.drawString(temp, midX - i, 
1143                                stemBottom + BULB_RADIUS + GAP_RADIUS);
1144                        break;
1145                    default:
1146                }
1147                /***/
1148            }
1149    
1150            g2.setPaint(this.thermometerPaint);
1151            g2.setFont(this.valueFont);
1152    
1153            //  draw units indicator
1154            metrics = g2.getFontMetrics();
1155            int tickX1 = midX - COLUMN_RADIUS - GAP_DIAMETER 
1156                         - metrics.stringWidth(UNITS[this.units]);
1157            if (tickX1 > area.getMinX()) {
1158                g2.drawString(UNITS[this.units], tickX1, 
1159                        (int) (area.getMinY() + 20));
1160            }
1161    
1162            // draw thermometer outline
1163            g2.setStroke(this.thermometerStroke);
1164            g2.draw(outerThermometer);
1165            g2.draw(innerThermometer);
1166    
1167            drawOutline(g2, area);
1168        }
1169    
1170        /**
1171         * A zoom method that does nothing.  Plots are required to support the 
1172         * zoom operation.  In the case of a thermometer chart, it doesn't make 
1173         * sense to zoom in or out, so the method is empty.
1174         *
1175         * @param percent  the zoom percentage.
1176         */
1177        public void zoom(double percent) {
1178            // intentionally blank
1179       }
1180    
1181        /**
1182         * Returns a short string describing the type of plot.
1183         *
1184         * @return A short string describing the type of plot.
1185         */
1186        public String getPlotType() {
1187            return localizationResources.getString("Thermometer_Plot");
1188        }
1189    
1190        /**
1191         * Checks to see if a new value means the axis range needs adjusting.
1192         *
1193         * @param event  the dataset change event.
1194         */
1195        public void datasetChanged(DatasetChangeEvent event) {
1196            if (this.dataset != null) {
1197                Number vn = this.dataset.getValue();
1198                if (vn != null) {
1199                    double value = vn.doubleValue();
1200                    if (inSubrange(NORMAL, value)) {
1201                        this.subrange = NORMAL;
1202                    }
1203                    else if (inSubrange(WARNING, value)) {
1204                       this.subrange = WARNING;
1205                    }
1206                    else if (inSubrange(CRITICAL, value)) {
1207                        this.subrange = CRITICAL;
1208                    }
1209                    else {
1210                        this.subrange = -1;
1211                    }
1212                    setAxisRange();
1213                }
1214            }
1215            super.datasetChanged(event);
1216        }
1217    
1218        /**
1219         * Returns the minimum value in either the domain or the range, whichever
1220         * is displayed against the vertical axis for the particular type of plot
1221         * implementing this interface.
1222         *
1223         * @return The minimum value in either the domain or the range.
1224         * 
1225         * @deprecated This method is not used.  Officially deprecated in version 
1226         *         1.0.6.
1227         */
1228        public Number getMinimumVerticalDataValue() {
1229            return new Double(this.lowerBound);
1230        }
1231    
1232        /**
1233         * Returns the maximum value in either the domain or the range, whichever
1234         * is displayed against the vertical axis for the particular type of plot
1235         * implementing this interface.
1236         *
1237         * @return The maximum value in either the domain or the range
1238         * 
1239         * @deprecated This method is not used.  Officially deprecated in version 
1240         *         1.0.6.
1241         */
1242        public Number getMaximumVerticalDataValue() {
1243            return new Double(this.upperBound);
1244        }
1245    
1246        /**
1247         * Returns the data range.
1248         *
1249         * @param axis  the axis.
1250         *
1251         * @return The range of data displayed.
1252         */
1253        public Range getDataRange(ValueAxis axis) {
1254           return new Range(this.lowerBound, this.upperBound);
1255        }
1256    
1257        /**
1258         * Sets the axis range to the current values in the rangeInfo array.
1259         */
1260        protected void setAxisRange() {
1261            if ((this.subrange >= 0) && (this.followDataInSubranges)) {
1262                this.rangeAxis.setRange(
1263                        new Range(this.subrangeInfo[this.subrange][DISPLAY_LOW],
1264                        this.subrangeInfo[this.subrange][DISPLAY_HIGH]));
1265            }
1266            else {
1267                this.rangeAxis.setRange(this.lowerBound, this.upperBound);
1268            }
1269        }
1270    
1271        /**
1272         * Returns the legend items for the plot.
1273         *
1274         * @return <code>null</code>.
1275         */
1276        public LegendItemCollection getLegendItems() {
1277            return null;
1278        }
1279    
1280        /**
1281         * Returns the orientation of the plot.
1282         * 
1283         * @return The orientation (always {@link PlotOrientation#VERTICAL}).
1284         */
1285        public PlotOrientation getOrientation() {
1286            return PlotOrientation.VERTICAL;    
1287        }
1288    
1289        /**
1290         * Determine whether a number is valid and finite.
1291         *
1292         * @param d  the number to be tested.
1293         *
1294         * @return <code>true</code> if the number is valid and finite, and 
1295         *         <code>false</code> otherwise.
1296         */
1297        protected static boolean isValidNumber(double d) {
1298            return (!(Double.isNaN(d) || Double.isInfinite(d)));
1299        }
1300    
1301        /**
1302         * Returns true if the value is in the specified range, and false otherwise.
1303         *
1304         * @param subrange  the subrange.
1305         * @param value  the value to check.
1306         *
1307         * @return A boolean.
1308         */
1309        private boolean inSubrange(int subrange, double value) {
1310            return (value > this.subrangeInfo[subrange][RANGE_LOW]
1311                && value <= this.subrangeInfo[subrange][RANGE_HIGH]);
1312        }
1313    
1314        /**
1315         * Returns the mercury paint corresponding to the current data value.
1316         * Called from the {@link #draw(Graphics2D, Rectangle2D, Point2D, 
1317         * PlotState, PlotRenderingInfo)} method.
1318         *
1319         * @return The paint (never <code>null</code>).
1320         */
1321        private Paint getCurrentPaint() {
1322            Paint result = this.mercuryPaint;
1323            if (this.useSubrangePaint) {
1324                double value = this.dataset.getValue().doubleValue();
1325                if (inSubrange(NORMAL, value)) {
1326                    result = this.subrangePaint[NORMAL];
1327                }
1328                else if (inSubrange(WARNING, value)) {
1329                    result = this.subrangePaint[WARNING];
1330                }
1331                else if (inSubrange(CRITICAL, value)) {
1332                    result = this.subrangePaint[CRITICAL];
1333                }
1334            }
1335            return result;
1336        }
1337    
1338        /**
1339         * Tests this plot for equality with another object.  The plot's dataset
1340         * is not considered in the test.
1341         *
1342         * @param obj  the object (<code>null</code> permitted).
1343         *
1344         * @return <code>true</code> or <code>false</code>.
1345         */
1346        public boolean equals(Object obj) {
1347            if (obj == this) {
1348                return true;
1349            }
1350            if (!(obj instanceof ThermometerPlot)) {
1351                return false;
1352            }
1353            ThermometerPlot that = (ThermometerPlot) obj;
1354            if (!super.equals(obj)) {
1355                return false;
1356            }
1357            if (!ObjectUtilities.equal(this.rangeAxis, that.rangeAxis)) {
1358                return false;
1359            }
1360            if (this.axisLocation != that.axisLocation) {
1361                return false;   
1362            }
1363            if (this.lowerBound != that.lowerBound) {
1364                return false;
1365            }
1366            if (this.upperBound != that.upperBound) {
1367                return false;
1368            }
1369            if (!ObjectUtilities.equal(this.padding, that.padding)) {
1370                return false;
1371            }
1372            if (!ObjectUtilities.equal(this.thermometerStroke, 
1373                    that.thermometerStroke)) {
1374                return false;
1375            }
1376            if (!PaintUtilities.equal(this.thermometerPaint, 
1377                    that.thermometerPaint)) {
1378                return false;
1379            }
1380            if (this.units != that.units) {
1381                return false;
1382            }
1383            if (this.valueLocation != that.valueLocation) {
1384                return false;
1385            }
1386            if (!ObjectUtilities.equal(this.valueFont, that.valueFont)) {
1387                return false;
1388            }
1389            if (!PaintUtilities.equal(this.valuePaint, that.valuePaint)) {
1390                return false;
1391            }
1392            if (!ObjectUtilities.equal(this.valueFormat, that.valueFormat)) {
1393                return false;
1394            }
1395            if (!PaintUtilities.equal(this.mercuryPaint, that.mercuryPaint)) {
1396                return false;
1397            }
1398            if (this.showValueLines != that.showValueLines) {
1399                return false;
1400            }
1401            if (this.subrange != that.subrange) {
1402                return false;
1403            }
1404            if (this.followDataInSubranges != that.followDataInSubranges) {
1405                return false;
1406            }
1407            if (!equal(this.subrangeInfo, that.subrangeInfo)) {
1408                return false;   
1409            }
1410            if (this.useSubrangePaint != that.useSubrangePaint) {
1411                return false;
1412            }
1413            for (int i = 0; i < this.subrangePaint.length; i++) {
1414                if (!PaintUtilities.equal(this.subrangePaint[i], 
1415                        that.subrangePaint[i])) {
1416                    return false;   
1417                }
1418            }
1419            return true;
1420        }
1421    
1422        /**
1423         * Tests two double[][] arrays for equality.
1424         * 
1425         * @param array1  the first array (<code>null</code> permitted).
1426         * @param array2  the second arrray (<code>null</code> permitted).
1427         * 
1428         * @return A boolean.
1429         */
1430        private static boolean equal(double[][] array1, double[][] array2) {
1431            if (array1 == null) {
1432                return (array2 == null);
1433            }
1434            if (array2 == null) {
1435                return false;
1436            }
1437            if (array1.length != array2.length) {
1438                return false;
1439            }
1440            for (int i = 0; i < array1.length; i++) {
1441                if (!Arrays.equals(array1[i], array2[i])) {
1442                    return false;
1443                }