001package jmri.jmrit.logixng.implementation; 002 003import java.awt.GraphicsEnvironment; 004import java.beans.*; 005import java.io.PrintWriter; 006import java.util.*; 007 008import javax.annotation.Nonnull; 009import javax.annotation.OverridingMethodsMustInvokeSuper; 010 011import jmri.*; 012import jmri.jmrit.logixng.*; 013import jmri.jmrit.logixng.Base.PrintTreeSettings; 014import jmri.jmrit.logixng.Module; 015import jmri.managers.AbstractManager; 016import jmri.util.LoggingUtil; 017import jmri.util.ThreadingUtil; 018import jmri.util.swing.JmriJOptionPane; 019 020import org.apache.commons.lang3.mutable.MutableInt; 021 022/** 023 * Class providing the basic logic of the LogixNG_Manager interface. 024 * 025 * @author Dave Duchamp Copyright (C) 2007 026 * @author Daniel Bergqvist Copyright (C) 2018 027 */ 028public class DefaultLogixNGManager extends AbstractManager<LogixNG> 029 implements LogixNG_Manager { 030 031 032 private final Map<String, Manager<? extends MaleSocket>> _managers = new HashMap<>(); 033 private final Clipboard _clipboard = new DefaultClipboard(); 034 private boolean _isActive = false; 035 private boolean _startLogixNGsOnLoad = true; 036 private boolean _loadDisabled = false; 037 private final List<Runnable> _setupTasks = new ArrayList<>(); 038 039 040 public DefaultLogixNGManager() { 041 // The LogixNGPreferences class may load plugins so we must ensure 042 // it's loaded here. 043 InstanceManager.getDefault(LogixNGPreferences.class); 044 } 045 046 @Override 047 public int getXMLOrder() { 048 return LOGIXNGS; 049 } 050 051 @Override 052 public char typeLetter() { 053 return 'Q'; 054 } 055 056 /** 057 * Test if parameter is a properly formatted system name. 058 * 059 * @param systemName the system name 060 * @return enum indicating current validity, which might be just as a prefix 061 */ 062 @Override 063 public NameValidity validSystemNameFormat(String systemName) { 064 return LogixNG_Manager.validSystemNameFormat( 065 getSubSystemNamePrefix(), systemName); 066// if (systemName.matches(getSubSystemNamePrefix()+"(:AUTO:)?\\d+")) { 067// return NameValidity.VALID; 068// } else { 069// return NameValidity.INVALID; 070// } 071 } 072 073 /** 074 * Method to create a new LogixNG if the LogixNG does not exist. 075 * <p> 076 * Returns null if 077 * a Logix with the same systemName or userName already exists, or if there 078 * is trouble creating a new LogixNG. 079 */ 080 @Override 081 public LogixNG createLogixNG(String systemName, String userName) 082 throws IllegalArgumentException { 083 return createLogixNG(systemName, userName, false); 084 } 085 086 /** 087 * Method to create a new LogixNG if the LogixNG does not exist. 088 * <p> 089 * Returns null if 090 * a Logix with the same systemName or userName already exists, or if there 091 * is trouble creating a new LogixNG. 092 */ 093 @Override 094 public LogixNG createLogixNG(String systemName, String userName, boolean inline) 095 throws IllegalArgumentException { 096 097 // Check that LogixNG does not already exist 098 LogixNG x; 099 if (userName != null && !userName.equals("")) { 100 x = getByUserName(userName); 101 if (x != null) { 102 return null; 103 } 104 } 105 x = getBySystemName(systemName); 106 if (x != null) { 107 return null; 108 } 109 // Check if system name is valid 110 if (this.validSystemNameFormat(systemName) != NameValidity.VALID) { 111 throw new IllegalArgumentException("SystemName " + systemName + " is not in the correct format"); 112 } 113 // LogixNG does not exist, create a new LogixNG 114 x = new DefaultLogixNG(systemName, userName, inline); 115 // save in the maps 116 register(x); 117 118 // Keep track of the last created auto system name 119 updateAutoNumber(systemName); 120 121 return x; 122 } 123 124 @Override 125 public LogixNG createLogixNG(String userName) throws IllegalArgumentException { 126 return createLogixNG(getAutoSystemName(), userName); 127 } 128 129 @Override 130 public LogixNG createLogixNG(String userName, boolean inline) 131 throws IllegalArgumentException { 132 return createLogixNG(getAutoSystemName(), userName, inline); 133 } 134 135 @Override 136 public LogixNG getLogixNG(String name) { 137 LogixNG x = getByUserName(name); 138 if (x != null) { 139 return x; 140 } 141 return getBySystemName(name); 142 } 143 144 @Override 145 public LogixNG getByUserName(String name) { 146 return _tuser.get(name); 147 } 148 149 @Override 150 public LogixNG getBySystemName(String name) { 151 return _tsys.get(name); 152 } 153 154 /** {@inheritDoc} */ 155 @Override 156 public String getBeanTypeHandled(boolean plural) { 157 return Bundle.getMessage(plural ? "BeanNameLogixNGs" : "BeanNameLogixNG"); 158 } 159 160 /** {@inheritDoc} */ 161 @Override 162 public void setLoadDisabled(boolean value) { 163 _loadDisabled = value; 164 } 165 166 /** {@inheritDoc} */ 167 @Override 168 public void startLogixNGsOnLoad(boolean value) { 169 _startLogixNGsOnLoad = value; 170 } 171 172 /** {@inheritDoc} */ 173 @Override 174 public boolean isStartLogixNGsOnLoad() { 175 return _startLogixNGsOnLoad; 176 } 177 178 /** {@inheritDoc} */ 179 @Override 180 public void setupAllLogixNGs() { 181 List<String> errors = new ArrayList<>(); 182 boolean result = true; 183 for (LogixNG logixNG : _tsys.values()) { 184 logixNG.setup(); 185 result = result && logixNG.setParentForAllChildren(errors); 186 } 187 for (Module module : InstanceManager.getDefault(ModuleManager.class).getNamedBeanSet()) { 188 module.setup(); 189 result = result && module.setParentForAllChildren(errors); 190 } 191 _clipboard.setup(); 192 for (Runnable r : _setupTasks) { 193 r.run(); 194 } 195 if (errors.size() > 0) { 196 messageDialog("SetupErrorsTitle", errors, null); 197 } 198 checkItemsHaveParents(); 199 } 200 201 /** 202 * Display LogixNG setup errors when not running in headless mode. 203 * @param titleKey The bundle key for the dialog title. 204 * @param messages A ArrayList of messages that have been localized. 205 * @param helpKey The bundle key for additional information about the errors 206 */ 207 private void messageDialog(String titleKey, List<String> messages, String helpKey) { 208 if (!GraphicsEnvironment.isHeadless() && !Boolean.getBoolean("jmri.test.no-dialogs")) { 209 StringBuilder sb = new StringBuilder("<html>"); 210 messages.forEach(msg -> { 211 sb.append(msg); 212 sb.append("<br>"); 213 }); 214 if (helpKey != null) { 215 sb.append("<br>"); 216 sb.append(Bundle.getMessage(helpKey)); 217 } 218 sb.append("/<html>"); 219 JmriJOptionPane.showMessageDialog(null, 220 sb.toString(), 221 Bundle.getMessage(titleKey), 222 JmriJOptionPane.WARNING_MESSAGE); 223 } 224 } 225 226 private void checkItemsHaveParents(SortedSet<? extends MaleSocket> set, List<MaleSocket> beansWithoutParentList) { 227 for (MaleSocket bean : set) { 228 if (bean.getParent() == null) { 229 beansWithoutParentList.add(bean); 230 } 231 } 232 } 233 234 private void checkItemsHaveParents() { 235 List<MaleSocket> beansWithoutParentList = new ArrayList<>(); 236 checkItemsHaveParents(InstanceManager.getDefault(AnalogActionManager.class).getNamedBeanSet(), beansWithoutParentList); 237 checkItemsHaveParents(InstanceManager.getDefault(DigitalActionManager.class).getNamedBeanSet(), beansWithoutParentList); 238 checkItemsHaveParents(InstanceManager.getDefault(DigitalBooleanActionManager.class).getNamedBeanSet(), beansWithoutParentList); 239 checkItemsHaveParents(InstanceManager.getDefault(StringActionManager.class).getNamedBeanSet(), beansWithoutParentList); 240 checkItemsHaveParents(InstanceManager.getDefault(AnalogExpressionManager.class).getNamedBeanSet(), beansWithoutParentList); 241 checkItemsHaveParents(InstanceManager.getDefault(DigitalExpressionManager.class).getNamedBeanSet(), beansWithoutParentList); 242 checkItemsHaveParents(InstanceManager.getDefault(StringExpressionManager.class).getNamedBeanSet(), beansWithoutParentList); 243 244 if (!beansWithoutParentList.isEmpty()) { 245 List<String> errors = new ArrayList<>(); 246 List<String> msgs = new ArrayList<>(); 247 for (Base b : beansWithoutParentList) { 248 b.setup(); 249 b.setParentForAllChildren(errors); 250 } 251 for (Base b : beansWithoutParentList) { 252 if (b.getParent() == null) { 253 log.error("Item has no parent: {}, {}, {}", 254 b.getSystemName(), 255 b.getUserName(), 256 b.getLongDescription()); 257 msgs.add(Bundle.getMessage("NoParentMessage", 258 b.getSystemName(), 259 b.getUserName(), 260 b.getLongDescription())); 261 262 for (int i=0; i < b.getChildCount(); i++) { 263 if (b.getChild(i).isConnected()) { 264 log.error(" Child: {}, {}, {}", 265 b.getChild(i).getConnectedSocket().getSystemName(), 266 b.getChild(i).getConnectedSocket().getUserName(), 267 b.getChild(i).getConnectedSocket().getLongDescription()); 268 } 269 } 270 log.error(" End Item"); 271 List<String> cliperrors = new ArrayList<String>(); 272 _clipboard.add((MaleSocket) b, cliperrors); 273 } 274 } 275 messageDialog("ParentErrorsTitle", msgs, "NoParentHelp"); 276 } 277 } 278 279 /** {@inheritDoc} */ 280 @Override 281 public void activateAllLogixNGs() { 282 activateAllLogixNGs(true, true); 283 } 284 285 /** {@inheritDoc} */ 286 @Override 287 public void activateAllLogixNGs(boolean runDelayed, boolean runOnSeparateThread) { 288 289 _isActive = true; 290 291 if (_loadDisabled) { 292 for (LogixNG logixNG : _tsys.values()) { 293 logixNG.setEnabled(false); 294 } 295 _loadDisabled = false; 296 } 297 298 // This may take a long time so it must not be done on the GUI thread. 299 // Therefore we create a new thread for this task. 300 Runnable runnable = () -> { 301 302 // Initialize the values of the global variables 303 Set<GlobalVariable> globalVariables = 304 InstanceManager.getDefault(GlobalVariableManager.class) 305 .getNamedBeanSet(); 306 307 for (GlobalVariable gv : globalVariables) { 308 try { 309 gv.initialize(); 310 } catch (JmriException | IllegalArgumentException e) { 311 log.warn("Variable {} could not be initialized", gv.getUserName(), e); 312 } 313 } 314 315 Set<LogixNG> activeLogixNGs = new HashSet<>(); 316 317 // Activate and execute the initialization LogixNGs first. 318 List<LogixNG> initLogixNGs = 319 InstanceManager.getDefault(LogixNG_InitializationManager.class) 320 .getList(); 321 322 for (LogixNG logixNG : initLogixNGs) { 323 logixNG.activate(); 324 if (logixNG.isActive()) { 325 logixNG.registerListeners(); 326 logixNG.execute(false, true); 327 activeLogixNGs.add(logixNG); 328 } else { 329 logixNG.unregisterListeners(); 330 } 331 } 332 333 // Activate and execute all the rest of the LogixNGs. 334 _tsys.values().stream() 335 .sorted() 336 .filter((logixNG) -> !(activeLogixNGs.contains(logixNG))) 337 .forEachOrdered((logixNG) -> { 338 339 logixNG.activate(); 340 341 if (logixNG.isActive()) { 342 logixNG.registerListeners(); 343 logixNG.execute(true, true); 344 } else { 345 logixNG.unregisterListeners(); 346 } 347 }); 348 349 // Clear the startup flag of the LogixNGs. 350 _tsys.values().stream().forEach((logixNG) -> { 351 logixNG.clearStartup(); 352 }); 353 }; 354 355 if (runOnSeparateThread) new Thread(runnable).start(); 356 else runnable.run(); 357 } 358 359 /** {@inheritDoc} */ 360 @Override 361 public void deActivateAllLogixNGs() { 362 for (LogixNG logixNG : _tsys.values()) { 363 logixNG.unregisterListeners(); 364 } 365 _isActive = false; 366 } 367 368 /** {@inheritDoc} */ 369 @Override 370 public boolean isActive() { 371 return _isActive; 372 } 373 374 /** {@inheritDoc} */ 375 @Override 376 public void deleteLogixNG(LogixNG x) { 377 // delete the LogixNG 378 deregister(x); 379 x.dispose(); 380 } 381 382 /** {@inheritDoc} */ 383 @Override 384 public void printTree( 385 PrintTreeSettings settings, 386 PrintWriter writer, 387 String indent, 388 MutableInt lineNumber) { 389 390 printTree(settings, Locale.getDefault(), writer, indent, lineNumber); 391 } 392 393 /** {@inheritDoc} */ 394 @Override 395 public void printTree( 396 PrintTreeSettings settings, 397 Locale locale, 398 PrintWriter writer, 399 String indent, 400 MutableInt lineNumber) { 401 402 for (LogixNG logixNG : getNamedBeanSet()) { 403 if (logixNG.isInline()) continue; 404 logixNG.printTree(settings, locale, writer, indent, "", lineNumber); 405 writer.println(); 406 } 407 408 for (LogixNG logixNG : getNamedBeanSet()) { 409 if (!logixNG.isInline()) continue; 410 logixNG.printTree(settings, locale, writer, indent, "", lineNumber); 411 writer.println(); 412 } 413 InstanceManager.getDefault(ModuleManager.class).printTree(settings, locale, writer, indent, lineNumber); 414 InstanceManager.getDefault(NamedTableManager.class).printTree(locale, writer, indent); 415 InstanceManager.getDefault(GlobalVariableManager.class).printTree(locale, writer, indent); 416 InstanceManager.getDefault(LogixNG_InitializationManager.class).printTree(locale, writer, indent); 417 } 418 419 420 static volatile DefaultLogixNGManager _instance = null; 421 422 @InvokeOnGuiThread // this method is not thread safe 423 static public DefaultLogixNGManager instance() { 424 if (!ThreadingUtil.isGUIThread()) { 425 LoggingUtil.warnOnce(log, "instance() called on wrong thread"); 426 } 427 428 if (_instance == null) { 429 _instance = new DefaultLogixNGManager(); 430 } 431 return (_instance); 432 } 433 434 /** {@inheritDoc} */ 435 @Override 436 public Class<LogixNG> getNamedBeanClass() { 437 return LogixNG.class; 438 } 439 440 /** {@inheritDoc} */ 441 @Override 442 public Clipboard getClipboard() { 443 return _clipboard; 444 } 445 446 /** {@inheritDoc} */ 447 @Override 448 public void registerManager(Manager<? extends MaleSocket> manager) { 449 _managers.put(manager.getClass().getName(), manager); 450 } 451 452 /** {@inheritDoc} */ 453 @Override 454 public Manager<? extends MaleSocket> getManager(String className) { 455 return _managers.get(className); 456 } 457 458 /** 459 * Inform all registered listeners of a vetoable change.If the propertyName 460 * is "CanDelete" ALL listeners with an interest in the bean will throw an 461 * exception, which is recorded returned back to the invoking method, so 462 * that it can be presented back to the user.However if a listener decides 463 * that the bean can not be deleted then it should throw an exception with 464 * a property name of "DoNotDelete", this is thrown back up to the user and 465 * the delete process should be aborted. 466 * 467 * @param p The programmatic name of the property that is to be changed. 468 * "CanDelete" will inquire with all listeners if the item can 469 * be deleted. "DoDelete" tells the listener to delete the item. 470 * @param old The old value of the property. 471 * @throws java.beans.PropertyVetoException If the recipients wishes the 472 * delete to be aborted (see above) 473 */ 474 @OverridingMethodsMustInvokeSuper 475 public void fireVetoableChange(String p, Object old) throws PropertyVetoException { 476 PropertyChangeEvent evt = new PropertyChangeEvent(this, p, old, null); 477 for (VetoableChangeListener vc : vetoableChangeSupport.getVetoableChangeListeners()) { 478 vc.vetoableChange(evt); 479 } 480 } 481 482 /** {@inheritDoc} */ 483 @Override 484// @OverridingMethodsMustInvokeSuper 485 public final void deleteBean(@Nonnull LogixNG logixNG, @Nonnull String property) throws PropertyVetoException { 486 for (int i=logixNG.getNumConditionalNGs()-1; i >= 0; i--) { 487 ConditionalNG child = logixNG.getConditionalNG(i); 488 InstanceManager.getDefault(ConditionalNG_Manager.class).deleteBean(child, property); 489 } 490 491 // throws PropertyVetoException if vetoed 492 fireVetoableChange(property, logixNG); 493 if (property.equals("DoDelete")) { // NOI18N 494 deregister(logixNG); 495 logixNG.dispose(); 496 } 497 } 498 499 /** {@inheritDoc} */ 500 @Override 501 public void registerSetupTask(Runnable task) { 502 _setupTasks.add(task); 503 } 504 505 /** {@inheritDoc} */ 506 @Override 507 public void executeModule(Module module, Object parameter) 508 throws IllegalArgumentException { 509 510 if (module == null) { 511 throw new IllegalArgumentException("The parameter \"module\" is null"); 512 } 513 // Get the parameters for the module 514 Collection<Module.Parameter> parameterNames = module.getParameters(); 515 516 // Ensure that there is only one parameter 517 if (parameterNames.size() != 1) { 518 throw new IllegalArgumentException("The module doesn't take exactly one parameter"); 519 } 520 521 // Get the parameter 522 Module.Parameter param = parameterNames.toArray(Module.Parameter[]::new)[0]; 523 if (!param.isInput()) { 524 throw new IllegalArgumentException("The module's parameter is not an input parameter"); 525 } 526 527 // Set the value of the parameter 528 Map<String, Object> parameters = new HashMap<>(); 529 parameters.put(param.getName(), parameter); 530 531 // Execute the module 532 executeModule(module, parameters); 533 } 534 535 /** {@inheritDoc} */ 536 @Override 537 public void executeModule(Module module, Map<String, Object> parameters) 538 throws IllegalArgumentException { 539 DefaultConditionalNG.executeModule(module, parameters); 540 } 541 542 /** 543 * The PropertyChangeListener interface in this class is intended to keep 544 * track of user name changes to individual NamedBeans. It is not completely 545 * implemented yet. In particular, listeners are not added to newly 546 * registered objects. 547 * 548 * @param e the event 549 */ 550 @Override 551 @OverridingMethodsMustInvokeSuper 552 public void propertyChange(PropertyChangeEvent e) { 553 super.propertyChange(e); 554 if (LogixNG.PROPERTY_INLINE.equals(e.getPropertyName())) { 555 // If a LogixNG changes its "inline" state, the number of items 556 // listed in the LogixNG table might change. 557 firePropertyChange("length", null, _beans.size()); 558 } 559 } 560 561 562 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultLogixNGManager.class); 563 564}