001package jmri.implementation;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.util.ArrayList;
005import java.util.List;
006import javax.annotation.Nonnull;
007import jmri.Light;
008import jmri.LightControl;
009
010/**
011 * Abstract class providing partial implementation of the Light interface.
012 * <p>
013 * Light objects require a number of instance variables. Since Light objects are
014 * created using the standard JMRI systemName/userName concept, accessor
015 * routines are provided for setting and editing these instance variables.
016 * <p>
017 * Each Light may have one or more control mechanisms, of the types defined in
018 * the Light interface. A Light may also not have any control mechanisms
019 * defined.
020 * <p>
021 * Information for each control mechanism is held in LightControl objects, which
022 * also implement the logic for control. A list of LightControls, if any, is
023 * kept here, and activation and deactivation of LightControls is through this
024 * module.
025 * <p>
026 * Instance variables are divided into system-independent and system dependent
027 * categories. System independent instance variables are defined here, and their
028 * accessor routines are implemented here.
029 * <p>
030 * This implementation provides a notional implementation of intensity and
031 * transitions. The user can set intensity so long as it's at least the max
032 * value (default 1.0) or no more than the minimum value (default 0.0). In that
033 * case, the setTargetIntensity operations become a setState to ON or OFF.
034 * Setting a target intensity between the min and max is an error, because this
035 * type of Light does not support a true analog intensity. Transitions never
036 * happen, and setting a TransitionTime greater than 0.0 gives an exception.
037 * <p>
038 * Since this form of Light does not do variable intensity nor transitions, it
039 * stores both CurrentIntensity and TargetIntensity in a single location,
040 * forcing them to be the same
041 *
042 * @author Dave Duchamp Copyright (C) 2004, 2010
043 * @author Ken Cameron Copyright (C) 2008
044 * @author Bob Jacobsen Copyright (C) 2008
045 */
046public abstract class AbstractLight extends AbstractNamedBean
047        implements Light {
048
049    public AbstractLight(String systemName, String userName) {
050        super(systemName, userName);
051    }
052
053    public AbstractLight(String systemName) {
054        super(systemName);
055    }
056
057    @Override
058    public String getBeanType() {
059        return Bundle.getMessage("BeanNameLight");
060    }
061
062    /**
063     * System independent instance variables (saved between runs).
064     */
065    protected List<LightControl> lightControlList = new ArrayList<>();
066    protected double mMaxIntensity = 1.0;
067    protected double mMinIntensity = 0.0;
068
069    /**
070     * System independent operational instance variables (not saved between
071     * runs).
072     */
073    protected double mCurrentIntensity = 0.0;
074    protected boolean mActive = false; // used to indicate if LightControls are active
075    protected boolean mEnabled = true;
076    protected int mState = OFF;
077
078    @Override
079    @Nonnull
080    public String describeState(int state) {
081        switch (state) {
082            case ON: return Bundle.getMessage("StateOn");
083            case OFF: return Bundle.getMessage("StateOff");
084            default: return super.describeState(state);
085        }
086    }
087
088    /**
089     * Get enabled status.
090     *
091     * @return enabled status
092     */
093    @Override
094    public boolean getEnabled() {
095        return mEnabled;
096    }
097
098    /**
099     * Set enabled status.
100     *
101     * @param v status to set
102     */
103    @Override
104    public void setEnabled(boolean v) {
105        boolean old = mEnabled;
106        mEnabled = v;
107        if (old != v) {
108            firePropertyChange(PROPERTY_ENABLED, old, v);
109        }
110    }
111
112    /**
113     * Handle a request for a state change. For these lights, ON and OFF just
114     * transition immediately between MinIntensity and MaxIntensity.
115     * Ignores any outputDelay setting for connection.
116     *
117     * @param newState new state
118     */
119    @Override
120    public void setState(int newState) {
121        log.debug("setState {} was {}", newState, mState);
122        
123        if (newState != ON && newState != OFF) {
124            throw new IllegalArgumentException("cannot set state value " + newState);
125        }
126
127        // do the state change in the hardware
128        doNewState(mState, newState); // old state, new state
129        // change value and tell listeners
130        notifyStateChange(mState, newState);
131    }
132
133    /**
134     * Change the stored target intensity value and do notification, but don't
135     * change anything in the hardware.
136     *
137     * @param intensity intensity value
138     */
139    @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY", justification = "OK to compare floating point")
140    protected void notifyTargetIntensityChange(double intensity) {
141        double oldValue = mCurrentIntensity;
142        mCurrentIntensity = intensity;
143        if (oldValue != intensity) {
144            firePropertyChange(PROPERTY_TARGET_INTENSITY, oldValue, intensity);
145        }
146    }
147
148    /**
149     * Change the stored state value and do notification, but don't change
150     * anything in the hardware.
151     *
152     * @param oldState old value
153     * @param newState new value
154     */
155    protected void notifyStateChange(int oldState, int newState) {
156        mState = newState;
157        if (oldState != newState) {
158            firePropertyChange(PROPERTY_KNOWN_STATE, oldState, newState);
159        }
160    }
161
162    /**
163     * Implement the specific change of state needed by hardware.
164     *
165     * @param oldState old state
166     * @param newState new state
167     */
168    protected void doNewState(int oldState, int newState) {
169    }
170
171    @Override
172    public int getState() {
173        return mState;
174    }
175
176    /**
177     * Activate a light activating all its LightControl objects.
178     */
179    @Override
180    public void activateLight() {
181        lightControlList.forEach(LightControl::activateLightControl);
182        mActive = true; // set flag for control listeners
183    }
184
185    /**
186     * Deactivate a light by deactivating each of its LightControl objects.
187     */
188    @Override
189    public void deactivateLight() {
190        // skip if Light is not active
191        if (mActive) { // check if flag set for control listeners
192            lightControlList.forEach(LightControl::deactivateLightControl);
193            mActive = false; // unset flag for control listeners
194        }
195    }
196
197    /*
198     * LightControl management methods
199     */
200
201    @Override
202    public void clearLightControls() {
203        // deactivate all Light Controls if any are active
204        deactivateLight();
205        // clear all LightControls, if there are any
206        for (int i = lightControlList.size() - 1; i >= 0; i--) {
207            lightControlList.remove(i);
208        }
209    }
210
211    /** {@inheritDoc}
212     */
213    @Override
214    public void addLightControl(LightControl c) {
215        if (lightControlList.contains(c)) {
216            log.debug("not adding duplicate LightControl {}", c);
217            return;
218        }
219        lightControlList.add(c);
220    }
221
222    @Override
223    public List<LightControl> getLightControlList() {
224        return new ArrayList<>(lightControlList);
225    }
226
227    @Override
228    public List<jmri.NamedBeanUsageReport> getUsageReport(jmri.NamedBean bean) {
229        List<jmri.NamedBeanUsageReport> report = new ArrayList<>();
230        jmri.SensorManager sm = jmri.InstanceManager.getDefault(jmri.SensorManager.class);
231        jmri.TurnoutManager tm = jmri.InstanceManager.getDefault(jmri.TurnoutManager.class);
232        if (bean != null) {
233            getLightControlList().forEach( control -> {
234                String descText = control.getDescriptionText("");
235                if (bean.equals(sm.getSensor(control.getControlSensorName()))) {
236                    report.add(new jmri.NamedBeanUsageReport("LightControlSensor1", descText));  // NOI18N
237                }
238                if (bean.equals(sm.getSensor(control.getControlSensor2Name()))) {
239                    report.add(new jmri.NamedBeanUsageReport("LightControlSensor2", descText));  // NOI18N
240                }
241                if (bean.equals(sm.getSensor(control.getControlTimedOnSensorName()))) {
242                    report.add(new jmri.NamedBeanUsageReport("LightControlSensorTimed", descText));  // NOI18N
243                }
244                if (bean.equals(tm.getTurnout(control.getControlTurnoutName()))) {
245                    report.add(new jmri.NamedBeanUsageReport("LightControlTurnout", descText));  // NOI18N
246                }
247            });
248        }
249        return report;
250    }
251
252    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractLight.class);
253
254}