001package jmri.jmrit.logixng.implementation;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.beans.*;
006import java.util.ArrayList;
007import java.util.List;
008
009import javax.annotation.Nonnull;
010import javax.annotation.OverridingMethodsMustInvokeSuper;
011
012import jmri.NamedBean;
013import jmri.jmrit.logixng.*;
014import jmri.managers.AbstractManager;
015
016/**
017 * Abstract partial implementation for the LogixNG action and expression managers.
018 *
019 * @param <E> the type of NamedBean supported by this manager
020 *
021 * @author Daniel Bergqvist 2020
022 */
023public abstract class AbstractBaseManager<E extends NamedBean> extends AbstractManager<E> implements BaseManager<E> {
024
025    protected List<MaleSocketFactory<E>> _maleSocketFactories = new ArrayList<>();
026
027
028    /**
029     * Inform all registered listeners of a vetoable change.If the propertyName
030     * is "CanDelete" ALL listeners with an interest in the bean will throw an
031     * exception, which is recorded returned back to the invoking method, so
032     * that it can be presented back to the user.However if a listener decides
033     * that the bean can not be deleted then it should throw an exception with
034     * a property name of "DoNotDelete", this is thrown back up to the user and
035     * the delete process should be aborted.
036     *
037     * @param p   The programmatic name of the property that is to be changed.
038     *            "CanDelete" will inquire with all listeners if the item can
039     *            be deleted. "DoDelete" tells the listener to delete the item.
040     * @param old The old value of the property.
041     * @throws java.beans.PropertyVetoException If the recipients wishes the
042     *                                          delete to be aborted (see above)
043     */
044    @OverridingMethodsMustInvokeSuper
045    public void fireVetoableChange(String p, Object old) throws PropertyVetoException {
046        PropertyChangeEvent evt = new PropertyChangeEvent(this, p, old, null);
047        for (VetoableChangeListener vc : vetoableChangeSupport.getVetoableChangeListeners()) {
048            vc.vetoableChange(evt);
049        }
050    }
051
052    /**
053     * Cast the maleSocket to E
054     * This method is needed since SpotBugs @SuppressWarnings("unchecked")
055     * does not work for the cast: (E)socket.
056     * @param maleSocket the maleSocket to cast
057     * @return the maleSocket as E
058     */
059    protected abstract E castBean(MaleSocket maleSocket);
060
061    /** {@inheritDoc} */
062    @Override
063    @SuppressFBWarnings(value = "OVERRIDING_METHODS_MUST_INVOKE_SUPER",
064            justification = "LogixNG is a tree that must be deleted recursively")
065    public final void deleteBean(@Nonnull E n, @Nonnull String property) throws PropertyVetoException {
066        this.deleteBean((MaleSocket)n, property);
067    }
068
069    /** {@inheritDoc} */
070    @Override
071    @OverridingMethodsMustInvokeSuper
072    public void deleteBean(@Nonnull MaleSocket socket, @Nonnull String property) throws PropertyVetoException {
073        for (int i=0; i < socket.getChildCount(); i++) {
074            FemaleSocket child = socket.getChild(i);
075            if (child.isConnected()) {
076                MaleSocket maleSocket = child.getConnectedSocket();
077                maleSocket.getManager().deleteBean(maleSocket, property);
078            }
079        }
080
081        // throws PropertyVetoException if vetoed
082        fireVetoableChange(property, socket);
083        if (property.equals("DoDelete")) { // NOI18N
084            deregister(castBean(socket));
085            socket.dispose();
086        }
087    }
088
089    /** {@inheritDoc} */
090    @Override
091    @OverridingMethodsMustInvokeSuper
092    public void deregister(@Nonnull E s) {
093        // A LogixNG action or expression is contained in one or more male
094        // sockets. A male socket might be contained in another male socket.
095        // In some cases, it seems that the male socket used in this call is
096        // not the male socket that's registered in the manager. To resolve
097        // this, we search for the registered bean with the system name and
098        // then deregister the bean we have found.
099        E bean = getBySystemName(s.getSystemName());
100        if (bean == null) {
101            // This should never happen.
102            throw new IllegalArgumentException(s.getSystemName() + " is not registered in manager");
103        }
104        super.deregister(bean);
105    }
106
107    /**
108     * Test if parameter is a properly formatted system name.
109     *
110     * @param systemName the system name
111     * @return enum indicating current validity, which might be just as a prefix
112     */
113    @Override
114    public final NameValidity validSystemNameFormat(String systemName) {
115        return LogixNG_Manager.validSystemNameFormat(
116                getSubSystemNamePrefix(), systemName);
117    }
118
119    @Override
120    @SuppressFBWarnings(value = "OVERRIDING_METHODS_MUST_INVOKE_SUPER",
121            justification = "This method must never be called")
122    public void register(@Nonnull E s) {
123        throw new RuntimeException("Use BaseManager.registerBean() instead");
124    }
125
126    @Override
127    public E registerBean(@Nonnull E s) {
128        E bean = s;
129        for (MaleSocketFactory<E> factory : _maleSocketFactories) {
130            bean = factory.encapsulateMaleSocket(this, bean);
131        }
132        super.register(bean);
133        return bean;
134    }
135
136    @Override
137    public void registerMaleSocketFactory(MaleSocketFactory<E> factory) {
138        _maleSocketFactories.add(factory);
139    }
140
141    /** {@inheritDoc} */
142    @Override
143    @SuppressWarnings("unchecked")   // Can't check generic types
144    protected E getOuterBean(E bean) {
145        if (bean == null) {
146            return null;
147        }
148        if (bean instanceof Base) {
149            Base b = (Base) bean;
150            while (b.getParent() instanceof MaleSocket) {
151                b = b.getParent();
152            }
153            return (E) b;
154        }
155        return bean;
156    }
157
158//    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractBaseManager.class);
159
160}