001package jmri.jmrit.logixng.implementation;
002
003import java.beans.*;
004import java.io.PrintWriter;
005import java.util.ArrayList;
006import java.util.List;
007import java.util.Locale;
008import java.util.Map;
009
010import javax.annotation.Nonnull;
011
012import jmri.*;
013import jmri.jmrit.logixng.*;
014import jmri.jmrit.logixng.SymbolTable.VariableData;
015import jmri.jmrit.logixng.implementation.swing.ErrorHandlingDialog;
016import jmri.jmrit.logixng.implementation.swing.ErrorHandlingDialog_MultiLine;
017import jmri.util.LoggingUtil;
018import jmri.util.ThreadingUtil;
019
020import org.apache.commons.lang3.mutable.MutableInt;
021import org.slf4j.Logger;
022
023/**
024 * The abstract class that is the base class for all LogixNG classes that
025 * implements the Base interface.
026 *
027 * @author Daniel Bergqvist 2020
028 */
029public abstract class AbstractMaleSocket implements MaleSocket {
030
031    private final Base _object;
032    private boolean _locked = false;
033    private boolean _system = false;
034    protected final List<VariableData> _localVariables = new ArrayList<>();
035    private final BaseManager<? extends NamedBean> _manager;
036    private Base _parent;
037    private ErrorHandlingType _errorHandlingType = ErrorHandlingType.Default;
038    private boolean _catchAbortExecution;
039    private boolean _listen = true;     // By default, actions and expressions listen
040
041    public AbstractMaleSocket(BaseManager<? extends NamedBean> manager, Base object) {
042        _manager = manager;
043        _object = object;
044    }
045
046    /** {@inheritDoc} */
047    @Override
048    public final Base getObject() {
049        return _object;
050    }
051
052    /** {@inheritDoc} */
053    @Override
054    public final Base getRoot() {
055        return _object.getRoot();
056    }
057
058    /** {@inheritDoc} */
059    @Override
060    public boolean isLocked() {
061        if (_object instanceof MaleSocket) {
062            return ((MaleSocket)_object).isLocked();
063        }
064        return _locked;
065    }
066
067    /** {@inheritDoc} */
068    @Override
069    public void setLocked(boolean locked) {
070        if (_object instanceof MaleSocket) {
071            ((MaleSocket)_object).setLocked(locked);
072        }
073        _locked = locked;
074    }
075
076    /** {@inheritDoc} */
077    @Override
078    public boolean isSystem() {
079        if (_object instanceof MaleSocket) {
080            return ((MaleSocket)_object).isSystem();
081        }
082        return _system;
083    }
084
085    /** {@inheritDoc} */
086    @Override
087    public void setSystem(boolean system) {
088        if (_object instanceof MaleSocket) {
089            ((MaleSocket)_object).setSystem(system);
090        }
091        _system = system;
092    }
093
094    /** {@inheritDoc} */
095    @Override
096    public final Category getCategory() {
097        return _object.getCategory();
098    }
099
100    @Override
101    public final FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
102        return _object.getChild(index);
103    }
104
105    @Override
106    public final int getChildCount() {
107        return _object.getChildCount();
108    }
109
110    @Override
111    public final String getShortDescription(Locale locale) {
112        return _object.getShortDescription(locale);
113    }
114
115    @Override
116    public final String getLongDescription(Locale locale) {
117        String s = _object.getLongDescription(locale);
118        if (!_listen) {
119            s += " ::: " + Base.getNoListenString();
120        }
121        return s;
122    }
123
124    @Override
125    public final String getUserName() {
126        return _object.getUserName();
127    }
128
129    @Override
130    public final void setUserName(String s) throws NamedBean.BadUserNameException {
131        _object.setUserName(s);
132    }
133
134    @Override
135    public final String getSystemName() {
136        return _object.getSystemName();
137    }
138
139    @Override
140    public final void addPropertyChangeListener(PropertyChangeListener l, String name, String listenerRef) {
141        _object.addPropertyChangeListener(l, name, listenerRef);
142    }
143
144    @Override
145    public final void addPropertyChangeListener(String propertyName, PropertyChangeListener l, String name, String listenerRef) {
146        _object.addPropertyChangeListener(propertyName, l, name, listenerRef);
147    }
148
149    @Override
150    public final void addPropertyChangeListener(PropertyChangeListener l) {
151        _object.addPropertyChangeListener(l);
152    }
153
154    @Override
155    public final void addPropertyChangeListener(String propertyName, PropertyChangeListener l) {
156        _object.addPropertyChangeListener(propertyName, l);
157    }
158
159    @Override
160    public final void removePropertyChangeListener(PropertyChangeListener l) {
161        _object.removePropertyChangeListener(l);
162    }
163
164    @Override
165    public final void removePropertyChangeListener(String propertyName, PropertyChangeListener l) {
166        _object.removePropertyChangeListener(propertyName, l);
167    }
168
169    @Override
170    public final void updateListenerRef(PropertyChangeListener l, String newName) {
171        _object.updateListenerRef(l, newName);
172    }
173
174    @Override
175    public final void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
176        _object.vetoableChange(evt);
177    }
178
179    @Override
180    public final String getListenerRef(PropertyChangeListener l) {
181        return _object.getListenerRef(l);
182    }
183
184    @Override
185    public final ArrayList<String> getListenerRefs() {
186        return _object.getListenerRefs();
187    }
188
189    @Override
190    public final int getNumPropertyChangeListeners() {
191        return _object.getNumPropertyChangeListeners();
192    }
193
194    @Override
195    public final synchronized PropertyChangeListener[] getPropertyChangeListeners() {
196        return _object.getPropertyChangeListeners();
197    }
198
199    @Override
200    public final synchronized PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
201        return _object.getPropertyChangeListeners(propertyName);
202    }
203
204    @Override
205    public final PropertyChangeListener[] getPropertyChangeListenersByReference(String name) {
206        return _object.getPropertyChangeListenersByReference(name);
207    }
208
209    @Override
210    public String getComment() {
211        return _object.getComment();
212    }
213
214    @Override
215    public void setComment(String comment) {
216        _object.setComment(comment);
217    }
218
219    @Override
220    public boolean getListen() {
221        if (getObject() instanceof MaleSocket) {
222            return ((MaleSocket)getObject()).getListen();
223        }
224        return _listen;
225    }
226
227    @Override
228    public void setListen(boolean listen)
229    {
230        if (getObject() instanceof MaleSocket) {
231            ((MaleSocket)getObject()).setListen(listen);
232        }
233        _listen = listen;
234    }
235
236    @Override
237    public boolean getCatchAbortExecution() {
238        return _catchAbortExecution;
239    }
240
241    @Override
242    public void setCatchAbortExecution(boolean catchAbortExecution)
243    {
244        _catchAbortExecution = catchAbortExecution;
245    }
246
247    @Override
248    public void addLocalVariable(
249            String name,
250            SymbolTable.InitialValueType initialValueType,
251            String initialValueData) {
252
253        if (getObject() instanceof MaleSocket) {
254            ((MaleSocket)getObject()).addLocalVariable(name, initialValueType, initialValueData);
255        } else {
256            _localVariables.add(new VariableData(name, initialValueType, initialValueData));
257        }
258    }
259
260    @Override
261    public void addLocalVariable(VariableData variableData) {
262
263        if (getObject() instanceof MaleSocket) {
264            ((MaleSocket)getObject()).addLocalVariable(variableData);
265        } else {
266            _localVariables.add(variableData);
267        }
268    }
269
270    @Override
271    public void clearLocalVariables() {
272        if (getObject() instanceof MaleSocket) {
273            ((MaleSocket)getObject()).clearLocalVariables();
274        } else {
275            _localVariables.clear();
276        }
277    }
278
279    @Override
280    public List<VariableData> getLocalVariables() {
281        if (getObject() instanceof MaleSocket) {
282            return ((MaleSocket)getObject()).getLocalVariables();
283        } else {
284            return _localVariables;
285        }
286    }
287
288    @Override
289    public Base getParent() {
290        return _parent;
291    }
292
293    @Override
294    public void setParent(Base parent) {
295        _parent = parent;
296    }
297
298    @Override
299    public final ConditionalNG getConditionalNG() {
300        if (getParent() == null) return null;
301        return getParent().getConditionalNG();
302    }
303
304    @Override
305    public final LogixNG getLogixNG() {
306        if (getParent() == null) return null;
307        return getParent().getLogixNG();
308    }
309
310    /** {@inheritDoc} */
311    @Override
312    public final boolean setParentForAllChildren(List<String> errors) {
313        boolean result = true;
314        for (int i=0; i < getChildCount(); i++) {
315            FemaleSocket femaleSocket = getChild(i);
316            if (femaleSocket.isConnected()) {
317                MaleSocket connectedSocket = femaleSocket.getConnectedSocket();
318                connectedSocket.setParent(femaleSocket);
319                result = result && connectedSocket.setParentForAllChildren(errors);
320            }
321        }
322        return result;
323    }
324
325    /**
326     * Register listeners if this object needs that.
327     * <P>
328     * Important: This method may be called more than once. Methods overriding
329     * this method must ensure that listeners are not registered more than once.
330     */
331    abstract protected void registerListenersForThisClass();
332
333    /**
334     * Unregister listeners if this object needs that.
335     * <P>
336     * Important: This method may be called more than once. Methods overriding
337     * this method must ensure that listeners are not unregistered more than once.
338     */
339    abstract protected void unregisterListenersForThisClass();
340
341    /** {@inheritDoc} */
342    @Override
343    public final void registerListeners() {
344        if (getObject() instanceof MaleSocket) {
345            getObject().registerListeners();
346        } else {
347            if (_listen) {
348                registerListenersForThisClass();
349                for (int i=0; i < getChildCount(); i++) {
350                    getChild(i).registerListeners();
351                }
352            }
353        }
354    }
355
356    /** {@inheritDoc} */
357    @Override
358    public final void unregisterListeners() {
359        if (getObject() instanceof MaleSocket) {
360            getObject().unregisterListeners();
361        } else {
362            unregisterListenersForThisClass();
363            for (int i=0; i < getChildCount(); i++) {
364                getChild(i).unregisterListeners();
365            }
366        }
367    }
368
369    /** {@inheritDoc} */
370    @Override
371    public final boolean isActive() {
372        return isEnabled() && ((getParent() == null) || getParent().isActive());
373    }
374
375    /**
376     * Print this row.
377     * If getObject() doesn't return an AbstractMaleSocket, print this row.
378     * <P>
379     * If a male socket that extends AbstractMaleSocket wants to print
380     * something here, it needs to override this method.
381     * <P>
382     * The reason this method doesn't print if getObject() returns an
383     * AbstractMaleSocket is to protect so it doesn't print itself twice if
384     * it's embedding an other AbstractMaleSocket. An example of this is the
385     * AbstractDebuggerMaleSocket which embeds other male sockets.
386     *
387     * @param settings settings for what to print
388     * @param locale The locale to be used
389     * @param writer the stream to print the tree to
390     * @param currentIndent the current indentation
391     * @param lineNumber the line number
392     */
393    protected void printTreeRow(
394            PrintTreeSettings settings,
395            Locale locale,
396            PrintWriter writer,
397            String currentIndent,
398            MutableInt lineNumber) {
399
400        if (!(getObject() instanceof AbstractMaleSocket)) {
401            String comment = getComment();
402            if (comment != null) {
403                comment = comment.replaceAll("\\r\\n", "\\n");
404                comment = comment.replaceAll("\\r", "\\n");
405                for (String s : comment.split("\\n", 0)) {
406                    if (settings._printLineNumbers) {
407                        writer.append(String.format(PRINT_LINE_NUMBERS_FORMAT, lineNumber.addAndGet(1)));
408                    }
409                    writer.append(currentIndent);
410                    writer.append("// ");
411                    writer.append(s);
412                    writer.println();
413                }
414            }
415            if (settings._printLineNumbers) {
416                writer.append(String.format(PRINT_LINE_NUMBERS_FORMAT, lineNumber.addAndGet(1)));
417            }
418            writer.append(currentIndent);
419            writer.append(getLongDescription(locale));
420            if (settings._printSystemNames) {
421                writer.append(" ::: ");
422                writer.append(this.getSystemName());
423            }
424            if (settings._printDisplayName) {
425                writer.append(" ::: ");
426                writer.append(Bundle.getMessage("LabelDisplayName"));
427                writer.append(" ");
428                writer.append(((NamedBean)this).getDisplayName(
429                        NamedBean.DisplayOptions.USERNAME_SYSTEMNAME));
430            } else if (!settings._hideUserName && getUserName() != null) {
431                writer.append(" ::: ");
432                writer.append(Bundle.getMessage("LabelUserName"));
433                writer.append(" ");
434                writer.append(getUserName());
435            }
436
437            if (settings._printErrorHandling) {
438                writer.append(" ::: ");
439                writer.append(getErrorHandlingType().toString());
440            }
441            if (!isEnabled()) {
442                writer.append(" ::: ");
443                writer.append(Bundle.getMessage("AbstractMaleSocket_Disabled"));
444            }
445            if (isLocked()) {
446                writer.append(" ::: ");
447                writer.append(Bundle.getMessage("AbstractMaleSocket_Locked"));
448            }
449            if (isSystem()) {
450                writer.append(" ::: ");
451                writer.append(Bundle.getMessage("AbstractMaleSocket_System"));
452            }
453            writer.println();
454        }
455    }
456
457    protected void printLocalVariable(
458            PrintTreeSettings settings,
459            Locale locale,
460            PrintWriter writer,
461            String currentIndent,
462            MutableInt lineNumber,
463            VariableData localVariable) {
464
465        if (settings._printLineNumbers) {
466            writer.append(String.format(PRINT_LINE_NUMBERS_FORMAT, lineNumber.addAndGet(1)));
467        }
468        writer.append(currentIndent);
469        writer.append("   ::: ");
470        writer.append(Bundle.getMessage(
471                locale,
472                "PrintLocalVariable",
473                localVariable._name,
474                localVariable._initialValueType.toString(),
475                localVariable._initialValueData));
476        writer.println();
477    }
478
479    /** {@inheritDoc} */
480    @Override
481    public void printTree(
482            PrintTreeSettings settings,
483            PrintWriter writer,
484            String indent,
485            MutableInt lineNumber) {
486        printTree(settings, Locale.getDefault(), writer, indent, "", lineNumber);
487    }
488
489    /** {@inheritDoc} */
490    @Override
491    public void printTree(
492            PrintTreeSettings settings,
493            Locale locale,
494            PrintWriter writer,
495            String indent,
496            MutableInt lineNumber) {
497        printTree(settings, locale, writer, indent, "", lineNumber);
498    }
499
500    /** {@inheritDoc} */
501    @Override
502    public void printTree(
503            PrintTreeSettings settings,
504            Locale locale,
505            PrintWriter writer,
506            String indent,
507            String currentIndent,
508            MutableInt lineNumber) {
509
510        printTreeRow(settings, locale, writer, currentIndent, lineNumber);
511
512        if (settings._printLocalVariables) {
513            for (VariableData localVariable : _localVariables) {
514                printLocalVariable(settings, locale, writer, currentIndent, lineNumber, localVariable);
515            }
516        }
517
518        if (getObject() instanceof MaleSocket) {
519            getObject().printTree(settings, locale, writer, indent, currentIndent, lineNumber);
520        } else {
521            for (int i=0; i < getChildCount(); i++) {
522                getChild(i).printTree(settings, locale, writer, indent, currentIndent+indent, lineNumber);
523            }
524        }
525    }
526
527    /** {@inheritDoc} */
528    @Override
529    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value="SLF4J_SIGN_ONLY_FORMAT",
530                                                        justification="Specific log message format")
531    public void getUsageTree(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
532        if (!(getObject() instanceof AbstractMaleSocket)) {
533            log.debug("*@ {} :: {}", level, this.getLongDescription());
534            _object.getUsageDetail(level, bean, report, cdl);
535        }
536
537        if (getObject() instanceof MaleSocket) {
538            getObject().getUsageTree(level, bean, report, cdl);
539        } else {
540            level++;
541            for (int i=0; i < getChildCount(); i++) {
542                getChild(i).getUsageTree(level, bean, report, cdl);
543            }
544        }
545    }
546
547    /** {@inheritDoc} */
548    @Override
549    public void getUsageDetail(int level, NamedBean bean, List<jmri.NamedBeanUsageReport> report, NamedBean cdl) {
550    }
551
552    @Override
553    public BaseManager<? extends NamedBean> getManager() {
554        return _manager;
555    }
556
557    @Override
558    public final Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames)
559            throws JmriException {
560
561        MaleSocket maleSocket = (MaleSocket)getObject().getDeepCopy(systemNames, userNames);
562
563        maleSocket.setComment(this.getComment());
564        if (maleSocket.getDebugConfig() != null) {
565            maleSocket.setDebugConfig(maleSocket.getDebugConfig().getCopy());
566        }
567        maleSocket.setEnabledFlag(isEnabled());
568        maleSocket.setListen(getListen());
569        maleSocket.setErrorHandlingType(getErrorHandlingType());
570        maleSocket.setLocked(isLocked());
571        maleSocket.setSystem(false);    // If a system item is copied, the new item is not treated as system
572        maleSocket.setCatchAbortExecution(getCatchAbortExecution());
573
574        for (VariableData data : _localVariables) {
575            maleSocket.addLocalVariable(data._name, data._initialValueType, data._initialValueData);
576        }
577
578        return maleSocket;
579    }
580
581    @Override
582    public final Base deepCopyChildren(Base original, Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
583        getObject().deepCopyChildren(original, systemNames, userNames);
584        return this;
585    }
586
587    /**
588     * Disposes this object.
589     * This must remove _all_ connections!
590     */
591    abstract protected void disposeMe();
592
593    /** {@inheritDoc} */
594    @Override
595    public final void dispose() {
596        for (int i=0; i < getChildCount(); i++) {
597            getChild(i).dispose();
598        }
599        disposeMe();
600    }
601
602    @Override
603    public ErrorHandlingType getErrorHandlingType() {
604        if (getObject() instanceof MaleSocket) {
605            return ((MaleSocket)getObject()).getErrorHandlingType();
606        } else {
607            return _errorHandlingType;
608        }
609    }
610
611    @Override
612    public void setErrorHandlingType(ErrorHandlingType errorHandlingType)
613    {
614        if (getObject() instanceof MaleSocket) {
615            ((MaleSocket)getObject()).setErrorHandlingType(errorHandlingType);
616        } else {
617            _errorHandlingType = errorHandlingType;
618        }
619    }
620
621    @Override
622    public void handleError(Base item, String message, JmriException e, Logger log) throws JmriException {
623
624        // Always throw AbortConditionalNGExecutionException exceptions
625        if (!_catchAbortExecution && (e instanceof AbortConditionalNGExecutionException)) throw e;
626
627        ErrorHandlingType errorHandlingType = getErrorHandlingType();
628        if (errorHandlingType == ErrorHandlingType.Default) {
629            errorHandlingType = InstanceManager.getDefault(LogixNGPreferences.class)
630                    .getErrorHandlingType();
631        }
632
633        switch (errorHandlingType) {
634            case ShowDialogBox:
635                boolean abort = ThreadingUtil.runOnGUIwithReturn(() -> {
636                    ErrorHandlingDialog dialog = new ErrorHandlingDialog();
637                    return dialog.showDialog(item, message);
638                });
639                if (abort) throw new AbortConditionalNGExecutionException(this, e);
640                break;
641
642            case LogError:
643                log.error("item {}, {} thrown an exception: {}", item.toString(), getObject().toString(), e, e);
644                break;
645
646            case LogErrorOnce:
647                LoggingUtil.warnOnce(log, "item {}, {} thrown an exception: {}", item.toString(), getObject().toString(), e, e);
648                break;
649
650            case ThrowException:
651                throw e;
652
653            case AbortExecution:
654                log.error("item {}, {} thrown an exception: {}", item.toString(), getObject().toString(), e, e);
655                throw new AbortConditionalNGExecutionException(this, e);
656
657            default:
658                throw e;
659        }
660    }
661
662    @Override
663    public void handleError(
664            Base item,
665            String message,
666            List<String> messageList,
667            JmriException e,
668            Logger log)
669            throws JmriException {
670
671        ErrorHandlingType errorHandlingType = getErrorHandlingType();
672        if (errorHandlingType == ErrorHandlingType.Default) {
673            errorHandlingType = InstanceManager.getDefault(LogixNGPreferences.class)
674                    .getErrorHandlingType();
675        }
676
677        switch (errorHandlingType) {
678            case ShowDialogBox:
679                boolean abort = ThreadingUtil.runOnGUIwithReturn(() -> {
680                    ErrorHandlingDialog_MultiLine dialog = new ErrorHandlingDialog_MultiLine();
681                    return dialog.showDialog(item, message, messageList);
682                });
683                if (abort) throw new AbortConditionalNGExecutionException(this, e);
684                break;
685
686            case LogError:
687                log.error("item {}, {} thrown an exception: {}", item.toString(), getObject().toString(), e, e);
688                break;
689
690            case LogErrorOnce:
691                LoggingUtil.warnOnce(log, "item {}, {} thrown an exception: {}", item.toString(), getObject().toString(), e, e);
692                break;
693
694            case ThrowException:
695                throw e;
696
697            case AbortExecution:
698                log.error("item {}, {} thrown an exception: {}", item.toString(), getObject().toString(), e, e);
699                throw new AbortConditionalNGExecutionException(this, e);
700
701            default:
702                throw e;
703        }
704    }
705
706    @Override
707    public void handleError(Base item, String message, RuntimeException e, Logger log) throws JmriException {
708
709        ErrorHandlingType errorHandlingType = getErrorHandlingType();
710        if (errorHandlingType == ErrorHandlingType.Default) {
711            errorHandlingType = InstanceManager.getDefault(LogixNGPreferences.class)
712                    .getErrorHandlingType();
713        }
714
715        switch (errorHandlingType) {
716            case ShowDialogBox:
717                boolean abort = ThreadingUtil.runOnGUIwithReturn(() -> {
718                    ErrorHandlingDialog dialog = new ErrorHandlingDialog();
719                    return dialog.showDialog(item, message);
720                });
721                if (abort) throw new AbortConditionalNGExecutionException(this, e);
722                break;
723
724            case LogError:
725//                e.printStackTrace();
726                log.error("item {}, {} thrown an exception: {}", item.toString(), getObject().toString(), e, e);
727                break;
728
729            case LogErrorOnce:
730//                e.printStackTrace();
731                LoggingUtil.warnOnce(log, "item {}, {} thrown an exception: {}", item.toString(), getObject().toString(), e, e);
732                break;
733
734            case ThrowException:
735                throw e;
736
737            case AbortExecution:
738                throw new AbortConditionalNGExecutionException(this, e);
739
740            default:
741                throw e;
742        }
743    }
744
745    /** {@inheritDoc} */
746    @Override
747    public void getListenerRefsIncludingChildren(List<String> list) {
748        list.addAll(getListenerRefs());
749        for (int i=0; i < getChildCount(); i++) {
750            getChild(i).getListenerRefsIncludingChildren(list);
751        }
752    }
753
754    @Override
755    public boolean hasChild(@Nonnull Base b) {
756        return getObject() == b;
757    }
758
759    /** {@inheritDoc} */
760    @Override
761    public String toString() {
762        return getObject().toString();
763    }
764
765    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractMaleSocket.class);
766}