    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     * ChartEntity.java
    029     * ----------------
    030     * (C) Copyright 2002-2008, by Object Refinery Limited and Contributors.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Richard Atkinson;
    034     *                   Xavier Poinsard;
    035     *                   Robert Fuller;
    036     *
    037     * Changes:
    038     * --------
    039     * 23-May-2002 : Version 1 (DG);
    040     * 12-Jun-2002 : Added Javadoc comments (DG);
    041     * 26-Jun-2002 : Added methods for image maps (DG);
    042     * 05-Aug-2002 : Added constructor and accessors for URL support in image maps
    043     *               Added getImageMapAreaTag() - previously in subclasses (RA);
    044     * 05-Sep-2002 : Added getImageMapAreaTag(boolean) to support OverLIB for
    045     *               tooltips http://www.bosrup.com/web/overlib (RA);
    046     * 03-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    047     * 08-Oct-2002 : Changed getImageMapAreaTag to use title instead of alt
    048     *               attribute so HTML image maps now work in Mozilla and Opera as
    049     *               well as Internet Explorer (RA);
    050     * 13-Mar-2003 : Change getImageMapAreaTag to only return a tag when there is a
    051     *               tooltip or URL, as suggested by Xavier Poinsard (see Feature
    052     *               Request 688079) (DG);
    053     * 12-Aug-2003 : Added support for custom image maps using
    054     *               ToolTipTagFragmentGenerator and URLTagFragmentGenerator (RA);
    055     * 02-Sep-2003 : Incorporated fix (791901) submitted by Robert Fuller (DG);
    056     * 19-May-2004 : Added equals() method and implemented Cloneable and
    057     *               Serializable (DG);
    058     * 29-Sep-2004 : Implemented PublicCloneable (DG);
    059     * 13-Jan-2005 : Fixed for compliance with XHTML 1.0 (DG);
    060     * 18-Apr-2005 : Use StringBuffer (DG);
    061     * 20-Apr-2005 : Added toString() implementation (DG);
    062     * ------------- JFREECHART 1.0.x ---------------------------------------------
    063     * 06-Feb-2007 : API doc update (DG);
    064     * 13-Nov-2007 : Reorganised equals(), implemented hashCode (DG);
    065     * 04-Dec-2007 : Added 'nohref' attribute in getImageMapAreaTag() method, to
    066     *               fix bug 1460195 (DG);
    067     * 04-Dec-2007 : Escape the toolTipText and urlText in getImageMapAreaTag() to
    068     *               prevent special characters corrupting the HTML (DG);
    069     * 05-Dec-2007 : Previous change reverted - let the tool tip and url tag
    070     *               generators handle filtering / escaping (DG);
    071     *
    072     */
    073
    074    package org.jfree.chart.entity;
    075
    076    import java.awt.Shape;
    077    import java.awt.geom.PathIterator;
    078    import java.awt.geom.Rectangle2D;
    079    import java.io.IOException;
    080    import java.io.ObjectInputStream;
    081    import java.io.ObjectOutputStream;
    082    import java.io.Serializable;
    083
    084    import org.jfree.chart.HashUtilities;
    085    import org.jfree.chart.imagemap.ToolTipTagFragmentGenerator;
    086    import org.jfree.chart.imagemap.URLTagFragmentGenerator;
    087    import org.jfree.io.SerialUtilities;
    088    import org.jfree.util.ObjectUtilities;
    089    import org.jfree.util.PublicCloneable;
    090
    091    /**
    092     * A class that captures information about some component of a chart (a bar,
    093     * line etc).
    094     */
    095    public class ChartEntity implements Cloneable, PublicCloneable, Serializable {
    096
    097        /** For serialization. */
    098        private static final long serialVersionUID = -4445994133561919083L;
    099
    100        /** The area occupied by the entity (in Java 2D space). */
    101        private transient Shape area;
    102
    103        /** The tool tip text for the entity. */
    104        private String toolTipText;
    105
    106        /** The URL text for the entity. */
    107        private String urlText;
    108
    109        /**
    110         * Creates a new chart entity.
    111         *
    112         * @param area  the area (<code>null</code> not permitted).
    113         */
    114        public ChartEntity(Shape area) {
    115            // defer argument checks...
    116            this(area, null);
    117        }
    118
    119        /**
    120         * Creates a new chart entity.
    121         *
    122         * @param area  the area (<code>null</code> not permitted).
    123         * @param toolTipText  the tool tip text (<code>null</code> permitted).
    124         */
    125        public ChartEntity(Shape area, String toolTipText) {
    126            // defer argument checks...
    127            this(area, toolTipText, null);
    128        }
    129
    130        /**
    131         * Creates a new entity.
    132         *
    133         * @param area  the area (<code>null</code> not permitted).
    134         * @param toolTipText  the tool tip text (<code>null</code> permitted).
    135         * @param urlText  the URL text for HTML image maps (<code>null</code>
    136         *                 permitted).
    137         */
    138        public ChartEntity(Shape area, String toolTipText, String urlText) {
    139            if (area == null) {
    140                throw new IllegalArgumentException("Null 'area' argument.");
    141            }
    142            this.area = area;
    143            this.toolTipText = toolTipText;
    144            this.urlText = urlText;
    145        }
    146
    147        /**
    148         * Returns the area occupied by the entity (in Java 2D space).
    149         *
    150         * @return The area (never <code>null</code>).
    151         */
    152        public Shape getArea() {
    153            return this.area;
    154        }
    155
    156        /**
    157         * Sets the area for the entity.
    158         * <P>
    159         * This class conveys information about chart entities back to a client.
    160         * Setting this area doesn't change the entity (which has already been
    161         * drawn).
    162         *
    163         * @param area  the area (<code>null</code> not permitted).
    164         */
    165        public void setArea(Shape area) {
    166            if (area == null) {
    167                throw new IllegalArgumentException("Null 'area' argument.");
    168            }
    169            this.area = area;
    170        }
    171
    172        /**
    173         * Returns the tool tip text for the entity.  Be aware that this text
    174         * may have been generated from user supplied data, so for security
    175         * reasons some form of filtering should be applied before incorporating
    176         * this text into any HTML output.
    177         *
    178         * @return The tool tip text (possibly <code>null</code>).
    179         */
    180        public String getToolTipText() {
    181            return this.toolTipText;
    182        }
    183
    184        /**
    185         * Sets the tool tip text.
    186         *
    187         * @param text  the text (<code>null</code> permitted).
    188         */
    189        public void setToolTipText(String text) {
    190            this.toolTipText = text;
    191        }
    192
    193        /**
    194         * Returns the URL text for the entity.  Be aware that this text
    195         * may have been generated from user supplied data, so some form of
    196         * filtering should be applied before this "URL" is used in any output.
    197         *
    198         * @return The URL text (possibly <code>null</code>).
    199         */
    200        public String getURLText() {
    201            return this.urlText;
    202        }
    203
    204        /**
    205         * Sets the URL text.
    206         *
    207         * @param text the text (<code>null</code> permitted).
    208         */
    209        public void setURLText(String text) {
    210            this.urlText = text;
    211        }
    212
    213        /**
    214         * Returns a string describing the entity area.  This string is intended
    215         * for use in an AREA tag when generating an image map.
    216         *
    217         * @return The shape type (never <code>null</code>).
    218         */
    219        public String getShapeType() {
    220            if (this.area instanceof Rectangle2D) {
    221                return "rect";
    222            }
    223            else {
    224                return "poly";
    225            }
    226        }
    227
    228        /**
    229         * Returns the shape coordinates as a string.
    230         *
    231         * @return The shape coordinates (never <code>null</code>).
    232         */
    233        public String getShapeCoords() {
    234            if (this.area instanceof Rectangle2D) {
    235                return getRectCoords((Rectangle2D) this.area);
    236            }
    237            else {
    238                return getPolyCoords(this.area);
    239            }
    240        }
    241
    242        /**
    243         * Returns a string containing the coordinates (x1, y1, x2, y2) for a given
    244         * rectangle.  This string is intended for use in an image map.
    245         *
    246         * @param rectangle  the rectangle (<code>null</code> not permitted).
    247         *
    248         * @return Upper left and lower right corner of a rectangle.
    249         */
    250        private String getRectCoords(Rectangle2D rectangle) {
    251            if (rectangle == null) {
    252                throw new IllegalArgumentException("Null 'rectangle' argument.");
    253            }
    254            int x1 = (int) rectangle.getX();
    255            int y1 = (int) rectangle.getY();
    256            int x2 = x1 + (int) rectangle.getWidth();
    257            int y2 = y1 + (int) rectangle.getHeight();
    258            //      fix by rfuller
    259            if (x2 == x1) {
    260                x2++;
    261            }
    262            if (y2 == y1) {
    263                y2++;
    264            }
    265            //      end fix by rfuller
    266            return x1 + "," + y1 + "," + x2 + "," + y2;
    267        }
    268
    269        /**
    270         * Returns a string containing the coordinates for a given shape.  This
    271         * string is intended for use in an image map.
    272         *
    273         * @param shape  the shape (<code>null</code> not permitted).
    274         *
    275         * @return The coordinates for a given shape as string.
    276         */
    277        private String getPolyCoords(Shape shape) {
    278            if (shape == null) {
    279                throw new IllegalArgumentException("Null 'shape' argument.");
    280            }
    281            StringBuffer result = new StringBuffer();
    282            boolean first = true;
    283            float[] coords = new float[6];
    284            PathIterator pi = shape.getPathIterator(null, 1.0);
    285            while (!pi.isDone()) {
    286                pi.currentSegment(coords);
    287                if (first) {
    288                    first = false;
    289                    result.append((int) coords[0]);
    290                    result.append(",").append((int) coords[1]);
    291                }
    292                else {
    293                    result.append(",");
    294                    result.append((int) coords[0]);
    295                    result.append(",");
    296                    result.append((int) coords[1]);
    297                }
    298                pi.next();
    299            }
    300            return result.toString();
    301        }
    302
    303        /**
    304         * Returns an HTML image map tag for this entity.  The returned fragment
    305         * should be <code>XHTML 1.0</code> compliant.
    306         *
    307         * @param toolTipTagFragmentGenerator  a generator for the HTML fragment
    308         *     that will contain the tooltip text (<code>null</code> not permitted
    309         *     if this entity contains tooltip information).
    310         * @param urlTagFragmentGenerator  a generator for the HTML fragment that
    311         *     will contain the URL reference (<code>null</code> not permitted if
    312         *     this entity has a URL).
    313         *
    314         * @return The HTML tag.
    315         */
    316        public String getImageMapAreaTag(
    317                ToolTipTagFragmentGenerator toolTipTagFragmentGenerator,
    318                URLTagFragmentGenerator urlTagFragmentGenerator) {
    319
    320            StringBuffer tag = new StringBuffer();
    321            boolean hasURL = (this.urlText == null ? false
    322                    : !this.urlText.equals(""));
    323            boolean hasToolTip = (this.toolTipText == null ? false
    324                    : !this.toolTipText.equals(""));
    325            if (hasURL || hasToolTip) {
    326                tag.append("<area shape=\"" + getShapeType() + "\"" + " coords=\""
    327                        + getShapeCoords() + "\"");
    328                if (hasToolTip) {
    329                    tag.append(toolTipTagFragmentGenerator.generateToolTipFragment(
    330                            this.toolTipText));
    331                }
    332                if (hasURL) {
    333                    tag.append(urlTagFragmentGenerator.generateURLFragment(
    334                            this.urlText));
    335                }
    336                else {
    337                    tag.append(" nohref=\"nohref\"");
    338                }
    339                // if there is a tool tip, we expect it to generate the title and
    340                // alt values, so we only add an empty alt if there is no tooltip
    341                if (!hasToolTip) {
    342                    tag.append(" alt=\"\"");
    343                }
    344                tag.append("/>");
    345            }
    346            return tag.toString();
    347        }
    348
    349        /**
    350         * Returns a string representation of the chart entity, useful for
    351         * debugging.
    352         *
    353         * @return A string.
    354         */
    355        public String toString() {
    356            StringBuffer buf = new StringBuffer("ChartEntity: ");
    357            buf.append("tooltip = ");
    358            buf.append(this.toolTipText);
    359            return buf.toString();
    360        }
    361
    362        /**
    363         * Tests the entity for equality with an arbitrary object.
    364         *
    365         * @param obj  the object to test against (<code>null</code> permitted).
    366         *
    367         * @return A boolean.
    368         */
    369        public boolean equals(Object obj) {
    370            if (obj == this) {
    371                return true;
    372            }
    373            if (!(obj instanceof ChartEntity)) {
    374                return false;
    375            }
    376            ChartEntity that = (ChartEntity) obj;
    377            if (!this.area.equals(that.area)) {
    378                return false;
    379            }
    380            if (!ObjectUtilities.equal(this.toolTipText, that.toolTipText)) {
    381                return false;
    382            }
    383            if (!ObjectUtilities.equal(this.urlText, that.urlText)) {
    384                return false;
    385            }
    386            return true;
    387        }
    388
    389        /**
    390         * Returns a hash code for this instance.
    391         *
    392         * @return A hash code.
    393         */
    394        public int hashCode() {
    395            int result = 37;
    396            result = HashUtilities.hashCode(result, this.toolTipText);
    397            result = HashUtilities.hashCode(result, this.urlText);
    398            return result;
    399        }
    400
    401        /**
    402         * Returns a clone of the entity.
    403         *
    404         * @return A clone.
    405         *
    406         * @throws CloneNotSupportedException if there is a problem cloning the
    407         *         entity.
    408         */
    409        public Object clone() throws CloneNotSupportedException {
    410            return super.clone();
    411        }
    412
    413        /**
    414         * Provides serialization support.
    415         *
    416         * @param stream  the output stream.
    417         *
    418         * @throws IOException  if there is an I/O error.
    419         */
    420        private void writeObject(ObjectOutputStream stream) throws IOException {
    421            stream.defaultWriteObject();
    422            SerialUtilities.writeShape(this.area, stream);
    423         }
    424
    425        /**
    426         * Provides serialization support.
    427         *
    428         * @param stream  the input stream.
    429         *
    430         * @throws IOException  if there is an I/O error.
    431         * @throws ClassNotFoundException  if there is a classpath problem.
    432         */
    433        private void readObject(ObjectInputStream stream)
    434            throws IOException, ClassNotFoundException {
    435            stream.defaultReadObject();
    436            this.area = SerialUtilities.readShape(stream);
    437        }
    438
    439    }