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