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