001package jmri.jmrit.logixng;
002
003import java.beans.*;
004import java.io.PrintWriter;
005import java.util.*;
006
007import javax.annotation.*;
008
009import jmri.JmriException;
010import jmri.NamedBean;
011import jmri.beans.PropertyChangeProvider;
012
013import org.apache.commons.lang3.mutable.MutableInt;
014
015/**
016 * The base interface for LogixNG expressions and actions.
017 * Used to simplify the user interface.
018 *
019 * @author Daniel Bergqvist Copyright 2018
020 */
021public interface Base extends PropertyChangeProvider {
022
023    /**
024     * Separator returned by enums toString() methods to get a separator
025     * in JComboBoxes. See {@link jmri.jmrit.logixng.expressions.ExpressionEntryExit.EntryExitState}
026     * for an example.
027     */
028    String SEPARATOR = "---------------";
029
030    /**
031     * The name of the property child count.
032     * To get the number of children, use the method getChildCount().
033     * This constant is used in calls to firePropertyChange().
034     * The class fires a property change then a child is added or removed.
035     * <p>
036     * If children are removed, the field oldValue of the PropertyChange event
037     * must be a List&lt;FemaleSocket&gt; with the FemaleSockets that are
038     * removed from the list so that the listener can unregister itself as a
039     * listener of this female socket.
040     * <p>
041     * If children are added, the field newValue of the PropertyChange event
042     * must be a List&lt;FemaleSocket&gt; with the FemaleSockets that are
043     * added to the list so that the listener can register itself as a
044     * listener of this female socket.
045     */
046    String PROPERTY_CHILD_COUNT = "ChildCount";
047
048    /**
049     * The name of the property child reorder.
050     * The number of children has remained the same, but the order of children
051     * has changed.
052     * <p>
053     * The field newValue of the PropertyChange event must be a
054     * List&lt;FemaleSocket&gt; with the FemaleSockets that are reordered so
055     * that the listener can update the tree.
056     */
057    String PROPERTY_CHILD_REORDER = "ChildReorder";
058
059    /**
060     * The socket has been connected.
061     * This constant is used in calls to firePropertyChange().
062     * The socket fires a property change when it is connected or disconnected.
063     */
064    String PROPERTY_SOCKET_CONNECTED = "SocketConnected";
065
066    /**
067     * The socket has been disconnected.
068     * This constant is used in calls to firePropertyChange().
069     * The socket fires a property change when it is connected or disconnected.
070     */
071    String PROPERTY_SOCKET_DISCONNECTED = "SocketDisconnected";
072
073    /**
074     * The last result of the expression has changed.
075     * This constant is used in calls to firePropertyChange().
076     */
077    String PROPERTY_LAST_RESULT_CHANGED = "LastResultChanged";
078
079    /**
080     * Constant representing an "connected" state of the socket
081     */
082    int SOCKET_CONNECTED = 0x02;
083
084    /**
085     * Constant representing an "disconnected" state of the socket
086     */
087    int SOCKET_DISCONNECTED = 0x04;
088
089
090    /**
091     * Get the system name.
092     * @return the system name
093     */
094    String getSystemName();
095
096    /**
097     * Get the user name.
098     * @return the user name
099     */
100    @CheckReturnValue
101    @CheckForNull
102    String getUserName();
103
104    /**
105     * Get associated comment text.
106     * A LogixNG comment can have multiple lines, separated with \n.
107     *
108     * @return the comment or null
109     */
110    @CheckReturnValue
111    @CheckForNull
112    String getComment();
113
114    /**
115     * Get the user name.
116     * @param s the new user name
117     */
118    void setUserName(@CheckForNull String s) throws NamedBean.BadUserNameException;
119
120    /**
121     * Create a deep copy of myself and my children
122     * The item needs to try to lookup itself in both systemNames and userNames
123     * to see if the user has given a new system name and/or a new user name.If no new system name is given, an auto system name is used.
124     * If no user name is given, a null user name is used.
125     *
126     * @param systemNames a map of old and new system name
127     * @param userNames a map of old system name and new user name
128     * @return a deep copy
129     * @throws jmri.JmriException in case of an error
130     */
131    Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames)
132            throws JmriException;
133
134    /**
135     * Do a deep copy of children from the original to me.
136     *
137     * @param original the item to copy from
138     * @param systemNames a map of old and new system name
139     * @param userNames a map of old system name and new user name
140     * @return myself
141     * @throws jmri.JmriException in case of an error
142     */
143    Base deepCopyChildren(
144            Base original,
145            Map<String, String> systemNames,
146            Map<String, String> userNames)
147            throws JmriException;
148
149    /**
150     * Set associated comment text.
151     * <p>
152     * Comments can be any valid text.
153     *
154     * @param comment the comment or null to remove an existing comment
155     */
156    void setComment(@CheckForNull String comment);
157
158    /**
159     * Get a short description of this item.
160     * @return a short description
161     */
162    default String getShortDescription() {
163        return getShortDescription(Locale.getDefault());
164    }
165
166    /**
167     * Get a long description of this item.
168     * @return a long description
169     */
170    default String getLongDescription() {
171        return getLongDescription(Locale.getDefault());
172    }
173
174    /**
175     * Get a short description of this item.
176     * @param locale The locale to be used
177     * @return a short description
178     */
179    String getShortDescription(Locale locale);
180
181    /**
182     * Get a long description of this item.
183     * @param locale The locale to be used
184     * @return a long description
185     */
186    String getLongDescription(Locale locale);
187
188    /**
189     * Get the Module of this item, if it's part of a module.
190     * @return the Module that owns this item or null if it's
191     *         owned by a ConditonalNG.
192     */
193    default Module getModule() {
194        Base parent = this.getParent();
195        while (parent != null) {
196            if (parent instanceof Module) {
197                return (Module) parent;
198            }
199            parent = parent.getParent();
200        }
201        return null;
202    }
203
204    /**
205     * Get the ConditionalNG of this item.
206     * @return the ConditionalNG that owns this item
207     */
208    ConditionalNG getConditionalNG();
209
210    /**
211     * Get the LogixNG of this item.
212     * @return the LogixNG that owns this item
213     */
214    LogixNG getLogixNG();
215
216    /**
217     * Get the root of the tree that this item belongs to.
218     * @return the top most item in the tree
219     */
220    Base getRoot();
221
222    /**
223     * Get the parent.
224     * <P>
225     * The following rules apply
226     * <ul>
227     * <li>LogixNGs has no parent. The method throws an UnsupportedOperationException if called.</li>
228     * <li>Expressions and actions has the male socket that they are connected to as their parent.</li>
229     * <li>Male sockets has the female socket that they are connected to as their parent.</li>
230     * <li>The parent of a female sockets is the LogixNG, expression or action that
231     * has this female socket.</li>
232     * <li>The parent of a male sockets is the same parent as the expression or
233     * action that it contains.</li>
234     * </ul>
235     *
236     * @return the parent of this object
237     */
238    Base getParent();
239
240    /**
241     * Set the parent.
242     * <P>
243     * The following rules apply
244     * <ul>
245     * <li>ExecutionGroups has no parent. The method throws an UnsupportedOperationException if called.</li>
246     * <li>LogixNGs has the execution group as its parent.</li>
247     * <li>Expressions and actions has the male socket that they are connected to as their parent.</li>
248     * <li>Male sockets has the female socket that they are connected to as their parent.</li>
249     * <li>The parent of a female sockets is the LogixNG, expression or action that
250     * has this female socket.</li>
251     * <li>The parent of a male sockets is the same parent as the expression or
252     * action that it contains.</li>
253     * </ul>
254     *
255     * @param parent the new parent of this object
256     */
257    void setParent(Base parent);
258
259    /**
260     * Set the parent for all the children.
261     *
262     * @param errors a list of potential errors
263     * @return true if success, false otherwise
264     */
265    boolean setParentForAllChildren(List<String> errors);
266
267    /**
268     * Get a child of this item
269     * @param index the index of the child to get
270     * @return the child
271     * @throws IllegalArgumentException if the index is less than 0 or greater
272     * or equal with the value returned by getChildCount()
273     */
274    FemaleSocket getChild(int index)
275            throws IllegalArgumentException, UnsupportedOperationException;
276
277    /**
278     * Get the number of children.
279     * @return the number of children
280     */
281    int getChildCount();
282
283    /**
284     * Is the operation allowed on this child?
285     * @param index the index of the child to do the operation on
286     * @param oper the operation to do
287     * @return true if operation is allowed, false otherwise
288     */
289    default boolean isSocketOperationAllowed(int index, FemaleSocketOperation oper) {
290        if (this instanceof MaleSocket) {
291            return ((MaleSocket)this).getObject().isSocketOperationAllowed(index, oper);
292        }
293        return false;
294    }
295
296    /**
297     * Do an operation on a child
298     * @param index the index of the child to do the operation on
299     * @param oper the operation to do
300     */
301    default void doSocketOperation(int index, FemaleSocketOperation oper) {
302        if (this instanceof MaleSocket) {
303            ((MaleSocket)this).getObject().doSocketOperation(index, oper);
304        }
305        // By default, do nothing if not a male socket
306    }
307
308    /**
309     * Get the category.
310     * @return the category
311     */
312    Category getCategory();
313
314    /**
315     * Is this item active? If this item is enabled and all the parents are
316     * enabled, this item is active.
317     * @return true if active, false otherwise.
318     */
319    boolean isActive();
320
321    /**
322     * Setup this object and its children.
323     * This method is used to lookup system names for child sockets, turnouts,
324     * sensors, and so on.
325     */
326    void setup();
327
328    /**
329     * Deactivate this object, so that it releases as many resources as possible
330     * and no longer effects others.
331     * <p>
332     * For example, if this object has listeners, after a call to this method it
333     * should no longer notify those listeners. Any native or system-wide
334     * resources it maintains should be released, including threads, files, etc.
335     * <p>
336     * It is an error to invoke any other methods on this object once dispose()
337     * has been called. Note, however, that there is no guarantee about behavior
338     * in that case.
339     * <p>
340     * Afterwards, references to this object may still exist elsewhere,
341     * preventing its garbage collection. But it's formally dead, and shouldn't
342     * be keeping any other objects alive. Therefore, this method should null
343     * out any references to other objects that this object contained.
344     */
345    void dispose();  // remove _all_ connections!
346
347    /**
348     * Set whenether this object is enabled or disabled.
349     * If the parent is disabled, this object must also be disabled, regardless
350     * of this flag.
351     *
352     * @param enable true if this object should be enabled, false otherwise
353     */
354//    void setEnabled(boolean enable);
355
356    /**
357     * Determines whether this object is enabled.
358     *
359     * @return true if the object is enabled, false otherwise
360     */
361    default boolean isEnabled() {
362        return true;
363    }
364
365    /**
366     * Register listeners if this object needs that.
367     * <P>
368     * Important: This method may be called more than once. Methods overriding
369     * this method must ensure that listeners are not registered more than once.
370     */
371    void registerListeners();
372
373    /**
374     * Unregister listeners if this object needs that.
375     * <P>
376     * Important: This method may be called more than once. Methods overriding
377     * this method must ensure that listeners are not unregistered more than once.
378     */
379    void unregisterListeners();
380
381    /**
382     * Print the tree to a stream.
383     *
384     * @param writer the stream to print the tree to
385     * @param indent the indentation of each level
386     * @param lineNumber the line number
387     */
388    default void printTree(
389            PrintWriter writer,
390            String indent,
391            MutableInt lineNumber) {
392        printTree(new PrintTreeSettings(), writer, indent, lineNumber);
393    }
394
395    /**
396     * Print the tree to a stream.
397     *
398     * @param settings settings for what to print
399     * @param writer the stream to print the tree to
400     * @param indent the indentation of each level
401     * @param lineNumber the line number
402     */
403    void printTree(
404            PrintTreeSettings settings,
405            PrintWriter writer,
406            String indent,
407            MutableInt lineNumber);
408
409    /**
410     * Print the tree to a stream.
411     *
412     * @param locale The locale to be used
413     * @param writer the stream to print the tree to
414     * @param indent the indentation of each level
415     * @param lineNumber the line number
416     */
417    default void printTree(
418            Locale locale,
419            PrintWriter writer,
420            String indent,
421            MutableInt lineNumber) {
422        printTree(new PrintTreeSettings(), locale, writer, indent, lineNumber);
423    }
424
425    /**
426     * Print the tree to a stream.
427     *
428     * @param settings settings for what to print
429     * @param locale The locale to be used
430     * @param writer the stream to print the tree to
431     * @param indent the indentation of each level
432     * @param lineNumber the line number
433     */
434    void printTree(
435            PrintTreeSettings settings,
436            Locale locale,
437            PrintWriter writer,
438            String indent,
439            MutableInt lineNumber);
440
441    /**
442     * Print the tree to a stream.
443     *
444     * @param settings settings for what to print
445     * @param locale The locale to be used
446     * @param writer the stream to print the tree to
447     * @param indent the indentation of each level
448     * @param currentIndent the current indentation
449     * @param lineNumber the line number
450     */
451    void printTree(
452            PrintTreeSettings settings,
453            Locale locale,
454            PrintWriter writer,
455            String indent,
456            String currentIndent,
457            MutableInt lineNumber);
458
459    static String getListenString(boolean listen) {
460        if (listen) {
461            return Bundle.getMessage("Base_Listen");
462        } else {
463            return Bundle.getMessage("Base_NoListen");
464        }
465    }
466
467    static String getNoListenString() {
468        return Bundle.getMessage("Base_NoListen");
469    }
470
471    /**
472     * Navigate the LogixNG tree.
473     *
474     * @param level  The current recursion level for debugging.
475     * @param bean   The named bean that is the object of the search.
476     * @param report A list of NamedBeanUsageReport usage reports.
477     * @param cdl    The current ConditionalNG bean.  Null for Module searches since there is no conditional
478     */
479    void getUsageTree(int level, NamedBean bean, List<jmri.NamedBeanUsageReport> report, NamedBean cdl);
480
481    /**
482     * Add a new NamedBeanUsageReport to the report list if there are any matches in this action or expresssion.
483     * <p>
484     * NamedBeanUsageReport Usage keys:
485     * <ul>
486     * <li>LogixNGAction</li>
487     * <li>LogixNGExpression</li>
488     * </ul>
489     *
490     * @param level  The current recursion level for debugging.
491     * @param bean   The named bean that is the object of the search.
492     * @param report A list of NamedBeanUsageReport usage reports.
493     * @param cdl    The current ConditionalNG bean.  Null for Module searches since there is no conditional
494     */
495    void getUsageDetail(int level, NamedBean bean, List<jmri.NamedBeanUsageReport> report, NamedBean cdl);
496
497    /**
498     * Request a call-back when a bound property changes. Bound properties are
499     * the known state, commanded state, user and system names.
500     *
501     * @param listener    The listener. This may change in the future to be a
502     *                        subclass of NamedProprtyChangeListener that
503     *                        carries the name and listenerRef values internally
504     * @param name        The name (either system or user) that the listener
505     *                        uses for this namedBean, this parameter is used to
506     *                        help determine when which listeners should be
507     *                        moved when the username is moved from one bean to
508     *                        another
509     * @param listenerRef A textual reference for the listener, that can be
510     *                        presented to the user when a delete is called
511     */
512    void addPropertyChangeListener(@Nonnull PropertyChangeListener listener, String name, String listenerRef);
513
514    /**
515     * Request a call-back when a bound property changes. Bound properties are
516     * the known state, commanded state, user and system names.
517     *
518     * @param propertyName The name of the property to listen to
519     * @param listener     The listener. This may change in the future to be a
520     *                         subclass of NamedProprtyChangeListener that
521     *                         carries the name and listenerRef values
522     *                         internally
523     * @param name         The name (either system or user) that the listener
524     *                         uses for this namedBean, this parameter is used
525     *                         to help determine when which listeners should be
526     *                         moved when the username is moved from one bean to
527     *                         another
528     * @param listenerRef  A textual reference for the listener, that can be
529     *                         presented to the user when a delete is called
530     */
531    void addPropertyChangeListener(@Nonnull String propertyName, @Nonnull PropertyChangeListener listener,
532            String name, String listenerRef);
533
534    void updateListenerRef(@Nonnull PropertyChangeListener l, String newName);
535
536    void vetoableChange(@Nonnull PropertyChangeEvent evt) throws PropertyVetoException;
537
538    /**
539     * Get the textual reference for the specific listener
540     *
541     * @param l the listener of interest
542     * @return the textual reference
543     */
544    @CheckReturnValue
545    String getListenerRef(@Nonnull PropertyChangeListener l);
546
547    /**
548     * Returns a list of all the listeners references
549     *
550     * @return a list of textual references
551     */
552    @CheckReturnValue
553    ArrayList<String> getListenerRefs();
554
555    /**
556     * Returns a list of all the listeners references for this object
557     * and all its children.
558     *
559     * @param list a list of textual references
560     */
561    @CheckReturnValue
562    void getListenerRefsIncludingChildren(List<String> list);
563
564    /**
565     * Number of current listeners. May return -1 if the information is not
566     * available for some reason.
567     *
568     * @return the number of listeners.
569     */
570    @CheckReturnValue
571    int getNumPropertyChangeListeners();
572
573    /**
574     * Get a list of all the property change listeners that are registered using
575     * a specific name
576     *
577     * @param name The name (either system or user) that the listener has
578     *                 registered as referencing this namedBean
579     * @return empty list if none
580     */
581    @CheckReturnValue
582    @Nonnull
583    PropertyChangeListener[] getPropertyChangeListenersByReference(@Nonnull String name);
584
585    /**
586     * Do something on every item in the sub tree of this item.
587     * @param r the action to do on all items.
588     */
589    default void forEntireTree(RunnableWithBase r) {
590        r.run(this);
591        for (int i=0; i < getChildCount(); i++) {
592            getChild(i).forEntireTree(r);
593        }
594    }
595
596    /**
597     * Do something on every item in the sub tree of this item.
598     * @param r the action to do on all items.
599     * @throws Exception if an exception occurs
600     */
601    default void forEntireTreeWithException(RunnableWithBaseThrowException r) throws Exception {
602        r.run(this);
603        for (int i=0; i < getChildCount(); i++) {
604            getChild(i).forEntireTreeWithException(r);
605        }
606    }
607
608    /**
609     * Does this item has the child b?
610     * @param  b the child
611     * @return true if this item has the child b, false otherwise
612     */
613    default boolean hasChild(@Nonnull Base b) {
614        for (int i=0; i < getChildCount(); i++) {
615            if (getChild(i) == b) return true;
616        }
617        return false;
618    }
619
620    /**
621     * Does this item exists in the tree?
622     * @return true if the item exists in the tree, false otherwise
623     */
624    default boolean existsInTree() {
625        Base parent = getParent();
626        return parent == null || (parent.hasChild(this) && parent.existsInTree());
627    }
628
629
630    interface RunnableWithBase {
631        void run(@Nonnull Base b);
632    }
633
634
635    interface RunnableWithBaseThrowException {
636        void run(@Nonnull Base b) throws Exception;
637    }
638
639
640
641    final String PRINT_LINE_NUMBERS_FORMAT = "%8d:  ";
642
643
644    static class PrintTreeSettings {
645        public boolean _printLineNumbers = false;
646        public boolean _printDisplayName = false;
647        public boolean _hideUserName = false;           // Used for tests
648        public boolean _printErrorHandling = true;
649        public boolean _printNotConnectedSockets = true;
650        public boolean _printLocalVariables = true;
651        public boolean _printSystemNames = false;
652    }
653
654}