001package jmri.jmrit.logixng.implementation;
002
003import java.beans.*;
004import java.io.PrintWriter;
005import java.util.*;
006
007import javax.annotation.Nonnull;
008
009import jmri.JmriException;
010import jmri.NamedBean;
011import jmri.jmrit.logixng.*;
012
013import org.apache.commons.lang3.mutable.MutableInt;
014
015/**
016 * Abstract female socket.
017 *
018 * @author Daniel Bergqvist 2019
019 */
020public abstract class AbstractFemaleSocket implements FemaleSocket {
021
022    private Base _parent;
023    protected final FemaleSocketListener _listener;
024    private MaleSocket _socket = null;
025    private String _name = null;
026    private boolean _listenersAreRegistered = false;
027    boolean _enableListeners = true;
028
029
030    public AbstractFemaleSocket(Base parent, FemaleSocketListener listener, String name) {
031        if (!validateName(name)) {
032            throw new IllegalArgumentException("the name is not valid: " + name);
033        }
034        if (listener == null) throw new IllegalArgumentException("FemaleSocketListener is null");
035        _parent = parent;
036        _listener = listener;
037        _name = name;
038    }
039
040    /** {@inheritDoc} */
041    @Override
042    public void setEnableListeners(boolean enable) {
043        _enableListeners = enable;
044    }
045
046    /** {@inheritDoc} */
047    @Override
048    public boolean getEnableListeners() {
049        return _enableListeners;
050    }
051
052    /** {@inheritDoc} */
053    @Override
054    public Base getParent() {
055        return _parent;
056    }
057
058    /** {@inheritDoc} */
059    @Override
060    public void setParent(@Nonnull Base parent) {
061        _parent = parent;
062    }
063
064    /** {@inheritDoc} */
065    @Override
066    public boolean setParentForAllChildren(List<String> errors) {
067        if (isConnected()) {
068            getConnectedSocket().setParent(this);
069            return getConnectedSocket().setParentForAllChildren(errors);
070        }
071        return true;
072    }
073
074    /** {@inheritDoc} */
075    @Override
076    public void connect(MaleSocket socket) throws SocketAlreadyConnectedException {
077        if (socket == null) {
078            throw new NullPointerException("socket cannot be null");
079        }
080
081        if (_listenersAreRegistered) {
082            throw new UnsupportedOperationException("A socket must not be connected when listeners are registered");
083        }
084
085        if (isConnected()) {
086            throw new SocketAlreadyConnectedException("Socket is already connected");
087        }
088
089        if (!isCompatible(socket)) {
090            throw new IllegalArgumentException("Socket "+socket.getClass().getName()+" is not compatible with "+this.getClass().getName());
091//            throw new IllegalArgumentException("Socket "+socket.getClass().getName()+" is not compatible with "+this.getClass().getName()+". Socket.getObject: "+socket.getObject().getClass().getName());
092        }
093
094        _socket = socket;
095        _socket.setParent(this);
096        _listener.connected(this);
097        pcs.firePropertyChange(new PropertyChangeEvent(this, Base.PROPERTY_SOCKET_CONNECTED, null, _socket));
098//        pcs.firePropertyChange(Base.PROPERTY_SOCKET_CONNECTED, null, _socket);
099    }
100
101    /** {@inheritDoc} */
102    @Override
103    public void disconnect() {
104        MaleSocket maleSocket = _socket;
105
106        if (_socket == null) {
107            return;
108        }
109
110        if (_listenersAreRegistered) {
111            throw new UnsupportedOperationException("A socket must not be disconnected when listeners are registered");
112        }
113
114        _socket.setParent(null);
115        _socket = null;
116        _listener.disconnected(this);
117        pcs.firePropertyChange(new PropertyChangeEvent(this, Base.PROPERTY_SOCKET_DISCONNECTED, maleSocket, null));
118//        pcs.firePropertyChange(Base.PROPERTY_SOCKET_DISCONNECTED, null, _socket);
119    }
120
121    /** {@inheritDoc} */
122    @Override
123    public MaleSocket getConnectedSocket() {
124        return _socket;
125    }
126
127    /** {@inheritDoc} */
128    @Override
129    public boolean isConnected() {
130        return _socket != null;
131    }
132
133    /** {@inheritDoc} */
134    @Override
135    public final boolean validateName(String name, boolean ignoreDuplicateErrors) {
136        // Empty name is not allowed
137        if (name.isEmpty()) return false;
138
139        // The name must start with a letter
140        if (!Character.isLetter(name.charAt(0))) return false;
141
142        // The name must consist of letters, digits or underscore
143        for (int i=0; i < name.length(); i++) {
144            if (!Character.isLetterOrDigit(name.charAt(i)) && (name.charAt(i) != '_')) {
145                return false;
146            }
147        }
148
149        if (!ignoreDuplicateErrors && (_parent != null)) {
150            // Check that no other female socket of the parent has the same name
151            for (int i=0; i < _parent.getChildCount(); i++) {
152                FemaleSocket child = _parent.getChild(i);
153                if ((child != this) && name.equals(child.getName())) return false;
154            }
155        }
156
157        // The name is valid
158        return true;
159    }
160
161    /** {@inheritDoc} */
162    @Override
163    public void setName(String name, boolean ignoreDuplicateErrors) {
164        if (!validateName(name, ignoreDuplicateErrors)) {
165            throw new IllegalArgumentException("the name is not valid: " + name);
166        }
167        _name = name;
168        _listener.socketNameChanged(this);
169    }
170
171    /** {@inheritDoc} */
172    @Override
173    public String getName() {
174        return _name;
175    }
176
177    abstract public void disposeMe();
178
179    /** {@inheritDoc} */
180    @Override
181    public final void dispose() {
182        if (_listenersAreRegistered) {
183            throw new UnsupportedOperationException("This is not currently supported");
184        }
185
186        if (isConnected()) {
187            MaleSocket aSocket = getConnectedSocket();
188            disconnect();
189            aSocket.dispose();
190        }
191        disposeMe();
192    }
193
194    /**
195     * Register listeners if this object needs that.
196     * <P>
197     * Important: This method may be called more than once. Methods overriding
198     * this method must ensure that listeners are not registered more than once.
199     */
200    protected void registerListenersForThisClass() {
201        // Do nothing
202    }
203
204    /**
205     * Unregister listeners if this object needs that.
206     * <P>
207     * Important: This method may be called more than once. Methods overriding
208     * this method must ensure that listeners are not unregistered more than once.
209     */
210    protected void unregisterListenersForThisClass() {
211        // Do nothing
212    }
213
214    /**
215     * Register listeners if this object needs that.
216     */
217    @Override
218    public void registerListeners() {
219        if (!_enableListeners) return;
220
221        _listenersAreRegistered = true;
222        registerListenersForThisClass();
223        if (isConnected()) {
224            getConnectedSocket().registerListeners();
225        }
226    }
227
228    /**
229     * Register listeners if this object needs that.
230     */
231    @Override
232    public void unregisterListeners() {
233        if (!_enableListeners) return;
234
235        unregisterListenersForThisClass();
236        if (isConnected()) {
237            getConnectedSocket().unregisterListeners();
238        }
239        _listenersAreRegistered = false;
240    }
241
242    /** {@inheritDoc} */
243    @Override
244    public final boolean isActive() {
245        return isEnabled() && ((_parent == null) || _parent.isActive());
246    }
247
248    /** {@inheritDoc} */
249    @Override
250    public Category getCategory() {
251        throw new UnsupportedOperationException("Not supported.");
252    }
253
254    /** {@inheritDoc} */
255    @Override
256    public FemaleSocket getChild(int index) {
257        throw new UnsupportedOperationException("Not supported.");
258    }
259
260    /** {@inheritDoc} */
261    @Override
262    public int getChildCount() {
263        throw new UnsupportedOperationException("Not supported.");
264    }
265
266    /** {@inheritDoc} */
267    @Override
268    public String getUserName() {
269        throw new UnsupportedOperationException("Not supported.");
270    }
271
272    /** {@inheritDoc} */
273    @Override
274    public void setUserName(String s) throws NamedBean.BadUserNameException {
275        throw new UnsupportedOperationException("Not supported.");
276    }
277
278    /** {@inheritDoc} */
279    @Override
280    public String getComment() {
281        throw new UnsupportedOperationException("Not supported.");
282    }
283
284    /** {@inheritDoc} */
285    @Override
286    public void setComment(String s) {
287        throw new UnsupportedOperationException("Not supported.");
288    }
289
290    /** {@inheritDoc} */
291    @Override
292    public String getSystemName() {
293        return getParent().getSystemName();
294    }
295
296    /** {@inheritDoc} */
297    @Override
298    public final ConditionalNG getConditionalNG() {
299        if (_parent == null) return null;
300        return _parent.getConditionalNG();
301    }
302
303    /** {@inheritDoc} */
304    @Override
305    public final LogixNG getLogixNG() {
306        if (_parent == null) return null;
307        return _parent.getLogixNG();
308    }
309
310    /** {@inheritDoc} */
311    @Override
312    public final Base getRoot() {
313        if (_parent == null) return null;
314        return _parent.getRoot();
315    }
316
317    protected void printTreeRow(
318            PrintTreeSettings settings,
319            Locale locale,
320            PrintWriter writer,
321            String currentIndent,
322            MutableInt lineNumber) {
323
324        if (settings._printLineNumbers) {
325            writer.append(String.format(PRINT_LINE_NUMBERS_FORMAT, lineNumber.addAndGet(1)));
326        }
327        writer.append(currentIndent);
328        writer.append(getLongDescription(locale));
329        writer.println();
330    }
331
332    /** {@inheritDoc} */
333    @Override
334    public void printTree(
335            PrintTreeSettings settings,
336            PrintWriter writer,
337            String indent,
338            MutableInt lineNumber) {
339
340        throw new UnsupportedOperationException("Not supported.");
341    }
342
343    /** {@inheritDoc} */
344    @Override
345    public void printTree(
346            PrintTreeSettings settings,
347            Locale locale,
348            PrintWriter writer,
349            String indent,
350            MutableInt lineNumber) {
351
352        throw new UnsupportedOperationException("Not supported.");
353    }
354
355    /** {@inheritDoc} */
356    @Override
357    public void printTree(
358            PrintTreeSettings settings,
359            Locale locale,
360            PrintWriter writer,
361            String indent,
362            String currentIndent,
363            MutableInt lineNumber) {
364
365        printTreeRow(settings, locale, writer, currentIndent, lineNumber);
366
367        if (isConnected()) {
368            getConnectedSocket().printTree(settings, locale, writer, indent, currentIndent+indent, lineNumber);
369        } else {
370            if (settings._printLineNumbers) {
371                writer.append(String.format(PRINT_LINE_NUMBERS_FORMAT, lineNumber.addAndGet(1)));
372            }
373            writer.append(currentIndent);
374            writer.append(indent);
375            if (settings._printNotConnectedSockets) writer.append("Socket not connected");
376            writer.println();
377        }
378    }
379
380    /** {@inheritDoc} */
381    @Override
382    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value="SLF4J_SIGN_ONLY_FORMAT",
383                                                        justification="Specific log message format")
384    public void getUsageTree(int level, NamedBean bean, List<jmri.NamedBeanUsageReport> report, NamedBean cdl) {
385        log.debug("** {} :: {}", level, this.getLongDescription());
386        level++;
387
388        if (isConnected()) {
389            getConnectedSocket().getUsageTree(level, bean, report, cdl);
390        }
391    }
392
393    /** {@inheritDoc} */
394    @Override
395    public void getUsageDetail(int level, NamedBean bean, List<jmri.NamedBeanUsageReport> report, NamedBean cdl) {
396    }
397
398    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
399
400    @Override
401    public void addPropertyChangeListener(PropertyChangeListener listener) {
402        pcs.addPropertyChangeListener(listener);
403    }
404
405    @Override
406    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
407        pcs.addPropertyChangeListener(propertyName, listener);
408    }
409
410    @Override
411    public PropertyChangeListener[] getPropertyChangeListeners() {
412        return pcs.getPropertyChangeListeners();
413    }
414
415    @Override
416    public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
417        return pcs.getPropertyChangeListeners(propertyName);
418    }
419
420    @Override
421    public void removePropertyChangeListener(PropertyChangeListener listener) {
422        pcs.removePropertyChangeListener(listener);
423    }
424
425    @Override
426    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
427        pcs.removePropertyChangeListener(propertyName, listener);
428    }
429
430    @Override
431    public void addPropertyChangeListener(PropertyChangeListener listener, String name, String listenerRef) {
432        throw new UnsupportedOperationException("Not supported");
433    }
434
435    @Override
436    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener, String name, String listenerRef) {
437        throw new UnsupportedOperationException("Not supported");
438    }
439
440    @Override
441    public void updateListenerRef(PropertyChangeListener l, String newName) {
442        throw new UnsupportedOperationException("Not supported");
443    }
444
445    @Override
446    public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
447        throw new UnsupportedOperationException("Not supported");
448    }
449
450    @Override
451    public String getListenerRef(PropertyChangeListener l) {
452        throw new UnsupportedOperationException("Not supported");
453    }
454
455    @Override
456    public ArrayList<String> getListenerRefs() {
457        throw new UnsupportedOperationException("Not supported");
458    }
459
460    @Override
461    public int getNumPropertyChangeListeners() {
462        return pcs.getPropertyChangeListeners().length;
463    }
464
465    @Override
466    public PropertyChangeListener[] getPropertyChangeListenersByReference(String name) {
467        throw new UnsupportedOperationException("Not supported");
468    }
469
470    /**
471     * Do something on every item in the sub tree of this item.
472     * @param r the action to do on all items.
473     */
474    @Override
475    public void forEntireTree(RunnableWithBase r) {
476        r.run(this);
477        if (isConnected()) getConnectedSocket().forEntireTree(r);
478    }
479
480    /**
481     * Do something on every item in the sub tree of this item.
482     * @param r the action to do on all items.
483     * @throws Exception if an exception occurs
484     */
485    @Override
486    public void forEntireTreeWithException(RunnableWithBaseThrowException r) throws Exception {
487        r.run(this);
488        if (isConnected()) getConnectedSocket().forEntireTreeWithException(r);
489    }
490
491    @Override
492    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) {
493        throw new UnsupportedOperationException("Not supported");
494    }
495
496    @Override
497    public Base deepCopyChildren(Base original, Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
498        throw new UnsupportedOperationException("Not supported");
499    }
500
501    /** {@inheritDoc} */
502    @Override
503    public void getListenerRefsIncludingChildren(List<String> list) {
504        if (isConnected()) {
505            getConnectedSocket().getListenerRefsIncludingChildren(list);
506        }
507    }
508
509    @Override
510    public boolean hasChild(@Nonnull Base b) {
511        return isConnected() && getConnectedSocket() == b;
512    }
513
514    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractFemaleSocket.class);
515
516}