001package jmri.util.startup; 002 003import java.util.ArrayList; 004import java.util.List; 005import java.util.Objects; 006 007import javax.annotation.Nonnull; 008import javax.annotation.CheckForNull; 009import javax.swing.Action; 010 011import jmri.JmriException; 012import jmri.SystemConnectionMemo; 013import jmri.jmrix.SystemConnectionMemoManager; 014import jmri.jmrix.swing.SystemConnectionAction; 015import jmri.util.ConnectionNameFromSystemName; 016 017import org.slf4j.Logger; 018import org.slf4j.LoggerFactory; 019 020/** 021 * Provide services for invoking actions during configuration and startup. 022 * <p> 023 * The action classes and corresponding human-readable names are provided by 024 * {@link jmri.util.startup.StartupActionFactory} instances. 025 * 026 * @author Bob Jacobsen Copyright 2003, 2007, 2014 027 * @see AbstractActionModelFactory 028 */ 029public abstract class AbstractActionModel implements StartupModel { 030 031 private String systemPrefix = ""; // NOI18N 032 private String className = ""; // NOI18N 033 private boolean enabled = true; 034 private final List<Exception> exceptions = new ArrayList<>(); 035 private static final Logger log = LoggerFactory.getLogger(AbstractActionModel.class); 036 037 public String getClassName() { 038 return this.className; 039 } 040 041 @Override 042 public String getName() { 043 if (className != null) { 044 return StartupActionModelUtil.getDefault().getActionName(className); 045 } 046 return null; 047 } 048 049 @Override 050 public void setName(String n) { 051 log.debug("setName(\"{}\")", n); 052 // can set className to null if no class found for n 053 this.className = StartupActionModelUtil.getDefault().getClassName(n); 054 } 055 056 public void setClassName(@Nonnull String n) { 057 log.debug("setClassName(\"{}\")", n); 058 Objects.requireNonNull(n, "Class name cannot be null"); 059 this.className = n; 060 } 061 062 @Nonnull 063 public String getSystemPrefix() { 064 return this.systemPrefix; 065 } 066 067 public void setSystemPrefix(@CheckForNull String prefix) { 068 if (prefix == null) { 069 this.systemPrefix = ""; // NOI18N 070 } else { 071 this.systemPrefix = prefix; 072 } 073 } 074 075 public boolean isSystemConnectionAction() { 076 String name = this.getClassName(); 077 if (name != null) { 078 return StartupActionModelUtil.getDefault().isSystemConnectionAction(name); 079 } 080 return false; 081 } 082 083 @Override 084 public boolean isValid() { 085 if (this.className != null && !this.className.isEmpty()) { 086 try { 087 // don't need return value, just want to know if exception is triggered 088 Class.forName(className); 089 if (isSystemConnectionAction()) { 090 return SystemConnectionMemoManager.getDefault().getSystemConnectionMemoForSystemPrefix(systemPrefix) != null; 091 } 092 return true; 093 } catch (ClassNotFoundException ex) { 094 return false; 095 } 096 } 097 return false; 098 } 099 100 /** {@inheritDoc} */ 101 @Override 102 public void setEnabled(boolean value) { 103 enabled = value; 104 } 105 106 /** {@inheritDoc} */ 107 @Override 108 public boolean isEnabled() { 109 return enabled; 110 } 111 112 @Override 113 public String toString() { 114 String name = this.getName(); 115 if (name != null) { 116 if (!this.systemPrefix.isEmpty()) { 117 return Bundle.getMessage("AbstractActionModel.ToolTip", name, ConnectionNameFromSystemName.getConnectionName(this.systemPrefix)); // NOI18N 118 } 119 return name; 120 } 121 if (this.className != null && this.isValid()) { 122 return Bundle.getMessage("AbstractActionModel.UnknownClass", this.className); 123 } else if (this.className != null && !this.className.isEmpty()) { 124 return Bundle.getMessage("AbstractActionModel.InvalidClass", this.className); 125 } 126 return Bundle.getMessage("AbstractActionModel.InvalidAction", super.toString()); 127 } 128 129 @SuppressWarnings("unchecked") // parametized cast of action cannot be checked 130 @Override 131 public void performAction() throws JmriException { 132 log.debug("Invoke Action from {}", className); 133 try { 134 Action action = (Action) Class.forName(className).getDeclaredConstructor().newInstance(); 135 if (SystemConnectionAction.class.isAssignableFrom(action.getClass())) { 136 SystemConnectionMemo memo = ConnectionNameFromSystemName.getSystemConnectionMemoFromSystemPrefix(this.getSystemPrefix()); 137 if (memo != null) { 138 ((SystemConnectionAction<SystemConnectionMemo>) action).setSystemConnectionMemo(memo); 139 } else { 140 log.warn("Connection \"{}\" does not exist and cannot be assigned to action {}\nThis warning can be silenced by configuring the connection associated with the startup action.", this.getSystemPrefix(), className); 141 } 142 } 143 jmri.util.ThreadingUtil.runOnLayout(() -> { 144 try { 145 this.performAction(action); 146 } catch (JmriException ex) { 147 log.error("Error while performing startup action for class: {}", className, ex); 148 } 149 }); 150 } catch (ClassNotFoundException ex) { 151 log.error("Could not find specified class: {}", className); 152 } catch (IllegalAccessException ex) { 153 log.error("Unexpected access exception for class: {}", className, ex); 154 throw new JmriException(ex); 155 } catch (InstantiationException ex) { 156 log.error("Could not instantiate specified class: {}", className, ex); 157 throw new JmriException(ex); 158 } catch (java.lang.reflect.InvocationTargetException ex) { 159 log.error("Error while invoking startup action for class: {}", className, ex); 160 throw new JmriException(ex); 161 } catch (NoSuchMethodException ex) { 162 log.error("Could not locate specified method: {}", className, ex); 163 throw new JmriException(ex); 164 } catch (Exception ex) { 165 log.error("Error while performing startup action for class: {}", className, ex); 166 throw new JmriException(ex); 167 } 168 } 169 170 @Override 171 public List<Exception> getExceptions() { 172 return new ArrayList<>(this.exceptions); 173 } 174 175 @Override 176 public void addException(Exception exception) { 177 this.exceptions.add(exception); 178 } 179 180 protected abstract void performAction(Action action) throws JmriException; 181}