001package jmri.jmrit.logix;
002
003import java.beans.PropertyChangeListener;
004import java.beans.PropertyChangeSupport;
005import java.util.ArrayList;
006import java.util.List;
007import jmri.Block;
008import jmri.InstanceManager;
009import jmri.NamedBean;
010import jmri.SignalHead;
011import jmri.SignalMast;
012import jmri.implementation.SignalSpeedMap;
013
014import javax.annotation.Nonnull;
015import javax.annotation.CheckForNull;
016import javax.annotation.OverridingMethodsMustInvokeSuper;
017
018/**
019 * A Portal is a boundary between two Blocks.
020 * <p>
021 * A Portal has Lists of the {@link OPath}s that connect through it.
022 * The direction of trains passing through the portal is managed from the
023 * BlockOrders of the Warrant the train is running under.
024 * The Portal fires a PropertyChangeEvent that a
025 * {@link jmri.jmrit.display.controlPanelEditor.PortalIcon} can listen
026 * for to set direction arrows for a given route.
027 *
028 * The Portal also supplies speed information from any signals set at its
029 * location that the Warrant passes on the Engineer.
030 *
031 * @author  Pete Cressman Copyright (C) 2009
032 */
033public class Portal {
034
035    private static final String NAME_CHANGE = "NameChange";
036    private static final String SIGNAL_CHANGE = "signalChange";
037    private static final String ENTRANCE = "entrance";
038    private final ArrayList<OPath> _fromPaths = new ArrayList<>();
039    private OBlock _fromBlock;
040    private NamedBean _fromSignal;          // may be either SignalHead or SignalMast
041    private float _fromSignalOffset;        // adjustment distance for speed change
042    private final ArrayList<OPath> _toPaths = new ArrayList<>();
043    private OBlock _toBlock;
044    private NamedBean _toSignal;            // may be either SignalHead or SignalMast
045    private float _toSignalOffset;          // adjustment distance for speed change
046    private String _name;
047    private int _state = UNKNOWN;
048    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
049
050    public static final int UNKNOWN = 0x01;
051    public static final int ENTER_TO_BLOCK = 0x02;
052    public static final int ENTER_FROM_BLOCK = 0x04;
053
054    public Portal(String uName) {
055        _name = uName;
056    }
057
058    /**
059     * Determine which list the Path belongs to and add it to that list.
060     *
061     * @param path OPath to add
062     * @return false if Path does not have a matching block for this Portal
063     */
064    public boolean addPath(@Nonnull OPath path) {
065        Block block = path.getBlock();
066        if (block == null) {
067            log.error("Path \"{}\" has no block.", path.getName());
068            return false;
069        }
070        if (!this.equals(path.getFromPortal())
071                && !this.equals(path.getToPortal())) {
072            return false;
073        }
074        if ((_fromBlock != null) && _fromBlock.equals(block)) {
075            return addPath(_fromPaths, path);
076        } else if ((_toBlock != null) && _toBlock.equals(block)) {
077            return addPath(_toPaths, path);
078        }
079        // portal is incomplete or path block not in this portal
080        return false;
081    }
082
083    /**
084     * Utility for both path lists.
085     * Checks for duplicate name.
086     */
087    private boolean addPath(@Nonnull List<OPath> list, @Nonnull OPath path) {
088        String pName = path.getName();
089        for (OPath p : list) {
090            if (p.equals(path)) {
091                if (pName.equals(p.getName())) {
092                    return true;    // OK, everything equal
093                } else {
094                    log.warn("Path \"{}\" is duplicate of path \"{}\" in Portal \"{}\" from block {}.", 
095                        path.getName(), p.getName(), _name, path.getBlock().getDisplayName());
096                    return false;
097                }
098            } else if (pName.equals(p.getName())) {
099                log.warn("Path \"{}\" is duplicate name for another path in Portal \"{}\" from block {}.",
100                    path.getName(), _name, path.getBlock().getDisplayName());
101                return false;
102            }
103        }
104        list.add(path);
105        return true;
106    }
107
108    /**
109     * Remove an OPath from this Portal.
110     * Checks both the _fromBlock list as the _toBlock list.
111     *
112     * @param path the OPath to remove
113     */
114    public void removePath(@Nonnull OPath path) {
115        Block block = path.getBlock();
116        if (block == null) {
117            log.error("Path \"{}\" has no block.", path.getName());
118            return;
119        }
120        log.debug("removePath: {}", this);
121        if (!this.equals(path.getFromPortal())
122                && !this.equals(path.getToPortal())) {
123            return;
124        }
125        if (_fromBlock != null && _fromBlock.equals(block)) {
126            _fromPaths.remove(path);
127        } else if (_toBlock != null && _toBlock.equals(block)) {
128            _toPaths.remove(path);
129        }
130//        pcs.firePropertyChange("RemovePath", block, path); not needed
131    }
132
133    /**
134     * Set userName of this Portal. Checks if name is available.
135     *
136     * @param newName name for path
137     * @return return error message, null if name change is OK
138     */
139    @CheckForNull
140    public String setName(@CheckForNull String newName) {
141        if (newName == null || newName.length() == 0) {
142            return null;
143        }
144        String oldName = _name;
145        if (newName.equals(oldName)) {
146            return null;
147        }
148        Portal p = InstanceManager.getDefault(PortalManager.class).getPortal(newName);
149        if (p != null) {
150            return Bundle.getMessage("DuplicatePortalName", newName, p.getDescription());
151        }
152        _name = newName;
153        InstanceManager.getDefault(WarrantManager.class).portalNameChange(oldName, newName);
154
155        // for some unknown reason, PortalManager firePropertyChange is not read by PortalTableModel
156        // so let OBlock do it
157        if (_toBlock != null) {
158            _toBlock.pseudoPropertyChange(NAME_CHANGE, oldName, this);
159        } else if (_fromBlock != null) {
160            _fromBlock.pseudoPropertyChange(NAME_CHANGE, oldName, this);
161        }
162        // CircuitBuilder PortalList needs this property change
163        pcs.firePropertyChange(NAME_CHANGE, oldName, newName);
164        return null;
165    }
166
167    public String getName() {
168        return _name;
169    }
170
171    /**
172     * Set this portal's toBlock. Remove this portal from old toBlock, if any.
173     * Add this portal in the new toBlock's list of portals.
174     *
175     * @param block to be the new toBlock
176     * @param changePaths if true, set block in paths. If false,
177     *                    verify that all toPaths are contained in the block.
178     * @return false if paths are not in the block
179     */
180    public boolean setToBlock(@CheckForNull OBlock block, boolean changePaths) {
181        if (((block != null) && block.equals(_toBlock)) || ((block == null) && (_toBlock == null))) {
182            return true;
183        }
184        if (changePaths) {
185            // Switch paths to new block. User will need to verify connections
186            for (OPath opa : _toPaths) {
187                opa.setBlock(block);
188            }
189        } else if (!verify(_toPaths, block)) {
190            return false;
191        }
192        log.debug("setToBlock: oldBlock= \"{}\" newBlock \"{}\".", getToBlockName(),
193              (block != null ? block.getDisplayName() : null));
194        OBlock oldBlock = _toBlock;
195        if (_toBlock != null) {
196            _toBlock.removePortal(this);    // may should not
197        }
198        _toBlock = block;
199        if (_toBlock != null) {
200            _toBlock.addPortal(this);
201        }
202        pcs.firePropertyChange("BlockChanged", oldBlock, _toBlock);
203        return true;
204    }
205
206    public OBlock getToBlock() {
207        return _toBlock;
208    }
209
210    // @CheckForNull needs further dev
211    public String getToBlockName() {
212        return (_toBlock != null ? _toBlock.getDisplayName() : null);
213    }
214
215    public List<OPath> getToPaths() {
216        return _toPaths;
217    }
218
219    /**
220     * Set this portal's fromBlock. Remove this portal from old fromBlock, if any.
221     * Add this portal in the new toBlock's list of portals.
222     *
223     * @param block to be the new fromBlock
224     * @param changePaths if true, set block in paths. If false,
225     *                    verify that all toPaths are contained in the block.
226     * @return false if paths are not in the block
227     */
228    public boolean setFromBlock(@CheckForNull OBlock block, boolean changePaths) {
229        if ((block != null && block.equals(_fromBlock)) || (block == null && _fromBlock == null)) {
230            return true;
231        }
232        if (changePaths) {
233            //Switch paths to new block.  User will need to verify connections
234            for (OPath fromPath : _fromPaths) {
235                fromPath.setBlock(block);
236            }
237        } else if (!verify(_fromPaths, block)) {
238            return false;
239        }
240        log.debug("setFromBlock: oldBlock= \"{}\" newBlock \"{}\".", getFromBlockName(),
241            (block != null ? block.getDisplayName() : null));
242        OBlock oldBlock = _fromBlock;
243        if (_fromBlock != null) {
244            _fromBlock.removePortal(this);
245        }
246        _fromBlock = block;
247        if (_fromBlock != null) {
248            _fromBlock.addPortal(this);
249        }
250        pcs.firePropertyChange("BlockChanged", oldBlock, _fromBlock);
251        return true;
252    }
253
254    public OBlock getFromBlock() {
255        return _fromBlock;
256    }
257
258    // @CheckForNull needs further dev
259    public String getFromBlockName() {
260        return (_fromBlock != null ? _fromBlock.getDisplayName() : null);
261    }
262
263    public List<OPath> getFromPaths() {
264        return _fromPaths;
265    }
266
267    /**
268     * Set a signal to protect an OBlock. Warrants look ahead for speed changes
269     * and change the train speed accordingly.
270     *
271     * @param signal either a SignalMast or a SignalHead. Set to null to remove (previous) signal from Portal
272     * @param length offset length in millimeters. This is additional
273     *               entrance space for the block. This distance added to or subtracted
274     *               from the calculation of the ramp distance when a warrant must slow
275     *               the train in response to the aspect or appearance of the signal.
276     * @param protectedBlock OBlock the signal protects
277     * @return true if signal is set
278     */
279    public boolean setProtectSignal(@CheckForNull NamedBean signal, float length, @CheckForNull OBlock protectedBlock) {
280        if (protectedBlock == null) {
281            return false;
282        }
283        boolean ret = false;
284        if ((_fromBlock != null) && _fromBlock.equals(protectedBlock)) {
285            _toSignal = signal;
286            _toSignalOffset = length;
287            log.debug("OPortal FromBlock Offset set to {} on signal {}", _toSignalOffset,
288                    (_toSignal != null ? _toSignal.getDisplayName() : "<removed>"));
289            ret = true;
290        }
291        if ((_toBlock != null) && _toBlock.equals(protectedBlock)) {
292            _fromSignal = signal;
293            _fromSignalOffset = length;
294            log.debug("OPortal ToBlock Offset set to {} on signal {}", _fromSignalOffset,
295                    (_fromSignal != null ? _fromSignal.getDisplayName() : "<removed>"));
296            ret = true;
297        }
298        if (ret) {
299            protectedBlock.pseudoPropertyChange(SIGNAL_CHANGE, false, true);
300            pcs.firePropertyChange(SIGNAL_CHANGE, false, true);
301            log.debug("setProtectSignal: \"{}\" for Block= {} at Portal {}",
302                    (signal != null ? signal.getDisplayName() : "null"), protectedBlock.getDisplayName(), _name);
303        }
304        return ret;
305    }
306
307    /**
308     * Get the signal (either a SignalMast or a SignalHead) protecting an OBlock.
309     *
310     * @param block is the direction of entry, i.e. the protected block
311     * @return signal protecting block, if block is protected, otherwise null.
312     */
313    @CheckForNull
314    public NamedBean getSignalProtectingBlock(@Nonnull OBlock block) {
315        if (block.equals(_toBlock)) {
316            return _fromSignal;
317        } else if (block.equals(_fromBlock)) {
318            return _toSignal;
319        }
320        return null;
321    }
322
323    /**
324     * Get the OBlock protected by a signal.
325     *
326     * @param signal is the signal, either a SignalMast or a SignalHead
327     * @return Protected OBlock, if it is protected, otherwise null.
328     */
329    @CheckForNull
330    public OBlock getProtectedBlock(@CheckForNull NamedBean signal) {
331        if (signal == null) {
332            return null;
333        }
334        if (signal.equals(_fromSignal)) {
335            return _toBlock;
336        } else if (signal.equals(_toSignal)) {
337            return _fromBlock;
338        }
339        return null;
340    }
341
342    public NamedBean getFromSignal() {
343        return _fromSignal;
344    }
345
346    public String getFromSignalName() {
347        return (_fromSignal != null ? _fromSignal.getDisplayName() : null);
348    }
349
350    public float getFromSignalOffset() {
351        return _fromSignalOffset; // it seems clear that this method should return what is asks
352    }
353
354    public NamedBean getToSignal() {
355        return _toSignal;
356    }
357
358    @CheckForNull
359    public String getToSignalName() {
360        return (_toSignal != null ? _toSignal.getDisplayName() : null);
361    }
362
363    public float getToSignalOffset() {
364        return _toSignalOffset;
365    }
366
367    public void deleteSignal(@Nonnull NamedBean signal) {
368        if (signal.equals(_toSignal)) {
369            _toSignal = null; // set the 2 _tos
370            _toSignalOffset = 0;
371            if (_fromBlock != null) {
372                _fromBlock.pseudoPropertyChange(SIGNAL_CHANGE, false, false);
373                pcs.firePropertyChange(SIGNAL_CHANGE, false, false);
374            }
375        } else if (signal.equals(_fromSignal)) {
376            _fromSignal = null; // set the 2 _froms
377            _fromSignalOffset = 0;
378            if (_toBlock != null) {
379                _toBlock.pseudoPropertyChange(SIGNAL_CHANGE, false, false);
380                pcs.firePropertyChange(SIGNAL_CHANGE, false, false);
381            }
382        }
383    }
384
385    @CheckForNull
386    public static NamedBean getSignal(String name) {
387        NamedBean signal = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(name);
388        if (signal == null) {
389            signal = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(name);
390        }
391        return signal;
392    }
393
394    /**
395     * Get the paths to the portal within the connected OBlock i.e. the paths in
396     * this (the param) block through the Portal.
397     *
398     * @param block OBlock
399     * @return null if portal does not connect to block
400     */
401    // @CheckForNull requires further dev
402    public List<OPath> getPathsWithinBlock(@CheckForNull OBlock block) {
403        if (block == null) {
404            return null;
405        }
406        if (block.equals(_fromBlock)) {
407            return _fromPaths;
408        } else if (block.equals(_toBlock)) {
409            return _toPaths;
410        }
411        return null;
412    }
413
414    /**
415     * Get the OBlock on the other side of the Portal from the given
416     * OBlock.
417     *
418     * @param block starting OBlock
419     * @return the opposite block
420     */
421    // @CheckForNull needs further dev
422    public OBlock getOpposingBlock(@Nonnull OBlock block) {
423        if (block.equals(_fromBlock)) {
424            return _toBlock;
425        } else if (block.equals(_toBlock)) {
426            return _fromBlock;
427        }
428        return null;
429    }
430
431    /**
432     * Get the paths from the portal in the next connected OBlock i.e. paths in
433     * the block on the other side of the portal from this (the param) block.
434     *
435     * @param block OBlock
436     * @return null if portal does not connect to block
437     */
438    // @CheckForNull requires further dev
439    public List<OPath> getPathsFromOpposingBlock(@Nonnull OBlock block) {
440        if (block.equals(_fromBlock)) {
441            return _toPaths;
442        } else if (block.equals(_toBlock)) {
443            return _fromPaths;
444        }
445        return null;
446    }
447
448    /**
449     * Call is from BlockOrder when setting the path.
450     *
451     * @param block OBlock
452     */
453    protected void setEntryState(@CheckForNull OBlock block) {
454        if (block == null) {
455            _state = UNKNOWN;
456        } else if (block.equals(_fromBlock)) {
457            setState(ENTER_FROM_BLOCK);
458        } else if (block.equals(_toBlock)) {
459            setState(ENTER_TO_BLOCK);
460        }
461    }
462
463    public void setState(int s) {
464        int old = _state;
465        _state = s;
466        pcs.firePropertyChange("Direction", old, _state);
467    }
468
469    public int getState() {
470        return _state;
471    }
472
473    @OverridingMethodsMustInvokeSuper
474    public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
475        pcs.addPropertyChangeListener(listener);
476    }
477
478    @OverridingMethodsMustInvokeSuper
479    public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
480        pcs.removePropertyChangeListener(listener);
481    }
482
483    /**
484     * Set the distance (plus or minus) in millimeters from the portal gap
485     * where the speed change indicated by the signal should be completed.
486     *
487     * @param block a protected OBlock
488     * @param distance length in millimeters, called Offset in the OBlock Signal Table
489     */
490    public void setEntranceSpaceForBlock(@Nonnull OBlock block, float distance) {
491        if (block.equals(_toBlock)) {
492            if (_fromSignal != null) {
493                _fromSignalOffset = distance;
494            }
495        } else if (block.equals(_fromBlock)) {
496            if (_toSignal != null) {
497                _toSignalOffset = distance;
498            }
499        }
500    }
501
502    /**
503     * Get the distance (plus or minus) in millimeters from the portal gap
504     * where the speed change indicated by the signal should be completed.
505     * Property is called Offset in the OBlock Signal Table.
506     *
507     * @param block a protected OBlock
508     * @return distance
509     */
510    public float getEntranceSpaceForBlock(@Nonnull OBlock block) {
511        if (block.equals(_toBlock)) {
512            if (_fromSignal != null) {
513                return _fromSignalOffset;
514            }
515        } else if (block.equals(_fromBlock)) {
516            if (_toSignal != null) {
517                return _toSignalOffset;
518            }
519        }
520        return 0;
521    }
522
523    /**
524     * Check signals, if any, for speed into/out of a given block. The signal that protects
525     * the "to" block is the signal facing the "from" Block, i.e. the "from"
526     * signal. (and vice-versa)
527     *
528     * @param block is the direction of entry, "from" block
529     * @param entrance true for EntranceSpeed, false for ExitSpeed
530     * @return permissible speed, null if no signal
531     */
532    public String getPermissibleSpeed(@Nonnull OBlock block, boolean entrance) {
533        String speed = null;
534        String blockName = block.getDisplayName();
535        if (block.equals(_toBlock)) {
536            if (_fromSignal != null) {
537                if (_fromSignal instanceof SignalHead) {
538                    speed = getPermissibleSignalSpeed((SignalHead) _fromSignal, entrance);
539                } else {
540                    speed = getPermissibleSignalSpeed((SignalMast) _fromSignal, entrance);
541                }
542            }
543        } else if (block.equals(_fromBlock)) {
544            if (_toSignal != null) {
545                if (_toSignal instanceof SignalHead) {
546                    speed = getPermissibleSignalSpeed((SignalHead) _toSignal, entrance);
547                } else {
548                    speed = getPermissibleSignalSpeed((SignalMast) _toSignal, entrance);
549                }
550            }
551        } else {
552            log.error("Block \"{}\" is not in Portal \"{}\".", blockName, _name);
553        }
554        if ( log.isDebugEnabled() && speed != null ) {
555            log.debug("Portal \"{}\" has {} speed= {} into \"{}\" from signal.",
556                _name, (entrance ? "ENTRANCE" : "EXIT"), speed, blockName);
557        }
558        // no signals, proceed at recorded speed
559        return speed;
560    }
561
562    /**
563     * Get entrance or exit speed set on signal head.
564     *
565     * @param signal signal head to query
566     * @param entrance true for EntranceSpeed, false for ExitSpeed
567     * @return permissible speed, Restricted if no speed set on signal
568     */
569    private static @Nonnull String getPermissibleSignalSpeed(@Nonnull SignalHead signal, boolean entrance) {
570        int appearance = signal.getAppearance();
571        String speed = InstanceManager.getDefault(SignalSpeedMap.class).
572            getAppearanceSpeed(signal.getAppearanceName(appearance));
573        // on head, speed is the same for entry and exit
574        if (speed == null) {
575            log.error("SignalHead \"{}\" has no {} speed specified for appearance \"{}\"! - Restricting Movement!",
576                    signal.getDisplayName(), (entrance ? ENTRANCE : "exit"), signal.getAppearanceName(appearance));
577            speed = "Restricted";
578        }
579        log.debug("SignalHead \"{}\" has {} speed notch= {} from appearance \"{}\"",
580                signal.getDisplayName(), (entrance ? ENTRANCE : "exit"), speed, signal.getAppearanceName(appearance));
581        return speed;
582    }
583
584    /**
585     * Get entrance or exit speed set on signal mast.
586     *
587     * @param signal signal mast to query
588     * @param entrance true for EntranceSpeed, false for ExitSpeed
589     * @return permissible speed, Restricted if no speed set on signal
590     */
591    private static @Nonnull String getPermissibleSignalSpeed(@Nonnull SignalMast signal, boolean entrance) {
592        String aspect = signal.getAspect(); 
593        String signalAspect = ( aspect == null ? "" : aspect );
594        String speed;
595        if (entrance) {
596            speed = InstanceManager.getDefault(SignalSpeedMap.class).
597                getAspectSpeed(signalAspect, signal.getSignalSystem());
598        } else {
599            speed = InstanceManager.getDefault(SignalSpeedMap.class).
600                getAspectExitSpeed(signalAspect, signal.getSignalSystem());
601        }
602        if (speed == null) {
603            log.error("SignalMast \"{}\" has no {} speed specified for aspect \"{}\"! - Restricting Movement!",
604                    signal.getDisplayName(), (entrance ? ENTRANCE : "exit"), aspect);
605            speed = "Restricted";
606        }
607        log.debug("SignalMast \"{}\" has {} speed notch= {} from aspect \"{}\"",
608                signal.getDisplayName(), (entrance ? ENTRANCE : "exit"), speed, aspect);
609        return speed;
610    }
611
612    /**
613     * Verify that each path has this potential block as its owning block.
614     * Block is a potential _toBlock and Paths are the current _toPaths 
615     * or
616     * Block is a potential _fromBlock and Paths are the current _fromPaths
617     */
618    private static boolean verify(@Nonnull List<OPath> paths, @CheckForNull OBlock block) {
619        if (block == null) {
620            return (paths.isEmpty());
621        }
622        String name = block.getSystemName();
623        for (OPath path : paths) {
624            Block blk = path.getBlock();
625            if (blk == null) {
626                log.error("Path \"{}\" belongs to null block. Cannot verify set block to \"{}\"",
627                        path.getName(), name);
628                return false;
629            }
630            String pathName = blk.getSystemName();
631            if (!pathName.equals(name)) {
632                log.warn("Path \"{}\" belongs to block \"{}\". Cannot verify set block to \"{}\"",
633                        path.getName(), pathName, name);
634                return false;
635            }
636        }
637        return true;
638    }
639
640    /**
641     * Check if path connects to Portal.
642     *
643     * @param path OPath to test
644     * @return true if valid
645     */
646    public boolean isValidPath(@Nonnull OPath path) {
647        String name = path.getName();
648        for (OPath toPath : _toPaths) {
649            if (toPath.getName().equals(name)) {
650                return true;
651            }
652        }
653        for (OPath fromPath : _fromPaths) {
654            if (fromPath.getName().equals(name)) {
655                return true;
656            }
657        }
658        return false;
659    }
660
661    /**
662     * Check portal has both blocks and they are different blocks.
663     *
664     * @return true if valid
665     */
666    public boolean isValid() {
667        if (_toBlock == null || _fromBlock==null) {
668            return false;
669        }
670        return (!_toBlock.equals(_fromBlock));
671    }
672
673    @OverridingMethodsMustInvokeSuper
674    public boolean dispose() {
675        if (!InstanceManager.getDefault(WarrantManager.class).okToRemovePortal(this)) {
676            return false;
677        }
678        if (_toBlock != null) {
679            _toBlock.removePortal(this);
680        }
681        if (_fromBlock != null) {
682            _fromBlock.removePortal(this);
683        }
684        pcs.firePropertyChange("portalDelete", true, false);
685        PropertyChangeListener[] listeners = pcs.getPropertyChangeListeners();
686        for (PropertyChangeListener l : listeners) {
687            pcs.removePropertyChangeListener(l);
688        }
689        return true;
690    }
691
692    public String getDescription() {
693        return Bundle.getMessage("PortalDescription",
694                _name, getFromBlockName(), getToBlockName());
695    }
696
697    @Override
698    @Nonnull
699    public String toString() {
700        StringBuilder sb = new StringBuilder("Portal \"");
701        sb.append(_name);
702        sb.append("\" from block \"");
703        sb.append(getFromBlockName());
704        sb.append("\" to block \"");
705        sb.append(getToBlockName());
706        sb.append("\"");
707        return sb.toString();
708    }
709
710    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Portal.class);
711
712}