KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > ca > directory > jxplorer > tree > SmartNode


1 package com.ca.directory.jxplorer.tree;
2
3 import com.ca.commons.cbutil.CBResourceLoader;
4 import com.ca.commons.cbutil.CBUtility;
5 import com.ca.commons.naming.*;
6 import com.ca.directory.jxplorer.JXplorer;
7
8 import javax.naming.NamingEnumeration JavaDoc;
9 import javax.swing.*;
10 import javax.swing.tree.DefaultMutableTreeNode JavaDoc;
11 import java.awt.datatransfer.*;
12 import java.io.File JavaDoc;
13 import java.io.IOException JavaDoc;
14 import java.text.CollationKey JavaDoc;
15 import java.text.Collator JavaDoc;
16 import java.util.*;
17 import java.util.logging.Level JavaDoc;
18 import java.util.logging.Logger JavaDoc;
19
20 /*
21  * SmartNode is a utility class for storing class/value/icon combinations, as
22  * well as the standard MutableTreeNode information from the swing.tree package.
23  * combinations. Takes arguments of form 'cn=Fred+sn=Nurk: ...'
24  * and parses them to:
25  * distinguishedValue = 'Fred+Nurk'
26  *
27  * The class also tries to work out an appropriate icon for displaying the node.
28  * It uses the objectClass attributes, when these are known, or a default icon
29  * keyed to the ldap RDN attribute (i.e. 'c','o','cn' etc.)
30  *
31  * WARNING: At present, this only works for <b>single</b> valued RDNs!
32  */

33  
34  // Mmmm... wonder if it's worth trying to include an explicit DN for every node,
35
// rather than generating it from the tree?
36
// ans. Yes it would be DAMN useful, but there might be some stress keeping them
37
// all updated when parent nodes changed... better to have them able to work it
38
// out dynamically...
39

40  
41 public class SmartNode extends DefaultMutableTreeNode JavaDoc implements Transferable, Comparable JavaDoc
42 {
43     private static RDN emptyRDN = new RDN();
44
45     public RDN rdn = emptyRDN;
46     
47     /*
48      * A misnomer - this is actually the naming attribute of the RDN.
49      */

50     String JavaDoc nodeObjectClass = null;
51
52     boolean dummy = false;
53     boolean root = false;
54     boolean alwaysRefresh = false;
55     boolean blankRoot = false; // if root is blank, use ROOTNAME or blank depending on context
56
//boolean objectClassSet = false; // whether a true object class is known.
57
boolean structural = false; // whether this is just to fill the tree out, but doesn't represent an entry... (i.e. in a search tree response)
58

59
60     static Hashtable icons = new Hashtable(16); // the icons displayed in the tree
61
static boolean useIcons;
62
63     public static final String JavaDoc ROOTNAME = "World";
64     public static final String JavaDoc DUMMYMESSAGE = "reading...";
65
66     ImageIcon icon = null;
67
68     JPopupMenu menu = null; // an optional popup menu.
69

70     private static boolean initialised = false;
71
72     final public static DataFlavor UNICODETEXT = DataFlavor.getTextPlainUnicodeFlavor();
73
74     DataFlavor[] flavours = { UNICODETEXT };
75
76     // get a single platform specific language collator for use in sorting.
77
private static Collator JavaDoc myCollator = Collator.getInstance();
78
79     // the collation key of the node, used for sorting.
80
private CollationKey JavaDoc collationKey;
81
82     private static Logger JavaDoc log = Logger.getLogger(SmartNode.class.getName());
83
84 /**
85  * Pre load the image icons, we'll be using them a lot.
86  *
87  * Image icons are stored in the 'icons' hashtable, keyed on their
88  * file name stem. (i.e. person.gif is stored with key 'person')
89  */

90  
91  
92 // XXX Rewrite to use resource loader as well...!
93
// XXX (and possibly only create icons when needed?)
94

95     static public void init(CBResourceLoader resourceLoader)
96     {
97         if (initialised) return; // we only need to run this method once, but
98
initialised = true; // it's not an error to call it multiple times.
99

100         String JavaDoc iconPath = JXplorer.getProperty("dir.icons");
101
102         if (iconPath == null)
103         {
104             useIcons = false;
105             return;
106         }
107         
108         String JavaDoc[] extensions = {"jpg","gif","jpeg"};
109         
110         String JavaDoc[] iconFiles = CBUtility.readFilteredDirectory(iconPath, extensions);
111         
112         /*
113          * Emergency BackUp : If the icon directory is bad, try to
114          * find a working one, and if successfull, save it as new
115          * default value.
116          */

117         if (iconFiles == null)
118         {
119             log.warning("can't find icon directory " + iconPath + " trying to find /icons directory");
120             iconPath = JXplorer.localDir + "icons" + File.separator;
121             iconFiles = CBUtility.readFilteredDirectory(iconPath, extensions);
122             if (iconFiles == null)
123             {
124                 log.warning("Can't find icon directory; check 'dir.icons=' line in dxconfig.txt.");
125                 return;
126             }
127             log.warning("Recovered! - iconPath reset to " + iconPath);
128             JXplorer.myProperties.setProperty("dir.icons", iconPath);
129         }
130          
131         for (int i=0; i<iconFiles.length; i++)
132         {
133             String JavaDoc stem = iconFiles[i].substring(0,iconFiles[i].lastIndexOf('.'));
134             // save icon names *in lower case*
135
icons.put(stem.toLowerCase(), new ImageIcon(iconPath + iconFiles[i]));
136         }
137         
138         // get any extra icons available in resource files...
139

140         try
141         {
142
143             String JavaDoc[] extraIcons = resourceLoader.getPrefixedResources("icons/");
144             for (int i=0; i<extraIcons.length; i++)
145             {
146                 String JavaDoc iconName = extraIcons[i];
147                 String JavaDoc stem = iconName.substring(6);
148                 int endpos = stem.lastIndexOf('.');
149                 if (stem.length() > 0 && endpos != -1)
150                 {
151             // save icon names *in lower case*
152
stem = stem.substring(0,endpos).toLowerCase();
153                     byte[] b = resourceLoader.getResource(iconName);
154                     icons.put(stem, new ImageIcon(b));
155                 }
156             }
157         }
158         catch (Exception JavaDoc e)
159         {
160             log.warning("Error trying to load icons from resource files: " + e);
161         }
162         useIcons = icons.containsKey("default"); // only use icons if we have a fallback default
163

164     }
165     
166     /**
167      * Constructor for dummy nodes; used to flag possibly expandable nodes
168      * when their children status is unknown (due to not having been read
169      * from the directory yet.)
170      */

171     
172     public SmartNode()
173     {
174         super();
175         log.finer("created null SmartNode (I)");
176         dummy = true;
177         nodeObjectClass="default";
178         //distinguishedValue = "null";
179
}
180     
181     /**
182      * Simple constructor, for when objectClass attributes are not known
183      * @param rdnString the relative distinguished name, e.g. 'cn=fnord'
184      */

185      
186     public SmartNode(String JavaDoc rdnString)
187     {
188         super();
189         log.finer("created SmartNode (II) :" + rdnString);
190         update(rdnString);
191     }
192     
193     /**
194      * Simple constructor, for when objectClass attributes are not known
195      * @param rdn the relative distinguished name, e.g. 'cn=fnord'
196      */

197      
198     public SmartNode(RDN rdn)
199     {
200         super();
201         log.finer("created SmartNode (IIb) :" + rdn);
202         update(rdn);
203     }
204     
205     /**
206      * Copy constructor, for when an RDN is the same, but the tree position
207      * (and hence the full DN) is different. <b>Does Not</b> makes copies of children:
208      * use copyChildren() separately if you want this.
209      *
210      * @param S the node to copy for initial values.
211      */

212      
213      public SmartNode(SmartNode S)
214      {
215          super();
216          log.finer("created SmartNode (III) :" + S.toString());
217          //distinguishedValue = new String(S.distinguishedValue);
218
nodeObjectClass = new String JavaDoc(S.nodeObjectClass);
219          icon = S.icon;
220          update(S.getRDN());
221          dummy = S.dummy;
222      }
223     
224     /**
225      * When objectClass attributes are known, we try to be cleverer getting
226      * the icon for this node.
227      *
228      * @param RDN the RDN of the new node (e.g. 'cn=fnord')
229      * @param objectClasses a javax.naming.directory Attribute containing
230      * a list of the node's ldap objectClasses.
231      */

232
233     // XXX how to choose between conflicting object classes? At the moment
234
// XXX this picks up the first one that it can match an icon to...
235

236     public SmartNode(String JavaDoc RDN, DXAttribute objectClasses)
237     {
238         super();
239         log.finer("created SmartNode (IV) :" + RDN);
240         
241         update(RDN);
242         
243         setTrueObjectClass(objectClasses);
244         
245     }
246
247     /**
248      * If available, it is best to use the object class of the
249      * entry (to determine which icon to display 'n stuff). This
250      * does a quick 'n dirty search if the object class attribute
251      * is known. Better is to manually set the deepest object class,
252      * but this requires knowledge of the schema 'n stuff...
253      */

254              
255     public void setTrueObjectClass(DXAttribute objectClasses)
256     {
257         try
258         {
259             NamingEnumeration JavaDoc obClasses = objectClasses.getAll();
260             while (obClasses.hasMoreElements()) // use non-error-checking form
261
{
262                 String JavaDoc value = obClasses.nextElement().toString();
263                 if (setTrueObjectClass(value))
264                     break;
265             }
266         }
267         catch (javax.naming.NamingException JavaDoc e)
268         {
269             log.log(Level.WARNING, "Naming Exception parsing " + rdn +"\n", e);
270         }
271     }
272
273     /**
274      * This attempts to set the object class of the node to a particular
275      * value. It returns true if an icon is available for that object
276      * class, false otherwise.
277      */

278      
279     public boolean setTrueObjectClass(String JavaDoc value)
280     {
281         value = value.toLowerCase();
282         if (icons.containsKey(value))
283         {
284             nodeObjectClass = value;
285             icon = (ImageIcon) icons.get(nodeObjectClass);
286             
287             return true;
288         }
289         else
290             return false;
291     }
292
293     /**
294      * Takes an ldap RDN string such as 'ou=DemoCorp', and breaks it into
295      * a nodeObjectClass ('ou') and a distinguished value ('DemoCorp'),
296      * and replaces the existing values of these variables.
297      *
298      * @param rdn the new RDN to replace the nodes current value.
299      */

300
301     public void update(String JavaDoc rdn)
302     {
303         try
304         {
305             update(new RDN(rdn));
306         }
307         catch (Exception JavaDoc e)
308         {
309             log.warning("unexpected error in SmartNode:update() " + e.toString());
310             e.printStackTrace();
311         } // should never throw an exception...
312
}
313     
314     public void update(RDN newRDN)
315     {
316
317         if (newRDN==null)
318             setRdn(emptyRDN);
319         else
320             setRdn(newRDN);
321
322         if (rdn.isEmpty()) // probably a root entry
323
{
324             nodeObjectClass="default";
325         }
326
327         if (nodeObjectClass == null) // in extremis, grab the ldap rdn attribute and use that for icons...
328
nodeObjectClass = rdn.getAtt(0); // use root 'cause DN has only one element...
329

330         // create a collation key for fast language-sensitive sorting...
331
// (note toLowerCase() for case insensitive sorting - remove for case sensitive...
332

333         // XXX this is where features such as sorting by object class, or sorting by naming attribute, can be
334
// XXX put...
335

336         // 'false' is the default
337
boolean sortByNamingAttribute = ("true".equals(JXplorer.getProperty("sort.by.naming.attribute")));
338
339         if (rdn.isMultiValued())
340         {
341             StringBuffer JavaDoc key = new StringBuffer JavaDoc(rdn.toString().length());
342             for (int i=0; i<rdn.size(); i++)
343             {
344                 if (sortByNamingAttribute)
345                     key.append(rdn.getRawVal(i)).append(rdn.getAtt(i));
346                 else
347                     key.append(rdn.getAtt(i)).append(rdn.getRawVal(i));
348             }
349             collationKey = myCollator.getCollationKey(key.toString().toLowerCase());
350         }
351         else
352         {
353             if (sortByNamingAttribute)
354                 collationKey = myCollator.getCollationKey(nodeObjectClass + getDistinguishedValue().toLowerCase());
355             else
356                 collationKey = myCollator.getCollationKey(getDistinguishedValue().toLowerCase() + nodeObjectClass);
357         }
358     }
359
360     /**
361      * A utility ftn that makes copies of all the child nodes
362      * given to it, and adds the copies to the current node.
363      * @param children an enumeration of SmartNode(s), to copy
364      * and add.
365      */

366      
367     public void copyChildren(Enumeration JavaDoc children)
368     {
369         while (children.hasMoreElements())
370         {
371             SmartNode A = new SmartNode((SmartNode)children.nextElement());
372             add(A);
373         }
374     }
375     
376     /**
377      * Returns the RDN of the tree node as a String.
378      * @return the RDN as a string.
379      */

380      
381     public RDN getRDN() {
382         return (blankRoot==true)?emptyRDN:rdn; }
383     
384     /**
385      * Writes out the nodes RDN as a string,
386      * @return the RDN, or a message string
387      * for special values (such as root, usually 'World', or dummy values, usually
388      * 'reading...'.
389      */

390     
391     public String JavaDoc toString()
392     {
393         if (blankRoot==true) return ROOTNAME;
394         if (dummy) return DUMMYMESSAGE;
395         //return distinguishedValue;
396
//return rdn.getRawVal(0);
397
//return getDistinguishedValue();
398
return rdn.toString();
399     }
400     
401     /**
402      * returns the ldap class type of the node
403      * WARNING - Currently not properly implemented; Returns (first) attribute naming value instead...
404      * @return the ldap class type as a string.
405      */

406     
407     public String JavaDoc getObjectClass() { return nodeObjectClass; }
408     
409     /**
410      * Writes out the nodes RDN as a string, displaying just the value of the RDN
411      * @return distinguishedValue the value part of the RDN
412      */

413     
414     //XXX performance - this is used by comparator, but currently is slow.
415
//XXX maybe speed up?
416

417     public String JavaDoc getDistinguishedValue()
418     {
419         if (rdn.isMultiValued())
420         {
421             int size = rdn.size();
422             StringBuffer JavaDoc val = new StringBuffer JavaDoc();
423             for (int i=0; i<size; i++)
424             {
425                 if (i>0)
426                     val.append(" + ");
427                 val.append(rdn.getRawVal(i));
428             }
429             return val.toString();
430         }
431         else
432             return rdn.getRawVal(0);
433     }
434
435     /**
436      * returns the icon name (without the extension). The Icon
437      * name is either one of the objectClass names (that an icon
438      * can be found for) or failing that the ldap class name.
439      * @return the string used to identify the icon.
440      */

441              
442     public String JavaDoc getIconName() { return nodeObjectClass; }
443
444     /**
445      * Sometimes a dummy node is used to signal that there may
446      * be child nodes below a given node, but we haven't actually
447      * read those nodes yet.
448      *
449      * @return whether this node is a dummy node holding no real data.
450      */

451
452     public boolean isDummy() { return dummy; }
453
454     /**
455      * Returns whether this node has a dummy node.
456      * (In a normal tree, the dummy node will be the only node)
457      *
458      */

459      
460     public boolean hasDummy()
461     {
462         if (getChildCount() == 0) return false;
463         
464         return ((SmartNode)getChildAt(0)).isDummy();
465     }
466
467     /**
468      * Returns the string to display when the node is a 'dummy'.
469      * @return dummy message, such as 'reading...'
470      */

471      
472      public String JavaDoc getDummyMessage()
473      {
474          return DUMMYMESSAGE;
475      }
476
477     /**
478      * The root node is sometimes blank, but displayed to the user with a
479      * different name. This simply returns whether the node is the
480      * root node - use @isBlankRoot to determine whether it is both
481      * a root node and blank...
482      */

483      
484     public boolean isRoot() { return root; }
485
486     /**
487      * The root node is sometimes blank. This means it is
488      * displayed as ROOTNAME (usually 'cn=World') in the tree,
489      * and written out as a zero length string in other contexts
490      * such as dumping ldif files.
491      */

492      
493     public boolean isBlankRoot() { return blankRoot; }
494     
495     /**
496      * Returns the name of the 'blank root' (i.e. "World" or similar).
497      * @return the blank root name, suitable for display to a user.
498      */

499      
500     public String JavaDoc getBlankRootName() { return ROOTNAME; }
501     
502     /**
503      * Experimental - returns true if the node should always
504      * 'refresh' when it is expanded by the tree...
505      */

506      
507     public boolean isAlwaysRefresh() { return alwaysRefresh; }
508
509     /**
510      * Experimental - sets if the node should always
511      * 'refresh' (i.e. reload all children) when it is expanded by the tree...
512      */

513      
514     public void setAlwaysRefresh(boolean state)
515     {
516         alwaysRefresh = state;
517     }
518
519     /**
520      * This sets the root status of the node (default=false).
521      * If set to true, the root node will return ROOTSTRING as
522      * a name for display, rather than a blank.
523      */

524      
525      public void setRoot(boolean state)
526      {
527          root = state;
528          blankRoot = false;
529          if ((root == true) && ("".equals(rdn.toString())))
530          {
531              update("");
532              blankRoot = true;
533              nodeObjectClass = ROOTNAME.toLowerCase();
534          }
535      }
536
537     /**
538      * gets the icon associated with this node. These are taken from a
539      * central pool of icons - there is no local copy of the icon held in
540      * the node.
541      *
542      * @return the associated icon. This may be null, if not even the default
543      * icon has been set.
544      */

545      
546     public ImageIcon getIcon()
547     {
548         if (icon != null) return icon;
549         
550         icon = (ImageIcon) icons.get(nodeObjectClass.toLowerCase());
551         
552         if (icon == null)
553             icon = (ImageIcon) icons.get("default"); // this may still be null if no icons found...
554

555         return icon;
556     }
557     
558     /**
559      * Rarely used method to force a particular icon to be used by this particular
560      * node.
561      */

562      
563     public void setIcon(ImageIcon newIcon) { icon = newIcon; }
564     
565     /**
566      * Returns whether this node has the corresponding node as a child.
567      * @param n the child node.
568      */

569      
570     public boolean hasChild(SmartNode n) { return hasChild(n.toString());}
571     
572     /**
573      * Returns whether this node has the corresponding node as a child.
574      * @param r the child node's RDN. (Test is case insensitive)
575      */

576      
577     public boolean hasChild(RDN r) { return hasChild(r.toString());}
578     
579     
580     /**
581      * Returns whether this node has the corresponding node as a child.
582      * @param testRDN the child node's RDN as a String. (Test is case insensitive)
583      */

584      
585     public boolean hasChild(String JavaDoc testRDN)
586     {
587         Enumeration JavaDoc children = children();
588         while (children.hasMoreElements())
589         {
590             if (testRDN.equalsIgnoreCase(children.nextElement().toString()))
591             {
592                 return true;
593             }
594         }
595         return false;
596     }
597     
598     public boolean isStructural() { return structural; }
599     
600     public void setStructural(boolean val) { structural = val; }
601     
602
603     /**
604      * Quicky comparator class to sort nodes by distinguished value, rather
605      * than (default) .toString()
606      */

607 /*
608     class NodeComparator implements Comparable
609     {
610         Object myObject;
611         String compareString;
612         
613         public NodeComparator(Object o)
614         {
615             myObject = (o==null)?"null":o;
616             
617             if (myObject instanceof SmartNode)
618                 compareString = ((SmartNode)myObject).getDistinguishedValue().toLowerCase();
619             else
620                 compareString = myObject.toString().toLowerCase();
621         }
622         
623         public int compareTo(Object compObject)
624         {
625             return compareString.compareTo(compObject.toString());
626         }
627         
628         public String toString() { return compareString; }
629         
630         public Object getObject() {return myObject; }
631     }
632 */

633
634     /**
635      * Used for sorting of Smart Nodes.
636      * @param o the Smart Node to compare against.
637      * @return a negative integer, zero, or a positive integer as this Collation Key is less
638      * than, equal to, or greater than the given Object.
639      * @throws ClassCastException Thrown if the passed object is not another SmartNode.
640      */

641     public int compareTo(Object JavaDoc o)
642         throws ClassCastException JavaDoc
643     {
644         if (o == null)
645             return -1;
646         if (((SmartNode)o).collationKey == null)
647             return -1;
648         if (collationKey == null)
649             return +1; // bingo
650

651         return collationKey.compareTo(((SmartNode)o).collationKey);
652     }
653
654     /**
655      * This sorts the children of the current node by the alphabetic
656      * value of their naming attribute value; e.g. in cn=doofus, by 'doofus'.
657      *
658      */

659
660     public void sort()
661     {
662         TreeSet sortedSet = new TreeSet();
663         Enumeration JavaDoc kids = children();
664
665         while (kids.hasMoreElements())
666         {
667             Object JavaDoc kid = kids.nextElement();
668             //System.out.println("add node: '" + kid + "'");
669
sortedSet.add(kid);
670         }
671         removeAllChildren();
672
673         Iterator sortedKids = sortedSet.iterator();
674         while (sortedKids.hasNext())
675         {
676             SmartNode newNode = (SmartNode)sortedKids.next();
677             //System.out.println("node: " + newNode.toString() + "'");
678
add(newNode);
679         }
680     }
681  
682  
683     /**
684      * Check if a SmartNode has the same RDN as the passed RDN.
685      *
686      * @param testRDN the RDN to test against
687      * @return true if the RDNs are equal
688      */

689      
690     public boolean rdnEquals(RDN testRDN)
691     {
692         return (testRDN.equals(rdn));
693     }
694
695     /**
696      * It is possible to register a special popup menu that is called when
697      * this node is 'right-clicked' on. Usually you wouldn't bother.
698      * @param popupMenu the popupmenu to register.
699      */

700      
701     public void setPopupMenu(JPopupMenu popupMenu) { menu = popupMenu; }
702     
703     /**
704      * It is possible to register a special popup menu that is called when
705      * this node is 'right-clicked' on. Usually you wouldn't bother, but
706      * this returns that menu if you have bothered, and null if you haven't.
707      * @return the pre-registered popupmenu, or (more usually) null.
708      */

709      
710     
711     public JPopupMenu getPopupMenu() { return menu; }
712     
713
714     
715     /*
716      * Returns whether the rdn is multi-valued.
717      */

718     public boolean isMultiValued()
719     {
720         return rdn.isMultiValued();
721     }
722
723     //
724
// *** DRAG 'n DROP MAGIC ***
725
//
726

727     public DN getDN()
728     {
729         if (root) return new DN();
730         
731         DN ret = ((SmartNode)getParent()).getDN();
732         ret.add(getRDN());
733         return ret;
734     }
735     
736     public Object JavaDoc getTransferData(DataFlavor df)
737         throws UnsupportedFlavorException, IOException JavaDoc
738     {
739         if (df.equals(flavours[0]) == false) // currently only support one flavour.
740
throw new UnsupportedFlavorException(df);
741         String JavaDoc dn = getDN().toString();
742         return dn;
743     }
744
745     //Returns an array of DataFlavor objects indicating the
746
//flavors the data can be provided in.
747
public DataFlavor[] getTransferDataFlavors()
748     {
749         return flavours;
750     }
751
752     //Returns whether or not the specified data flavor is supported for this object.
753
public boolean isDataFlavorSupported(DataFlavor flavour)
754     {
755          for (int i=0; i<flavours.length; i++)
756             if ( flavours[i].equals(flavour) ) return true;
757          return false;
758     }
759
760     public void setRdn(RDN rdn)
761     {
762         this.rdn = rdn;
763     }
764
765 }
766
Popular Tags