001package jmri.jmrit.catalog;
002
003import java.beans.PropertyChangeListener;
004import java.beans.PropertyChangeSupport;
005import java.util.ArrayList;
006import java.util.HashMap;
007import javax.annotation.CheckReturnValue;
008import javax.annotation.Nonnull;
009import javax.swing.tree.DefaultTreeModel;
010import jmri.CatalogTree;
011import jmri.CatalogTreeNode;
012import jmri.NamedBean;
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016/**
017 * TreeModel used by CatalogPanel to create a tree of resources.
018 *
019 * @author Pete Cressman Copyright 2009
020 */
021public abstract class AbstractCatalogTree extends DefaultTreeModel implements CatalogTree {
022
023    // force changes through setUserName() to ensure rules are applied
024    // as a side effect require reads through getUserName()
025    private String mUserName;
026    // final so does not need to be private to protect against changes
027    protected final String mSystemName;
028
029    public AbstractCatalogTree(String sysname, String username) {
030        super(new CatalogTreeNode(username));
031        mSystemName = sysname;
032        // use this form to prevent subclass from overriding setUserName
033        // during construction
034        AbstractCatalogTree.this.setUserName(username);
035    }
036
037    public AbstractCatalogTree(String sysname) {
038        this(sysname, "root");
039    }
040
041    @CheckReturnValue
042    @Override
043    public String getBeanType() {
044        return Bundle.getMessage("BeanNameCatalog");
045    }
046
047    /**
048     * Recursively add nodes to the tree
049     *
050     * @param pName   Name of the resource to be scanned; this is only used for
051     *                the human-readable tree
052     * @param pPath   Path to this resource, including the pName part
053     * @param pParent Node for the parent of the resource to be scanned, e.g.
054     *                where in the tree to insert it.
055     */
056    @Override
057    public abstract void insertNodes(String pName, String pPath, CatalogTreeNode pParent);
058
059    /**
060     * Starting point to recursively add nodes to the tree by scanning a file
061     * directory
062     *
063     * @param pathToRoot Path to Directory to be scanned
064     */
065    @Override
066    public void insertNodes(String pathToRoot) {
067        // root is a field in the super class, so use r for root
068        CatalogTreeNode r = getRoot();
069        log.debug("insertNodes: rootName= {}, pathToRoot= {}", r.getUserObject(), pathToRoot);
070        insertNodes((String) r.getUserObject(), pathToRoot, r);
071    }
072
073    /**
074     * Get the root element of the tree as a jmri.CatalogTreeNode object
075     * (Instead of Object, as parent swing.TreeModel provides).
076     *
077     * @return the root element
078     */
079    @CheckReturnValue
080    @Override
081    public CatalogTreeNode getRoot() {
082        return (CatalogTreeNode) super.getRoot();
083    }
084
085    /*
086     * NamedBean implementation (Copied from AbstractNamedBean) *********
087     */
088    /**
089     * Get associated comment text.
090     */
091    @CheckReturnValue
092    @Override
093    public String getComment() {
094        return this.comment;
095    }
096
097    /**
098     * Set associated comment text.
099     * <p>
100     * Comments can be any valid text.
101     *
102     * @param comment Null means no comment associated.
103     */
104    @Override
105    public void setComment(String comment) {
106        String old = this.comment;
107        this.comment = comment;
108        firePropertyChange("Comment", old, comment);
109    }
110    private String comment;
111
112    // implementing classes will typically have a function/listener to get
113    // updates from the layout, which will then call
114    //  public void firePropertyChange(String propertyName,
115    //             Object oldValue,
116    //      Object newValue)
117    // _once_ if anything has changed state
118    // since we can't do a "super(this)" in the ctor to inherit from PropertyChangeSupport, we'll
119    // reflect to it
120    java.beans.PropertyChangeSupport pcs = new PropertyChangeSupport(this);
121
122    @Override
123    public synchronized void addPropertyChangeListener(PropertyChangeListener l) {
124        pcs.addPropertyChangeListener(l);
125    }
126
127    @Override
128    public synchronized void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
129        pcs.addPropertyChangeListener(propertyName, listener);
130    }
131
132    @Override
133    public synchronized void removePropertyChangeListener(PropertyChangeListener l) {
134        pcs.removePropertyChangeListener(l);
135    }
136
137    @Override
138    public synchronized void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
139        pcs.removePropertyChangeListener(propertyName, listener);
140    }
141
142    @Override
143    public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
144        return pcs.getPropertyChangeListeners();
145    }
146
147    @Override
148    public synchronized PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
149        return pcs.getPropertyChangeListeners(propertyName);
150    }
151
152    /**
153     * Number of current listeners. May return -1 if the information is not
154     * available for some reason.
155     */
156    @CheckReturnValue
157    @Override
158    public synchronized int getNumPropertyChangeListeners() {
159        return pcs.getPropertyChangeListeners().length;
160    }
161
162    HashMap<PropertyChangeListener, String> register = new HashMap<>();
163    HashMap<PropertyChangeListener, String> listenerRefs = new HashMap<>();
164
165    @Override
166    public synchronized void addPropertyChangeListener(PropertyChangeListener l, String beanRef, String listenerRef) {
167        pcs.addPropertyChangeListener(l);
168        if (beanRef != null) {
169            register.put(l, beanRef);
170        }
171        if (listenerRef != null) {
172            listenerRefs.put(l, listenerRef);
173        }
174    }
175
176    @Override
177    public synchronized void addPropertyChangeListener(String propertyName, PropertyChangeListener listener, String beanRef, String listenerRef) {
178        pcs.addPropertyChangeListener(propertyName, listener);
179        if (beanRef != null) {
180            register.put(listener, beanRef);
181        }
182        if (listenerRef != null) {
183            listenerRefs.put(listener, listenerRef);
184        }
185    }
186
187    @CheckReturnValue
188    @Override
189    public synchronized PropertyChangeListener[] getPropertyChangeListenersByReference(String name) {
190        ArrayList<PropertyChangeListener> list = new ArrayList<>();
191        register.keySet().stream().filter((l) -> (register.get(l).equals(name))).forEachOrdered((l) -> {
192            list.add(l);
193        });
194        return list.toArray(new PropertyChangeListener[list.size()]);
195    }
196
197    /* This allows a meaning full list of places where the bean is in use!*/
198    @CheckReturnValue
199    @Override
200    public synchronized ArrayList<String> getListenerRefs() {
201        return new ArrayList<>(listenerRefs.values());
202    }
203
204    @Override
205    public synchronized void updateListenerRef(PropertyChangeListener l, String newName) {
206        if (listenerRefs.containsKey(l)) {
207            listenerRefs.put(l, newName);
208        }
209    }
210
211    @CheckReturnValue
212    @Override
213    public synchronized String getListenerRef(java.beans.PropertyChangeListener l) {
214        return listenerRefs.get(l);
215    }
216
217    @CheckReturnValue
218    @Override
219    public String getSystemName() {
220        return mSystemName;
221    }
222
223    @CheckReturnValue
224    @Override
225    public String getUserName() {
226        return mUserName;
227    }
228
229    @Override
230    public void setUserName(String s) {
231        String old = mUserName;
232        mUserName = NamedBean.normalizeUserName(s);
233        firePropertyChange("UserName", old, mUserName);
234    }
235
236    protected void firePropertyChange(String p, Object old, Object n) {
237        pcs.firePropertyChange(p, old, n);
238    }
239
240    @Override
241    public void dispose() {
242        pcs = null;
243    }
244
245    @Override
246    @CheckReturnValue
247    public int getState() {
248        return 0;
249    }
250
251    @Override
252    @CheckReturnValue
253    public String describeState(int state) {
254        switch (state) {
255            case UNKNOWN:
256                return Bundle.getMessage("BeanStateUnknowng");
257            case INCONSISTENT:
258                return Bundle.getMessage("BeanStateInconsistent");
259            default:
260                return Bundle.getMessage("BeanStateUnexpected", state);
261        }
262    }
263
264    @Override
265    public void setState(int s) throws jmri.JmriException {
266    }
267
268    public void addDeleteLock(jmri.NamedBean lock) {
269    }
270
271    public void removeDeleteLock(jmri.NamedBean lock) {
272    }
273
274    @CheckReturnValue
275    public boolean isDeleteAllowed() {
276        return true;
277    }
278
279    @Override
280    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
281    }
282
283    /**
284     * {@inheritDoc} 
285     * 
286     * By default, does an alphanumeric-by-chunks comparison
287     */
288    @CheckReturnValue
289    @Override
290    public int compareSystemNameSuffix(@Nonnull String suffix1, @Nonnull String suffix2, @Nonnull NamedBean n) {
291        jmri.util.AlphanumComparator ac = new jmri.util.AlphanumComparator();
292        return ac.compare(suffix1, suffix2);
293    }
294
295    private final static Logger log = LoggerFactory.getLogger(AbstractCatalogTree.class);
296
297}