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}