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