001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-present, by David Gilbert 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 * Marker.java
029 * -----------
030 * (C) Copyright 2002-present, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   Nicolas Brodu;
034 *                   Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier);
035 *
036 */
037
038package org.jfree.chart.plot;
039
040import java.awt.BasicStroke;
041import java.awt.Color;
042import java.awt.Font;
043import java.awt.Paint;
044import java.awt.Stroke;
045import java.io.IOException;
046import java.io.ObjectInputStream;
047import java.io.ObjectOutputStream;
048import java.io.Serializable;
049import java.util.EventListener;
050import java.util.Objects;
051
052import javax.swing.event.EventListenerList;
053import org.jfree.chart.HashUtils;
054
055import org.jfree.chart.event.MarkerChangeEvent;
056import org.jfree.chart.event.MarkerChangeListener;
057import org.jfree.chart.ui.LengthAdjustmentType;
058import org.jfree.chart.ui.RectangleAnchor;
059import org.jfree.chart.ui.RectangleInsets;
060import org.jfree.chart.ui.TextAnchor;
061import org.jfree.chart.util.PaintUtils;
062import org.jfree.chart.util.Args;
063import org.jfree.chart.util.SerialUtils;
064
065/**
066 * The base class for markers that can be added to plots to highlight a value
067 * or range of values.
068 * <br><br>
069 * An event notification mechanism was added to this class in JFreeChart
070 * version 1.0.3.
071 */
072public abstract class Marker implements Cloneable, Serializable {
073
074    /** For serialization. */
075    private static final long serialVersionUID = -734389651405327166L;
076
077    /** The paint (null is not allowed). */
078    private transient Paint paint;
079
080    /** The stroke (null is not allowed). */
081    private transient Stroke stroke;
082
083    /** The outline paint. */
084    private transient Paint outlinePaint;
085
086    /** The outline stroke. */
087    private transient Stroke outlineStroke;
088
089    /** The alpha transparency. */
090    private float alpha;
091
092    /** The label. */
093    private String label = null;
094
095    /** The label font. */
096    private Font labelFont;
097
098    /** The label paint. */
099    private transient Paint labelPaint;
100    
101    /** The label background color. */
102    private Color labelBackgroundColor;
103
104    /** The label position. */
105    private RectangleAnchor labelAnchor;
106
107    /** The text anchor for the label. */
108    private TextAnchor labelTextAnchor;
109
110    /** The label offset from the marker rectangle. */
111    private RectangleInsets labelOffset;
112
113    /**
114     * The offset type for the domain or range axis (never {@code null}).
115     */
116    private LengthAdjustmentType labelOffsetType;
117
118    /** Storage for registered change listeners. */
119    private transient EventListenerList listenerList;
120
121    /**
122     * Creates a new marker with default attributes.
123     */
124    protected Marker() {
125        this(Color.GRAY);
126    }
127
128    /**
129     * Constructs a new marker.
130     *
131     * @param paint  the paint ({@code null} not permitted).
132     */
133    protected Marker(Paint paint) {
134        this(paint, new BasicStroke(0.5f), Color.GRAY, new BasicStroke(0.5f),
135                0.80f);
136    }
137
138    /**
139     * Constructs a new marker.
140     *
141     * @param paint  the paint ({@code null} not permitted).
142     * @param stroke  the stroke ({@code null} not permitted).
143     * @param outlinePaint  the outline paint ({@code null} permitted).
144     * @param outlineStroke  the outline stroke ({@code null} permitted).
145     * @param alpha  the alpha transparency (must be in the range 0.0f to
146     *     1.0f).
147     *
148     * @throws IllegalArgumentException if {@code paint} or
149     *     {@code stroke} is {@code null}, or {@code alpha} is
150     *     not in the specified range.
151     */
152    protected Marker(Paint paint, Stroke stroke, Paint outlinePaint, 
153            Stroke outlineStroke, float alpha) {
154
155        Args.nullNotPermitted(paint, "paint");
156        Args.nullNotPermitted(stroke, "stroke");
157        if (alpha < 0.0f || alpha > 1.0f) {
158            throw new IllegalArgumentException(
159                    "The 'alpha' value must be in the range 0.0f to 1.0f");
160        }
161
162        this.paint = paint;
163        this.stroke = stroke;
164        this.outlinePaint = outlinePaint;
165        this.outlineStroke = outlineStroke;
166        this.alpha = alpha;
167
168        this.labelFont = new Font("SansSerif", Font.PLAIN, 9);
169        this.labelPaint = Color.BLACK;
170        this.labelBackgroundColor = new Color(100, 100, 100, 100);
171        this.labelAnchor = RectangleAnchor.TOP_LEFT;
172        this.labelOffset = new RectangleInsets(3.0, 3.0, 3.0, 3.0);
173        this.labelOffsetType = LengthAdjustmentType.CONTRACT;
174        this.labelTextAnchor = TextAnchor.CENTER;
175
176        this.listenerList = new EventListenerList();
177    }
178
179    /**
180     * Returns the paint.
181     *
182     * @return The paint (never {@code null}).
183     *
184     * @see #setPaint(Paint)
185     */
186    public Paint getPaint() {
187        return this.paint;
188    }
189
190    /**
191     * Sets the paint and sends a {@link MarkerChangeEvent} to all registered
192     * listeners.
193     *
194     * @param paint  the paint ({@code null} not permitted).
195     *
196     * @see #getPaint()
197     */
198    public void setPaint(Paint paint) {
199        Args.nullNotPermitted(paint, "paint");
200        this.paint = paint;
201        notifyListeners(new MarkerChangeEvent(this));
202    }
203
204    /**
205     * Returns the stroke.
206     *
207     * @return The stroke (never {@code null}).
208     *
209     * @see #setStroke(Stroke)
210     */
211    public Stroke getStroke() {
212        return this.stroke;
213    }
214
215    /**
216     * Sets the stroke and sends a {@link MarkerChangeEvent} to all registered
217     * listeners.
218     *
219     * @param stroke  the stroke ({@code null}not permitted).
220     *
221     * @see #getStroke()
222     */
223    public void setStroke(Stroke stroke) {
224        Args.nullNotPermitted(stroke, "stroke");
225        this.stroke = stroke;
226        notifyListeners(new MarkerChangeEvent(this));
227    }
228
229    /**
230     * Returns the outline paint.
231     *
232     * @return The outline paint (possibly {@code null}).
233     *
234     * @see #setOutlinePaint(Paint)
235     */
236    public Paint getOutlinePaint() {
237        return this.outlinePaint;
238    }
239
240    /**
241     * Sets the outline paint and sends a {@link MarkerChangeEvent} to all
242     * registered listeners.
243     *
244     * @param paint  the paint ({@code null} permitted).
245     *
246     * @see #getOutlinePaint()
247     */
248    public void setOutlinePaint(Paint paint) {
249        this.outlinePaint = paint;
250        notifyListeners(new MarkerChangeEvent(this));
251    }
252
253    /**
254     * Returns the outline stroke.
255     *
256     * @return The outline stroke (possibly {@code null}).
257     *
258     * @see #setOutlineStroke(Stroke)
259     */
260    public Stroke getOutlineStroke() {
261        return this.outlineStroke;
262    }
263
264    /**
265     * Sets the outline stroke and sends a {@link MarkerChangeEvent} to all
266     * registered listeners.
267     *
268     * @param stroke  the stroke ({@code null} permitted).
269     *
270     * @see #getOutlineStroke()
271     */
272    public void setOutlineStroke(Stroke stroke) {
273        this.outlineStroke = stroke;
274        notifyListeners(new MarkerChangeEvent(this));
275    }
276
277    /**
278     * Returns the alpha transparency.
279     *
280     * @return The alpha transparency.
281     *
282     * @see #setAlpha(float)
283     */
284    public float getAlpha() {
285        return this.alpha;
286    }
287
288    /**
289     * Sets the alpha transparency that should be used when drawing the
290     * marker, and sends a {@link MarkerChangeEvent} to all registered
291     * listeners.  The alpha transparency is a value in the range 0.0f
292     * (completely transparent) to 1.0f (completely opaque).
293     *
294     * @param alpha  the alpha transparency (must be in the range 0.0f to
295     *     1.0f).
296     *
297     * @throws IllegalArgumentException if {@code alpha} is not in the
298     *     specified range.
299     *
300     * @see #getAlpha()
301     */
302    public void setAlpha(float alpha) {
303        if (alpha < 0.0f || alpha > 1.0f) {
304            throw new IllegalArgumentException(
305                    "The 'alpha' value must be in the range 0.0f to 1.0f");
306        }
307        this.alpha = alpha;
308        notifyListeners(new MarkerChangeEvent(this));
309    }
310
311    /**
312     * Returns the label (if {@code null} no label is displayed).
313     *
314     * @return The label (possibly {@code null}).
315     *
316     * @see #setLabel(String)
317     */
318    public String getLabel() {
319        return this.label;
320    }
321
322    /**
323     * Sets the label (if {@code null} no label is displayed) and sends a
324     * {@link MarkerChangeEvent} to all registered listeners.
325     *
326     * @param label  the label ({@code null} permitted).
327     *
328     * @see #getLabel()
329     */
330    public void setLabel(String label) {
331        this.label = label;
332        notifyListeners(new MarkerChangeEvent(this));
333    }
334
335    /**
336     * Returns the label font.
337     *
338     * @return The label font (never {@code null}).
339     *
340     * @see #setLabelFont(Font)
341     */
342    public Font getLabelFont() {
343        return this.labelFont;
344    }
345
346    /**
347     * Sets the label font and sends a {@link MarkerChangeEvent} to all
348     * registered listeners.
349     *
350     * @param font  the font ({@code null} not permitted).
351     *
352     * @see #getLabelFont()
353     */
354    public void setLabelFont(Font font) {
355        Args.nullNotPermitted(font, "font");
356        this.labelFont = font;
357        notifyListeners(new MarkerChangeEvent(this));
358    }
359
360    /**
361     * Returns the label paint.
362     *
363     * @return The label paint (never {@code null}).
364     *
365     * @see #setLabelPaint(Paint)
366     */
367    public Paint getLabelPaint() {
368        return this.labelPaint;
369    }
370
371    /**
372     * Sets the label paint and sends a {@link MarkerChangeEvent} to all
373     * registered listeners.
374     *
375     * @param paint  the paint ({@code null} not permitted).
376     *
377     * @see #getLabelPaint()
378     */
379    public void setLabelPaint(Paint paint) {
380        Args.nullNotPermitted(paint, "paint");
381        this.labelPaint = paint;
382        notifyListeners(new MarkerChangeEvent(this));
383    }
384    
385    /**
386     * Returns the label background color.  The default value is 
387     * {@code Color(100, 100, 100, 100)}..
388     * 
389     * @return The label background color (never {@code null}).
390     */
391    public Color getLabelBackgroundColor() {
392        return this.labelBackgroundColor;
393    }
394
395    /**
396     * Sets the label background color.
397     * 
398     * @param color  the color ({@code null} not permitted).
399     */
400    public void setLabelBackgroundColor(Color color) {
401        Args.nullNotPermitted(color, "color");
402        this.labelBackgroundColor = color;
403    }
404
405    /**
406     * Returns the label anchor.  This defines the position of the label
407     * anchor, relative to the bounds of the marker.
408     *
409     * @return The label anchor (never {@code null}).
410     *
411     * @see #setLabelAnchor(RectangleAnchor)
412     */
413    public RectangleAnchor getLabelAnchor() {
414        return this.labelAnchor;
415    }
416
417    /**
418     * Sets the label anchor and sends a {@link MarkerChangeEvent} to all
419     * registered listeners.  The anchor defines the position of the label
420     * anchor, relative to the bounds of the marker.
421     *
422     * @param anchor  the anchor ({@code null} not permitted).
423     *
424     * @see #getLabelAnchor()
425     */
426    public void setLabelAnchor(RectangleAnchor anchor) {
427        Args.nullNotPermitted(anchor, "anchor");
428        this.labelAnchor = anchor;
429        notifyListeners(new MarkerChangeEvent(this));
430    }
431
432    /**
433     * Returns the label offset.
434     *
435     * @return The label offset (never {@code null}).
436     *
437     * @see #setLabelOffset(RectangleInsets)
438     */
439    public RectangleInsets getLabelOffset() {
440        return this.labelOffset;
441    }
442
443    /**
444     * Sets the label offset and sends a {@link MarkerChangeEvent} to all
445     * registered listeners.
446     *
447     * @param offset  the label offset ({@code null} not permitted).
448     *
449     * @see #getLabelOffset()
450     */
451    public void setLabelOffset(RectangleInsets offset) {
452        Args.nullNotPermitted(offset, "offset");
453        this.labelOffset = offset;
454        notifyListeners(new MarkerChangeEvent(this));
455    }
456
457    /**
458     * Returns the label offset type.
459     *
460     * @return The type (never {@code null}).
461     *
462     * @see #setLabelOffsetType(LengthAdjustmentType)
463     */
464    public LengthAdjustmentType getLabelOffsetType() {
465        return this.labelOffsetType;
466    }
467
468    /**
469     * Sets the label offset type and sends a {@link MarkerChangeEvent} to all
470     * registered listeners.
471     *
472     * @param adj  the type ({@code null} not permitted).
473     *
474     * @see #getLabelOffsetType()
475     */
476    public void setLabelOffsetType(LengthAdjustmentType adj) {
477        Args.nullNotPermitted(adj, "adj");
478        this.labelOffsetType = adj;
479        notifyListeners(new MarkerChangeEvent(this));
480    }
481
482    /**
483     * Returns the label text anchor.
484     *
485     * @return The label text anchor (never {@code null}).
486     *
487     * @see #setLabelTextAnchor(TextAnchor)
488     */
489    public TextAnchor getLabelTextAnchor() {
490        return this.labelTextAnchor;
491    }
492
493    /**
494     * Sets the label text anchor and sends a {@link MarkerChangeEvent} to
495     * all registered listeners.
496     *
497     * @param anchor  the label text anchor ({@code null} not permitted).
498     *
499     * @see #getLabelTextAnchor()
500     */
501    public void setLabelTextAnchor(TextAnchor anchor) {
502        Args.nullNotPermitted(anchor, "anchor");
503        this.labelTextAnchor = anchor;
504        notifyListeners(new MarkerChangeEvent(this));
505    }
506
507    /**
508     * Registers an object for notification of changes to the marker.
509     *
510     * @param listener  the object to be registered.
511     *
512     * @see #removeChangeListener(MarkerChangeListener)
513     */
514    public void addChangeListener(MarkerChangeListener listener) {
515        this.listenerList.add(MarkerChangeListener.class, listener);
516    }
517
518    /**
519     * Unregisters an object for notification of changes to the marker.
520     *
521     * @param listener  the object to be unregistered.
522     *
523     * @see #addChangeListener(MarkerChangeListener)
524     */
525    public void removeChangeListener(MarkerChangeListener listener) {
526        this.listenerList.remove(MarkerChangeListener.class, listener);
527    }
528
529    /**
530     * Notifies all registered listeners that the marker has been modified.
531     *
532     * @param event  information about the change event.
533     */
534    public void notifyListeners(MarkerChangeEvent event) {
535
536        Object[] listeners = this.listenerList.getListenerList();
537        for (int i = listeners.length - 2; i >= 0; i -= 2) {
538            if (listeners[i] == MarkerChangeListener.class) {
539                ((MarkerChangeListener) listeners[i + 1]).markerChanged(event);
540            }
541        }
542
543    }
544
545    /**
546     * Returns an array containing all the listeners of the specified type.
547     *
548     * @param listenerType  the listener type.
549     *
550     * @return The array of listeners.
551     */
552    public EventListener[] getListeners(Class listenerType) {
553        return this.listenerList.getListeners(listenerType);
554    }
555
556    /**
557     * Tests the marker for equality with an arbitrary object.
558     *
559     * @param obj  the object ({@code null} permitted).
560     *
561     * @return A boolean.
562     */
563    @Override
564    public boolean equals(Object obj) {
565        if (obj == this) {
566            return true;
567        }
568        if (!(obj instanceof Marker)) {
569            return false;
570        }
571        Marker that = (Marker) obj;
572        if (!that.canEqual(this)) {
573            return false;
574        }
575        if (!PaintUtils.equal(this.paint, that.paint)) {
576            return false;
577        }
578        if (!Objects.equals(this.stroke, that.stroke)) {
579            return false;
580        }
581        if (!PaintUtils.equal(this.outlinePaint, that.outlinePaint)) {
582            return false;
583        }
584        if (!Objects.equals(this.outlineStroke, that.outlineStroke)) {
585            return false;
586        }
587        if (Float.floatToIntBits(this.alpha) !=
588            Float.floatToIntBits(that.alpha)) {
589            return false;
590        }
591        if (!Objects.equals(this.label, that.label)) {
592            return false;
593        }
594        if (!Objects.equals(this.labelFont, that.labelFont)) {
595            return false;
596        }
597        if (!PaintUtils.equal(this.labelPaint, that.labelPaint)) {
598            return false;
599        }
600        if (!Objects.equals(this.labelBackgroundColor,that.labelBackgroundColor)) {
601            return false;
602        }
603        if (!Objects.equals(this.labelAnchor, that.labelAnchor)) {
604            return false;
605        }
606        if (!Objects.equals(this.labelTextAnchor, that.labelTextAnchor)) {
607            return false;
608        }
609        if (!Objects.equals(this.labelOffset, that.labelOffset)) {
610            return false;
611        }
612        if (!Objects.equals(this.labelOffsetType,that.labelOffsetType)) {
613            return false;
614        }
615        return true;
616    }
617
618    /**
619     * Ensures symmetry between super/subclass implementations of equals. For
620     * more detail, see http://jqno.nl/equalsverifier/manual/inheritance.
621     *
622     * @param other Object
623     * 
624     * @return true ONLY if the parameter is THIS class type
625     */
626    public boolean canEqual(Object other) {
627        // Solves Problem: equals not symmetric
628        return (other instanceof Marker);
629    }
630
631    @Override
632    public int hashCode() {
633        int hash = 7;
634        hash = 43 * hash + HashUtils.hashCodeForPaint(this.paint);
635        hash = 43 * hash + Objects.hashCode(this.stroke);
636        hash = 43 * hash + HashUtils.hashCodeForPaint(this.outlinePaint);
637        hash = 43 * hash + Objects.hashCode(this.outlineStroke);
638        hash = 43 * hash + Float.floatToIntBits(this.alpha);
639        hash = 43 * hash + Objects.hashCode(this.label);
640        hash = 43 * hash + Objects.hashCode(this.labelFont);
641        hash = 43 * hash + HashUtils.hashCodeForPaint(this.labelPaint);
642        hash = 43 * hash + Objects.hashCode(this.labelBackgroundColor);
643        hash = 43 * hash + Objects.hashCode(this.labelAnchor);
644        hash = 43 * hash + Objects.hashCode(this.labelTextAnchor);
645        hash = 43 * hash + Objects.hashCode(this.labelOffset);
646        hash = 43 * hash + Objects.hashCode(this.labelOffsetType);
647        return hash;
648    }
649
650    /**
651     * Creates a clone of the marker.
652     *
653     * @return A clone.
654     *
655     * @throws CloneNotSupportedException never.
656     */
657    @Override
658    public Object clone() throws CloneNotSupportedException {
659        return super.clone();
660    }
661
662    /**
663     * Provides serialization support.
664     *
665     * @param stream  the output stream.
666     *
667     * @throws IOException  if there is an I/O error.
668     */
669    private void writeObject(ObjectOutputStream stream) throws IOException {
670        stream.defaultWriteObject();
671        SerialUtils.writePaint(this.paint, stream);
672        SerialUtils.writeStroke(this.stroke, stream);
673        SerialUtils.writePaint(this.outlinePaint, stream);
674        SerialUtils.writeStroke(this.outlineStroke, stream);
675        SerialUtils.writePaint(this.labelPaint, stream);
676    }
677
678    /**
679     * Provides serialization support.
680     *
681     * @param stream  the input stream.
682     *
683     * @throws IOException  if there is an I/O error.
684     * @throws ClassNotFoundException  if there is a classpath problem.
685     */
686    private void readObject(ObjectInputStream stream)
687        throws IOException, ClassNotFoundException {
688        stream.defaultReadObject();
689        this.paint = SerialUtils.readPaint(stream);
690        this.stroke = SerialUtils.readStroke(stream);
691        this.outlinePaint = SerialUtils.readPaint(stream);
692        this.outlineStroke = SerialUtils.readStroke(stream);
693        this.labelPaint = SerialUtils.readPaint(stream);
694        this.listenerList = new EventListenerList();
695    }
696
697}