001package jmri.jmrix.openlcb; 002 003import java.util.*; 004 005import javax.annotation.Nonnull; 006import jmri.BooleanPropertyDescriptor; 007import jmri.JmriException; 008import jmri.NamedBean; 009import jmri.NamedBeanPropertyDescriptor; 010import jmri.Sensor; 011import jmri.jmrix.can.CanListener; 012import jmri.jmrix.can.CanMessage; 013import jmri.jmrix.can.CanReply; 014import jmri.jmrix.can.CanSystemConnectionMemo; 015 016/** 017 * Manage the OpenLCB-specific Sensor implementation. 018 * 019 * System names are "MSnnn", where M is the user configurable system prefix, 020 * nnn is the sensor number without padding. 021 * 022 * @author Bob Jacobsen Copyright (C) 2008, 2010 023 */ 024public class OlcbSensorManager extends jmri.managers.AbstractSensorManager implements CanListener { 025 026 // Whether we accumulate partially loaded objects in pendingSensors. 027 private boolean isLoading = false; 028 // Sensors that are being loaded from XML. 029 private final ArrayList<OlcbSensor> pendingSensors = new ArrayList<>(); 030 031 /** 032 * {@inheritDoc} 033 */ 034 @Override 035 @Nonnull 036 public CanSystemConnectionMemo getMemo() { 037 return (CanSystemConnectionMemo) memo; 038 } 039 040 @Override 041 @Nonnull 042 public List<NamedBeanPropertyDescriptor<?>> getKnownBeanProperties() { 043 List<NamedBeanPropertyDescriptor<?>> l = new ArrayList<>(); 044 l.add(new BooleanPropertyDescriptor(OlcbUtils.PROPERTY_IS_AUTHORITATIVE, OlcbTurnout 045 .DEFAULT_IS_AUTHORITATIVE) { 046 @Override 047 public String getColumnHeaderText() { 048 return Bundle.getMessage("OlcbStateAuthHeader"); 049 } 050 051 @Override 052 public boolean isEditable(NamedBean bean) { 053 return OlcbUtils.isOlcbBean(bean); 054 } 055 }); 056 l.add(new BooleanPropertyDescriptor(OlcbUtils.PROPERTY_LISTEN, OlcbTurnout 057 .DEFAULT_LISTEN) { 058 @Override 059 public String getColumnHeaderText() { 060 return Bundle.getMessage("OlcbStateListenHeader"); 061 } 062 063 @Override 064 public boolean isEditable(NamedBean bean) { 065 return OlcbUtils.isOlcbBean(bean); 066 } 067 }); 068 return l; 069 } 070 071 // to free resources when no longer used 072 @Override 073 public void dispose() { 074 getMemo().getTrafficController().removeCanListener(this); 075 super.dispose(); 076 } 077 078 // Implemented ready for new system connection memo 079 public OlcbSensorManager(CanSystemConnectionMemo memo) { 080 super(memo); 081 memo.getTrafficController().addCanListener(this); 082 } 083 084 /** 085 * {@inheritDoc} 086 * 087 * @throws IllegalArgumentException when SystemName can't be converted 088 */ 089 @Override 090 @Nonnull 091 protected Sensor createNewSensor(@Nonnull String systemName, String userName) throws IllegalArgumentException { 092 String addr = systemName.substring(getSystemNamePrefix().length()); 093 // first, check validity 094 try { 095 validateSystemNameFormat(systemName,Locale.getDefault()); 096 } catch (jmri.NamedBean.BadSystemNameException e) { 097 log.error("Exception: {}", e.getMessage()); 098 throw e; 099 } 100 // OK, make 101 OlcbSensor s = new OlcbSensor(getSystemPrefix(), addr, (CanSystemConnectionMemo) memo); 102 s.setUserName(userName); 103 104 synchronized (pendingSensors) { 105 if (isLoading) { 106 pendingSensors.add(s); 107 } else { 108 s.finishLoad(); 109 } 110 } 111 return s; 112 } 113 114 /** 115 * This function is invoked before an XML load is started. We defer initialization of the 116 * newly created Sensors until finishLoad because the feedback type might be changing as we 117 * are parsing the XML. 118 */ 119 public void startLoad() { 120 log.debug("Sensor manager : start load"); 121 synchronized (pendingSensors) { 122 isLoading = true; 123 } 124 } 125 126 /** 127 * This function is invoked after the XML load is complete and all Sensors are instantiated 128 * and their feedback type is read in. We use this hook to finalize the construction of the 129 * OpenLCB objects whose instantiation was deferred until the feedback type was known. 130 */ 131 public void finishLoad() { 132 log.debug("Sensor manager : finish load"); 133 synchronized (pendingSensors) { 134 pendingSensors.forEach(OlcbSensor::finishLoad); 135 pendingSensors.clear(); 136 isLoading = false; 137 } 138 } 139 140 @Override 141 public boolean allowMultipleAdditions(@Nonnull String systemName) { 142 return false; 143 } 144 145 @Override 146 @Nonnull 147 public String createSystemName(@Nonnull String curAddress, @Nonnull String prefix) throws JmriException { 148 String tmpPrefix = prefix + typeLetter(); 149 String tmpSName = tmpPrefix + curAddress; 150 try { 151 OlcbAddress.validateSystemNameFormat(tmpSName,Locale.getDefault(),tmpPrefix, (CanSystemConnectionMemo) memo); 152 } 153 catch ( jmri.NamedBean.BadSystemNameException ex ){ 154 throw new JmriException(ex.getMessage()); 155 } 156 return prefix + typeLetter() + curAddress; 157 } 158 159 @Override 160 @javax.annotation.Nonnull 161 @javax.annotation.CheckReturnValue 162 public String getNextValidSystemName(@Nonnull NamedBean currentBean) throws JmriException { 163 throw new jmri.JmriException("getNextValidSystemName should not have been called"); 164 } 165 166 /** 167 * {@inheritDoc} 168 */ 169 @Override 170 public String getEntryToolTip() { 171 return Bundle.getMessage("AddSensorEntryToolTip"); 172 } 173 174 // listen for sensors, creating them as needed 175 @Override 176 public void reply(CanReply l) { 177 // doesn't do anything, because for now 178 // we want you to create manually 179 } 180 181 @Override 182 public void message(CanMessage l) { 183 // doesn't do anything, because 184 // messages come from us 185 } 186 187 /** 188 * Validates to OpenLCB format. 189 * {@inheritDoc} 190 */ 191 @Override 192 @Nonnull 193 public String validateSystemNameFormat(@Nonnull String name, @Nonnull java.util.Locale locale) throws jmri.NamedBean.BadSystemNameException { 194 name = super.validateSystemNameFormat(name,locale); 195 name = OlcbAddress.validateSystemNameFormat(name,locale,getSystemNamePrefix(), (CanSystemConnectionMemo) memo); 196 return name; 197 } 198 199 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(OlcbSensorManager.class); 200 201} 202 203