001package jmri.managers; 002 003import java.util.*; 004import java.util.regex.Matcher; 005import java.util.regex.Pattern; 006import javax.annotation.Nonnull; 007import jmri.Conditional; 008import jmri.ConditionalManager; 009import jmri.InstanceManager; 010import jmri.Logix; 011import jmri.implementation.DefaultConditional; 012import jmri.implementation.SensorGroupConditional; 013import jmri.jmrit.sensorgroup.SensorGroupFrame; 014import jmri.jmrix.internal.InternalSystemConnectionMemo; 015import org.slf4j.Logger; 016import org.slf4j.LoggerFactory; 017 018/** 019 * Basic Implementation of a ConditionalManager. 020 * <p> 021 * Note that Conditionals always have an associated parent Logix. 022 * <p> 023 * Logix system names must begin with IX, and be followed by a string, usually, 024 * but not always, a number. The system names of Conditionals always begin with 025 * the parent Logix's system name, then there is a capital C and a number. 026 * <p> 027 * Conditional system names are set automatically when the Conditional is 028 * created. All alphabetic characters in a Conditional system name must be upper 029 * case. This is enforced when a new Conditional is created via 030 * {@link jmri.jmrit.beantable.LogixTableAction} 031 * <p> 032 * Conditional user names have specific requirements that are 033 * addressed in the {@link jmri.Conditional} class. 034 * 035 * @author Dave Duchamp Copyright (C) 2007 036 * @author Pete Cresman Copyright (C) 2009 037 */ 038public class DefaultConditionalManager extends AbstractManager<Conditional> 039 implements ConditionalManager { 040 041 public DefaultConditionalManager(InternalSystemConnectionMemo memo) { 042 super(memo); 043 } 044 045 @Override 046 public int getXMLOrder() { 047 return jmri.Manager.CONDITIONALS; 048 } 049 050 @Override 051 public char typeLetter() { 052 return 'X'; 053 } 054 055 /** 056 * Method to create a new Conditional if the Conditional does not exist If 057 * the parent Logix cannot be found, the userName cannot be checked, but the 058 * Conditional is still created. The scenario can happen when a Logix is 059 * loaded from a file after its Conditionals. 060 * 061 * @param systemName properly formatted system name for the new Conditional 062 * @param userName must not be null, use "" instead 063 * @return null if a Conditional with the same systemName or userName 064 * already exists, or if there is trouble creating a new Conditional 065 */ 066 @Override 067 public Conditional createNewConditional(String systemName, String userName) { 068 Conditional c = null; 069 070 // Check system name 071 if (systemName == null || systemName.length() < 1) { 072 log.error("createNewConditional: systemName is null or empty"); 073 return null; 074 } 075 c = getBySystemName(systemName); 076 if (c != null) { 077 return null; // Conditional already exists 078 } 079 080 // Get the potential parent Logix 081 Logix lgx = getParentLogix(systemName); 082 if (lgx == null) { 083 log.error("Unable to find the parent logix for condtional '{}'", systemName); 084 return null; 085 } 086 087 // Check the user name 088 if (userName != null && userName.length() > 0) { 089 c = getByUserName(lgx, userName); 090 if (c != null) { 091 return null; // Duplicate user name within the parent Logix 092 } 093 } 094 095 // Conditional does not exist, create a new Conditional 096 if (systemName.startsWith(SensorGroupFrame.logixSysName)) { 097 c = new SensorGroupConditional(systemName, userName); 098 } else { 099 c = new DefaultConditional(systemName, userName); 100 } 101 // save in the maps 102//@ register(c); 103 104 boolean addCompleted = lgx.addConditional(systemName, c); 105 if (!addCompleted) { 106 return null; 107 } 108 109 return c; 110 } 111 112 /** 113 * Do not insist that Conditional user names are unique, 114 * unlike the usual NamedBean support 115 */ 116 @Override 117 protected void handleUserNameUniqueness(jmri.Conditional s) { 118 // eventually needs error checking and reporting 119 } 120 121 /** 122 * Regex patterns to derive the logix system name from the conditional system name 123 * The 3 route patterns deal with Route Logix names that end with a number, 124 * such as Logix RTX123 with Conditional RTX1231T. 125 */ 126 private static final String[] PATTERNS = { 127 "(.*?)(C\\d+$)", // Standard IX 128 "(.*?)([1-9]{1}[ALT]$)", // LRoute/Route, 1-9 129 "(.*?)([0-9]{2}[ALT]$)", // LRoute/Route, 10-99 130 "(.*?)([0-9]{3}[ALT]$)" // LRoute/Route, 100-999 131 }; 132 133 /** 134 * Parses the Conditional system name to get the parent Logix system name, 135 * then gets the parent Logix, and returns it. For sensor groups, the parent 136 * Logix name is 'SYS'. LRoutes and exported Routes (RTX prefix) require 137 * special logic 138 * 139 * @param name system name of Conditionals 140 * @return the parent Logix or null 141 */ 142 @Override 143 public Logix getParentLogix(String name) { 144 if (name == null || name.length() < 4) { 145 return null; 146 } 147 148 // Check for standard names 149 for (String pattern : PATTERNS) { 150 Pattern r = Pattern.compile(pattern); 151 Matcher m = r.matcher(name); 152 if (m.find()) { 153 Logix lgx = InstanceManager.getDefault(jmri.LogixManager.class).getBySystemName(m.group(1)); 154 if (lgx != null) { 155 return lgx; 156 } 157 158 } 159 } 160 161 // Now try non-standard names using a brute force scan 162 jmri.LogixManager logixManager = InstanceManager.getDefault(jmri.LogixManager.class); 163 for (Logix lgx : logixManager.getNamedBeanSet()) { 164 for (int i = 0; i < lgx.getNumConditionals(); i++) { 165 String cdlName = lgx.getConditionalByNumberOrder(i); 166 if (cdlName.equals(name)) { 167 return lgx; 168 } 169 } 170 } 171 return null; 172 } 173 174 /** 175 * Remove an existing Conditional. Parent Logix must have been deactivated 176 * before invoking this. 177 */ 178 @Override 179 public void deleteConditional(Conditional c) { 180//@ deregister(c); 181 } 182 183 /** 184 * Method to get an existing Conditional. First looks up assuming that name 185 * is a User Name. Note: the parent Logix must be passed in x for user name 186 * lookup. If this fails, or if x == null, looks up assuming that name is a 187 * System Name. If both fail, returns null. 188 * 189 * @param x parent Logix (may be null) 190 * @param name name to look up 191 * @return null if no match found 192 */ 193 @Override 194 public Conditional getConditional(Logix x, String name) { 195 Conditional c = null; 196 if (x != null) { 197 c = getByUserName(x, name); 198 if (c != null) { 199 return c; 200 } 201 } 202 return getBySystemName(name); 203 } 204 205 @Override 206 public Conditional getConditional(String name) { 207 Conditional c = getBySystemName(name); 208 if (c == null) { 209 c = getByUserName(name); 210 } 211 return c; 212 } 213 214 /* 215 * Conditional user names are NOT unique. 216 * @param key The user name 217 * @return the conditional or null when not found or a duplicate 218 */ 219 @Override 220 public Conditional getByUserName(String key) { 221 if (key == null) { 222 return null; 223 } 224 225 Conditional c = null; 226 for (Conditional chkC : getNamedBeanSet()) { 227 if (key.equals(chkC.getUserName())) { 228 if (c == null) { 229 // Save first match 230 c = chkC; 231 continue; 232 } 233 // Found a second match, give up 234 log.warn("Duplicate conditional user names found, key = {}", key); 235 return null; 236 } 237 } 238 return c; 239 } 240 241 @Override 242 public Conditional getByUserName(Logix x, String key) { 243 if (x == null) { 244 return null; 245 } 246 for (int i = 0; i < x.getNumConditionals(); i++) { 247 Conditional c = getBySystemName(x.getConditionalByNumberOrder(i)); 248 if (c != null) { 249 String uName = c.getUserName(); 250 if (key.equals(uName)) { 251 return c; 252 } 253 } 254 } 255 return null; 256 } 257 258 @Override 259 public Conditional getBySystemName(String name) { 260 if (name == null) { 261 return null; 262 } 263 Logix lgx = getParentLogix(name); 264 if (lgx == null) { 265 return null; 266 } 267 return lgx.getConditional(name); 268//@ return (Conditional) _tsys.get(name); 269 } 270 271 /** 272 * Get a list of all Conditional system names with the specified Logix 273 * parent 274 */ 275 @Override 276 public List<String> getSystemNameListForLogix(Logix x) { 277 if (x == null) { 278 return null; 279 } 280 List<String> nameList = new ArrayList<>(); 281 282 for (int i = 0; i < x.getNumConditionals(); i++) { 283 nameList.add(x.getConditionalByNumberOrder(i)); 284 } 285 return nameList; 286 } 287 288 /** 289 * Create a named bean set for conditionals. This requires special logic since conditional 290 * beans are not registered. 291 * @since 4.17.5 292 * @return a sorted named bean set of conditionals. 293 */ 294 @Override 295 @Nonnull 296 public SortedSet<Conditional> getNamedBeanSet() { 297 TreeSet<Conditional> conditionals = new TreeSet<>(new jmri.util.NamedBeanComparator<>()); 298 299 jmri.LogixManager logixManager = InstanceManager.getDefault(jmri.LogixManager.class); 300 for (Logix lgx : logixManager.getNamedBeanSet()) { 301 for (int i = 0; i < lgx.getNumConditionals(); i++) { 302 Conditional cdl = getBySystemName(lgx.getConditionalByNumberOrder(i)); 303 if (cdl != null) { 304 conditionals.add(cdl); 305 } 306 } 307 } 308 return Collections.unmodifiableSortedSet(conditionals); 309 } 310 311 @Override 312 @Nonnull 313 public String getBeanTypeHandled(boolean plural) { 314 return Bundle.getMessage(plural ? "BeanNameConditionals" : "BeanNameConditional"); 315 } 316 317 /** 318 * {@inheritDoc} 319 */ 320 @Override 321 public Class<Conditional> getNamedBeanClass() { 322 return Conditional.class; 323 } 324 325 // --- Conditional Where Used processes --- 326 327 /** 328 * Maintain a list of conditionals that refer to a particular conditional. 329 * @since 4.7.4 330 */ 331 private final HashMap<String, ArrayList<String>> conditionalWhereUsed = new HashMap<>(); 332 333 /** 334 * Return a copy of the entire map. Used by 335 * {@link jmri.jmrit.beantable.LogixTableAction#buildWhereUsedListing} 336 * @since 4.7.4 337 * @return a copy of the map 338 */ 339 @Override 340 public HashMap<String, ArrayList<String>> getWhereUsedMap() { 341 return new HashMap<>(conditionalWhereUsed); 342 } 343 344 /** 345 * Add a conditional reference to the array indicated by the target system name. 346 * @since 4.7.4 347 * @param target The system name for the target conditional 348 * @param reference The system name of the conditional that contains the conditional reference 349 */ 350 @Override 351 public void addWhereUsed(String target, String reference) { 352 if (target == null || target.equals("")) { 353 log.error("Invalid target name for addWhereUsed"); 354 return; 355 } 356 if (reference == null || reference.equals("")) { 357 log.error("Invalid reference name for addWhereUsed"); 358 return; 359 } 360 361 if (conditionalWhereUsed.containsKey(target)) { 362 ArrayList<String> refList = conditionalWhereUsed.get(target); 363 if (!refList.contains(reference)) { 364 refList.add(reference); 365 conditionalWhereUsed.replace(target, refList); 366 } 367 } else { 368 ArrayList<String> refList = new ArrayList<>(); 369 refList.add(reference); 370 conditionalWhereUsed.put(target, refList); 371 } 372 } 373 374 /** 375 * Get a list of conditional references for the indicated conditional 376 * @since 4.7.4 377 * @param target The target conditional for a conditional reference 378 * @return an ArrayList or null if none 379 */ 380 @Override 381 public ArrayList<String> getWhereUsed(String target) { 382 if (target == null || target.equals("")) { 383 log.error("Invalid target name for getWhereUsed"); 384 return null; 385 } 386 return conditionalWhereUsed.get(target); 387 } 388 389 /** 390 * Remove a conditional reference from the array indicated by the target system name. 391 * @since 4.7.4 392 * @param target The system name for the target conditional 393 * @param reference The system name of the conditional that contains the conditional reference 394 */ 395 @Override 396 public void removeWhereUsed(String target, String reference) { 397 if (target == null || target.equals("")) { 398 log.error("Invalid target name for removeWhereUsed"); 399 return; 400 } 401 if (reference == null || reference.equals("")) { 402 log.error("Invalid reference name for removeWhereUsed"); 403 return; 404 } 405 406 if (conditionalWhereUsed.containsKey(target)) { 407 ArrayList<?> refList = conditionalWhereUsed.get(target); 408 refList.remove(reference); 409 if (refList.size() == 0) { 410 conditionalWhereUsed.remove(target); 411 } 412 } 413 } 414 415 /** 416 * Display the complete structure, used for debugging purposes. 417 * @since 4.7.4 418 */ 419 @Override 420 public void displayWhereUsed() { 421 log.info("- Display Conditional Where Used "); 422 SortedSet<String> keys = new TreeSet<>(conditionalWhereUsed.keySet()); 423 for (String key : keys) { 424 log.info(" Target: {} ", key); 425 ArrayList<String> refList = conditionalWhereUsed.get(key); 426 for (String ref : refList) { 427 log.info(" Reference: {} ", ref); 428 } 429 } 430 } 431 432 /** 433 * Get the target system names used by this conditional 434 * @since 4.7.4 435 * @param reference The system name of the conditional the refers to other conditionals. 436 * @return a list of the target conditionals 437 */ 438 @Override 439 public ArrayList<String> getTargetList(String reference) { 440 ArrayList<String> targetList = new ArrayList<>(); 441 SortedSet<String> keys = new TreeSet<>(conditionalWhereUsed.keySet()); 442 for (String key : keys) { 443 ArrayList<String> refList = conditionalWhereUsed.get(key); 444 for (String ref : refList) { 445 if (ref.equals(reference)) { 446 targetList.add(key); 447 } 448 } 449 } 450 return targetList; 451 } 452 453 private final static Logger log = LoggerFactory.getLogger(DefaultConditionalManager.class); 454}