/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 1999-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2009-2012, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.display.axis; import java.util.Locale; import java.util.Objects; import java.io.Serializable; import java.awt.RenderingHints; import java.beans.PropertyChangeSupport; import java.beans.PropertyChangeListener; import javax.measure.Unit; import org.geotoolkit.resources.Errors; import org.apache.sis.util.Classes; import static org.apache.sis.util.ArgumentChecks.ensureNonNull; /** * Base class for graduation. * * @author Martin Desruisseaux (MPO, IRD) * @version 3.00 * * @since 2.0 * @module */ public abstract class AbstractGraduation implements Graduation, Serializable { /** * Serial number for inter-operability with different versions. */ private static final long serialVersionUID = 5215728323932315112L; /** * The axis units, or {@code null} if unknown. */ Unit<?> unit; /** * The axis title for this graduation. */ private String title; /** * The locale for formatting labels. */ private Locale locale = Locale.getDefault(Locale.Category.FORMAT); /** * A list of event listeners for this component. */ protected final PropertyChangeSupport listenerList; /** * Constructs a graduation with the supplied units. * * @param unit The axis's units, or {@code null} if unknown. */ public AbstractGraduation(final Unit<?> unit) { listenerList = new PropertyChangeSupport(this); this.unit = unit; } /** * Sets the minimum value for this graduation. If the new minimum is greater than the current * maximum, then the maximum will also be set to a value greater than or equal to the minimum. * * @param value The new minimum in {@link #getUnit} units. * @return {@code true} if the state of this graduation changed as a result of this call, or * {@code false} if the new value is identical to the previous one. * @throws IllegalArgumentException If {@code value} is NaN ou infinite. * * @see #getMinimum * @see #setMaximum(double) */ public abstract boolean setMinimum(final double value) throws IllegalArgumentException; /** * Sets the maximum value for this graduation. If the new maximum is less than the current * minimum, then the minimum will also be set to a value less than or equal to the maximum. * * @param value The new maximum in {@link #getUnit} units. * @return {@code true} if the state of this graduation changed as a result of this call, or * {@code false} if the new value is identical to the previous one. * @throws IllegalArgumentException If {@code value} is NaN ou infinite. * * @see #getMaximum * @see #setMinimum(double) */ public abstract boolean setMaximum(final double value) throws IllegalArgumentException; /** * Returns the axis title. If {@code includeUnits} is {@code true}, then the returned string * will includes units as in "Temperature (°C)". The exact formatting is local-dependent. * * @param includeSymbol {@code true} to format unit symbol after the name. * @return The graduation name (also to be use as axis title). * * @todo Add localization support. */ @Override public synchronized String getTitle(final boolean includeSymbol) { if (includeSymbol) { final String symbol = getSymbol(); if (symbol != null && !symbol.isEmpty()) { return (title != null) ? title + " (" + symbol + ')' : symbol; } } return title; } /** * Sets the axis title, not including unit symbol. This method will fire a * property change event with the {@code "title"} property name. * * @param title New axis title, or {@code null} to remove any previous setting. */ public void setTitle(final String title) { final String old; synchronized (this) { old = this.title; this.title = title; } listenerList.firePropertyChange("title", old, title); } /** * Returns a string representation of axis's units, or {@code null} * if there is none. The default implementation returns the string * representation of {@link #getUnit}. */ String getSymbol() { assert Thread.holdsLock(this); final Unit<?> unit = getUnit(); return (unit != null) ? unit.toString() : null; } /** * Returns the graduation's units, or {@code null} if unknown. */ @Override public synchronized Unit<?> getUnit() { return unit; } /** * Changes the graduation's units. Subclasses will automatically convert minimum and maximum * values from the old units to the new one. This method fires a property change event with the * {@code "unit"} property name. * * @param unit The new units, or {@code null} if unknown. If null, minimum and maximum values * are not converted. * @throws IllegalArgumentException if units are not convertible, or if the * specified units is illegal for this graduation. */ public void setUnit(final Unit<?> unit) throws IllegalArgumentException { final Unit<?> oldUnit; synchronized (this) { oldUnit = this.unit; this.unit = unit; } listenerList.firePropertyChange("unit", oldUnit, unit); } /** * Returns the locale to use for formatting labels. */ @Override public synchronized Locale getLocale() { return locale; } /** * Sets the locale to use for formatting labels. * This will fire a property change event with the {@code "locale"} property name. * * @param locale The new labels format. */ public synchronized void setLocale(final Locale locale) { ensureNonNull("locale", locale); final Locale old; synchronized (this) { old = this.locale; this.locale = locale; if (!locale.equals(old)) { clearFormat(); } } listenerList.firePropertyChange("locale", old, locale); } /** * Convenience hook for reseting the format when the local changed. */ void clearFormat() { } /** * Adds a {@link PropertyChangeListener} to the listener list. The listener is registered * for all properties. A {@link java.beans.PropertyChangeEvent} will get fired in response * to setting a property, such as {@link #setTitle} or {@link #setLocale}. */ @Override public void addPropertyChangeListener(final PropertyChangeListener listener) { listenerList.addPropertyChangeListener(listener); } /** * Removes a {@link PropertyChangeListener} from the listener list. */ @Override public void removePropertyChangeListener(final PropertyChangeListener listener) { listenerList.removePropertyChangeListener(listener); } /** * Retourne la longueur de l'axe, en pixels ou en points (1/72 de pouce). */ static float getVisualAxisLength(final RenderingHints hints) { return getValue(hints, VISUAL_AXIS_LENGTH, 600); } /** * Retourne l'espace approximatif (en pixels ou en points) à laisser entre les * graduations principales. L'espace réel entre les graduations peut être légèrement * différent, par exemple pour avoir des étiquettes qui correspondent à des valeurs * arrondies. */ static float getVisualTickSpacing(final RenderingHints hints) { return getValue(hints, VISUAL_TICK_SPACING, 48); } /** * Retourne une valeur sous forme de nombre réel. */ private static float getValue(final RenderingHints hints, final RenderingHints.Key key, final float defaultValue) { if (hints != null) { final Object object = hints.get(key); if (object instanceof Number) { final float value = ((Number) object).floatValue(); if (value!=0 && !Float.isInfinite(value)) { return value; } } } return defaultValue; } /** * Vérifie que le nombre spécifié est non-nul. S'il * est 0, NaN ou infini, une exception sera lancée. * * @param name Nom de l'argument. * @param n Nombre à vérifier. * @throws IllegalArgumentException Si <var>n</var> est NaN ou infini. */ static void ensureNonZero(final String name, final double n) throws IllegalArgumentException { if (Double.isNaN(n) || Double.isInfinite(n) || n==0) { throw new IllegalArgumentException(Errors.format( Errors.Keys.IllegalArgument_2, name, n)); } } /** * Vérifie que le nombre spécifié est réel. S'il est NaN ou infini, une exception sera lancée. * * @param name Nom de l'argument. * @param n Nombre à vérifier. * @throws IllegalArgumentException Si <var>n</var> est NaN ou infini. */ static void ensureFinite(final String name, final double n) throws IllegalArgumentException { if (Double.isNaN(n) || Double.isInfinite(n)) { throw new IllegalArgumentException(Errors.format( Errors.Keys.IllegalArgument_2, name, n)); } } /** * Vérifie que le nombre spécifié est réel. S'il est NaN ou infini, une exception sera lancée. * * @param name Nom de l'argument. * @param n Nombre à vérifier. * @throws IllegalArgumentException Si <var>n</var> est NaN ou infini. */ static void ensureFinite(final String name, final float n) throws IllegalArgumentException { if (Float.isNaN(n) || Float.isInfinite(n)) { throw new IllegalArgumentException(Errors.format( Errors.Keys.IllegalArgument_2, name, n)); } } /** * Compares this graduation with the specified object for equality. * This method do not compare listeners registered in {@link #listenerList}. * * @param object The object to compare with. * @return {@code true} if this graduation is equal to the given object. */ @Override public boolean equals(final Object object) { if (object != null && object.getClass() == getClass()) { final AbstractGraduation that = (AbstractGraduation) object; // We should lock object as well, but can't because of deadlocks. synchronized (this) { return Objects.equals(this.unit, that.unit ) && Objects.equals(this.title, that.title ) && Objects.equals(this.locale, that.locale); } } return false; } /** * Returns a hash value for this graduation. */ @Override public synchronized int hashCode() { int code = (int) serialVersionUID; if (title != null) { code ^= title.hashCode(); } if (unit != null) { code ^= unit.hashCode(); } return code; } /** * Returns a string representation for debugging purpose. */ @Override public String toString() { return Classes.getShortClassName(this) + '[' + getMinimum() + " \u2026 " + getMaximum() + ']'; } }