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