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 deleteChildren(@Nonnull MaleSocket socket, @Nonnull String property) throws PropertyVetoException {
093        for (int i=socket.getChildCount()-1; i >= 0; i--) {
094            FemaleSocket child = socket.getChild(i);
095            if (child.isConnected()) {
096                MaleSocket maleSocket = child.getConnectedSocket();
097                maleSocket.getManager().deleteBean(maleSocket, property);
098                if (property.equals("DoDelete")) { // NOI18N
099                    child.disconnect();
100                }
101            }
102        }
103    }
104
105    /** {@inheritDoc} */
106    @Override
107    @OverridingMethodsMustInvokeSuper
108    public void deregister(@Nonnull E s) {
109        // A LogixNG action or expression is contained in one or more male
110        // sockets. A male socket might be contained in another male socket.
111        // In some cases, it seems that the male socket used in this call is
112        // not the male socket that's registered in the manager. To resolve
113        // this, we search for the registered bean with the system name and
114        // then deregister the bean we have found.
115        E bean = getBySystemName(s.getSystemName());
116        if (bean == null) {
117            // This should never happen.
118            throw new IllegalArgumentException(s.getSystemName() + " is not registered in manager");
119        }
120        super.deregister(bean);
121    }
122
123    /**
124     * Test if parameter is a properly formatted system name.
125     *
126     * @param systemName the system name
127     * @return enum indicating current validity, which might be just as a prefix
128     */
129    @Override
130    public final NameValidity validSystemNameFormat(String systemName) {
131        return LogixNG_Manager.validSystemNameFormat(
132                getSubSystemNamePrefix(), systemName);
133    }
134
135    @Override
136    @SuppressFBWarnings(value = "OVERRIDING_METHODS_MUST_INVOKE_SUPER",
137            justification = "This method must never be called")
138    public void register(@Nonnull E s) {
139        throw new RuntimeException("Use BaseManager.registerBean() instead");
140    }
141
142    @Override
143    public E registerBean(@Nonnull E s) {
144        E bean = s;
145        for (MaleSocketFactory<E> factory : _maleSocketFactories) {
146            bean = factory.encapsulateMaleSocket(this, bean);
147        }
148        super.register(bean);
149        return bean;
150    }
151
152    @Override
153    public void registerMaleSocketFactory(MaleSocketFactory<E> factory) {
154        _maleSocketFactories.add(factory);
155    }
156
157    /** {@inheritDoc} */
158    @Override
159    @SuppressWarnings("unchecked")   // Can't check generic types
160    protected E getOuterBean(E bean) {
161        if (bean == null) {
162            return null;
163        }
164        if (bean instanceof Base) {
165            Base b = (Base) bean;
166            while (b.getParent() instanceof MaleSocket) {
167                b = b.getParent();
168            }
169            return (E) b;
170        }
171        return bean;
172    }
173
174//    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractBaseManager.class);
175
176}