001package jmri;
002
003import java.beans.PropertyVetoException;
004import javax.annotation.Nonnull;
005import jmri.beans.ConstrainedBean;
006
007/**
008 * Define the characteristics of a layout scale.  A scale has four properties.
009 * <ul>
010 * <li>Name - A fixed string, such N or HO.
011 * <li>User name - An alternate name that can be changed.  It defaults to the scale name.
012 * <li>Ratio - The ratio for the scale, such as 160 for N scale.
013 * <li>Factor - A derived value created by dividing 1 by the scale ratio.
014 * </ul>
015 * In addition to the standard scales, there is custom entry.  Custom settings
016 * are retained in a local copy of ScaleData.xml.
017 * <p>
018 * Methods are provided to set/get the user name and the scale ratio.  The scale
019 * factor is generated from the scale ratio and is read only, as is the scale name.
020 * <p>
021 * While changing the ratio and user names of the standard scales is not
022 * prohibited, doing so is not recommended due to potential conflicts with other
023 * applications.
024 * <p>
025 * Changes to user names and ratios send a <strong>vetoableChange</strong> event.
026 * Interested applications can add a <strong>vetoableChange</strong> listener
027 * in order to be notified when an event occurs. If the listener determines that
028 * the change cannot occur, it can throw a <strong>PropertyVetoException</strong>.
029 * <p>
030 * See {@link jmri.ScaleManager Scale Manager} for manager details.
031 *
032 * @author Dave Sand Copyright (C) 2018
033 * @since 4.13.6
034 */
035public class Scale extends ConstrainedBean {
036
037    public Scale() {
038        super();
039    }
040
041    public Scale(@Nonnull String name, double ratio, String userName) {
042        super();
043        _name = name;
044        _userName = (userName == null) ? name : userName;
045        _ratio = ratio;
046        _factor = 1.0 / _ratio;
047    }
048
049    private String _name = "HO";  // NOI18N
050    private String _userName = "HO";  // NOI18N
051    private double _ratio = 87.1;
052    private double _factor = 1 / 87.1;
053
054    /**
055     * Get the Name of the Scale.
056     * @return the Scale name.
057     */
058    public String getScaleName() {
059        return _name;
060    }
061
062    /**
063     * Get the UserName of the Scale.
064     * @return the UserName.
065     */
066    public String getUserName() {
067        return _userName;
068    }
069
070    /**
071     * Get the Scale Ratio.
072     * @return e.g. 87.1
073     */
074    public double getScaleRatio() {
075        return _ratio;
076    }
077
078    /**
079     * Get the Scale Factor
080     * @return e.g. 1 divided by 87.1
081     */
082    public double getScaleFactor() {
083        return _factor;
084    }
085
086    /**
087     * Set the user name for the current scale.
088     * Registered listeners can veto the change.
089     * @param newName The name to be applied if unique.
090     * @throws IllegalArgumentException The supplied name is a duplicate.
091     * @throws PropertyVetoException The user name change was vetoed.
092     */
093    public void setUserName(@Nonnull String newName) throws IllegalArgumentException, PropertyVetoException {
094        for (Scale scale : ScaleManager.getScales()) {
095            if ( scale.getUserName().equals(newName)
096                && !scale.getScaleName().equals(_name)) {
097                throw new IllegalArgumentException("Duplicate scale user name: " + newName);
098            }
099        }
100
101        String oldName = _userName;
102        _userName = newName;
103
104        try {
105            fireVetoableChange("ScaleUserName", oldName, newName);  // NOI18N
106        } catch (PropertyVetoException ex) {
107            // Roll back change
108            log.warn("The user name change for {} scale to {} was rejected: Reason: {}",  // NOI18N
109                     _name, _userName, ex.getMessage());
110            _userName = oldName;
111            throw ex;  // Notify caller
112        }
113        jmri.configurexml.ScaleConfigXML.doStore();
114    }
115
116    /**
117     * Set the new scale ratio and calculate the scale factor.
118     * Registered listeners can veto the change.
119     * @param newRatio A double value containing the ratio.
120     * @throws IllegalArgumentException The new ratio is less than 1.
121     * @throws PropertyVetoException The ratio change was vetoed.
122     */
123    public void setScaleRatio(double newRatio) throws IllegalArgumentException, PropertyVetoException {
124        if (newRatio < 1.0) {
125            throw new IllegalArgumentException("The scale ratio is less than 1");  // NOI18N
126        }
127
128        double oldRatio = _ratio;
129        _ratio = newRatio;
130        _factor = 1.0 / _ratio;
131
132        try {
133            fireVetoableChange("ScaleRatio", oldRatio, newRatio);  // NOI18N
134        } catch (PropertyVetoException ex) {
135            // Roll back change
136            log.warn("The ratio change for {} scale to {} was rejected: Reason: {}",  // NOI18N
137                     _name, _ratio, ex.getMessage());
138            _ratio = oldRatio;
139            _factor = 1.0 / oldRatio;
140            throw ex;  // Notify caller
141        }
142        jmri.configurexml.ScaleConfigXML.doStore();
143    }
144
145    @Override
146    public String toString() {
147        return String.format("%s (%.1f)", getUserName(), getScaleRatio());
148    }
149
150    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Scale.class);
151
152}