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