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