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    public void getUsageTree(int level, NamedBean bean, List<jmri.NamedBeanUsageReport> report, NamedBean cdl) {
383        log.debug("** {} :: {}", level, this.getLongDescription());
384        level++;
385
386        if (isConnected()) {
387            getConnectedSocket().getUsageTree(level, bean, report, cdl);
388        }
389    }
390
391    /** {@inheritDoc} */
392    @Override
393    public void getUsageDetail(int level, NamedBean bean, List<jmri.NamedBeanUsageReport> report, NamedBean cdl) {
394    }
395
396    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
397
398    @Override
399    public void addPropertyChangeListener(PropertyChangeListener listener) {
400        pcs.addPropertyChangeListener(listener);
401    }
402
403    @Override
404    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
405        pcs.addPropertyChangeListener(propertyName, listener);
406    }
407
408    @Override
409    public PropertyChangeListener[] getPropertyChangeListeners() {
410        return pcs.getPropertyChangeListeners();
411    }
412
413    @Override
414    public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
415        return pcs.getPropertyChangeListeners(propertyName);
416    }
417
418    @Override
419    public void removePropertyChangeListener(PropertyChangeListener listener) {
420        pcs.removePropertyChangeListener(listener);
421    }
422
423    @Override
424    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
425        pcs.removePropertyChangeListener(propertyName, listener);
426    }
427
428    @Override
429    public void addPropertyChangeListener(PropertyChangeListener listener, String name, String listenerRef) {
430        throw new UnsupportedOperationException("Not supported");
431    }
432
433    @Override
434    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener, String name, String listenerRef) {
435        throw new UnsupportedOperationException("Not supported");
436    }
437
438    @Override
439    public void updateListenerRef(PropertyChangeListener l, String newName) {
440        throw new UnsupportedOperationException("Not supported");
441    }
442
443    @Override
444    public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
445        throw new UnsupportedOperationException("Not supported");
446    }
447
448    @Override
449    public String getListenerRef(PropertyChangeListener l) {
450        throw new UnsupportedOperationException("Not supported");
451    }
452
453    @Override
454    public ArrayList<String> getListenerRefs() {
455        throw new UnsupportedOperationException("Not supported");
456    }
457
458    @Override
459    public int getNumPropertyChangeListeners() {
460        return pcs.getPropertyChangeListeners().length;
461    }
462
463    @Override
464    public PropertyChangeListener[] getPropertyChangeListenersByReference(String name) {
465        throw new UnsupportedOperationException("Not supported");
466    }
467
468    /**
469     * Do something on every item in the sub tree of this item.
470     * @param r the action to do on all items.
471     */
472    @Override
473    public void forEntireTree(RunnableWithBase r) {
474        r.run(this);
475        if (isConnected()) getConnectedSocket().forEntireTree(r);
476    }
477
478    @Override
479    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) {
480        throw new UnsupportedOperationException("Not supported");
481    }
482
483    @Override
484    public Base deepCopyChildren(Base original, Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
485        throw new UnsupportedOperationException("Not supported");
486    }
487
488   private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractFemaleSocket.class);
489
490}