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