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