001package jmri.jmrix.openlcb; 002 003import jmri.BooleanPropertyDescriptor; 004import jmri.JmriException; 005import jmri.NamedBean; 006import jmri.NamedBeanPropertyDescriptor; 007import jmri.Reporter; 008import jmri.jmrix.can.CanSystemConnectionMemo; 009 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013import javax.annotation.Nonnull; 014import java.util.ArrayList; 015import java.util.List; 016import java.util.Locale; 017 018/** 019 * Manage the OpenLCB-specific Reporter implementation. 020 * 021 * System names are "MRaa.aa.aa.aa.aa.aa.00.00", where M is the user configurable system prefix, 022 * aa.aa....aa is an OpenLCB Event ID with the last two bytes as zero. 023 * 024 * Typical event IDs for reporters come out of the range 06.4* and 06.5*. 025 * 026 * @author Bob Jacobsen Copyright (C) 2008, 2010 027 * @author Balazs Racz Copyright (C) 2023 028 * @since 5.3.5 029 */ 030public class OlcbReporterManager extends jmri.managers.AbstractReporterManager { 031 032 // Whether we accumulate loaded objects in pendingReporters. 033 private boolean isLoading = false; 034 // Turnouts that are being loaded from XML. 035 private final ArrayList<OlcbReporter> pendingReporters = new ArrayList<>(); 036 037 /** 038 * {@inheritDoc} 039 */ 040 @Override 041 @Nonnull 042 public CanSystemConnectionMemo getMemo() { 043 return (CanSystemConnectionMemo) memo; 044 } 045 046 @Override 047 @Nonnull 048 public List<NamedBeanPropertyDescriptor<?>> getKnownBeanProperties() { 049 List<NamedBeanPropertyDescriptor<?>> l = new ArrayList<>(); 050 l.add(new BooleanPropertyDescriptor(OlcbUtils.PROPERTY_IS_AUTHORITATIVE, OlcbTurnout 051 .DEFAULT_IS_AUTHORITATIVE) { 052 @Override 053 public String getColumnHeaderText() { 054 return Bundle.getMessage("OlcbStateAuthHeader"); 055 } 056 057 @Override 058 public boolean isEditable(NamedBean bean) { 059 return OlcbUtils.isOlcbBean(bean); 060 } 061 }); 062 l.add(new BooleanPropertyDescriptor(OlcbUtils.PROPERTY_LISTEN, OlcbTurnout 063 .DEFAULT_LISTEN) { 064 @Override 065 public String getColumnHeaderText() { 066 return Bundle.getMessage("OlcbStateListenHeader"); 067 } 068 069 @Override 070 public boolean isEditable(NamedBean bean) { 071 return OlcbUtils.isOlcbBean(bean); 072 } 073 }); 074 return l; 075 } 076 077 // to free resources when no longer used 078 @Override 079 public void dispose() { 080 super.dispose(); 081 } 082 083 // Implemented ready for new system connection memo 084 public OlcbReporterManager(CanSystemConnectionMemo memo) { 085 super(memo); 086 } 087 088 /** 089 * {@inheritDoc} 090 * 091 * @throws IllegalArgumentException when SystemName can't be converted 092 */ 093 @Override 094 @Nonnull 095 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value = "SLF4J_FORMAT_SHOULD_BE_CONST", 096 justification = "passing exception text") 097 protected Reporter createNewReporter(@Nonnull String systemName, String userName) throws IllegalArgumentException { 098 String addr = systemName.substring(getSystemNamePrefix().length()); 099 // first, check validity 100 try { 101 validateSystemNameFormat(systemName,Locale.getDefault()); 102 } catch (NamedBean.BadSystemNameException e) { 103 log.error(e.getMessage()); 104 throw e; 105 } 106 // OK, make 107 OlcbReporter s = new OlcbReporter(getSystemPrefix(), addr, (CanSystemConnectionMemo) memo); 108 s.setUserName(userName); 109 110 synchronized (pendingReporters) { 111 if (isLoading) { 112 pendingReporters.add(s); 113 } else { 114 s.finishLoad(); 115 } 116 } 117 return s; 118 } 119 120 /** 121 * This function is invoked before an XML load is started. We defer initialization of the 122 * newly created Reporters until finishLoad. This avoids certain quadratic run-time 123 * operations due to update listeners. 124 */ 125 public void startLoad() { 126 log.debug("Reporter manager : start load"); 127 synchronized (pendingReporters) { 128 isLoading = true; 129 } 130 } 131 132 /** 133 * This function is invoked after the XML load is complete and all Reporters are instantiated 134 * and their feedback type is read in. We use this hook to finalize the construction of the 135 * OpenLCB objects whose instantiation was deferred until the feedback type was known. 136 */ 137 public void finishLoad() { 138 log.debug("Reporter manager : finish load"); 139 synchronized (pendingReporters) { 140 pendingReporters.forEach(OlcbReporter::finishLoad); 141 pendingReporters.clear(); 142 isLoading = false; 143 } 144 } 145 146 @Override 147 @Nonnull 148 public String createSystemName(@Nonnull String curAddress, @Nonnull String prefix) throws JmriException { 149 String tmpPrefix = prefix + typeLetter(); 150 String tmpSName = tmpPrefix + curAddress; 151 try { 152 OlcbAddress.validateSystemNameFormat(tmpSName,Locale.getDefault(),tmpPrefix, (CanSystemConnectionMemo) memo); 153 } 154 catch ( NamedBean.BadSystemNameException ex ){ 155 throw new JmriException(ex.getMessage()); 156 } 157 // don't check for integer; should check for validity here 158 return prefix + typeLetter() + curAddress; 159 } 160 161 @Override 162 public boolean allowMultipleAdditions(@Nonnull String systemName) { 163 return true; 164 } 165 166 @Override 167 @Nonnull 168 @javax.annotation.CheckReturnValue 169 public String getNextValidSystemName(@Nonnull NamedBean currentBean) throws JmriException { 170 String currentName = currentBean.getSystemName(); 171 return incrementSystemName(currentName); 172 } 173 174 /** 175 * Computes the system name for the next block sensor. This increments the unique ID 176 * of the manufacturer-assigned range (bytes 4-5-6) by one. 177 * @param currentName system name for a reporter of a given block 178 * @return next block's system name. 179 */ 180 public String incrementSystemName(String currentName) { 181 String oAddr = currentName.substring(getSystemNamePrefix().length()); 182 OlcbAddress a = new OlcbAddress(oAddr, (CanSystemConnectionMemo) memo); 183 // Increments address elements 4-5-6 with overflow. 184 int[] e = a.elements(); 185 int idx = 5; 186 while(idx > 2) { 187 e[idx]++; 188 if (e[idx] > 255) { 189 e[idx] = 0; 190 --idx; 191 } else { 192 break; 193 } 194 } 195 // Render new value. 196 String newValue = a.toDottedString(); 197 return getSystemNamePrefix() + newValue; 198 } 199 200 /** 201 * {@inheritDoc} 202 */ 203 @Override 204 public String getEntryToolTip() { 205 return Bundle.getMessage("AddReporterEntryToolTip"); 206 } 207 208 /** 209 * Validates to OpenLCB format. 210 * {@inheritDoc} 211 */ 212 @Override 213 @Nonnull 214 public String validateSystemNameFormat(@Nonnull String name, @Nonnull Locale locale) throws NamedBean.BadSystemNameException { 215 name = super.validateSystemNameFormat(name,locale); 216 name = OlcbAddress.validateSystemNameFormat(name,locale,getSystemNamePrefix(), (CanSystemConnectionMemo) memo); 217 return name; 218 } 219 220 private static final Logger log = LoggerFactory.getLogger(OlcbReporterManager.class); 221 222} 223 224