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