001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2008, 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     * 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    }