Source for org.jfree.chart.entity.ChartEntity

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * ----------------
  28:  * ChartEntity.java
  29:  * ----------------
  30:  * (C) Copyright 2002-2007, by Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Richard Atkinson;
  34:  *                   Xavier Poinsard;
  35:  *                   Robert Fuller;
  36:  *
  37:  * Changes:
  38:  * --------
  39:  * 23-May-2002 : Version 1 (DG);
  40:  * 12-Jun-2002 : Added Javadoc comments (DG);
  41:  * 26-Jun-2002 : Added methods for image maps (DG);
  42:  * 05-Aug-2002 : Added constructor and accessors for URL support in image maps
  43:  *               Added getImageMapAreaTag() - previously in subclasses (RA);
  44:  * 05-Sep-2002 : Added getImageMapAreaTag(boolean) to support OverLIB for 
  45:  *               tooltips http://www.bosrup.com/web/overlib (RA);
  46:  * 03-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  47:  * 08-Oct-2002 : Changed getImageMapAreaTag to use title instead of alt 
  48:  *               attribute so HTML image maps now work in Mozilla and Opera as 
  49:  *               well as Internet Explorer (RA);
  50:  * 13-Mar-2003 : Change getImageMapAreaTag to only return a tag when there is a
  51:  *               tooltip or URL, as suggested by Xavier Poinsard (see Feature 
  52:  *               Request 688079) (DG);
  53:  * 12-Aug-2003 : Added support for custom image maps using 
  54:  *               ToolTipTagFragmentGenerator and URLTagFragmentGenerator (RA);
  55:  * 02-Sep-2003 : Incorporated fix (791901) submitted by Robert Fuller (DG);
  56:  * 19-May-2004 : Added equals() method and implemented Cloneable and 
  57:  *               Serializable (DG);
  58:  * 29-Sep-2004 : Implemented PublicCloneable (DG);
  59:  * 13-Jan-2005 : Fixed for compliance with XHTML 1.0 (DG);
  60:  * 18-Apr-2005 : Use StringBuffer (DG);
  61:  * 20-Apr-2005 : Added toString() implementation (DG);
  62:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  63:  * 06-Feb-2007 : API doc update (DG);
  64:  * 13-Nov-2007 : Reorganised equals(), implemented hashCode (DG);
  65:  * 04-Dec-2007 : Added 'nohref' attribute in getImageMapAreaTag() method, to 
  66:  *               fix bug 1460195 (DG);
  67:  * 04-Dec-2007 : Escape the toolTipText and urlText in getImageMapAreaTag() to
  68:  *               prevent special characters corrupting the HTML (DG);
  69:  * 05-Dec-2007 : Previous change reverted - let the tool tip and url tag
  70:  *               generators handle filtering / escaping (DG);
  71:  *
  72:  */
  73: 
  74: package org.jfree.chart.entity;
  75: 
  76: import java.awt.Shape;
  77: import java.awt.geom.PathIterator;
  78: import java.awt.geom.Rectangle2D;
  79: import java.io.IOException;
  80: import java.io.ObjectInputStream;
  81: import java.io.ObjectOutputStream;
  82: import java.io.Serializable;
  83: 
  84: import org.jfree.chart.HashUtilities;
  85: import org.jfree.chart.imagemap.ToolTipTagFragmentGenerator;
  86: import org.jfree.chart.imagemap.URLTagFragmentGenerator;
  87: import org.jfree.io.SerialUtilities;
  88: import org.jfree.util.ObjectUtilities;
  89: import org.jfree.util.PublicCloneable;
  90: 
  91: /**
  92:  * A class that captures information about some component of a chart (a bar, 
  93:  * line etc).
  94:  */
  95: public class ChartEntity implements Cloneable, PublicCloneable, Serializable {
  96: 
  97:     /** For serialization. */
  98:     private static final long serialVersionUID = -4445994133561919083L;
  99:     
 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: }