    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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 * XYSeriesCollection.java
    029 * -----------------------
    030 * (C) Copyright 2001-2011, by Object Refinery Limited and Contributors.
    031 *
    032 * Original Author:  David Gilbert (for Object Refinery Limited);
    033 * Contributor(s):   Aaron Metzger;
    034 *
    035 * Changes
    036 * -------
    037 * 15-Nov-2001 : Version 1 (DG);
    038 * 03-Apr-2002 : Added change listener code (DG);
    039 * 29-Apr-2002 : Added removeSeries, removeAllSeries methods (ARM);
    040 * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    041 * 26-Mar-2003 : Implemented Serializable (DG);
    042 * 04-Aug-2003 : Added getSeries() method (DG);
    043 * 31-Mar-2004 : Modified to use an XYIntervalDelegate.
    044 * 05-May-2004 : Now extends AbstractIntervalXYDataset (DG);
    045 * 18-Aug-2004 : Moved from org.jfree.data --> org.jfree.data.xy (DG);
    046 * 17-Nov-2004 : Updated for changes to DomainInfo interface (DG);
    047 * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
    048 * 28-Mar-2005 : Fixed bug in getSeries(int) method (1170825) (DG);
    049 * 05-Oct-2005 : Made the interval delegate a dataset listener (DG);
    050 * ------------- JFREECHART 1.0.x ---------------------------------------------
    051 * 27-Nov-2006 : Added clone() override (DG);
    052 * 08-May-2007 : Added indexOf(XYSeries) method (DG);
    053 * 03-Dec-2007 : Added getSeries(Comparable) method (DG);
    054 * 22-Apr-2008 : Implemented PublicCloneable (DG);
    055 * 27-Feb-2009 : Overridden getDomainOrder() to detect when all series are
    056 *               sorted in ascending order (DG);
    057 * 06-Mar-2009 : Implemented RangeInfo (DG);
    058 * 06-Mar-2009 : Fixed equals() implementation (DG);
    059 * 10-Jun-2009 : Simplified code in getX() and getY() methods (DG);
    060 *
    061 */
    062
    063package org.jfree.data.xy;
    064
    065import java.beans.PropertyChangeEvent;
    066import java.beans.PropertyVetoException;
    067import java.beans.VetoableChangeListener;
    068import java.io.Serializable;
    069import java.util.Collections;
    070import java.util.Iterator;
    071import java.util.List;
    072
    073import org.jfree.chart.HashUtilities;
    074import org.jfree.chart.util.ParamChecks;
    075import org.jfree.data.DomainInfo;
    076import org.jfree.data.DomainOrder;
    077import org.jfree.data.Range;
    078import org.jfree.data.RangeInfo;
    079import org.jfree.data.UnknownKeyException;
    080import org.jfree.data.general.DatasetChangeEvent;
    081import org.jfree.data.general.Series;
    082import org.jfree.util.ObjectUtilities;
    083import org.jfree.util.PublicCloneable;
    084
    085/**
    086 * Represents a collection of {@link XYSeries} objects that can be used as a
    087 * dataset.
    088 */
    089public class XYSeriesCollection extends AbstractIntervalXYDataset
    090        implements IntervalXYDataset, DomainInfo, RangeInfo,
    091        VetoableChangeListener, PublicCloneable, Serializable {
    092
    093    /** For serialization. */
    094    private static final long serialVersionUID = -7590013825931496766L;
    095
    096    /** The series that are included in the collection. */
    097    private List data;
    098
    099    /** The interval delegate (used to calculate the start and end x-values). */
    100    private IntervalXYDelegate intervalDelegate;
    101
    102    /**
    103     * Constructs an empty dataset.
    104     */
    105    public XYSeriesCollection() {
    106        this(null);
    107    }
    108
    109    /**
    110     * Constructs a dataset and populates it with a single series.
    111     *
    112     * @param series  the series (<code>null</code> ignored).
    113     */
    114    public XYSeriesCollection(XYSeries series) {
    115        this.data = new java.util.ArrayList();
    116        this.intervalDelegate = new IntervalXYDelegate(this, false);
    117        addChangeListener(this.intervalDelegate);
    118        if (series != null) {
    119            this.data.add(series);
    120            series.addChangeListener(this);
    121            series.addVetoableChangeListener(this);
    122        }
    123    }
    124
    125    /**
    126     * Returns the order of the domain (X) values, if this is known.
    127     *
    128     * @return The domain order.
    129     */
    130    public DomainOrder getDomainOrder() {
    131        int seriesCount = getSeriesCount();
    132        for (int i = 0; i < seriesCount; i++) {
    133            XYSeries s = getSeries(i);
    134            if (!s.getAutoSort()) {
    135                return DomainOrder.NONE;  // we can't be sure of the order
    136            }
    137        }
    138        return DomainOrder.ASCENDING;
    139    }
    140
    141    /**
    142     * Adds a series to the collection and sends a {@link DatasetChangeEvent}
    143     * to all registered listeners.
    144     *
    145     * @param series  the series (<code>null</code> not permitted).
    146     *
    147     * @throws IllegalArgumentException if the key for the series is null or
    148     *     not unique within the dataset.
    149     */
    150    public void addSeries(XYSeries series) {
    151        ParamChecks.nullNotPermitted(series, "series");
    152        if (getSeriesIndex(series.getKey()) >= 0) {
    153            throw new IllegalArgumentException(
    154                "This dataset already contains a series with the key "
    155                + series.getKey());
    156        }
    157        this.data.add(series);
    158        series.addChangeListener(this);
    159        series.addVetoableChangeListener(this);
    160        fireDatasetChanged();
    161    }
    162
    163    /**
    164     * Removes a series from the collection and sends a
    165     * {@link DatasetChangeEvent} to all registered listeners.
    166     *
    167     * @param series  the series index (zero-based).
    168     */
    169    public void removeSeries(int series) {
    170        if ((series < 0) || (series >= getSeriesCount())) {
    171            throw new IllegalArgumentException("Series index out of bounds.");
    172        }
    173
    174        // fetch the series, remove the change listener, then remove the series.
    175        XYSeries ts = (XYSeries) this.data.get(series);
    176        ts.removeChangeListener(this);
    177        this.data.remove(series);
    178        fireDatasetChanged();
    179    }
    180
    181    /**
    182     * Removes a series from the collection and sends a
    183     * {@link DatasetChangeEvent} to all registered listeners.
    184     *
    185     * @param series  the series (<code>null</code> not permitted).
    186     */
    187    public void removeSeries(XYSeries series) {
    188        if (series == null) {
    189            throw new IllegalArgumentException("Null 'series' argument.");
    190        }
    191        if (this.data.contains(series)) {
    192            series.removeChangeListener(this);
    193            series.removeVetoableChangeListener(this);
    194            this.data.remove(series);
    195            fireDatasetChanged();
    196        }
    197    }
    198
    199    /**
    200     * Removes all the series from the collection and sends a
    201     * {@link DatasetChangeEvent} to all registered listeners.
    202     */
    203    public void removeAllSeries() {
    204        // Unregister the collection as a change listener to each series in
    205        // the collection.
    206        for (int i = 0; i < this.data.size(); i++) {
    207          XYSeries series = (XYSeries) this.data.get(i);
    208          series.removeChangeListener(this);
    209          series.removeVetoableChangeListener(this);
    210        }
    211
    212        // Remove all the series from the collection and notify listeners.
    213        this.data.clear();
    214        fireDatasetChanged();
    215    }
    216
    217    /**
    218     * Returns the number of series in the collection.
    219     *
    220     * @return The series count.
    221     */
    222    public int getSeriesCount() {
    223        return this.data.size();
    224    }
    225
    226    /**
    227     * Returns a list of all the series in the collection.
    228     *
    229     * @return The list (which is unmodifiable).
    230     */
    231    public List getSeries() {
    232        return Collections.unmodifiableList(this.data);
    233    }
    234
    235    /**
    236     * Returns the index of the specified series, or -1 if that series is not
    237     * present in the dataset.
    238     *
    239     * @param series  the series (<code>null</code> not permitted).
    240     *
    241     * @return The series index.
    242     *
    243     * @since 1.0.6
    244     */
    245    public int indexOf(XYSeries series) {
    246        if (series == null) {
    247            throw new IllegalArgumentException("Null 'series' argument.");
    248        }
    249        return this.data.indexOf(series);
    250    }
    251
    252    /**
    253     * Returns a series from the collection.
    254     *
    255     * @param series  the series index (zero-based).
    256     *
    257     * @return The series.
    258     *
    259     * @throws IllegalArgumentException if <code>series</code> is not in the
    260     *     range <code>0</code> to <code>getSeriesCount() - 1</code>.
    261     */
    262    public XYSeries getSeries(int series) {
    263        if ((series < 0) || (series >= getSeriesCount())) {
    264            throw new IllegalArgumentException("Series index out of bounds");
    265        }
    266        return (XYSeries) this.data.get(series);
    267    }
    268
    269    /**
    270     * Returns a series from the collection.
    271     *
    272     * @param key  the key (<code>null</code> not permitted).
    273     *
    274     * @return The series with the specified key.
    275     *
    276     * @throws UnknownKeyException if <code>key</code> is not found in the
    277     *         collection.
    278     *
    279     * @since 1.0.9
    280     */
    281    public XYSeries getSeries(Comparable key) {
    282        if (key == null) {
    283            throw new IllegalArgumentException("Null 'key' argument.");
    284        }
    285        Iterator iterator = this.data.iterator();
    286        while (iterator.hasNext()) {
    287            XYSeries series = (XYSeries) iterator.next();
    288            if (key.equals(series.getKey())) {
    289                return series;
    290            }
    291        }
    292        throw new UnknownKeyException("Key not found: " + key);
    293    }
    294
    295    /**
    296     * Returns the key for a series.
    297     *
    298     * @param series  the series index (in the range <code>0</code> to
    299     *     <code>getSeriesCount() - 1</code>).
    300     *
    301     * @return The key for a series.
    302     *
    303     * @throws IllegalArgumentException if <code>series</code> is not in the
    304     *     specified range.
    305     */
    306    public Comparable getSeriesKey(int series) {
    307        // defer argument checking
    308        return getSeries(series).getKey();
    309    }
    310
    311    /**
    312     * Returns the index of the series with the specified key, or -1 if no
    313     * series has that key.
    314     *
    315     * @param key  the key (<code>null</code> not permitted).
    316     *
    317     * @return The index.
    318     *
    319     * @since 1.0.14
    320     */
    321    public int getSeriesIndex(Comparable key) {
    322        ParamChecks.nullNotPermitted(key, "key");
    323        int seriesCount = getSeriesCount();
    324        for (int i = 0; i < seriesCount; i++) {
    325            XYSeries series = (XYSeries) this.data.get(i);
    326            if (key.equals(series.getKey())) {
    327                return i;
    328            }
    329        }
    330        return -1;
    331    }
    332
    333    /**
    334     * Returns the number of items in the specified series.
    335     *
    336     * @param series  the series (zero-based index).
    337     *
    338     * @return The item count.
    339     *
    340     * @throws IllegalArgumentException if <code>series</code> is not in the
    341     *     range <code>0</code> to <code>getSeriesCount() - 1</code>.
    342     */
    343    public int getItemCount(int series) {
    344        // defer argument checking
    345        return getSeries(series).getItemCount();
    346    }
    347
    348    /**
    349     * Returns the x-value for the specified series and item.
    350     *
    351     * @param series  the series (zero-based index).
    352     * @param item  the item (zero-based index).
    353     *
    354     * @return The value.
    355     */
    356    public Number getX(int series, int item) {
    357        XYSeries s = (XYSeries) this.data.get(series);
    358        return s.getX(item);
    359    }
    360
    361    /**
    362     * Returns the starting X value for the specified series and item.
    363     *
    364     * @param series  the series (zero-based index).
    365     * @param item  the item (zero-based index).
    366     *
    367     * @return The starting X value.
    368     */
    369    public Number getStartX(int series, int item) {
    370        return this.intervalDelegate.getStartX(series, item);
    371    }
    372
    373    /**
    374     * Returns the ending X value for the specified series and item.
    375     *
    376     * @param series  the series (zero-based index).
    377     * @param item  the item (zero-based index).
    378     *
    379     * @return The ending X value.
    380     */
    381    public Number getEndX(int series, int item) {
    382        return this.intervalDelegate.getEndX(series, item);
    383    }
    384
    385    /**
    386     * Returns the y-value for the specified series and item.
    387     *
    388     * @param series  the series (zero-based index).
    389     * @param index  the index of the item of interest (zero-based).
    390     *
    391     * @return The value (possibly <code>null</code>).
    392     */
    393    public Number getY(int series, int index) {
    394        XYSeries s = (XYSeries) this.data.get(series);
    395        return s.getY(index);
    396    }
    397
    398    /**
    399     * Returns the starting Y value for the specified series and item.
    400     *
    401     * @param series  the series (zero-based index).
    402     * @param item  the item (zero-based index).
    403     *
    404     * @return The starting Y value.
    405     */
    406    public Number getStartY(int series, int item) {
    407        return getY(series, item);
    408    }
    409
    410    /**
    411     * Returns the ending Y value for the specified series and item.
    412     *
    413     * @param series  the series (zero-based index).
    414     * @param item  the item (zero-based index).
    415     *
    416     * @return The ending Y value.
    417     */
    418    public Number getEndY(int series, int item) {
    419        return getY(series, item);
    420    }
    421
    422    /**
    423     * Tests this collection for equality with an arbitrary object.
    424     *
    425     * @param obj  the object (<code>null</code> permitted).
    426     *
    427     * @return A boolean.
    428     */
    429    public boolean equals(Object obj) {
    430        if (obj == this) {
    431            return true;
    432        }
    433        if (!(obj instanceof XYSeriesCollection)) {
    434            return false;
    435        }
    436        XYSeriesCollection that = (XYSeriesCollection) obj;
    437        if (!this.intervalDelegate.equals(that.intervalDelegate)) {
    438            return false;
    439        }
    440        return ObjectUtilities.equal(this.data, that.data);
    441    }
    442
    443    /**
    444     * Returns a clone of this instance.
    445     *
    446     * @return A clone.
    447     *
    448     * @throws CloneNotSupportedException if there is a problem.
    449     */
    450    public Object clone() throws CloneNotSupportedException {
    451        XYSeriesCollection clone = (XYSeriesCollection) super.clone();
    452        clone.data = (List) ObjectUtilities.deepClone(this.data);
    453        clone.intervalDelegate
    454                = (IntervalXYDelegate) this.intervalDelegate.clone();
    455        return clone;
    456    }
    457
    458    /**
    459     * Returns a hash code.
    460     *
    461     * @return A hash code.
    462     */
    463    public int hashCode() {
    464        int hash = 5;
    465        hash = HashUtilities.hashCode(hash, this.intervalDelegate);
    466        hash = HashUtilities.hashCode(hash, this.data);
    467        return hash;
    468    }
    469
    470    /**
    471     * Returns the minimum x-value in the dataset.
    472     *
    473     * @param includeInterval  a flag that determines whether or not the
    474     *                         x-interval is taken into account.
    475     *
    476     * @return The minimum value.
    477     */
    478    public double getDomainLowerBound(boolean includeInterval) {
    479        if (includeInterval) {
    480            return this.intervalDelegate.getDomainLowerBound(includeInterval);
    481        }
    482        double result = Double.NaN;
    483        int seriesCount = getSeriesCount();
    484        for (int s = 0; s < seriesCount; s++) {
    485            XYSeries series = getSeries(s);
    486            double lowX = series.getMinX();
    487            if (Double.isNaN(result)) {
    488                result = lowX;
    489            }
    490            else {
    491                if (!Double.isNaN(lowX)) {
    492                    result = Math.min(result, lowX);
    493                }
    494            }
    495        }
    496        return result;
    497    }
    498
    499    /**
    500     * Returns the maximum x-value in the dataset.
    501     *
    502     * @param includeInterval  a flag that determines whether or not the
    503     *                         x-interval is taken into account.
    504     *
    505     * @return The maximum value.
    506     */
    507    public double getDomainUpperBound(boolean includeInterval) {
    508        if (includeInterval) {
    509            return this.intervalDelegate.getDomainUpperBound(includeInterval);
    510        }
    511        else {
    512            double result = Double.NaN;
    513            int seriesCount = getSeriesCount();
    514            for (int s = 0; s < seriesCount; s++) {
    515                XYSeries series = getSeries(s);
    516                double hiX = series.getMaxX();
    517                if (Double.isNaN(result)) {
    518                    result = hiX;
    519                }
    520                else {
    521                    if (!Double.isNaN(hiX)) {
    522                        result = Math.max(result, hiX);
    523                    }
    524                }
    525            }
    526            return result;
    527        }
    528    }
    529
    530    /**
    531     * Returns the range of the values in this dataset's domain.
    532     *
    533     * @param includeInterval  a flag that determines whether or not the
    534     *                         x-interval is taken into account.
    535     *
    536     * @return The range (or <code>null</code> if the dataset contains no
    537     *     values).
    538     */
    539    public Range getDomainBounds(boolean includeInterval) {
    540        if (includeInterval) {
    541            return this.intervalDelegate.getDomainBounds(includeInterval);
    542        }
    543        else {
    544            double lower = Double.POSITIVE_INFINITY;
    545            double upper = Double.NEGATIVE_INFINITY;
    546            int seriesCount = getSeriesCount();
    547            for (int s = 0; s < seriesCount; s++) {
    548                XYSeries series = getSeries(s);
    549                double minX = series.getMinX();
    550                if (!Double.isNaN(minX)) {
    551                    lower = Math.min(lower, minX);
    552                }
    553                double maxX = series.getMaxX();
    554                if (!Double.isNaN(maxX)) {
    555                    upper = Math.max(upper, maxX);
    556                }
    557            }
    558            if (lower > upper) {
    559                return null;
    560            }
    561            else {
    562                return new Range(lower, upper);
    563            }
    564        }
    565    }
    566
    567    /**
    568     * Returns the interval width. This is used to calculate the start and end
    569     * x-values, if/when the dataset is used as an {@link IntervalXYDataset}.
    570     *
    571     * @return The interval width.
    572     */
    573    public double getIntervalWidth() {
    574        return this.intervalDelegate.getIntervalWidth();
    575    }
    576
    577    /**
    578     * Sets the interval width and sends a {@link DatasetChangeEvent} to all
    579     * registered listeners.
    580     *
    581     * @param width  the width (negative values not permitted).
    582     */
    583    public void setIntervalWidth(double width) {
    584        if (width < 0.0) {
    585            throw new IllegalArgumentException("Negative 'width' argument.");
    586        }
    587        this.intervalDelegate.setFixedIntervalWidth(width);
    588        fireDatasetChanged();
    589    }
    590
    591    /**
    592     * Returns the interval position factor.
    593     *
    594     * @return The interval position factor.
    595     */
    596    public double getIntervalPositionFactor() {
    597        return this.intervalDelegate.getIntervalPositionFactor();
    598    }
    599
    600    /**
    601     * Sets the interval position factor. This controls where the x-value is in
    602     * relation to the interval surrounding the x-value (0.0 means the x-value
    603     * will be positioned at the start, 0.5 in the middle, and 1.0 at the end).
    604     *
    605     * @param factor  the factor.
    606     */
    607    public void setIntervalPositionFactor(double factor) {
    608        this.intervalDelegate.setIntervalPositionFactor(factor);
    609        fireDatasetChanged();
    610    }
    611
    612    /**
    613     * Returns whether the interval width is automatically calculated or not.
    614     *
    615     * @return Whether the width is automatically calculated or not.
    616     */
    617    public boolean isAutoWidth() {
    618        return this.intervalDelegate.isAutoWidth();
    619    }
    620
    621    /**
    622     * Sets the flag that indicates wether the interval width is automatically
    623     * calculated or not.
    624     *
    625     * @param b  a boolean.
    626     */
    627    public void setAutoWidth(boolean b) {
    628        this.intervalDelegate.setAutoWidth(b);
    629        fireDatasetChanged();
    630    }
    631
    632    /**
    633     * Returns the range of the values in this dataset's range.
    634     *
    635     * @param includeInterval  ignored.
    636     *
    637     * @return The range (or <code>null</code> if the dataset contains no
    638     *     values).
    639     */
    640    public Range getRangeBounds(boolean includeInterval) {
    641        double lower = Double.POSITIVE_INFINITY;
    642        double upper = Double.NEGATIVE_INFINITY;
    643        int seriesCount = getSeriesCount();
    644        for (int s = 0; s < seriesCount; s++) {
    645            XYSeries series = getSeries(s);
    646            double minY = series.getMinY();
    647            if (!Double.isNaN(minY)) {
    648                lower = Math.min(lower, minY);
    649            }
    650            double maxY = series.getMaxY();
    651            if (!Double.isNaN(maxY)) {
    652                upper = Math.max(upper, maxY);
    653            }
    654        }
    655        if (lower > upper) {
    656            return null;
    657        }
    658        else {
    659            return new Range(lower, upper);
    660        }
    661    }
    662
    663    /**
    664     * Returns the minimum y-value in the dataset.
    665     *
    666     * @param includeInterval  a flag that determines whether or not the
    667     *                         y-interval is taken into account.
    668     *
    669     * @return The minimum value.
    670     */
    671    public double getRangeLowerBound(boolean includeInterval) {
    672        double result = Double.NaN;
    673        int seriesCount = getSeriesCount();
    674        for (int s = 0; s < seriesCount; s++) {
    675            XYSeries series = getSeries(s);
    676            double lowY = series.getMinY();
    677            if (Double.isNaN(result)) {
    678                result = lowY;
    679            }
    680            else {
    681                if (!Double.isNaN(lowY)) {
    682                    result = Math.min(result, lowY);
    683                }
    684            }
    685        }
    686        return result;
    687    }
    688
    689    /**
    690     * Returns the maximum y-value in the dataset.
    691     *
    692     * @param includeInterval  a flag that determines whether or not the
    693     *                         y-interval is taken into account.
    694     *
    695     * @return The maximum value.
    696     */
    697    public double getRangeUpperBound(boolean includeInterval) {
    698        double result = Double.NaN;
    699        int seriesCount = getSeriesCount();
    700        for (int s = 0; s < seriesCount; s++) {
    701            XYSeries series = getSeries(s);
    702            double hiY = series.getMaxY();
    703            if (Double.isNaN(result)) {
    704                result = hiY;
    705            }
    706            else {
    707                if (!Double.isNaN(hiY)) {
    708                    result = Math.max(result, hiY);
    709                }
    710            }
    711        }
    712        return result;
    713    }
    714
    715    /**
    716     * Receives notification that the key for one of the series in the
    717     * collection has changed, and vetos it if the key is already present in
    718     * the collection.
    719     *
    720     * @param e  the event.
    721     *
    722     * @since 1.0.14
    723     */
    724    public void vetoableChange(PropertyChangeEvent e)
    725            throws PropertyVetoException {
    726        // if it is not the series name, then we have no interest
    727        if (!"Key".equals(e.getPropertyName())) {
    728            return;
    729        }
    730
    731        // to be defensive, let's check that the source series does in fact
    732        // belong to this collection
    733        Series s = (Series) e.getSource();
    734        if (getSeries(s.getKey()) == null) {
    735            throw new IllegalStateException("Receiving events from a series " +
    736                    "that does not belong to this collection.");
    737        }
    738        // check if the new series name already exists for another series
    739        Comparable key = (Comparable) e.getNewValue();
    740        if (this.getSeries(key) != null) {
    741            throw new PropertyVetoException("Duplicate key2", e);
    742        }
    743    }
    744
    745}