KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > enode > SubMenuCache


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Nokia. Portions Copyright 2003-2004 Nokia.
17  * All Rights Reserved.
18  */

19 package org.netbeans.modules.enode;
20
21 import java.util.ArrayList JavaDoc;
22 import java.util.Collections JavaDoc;
23 import java.util.HashMap JavaDoc;
24 import java.util.HashSet JavaDoc;
25 import java.util.Iterator JavaDoc;
26 import java.util.List JavaDoc;
27 import java.util.Map JavaDoc;
28 import java.util.Set JavaDoc;
29 import javax.swing.Action JavaDoc;
30 import javax.swing.JComponent JavaDoc;
31 import javax.swing.JSeparator JavaDoc;
32 import org.netbeans.api.registry.AttributeEvent;
33 import org.netbeans.api.registry.BindingEvent;
34 import org.netbeans.api.registry.Context;
35 import org.netbeans.api.registry.ContextListener;
36 import org.netbeans.api.registry.SubcontextEvent;
37 import org.openide.ErrorManager;
38 import org.openide.filesystems.FileObject;
39 import org.openide.filesystems.Repository;
40 import org.netbeans.api.enode.ExtensibleNode;
41 import org.openide.util.WeakListeners;
42
43 /**
44  * Caches the information contained in the folder /ExtensibleNode/SubMenu/.
45  * @author David Strupl
46  */

47 class SubMenuCache {
48     
49     private static ErrorManager log = ErrorManager.getDefault().getInstance(SubMenuCache.class.getName());
50     private static boolean LOGGABLE = log.isLoggable(ErrorManager.INFORMATIONAL);
51
52     /** Extension of the files we are interested in (in the SubMenu folder). */
53     private static final String JavaDoc SHADOW_EXTENSION = "shadow";
54     
55     
56     /** Singleton instance of this class. */
57     private static SubMenuCache instance;
58     
59     /**
60      * For fast finding the entry associated with the path. Maps:
61      * String: path to the original action (in the Actions/ folder
62      * to
63      * SubMenuCacheEntry representing the action in this cache
64      */

65     private Map JavaDoc/*<String,CacheEntry>*/ pathToEntry;
66     
67     /**
68      * We hold a reference to the listener for preventing
69      * the garbage collection.
70      */

71     private Listener listener;
72     
73     /**
74      * Prevent the listeners to be attached more than once.
75      */

76     private boolean listenersAttached = false;
77
78     /**
79      * To prevent garbage collection of context where we attached
80      * listeners. We just add items to the set and never do anything
81      * with them. But that is the reason why it is here - to hold
82      * strong references to the Context objects.
83      */

84     private Set JavaDoc listenersAttachedTo = new HashSet JavaDoc();
85     
86     /** Creates a new instance of SubMenuCache */
87     private SubMenuCache() {
88         buildTheCache();
89     }
90     
91     /**
92      * Lazy creation of the singleton instance of this class.
93      * NEVER keep the resulting reference for long. Always ask this method
94      * if you need it.
95      */

96     public static SubMenuCache getInstance() {
97         if (instance == null) {
98             instance = new SubMenuCache();
99         }
100         return instance;
101     }
102
103     /**
104      * Finds the cached entry for given path. If there is none returns null.
105      */

106     public CacheEntry getCacheEntry(String JavaDoc originalPath) {
107         return (CacheEntry)getPathToEntryMap().get(originalPath);
108     }
109     
110     private FileObject getSubMenusRoot() {
111         return Repository.getDefault().getDefaultFileSystem().findResource(
112                 ExtensibleNode.E_NODE_SUBMENUS);
113     }
114     
115     private void buildTheCache() {
116         long startTime = System.currentTimeMillis();
117         FileObject root = getSubMenusRoot();
118         Context con = Context.getDefault().getSubcontext(root.getPath());
119         if (con == null) {
120             if (LOGGABLE) log.log("buildTheCache() returning - SubMenu context does not exist.");
121             return;
122         }
123         if (!listenersAttached) {
124             ContextListener l1 = getContextListener(con);
125             con.addContextListener(l1);
126             listenersAttachedTo.add(con);
127         }
128         MenuEntry rootMenu = scanFolder(root, null);
129         getPathToEntryMap().put("", rootMenu);
130         long finishTime = System.currentTimeMillis();
131         log.log(ErrorManager.USER, "SubMenuCache building has taken " + (finishTime - startTime));
132         if (LOGGABLE) log.log(this.toString());
133     }
134     
135     /**
136      * Creates part of the cache by traversing the given folder
137      * and creating SubMenuCacheEntries.
138      */

139     private MenuEntry scanFolder(FileObject folder, MenuEntry parent) {
140         if (LOGGABLE) log.log("scanFolder(" + folder.getPath() + ") START");
141         String JavaDoc displayName = folder.getName();
142         try {
143             displayName = folder.getFileSystem ().getStatus ().annotateName(folder.getName(), Collections.singleton(folder));
144         } catch (Exception JavaDoc x) {
145             log.notify(ErrorManager.EXCEPTION, x);
146         }
147         MenuEntry result = new MenuEntry(displayName, parent);
148         // in order to get the order we need Registry API:
149
Context con = Context.getDefault().getSubcontext(folder.getPath());
150         List JavaDoc orderedNames = con.getOrderedNames();
151         for (Iterator JavaDoc it = orderedNames.iterator(); it.hasNext();) {
152             String JavaDoc name = (String JavaDoc) it.next();
153             if (LOGGABLE) log.log("scanFolder checking " + name);
154             if (name.endsWith("/")) {
155                 name = name.substring(0, name.length()-1);
156             }
157             FileObject child = folder.getFileObject(name);
158             if (child == null) {
159                 // try with extension:
160
child = folder.getFileObject(name, SHADOW_EXTENSION);
161             }
162             if (child == null) {
163                 log.log("child == null: Registry returned an invalid name " + name + " in folder " + folder.getPath());
164                 continue;
165             }
166             if (! child.isValid()) {
167                 log.log("!child.isValid(): Registry returned an invalid name " + name + " in folder " + folder.getPath());
168                 continue;
169             }
170             if (child.isData()) {
171                 String JavaDoc ext = child.getExt();
172                 if (!SHADOW_EXTENSION.equals(ext)) {
173                     log.log("Only .shadows files are allowed in SubMenu folder. Illegal file: " + child.getPath());
174                     continue;
175                 }
176                 String JavaDoc origPathAttr = (String JavaDoc)child.getAttribute("originalFile");
177                 if (origPathAttr == null) {
178                     log.log("Shadow file " + child.getPath() + " is missing the originalFile attribute");
179                     continue;
180                 }
181                 FileObject origAction = Repository.getDefault().getDefaultFileSystem().findResource(origPathAttr);
182                 if (origAction == null) {
183                     log.log("originalFile attribute (" + origPathAttr + ") of " + child.getPath() + " does not reference existing action.");
184                     continue;
185                 }
186                 int lastDotIndex = origPathAttr.lastIndexOf('.');
187                 String JavaDoc pathWithoutExt = origPathAttr.substring(0, lastDotIndex);
188                 if (LOGGABLE) log.log("adding result " + result + " with path " + pathWithoutExt);
189                 ActionEntry ae = new ActionEntry(pathWithoutExt, result);
190                 result.addChild(ae);
191                 getPathToEntryMap().put(pathWithoutExt, ae);
192             }
193             if (child.isFolder()) {
194                 scanFolder(child, result);
195             }
196         }
197         if (parent != null) {
198             parent.addChild(result);
199         }
200         return result;
201     }
202     
203     /** Lazy init of pathToEntry */
204     private Map JavaDoc getPathToEntryMap() {
205         if (pathToEntry == null) {
206             pathToEntry = new HashMap JavaDoc();
207         }
208         return pathToEntry;
209     }
210
211     /** Debugging output */
212     public String JavaDoc toString() {
213         String JavaDoc result = "SubMenuCache[";
214         CacheEntry root = getCacheEntry("");
215         result += convertToString(0, root);
216         return result + "]";
217     }
218     
219     /**
220      * Called from toString(). Calls toString() on all cached elements.
221      */

222     private String JavaDoc convertToString(int indent, CacheEntry entry) {
223         StringBuffer JavaDoc sb = new StringBuffer JavaDoc(150);
224         for (int i = 0; i < indent; i++) {
225             sb.append(' ');
226         }
227         sb.append(entry.toString() + "\n");
228         if (entry instanceof MenuEntry) {
229             MenuEntry me = (MenuEntry)entry;
230             for (int i = 0; i < me.getChildrenCount(); i++) {
231                 sb.append(convertToString(indent + 4, me.getChild(i)));
232             }
233         }
234         return sb.toString();
235     }
236     
237     /**
238      * Lazy initialization of the listener variable. This method
239      * will return a weak listener.
240      * The weak listener references the object hold
241      * by the <code> listener </code> variable.
242      */

243     private ContextListener getContextListener(Object JavaDoc source) {
244         if (listener == null) {
245             listener = new Listener();
246         }
247         return (ContextListener)WeakListeners.create(ContextListener.class, listener, source);
248     }
249     
250     /**
251      * Entry for one action or folder.
252      */

253     public static class CacheEntry {
254         /** Parent in our cache and also in the storage under SubMenu folder. */
255         private MenuEntry parent;
256         
257         public CacheEntry(MenuEntry parent) {
258             this.parent = parent;
259         }
260         
261         /** returns index in the parent menu */
262         public int getIndex() {
263             if (parent == null) {
264                 return 0;
265             }
266             return parent.getChildIndex(this);
267         }
268         
269         public MenuEntry getParent() {
270             return parent;
271         }
272     }
273     
274     /**
275      * Entry for one folder.
276      */

277     public static class MenuEntry extends CacheEntry {
278         /**
279          * Children of the menu are actions or menus
280          */

281         private List JavaDoc/*<CacheEntry>*/ children;
282         
283         /** Text for the menu item:*/
284         private String JavaDoc displayName;
285         
286         public MenuEntry(String JavaDoc displayName, MenuEntry parent) {
287             super(parent);
288             this.displayName = displayName;
289             children = new ArrayList JavaDoc();
290         }
291         
292         public CacheEntry getChild(int index) {
293             return (CacheEntry) children.get(index);
294         }
295         
296         public void addChild(CacheEntry child) {
297             children.add(child);
298         }
299         
300         public int getChildIndex(CacheEntry child) {
301             return children.indexOf(child);
302         }
303
304         public int getChildrenCount() {
305             return children.size();
306         }
307         
308         public String JavaDoc getDisplayName() {
309             return displayName;
310         }
311         
312         public String JavaDoc toString() {
313             return "MenuEntry[" + displayName +",childrenCount=" + children.size() + "]";
314         }
315     }
316     
317     /**
318      * Entry for one action.
319      */

320     public static class ActionEntry extends CacheEntry {
321         private String JavaDoc originalActionPath;
322         public ActionEntry(String JavaDoc originalActionPath, MenuEntry parent) {
323             super(parent);
324             this.originalActionPath = originalActionPath;
325         }
326         public String JavaDoc toString() {
327             return "ActionEntry["+originalActionPath+",parent="+getParent()+"]";
328         }
329         /**
330          * Returns one of the following:
331          * Action
332          * JSeparator
333          * JComponent
334          */

335         public Object JavaDoc getActionObject() {
336             int lastSlash = originalActionPath.lastIndexOf('/');
337             String JavaDoc contextName = originalActionPath.substring(0, lastSlash);
338             Context ctx = Context.getDefault().getSubcontext(contextName);
339             if (ctx == null) {
340                 throw new IllegalStateException JavaDoc("Context " + contextName + " was not found.");
341             }
342             Object JavaDoc obj = ctx.getObject(originalActionPath.substring(lastSlash+1), null);
343             if (obj instanceof Action JavaDoc) {
344                 return obj;
345             }
346             if (obj instanceof JSeparator JavaDoc) {
347                 return obj;
348             }
349             if (obj instanceof JComponent JavaDoc) {
350                 return obj;
351             }
352             throw new IllegalStateException JavaDoc("Path " + originalActionPath + " cannot be converted to Action.\n" +
353                     "Object with name " + originalActionPath.substring(lastSlash+1) + " was not found in " + ctx);
354         }
355     }
356     
357     /**
358      * Whatever happens in the selected context this listener only clears
359      * the actions reference. This cause the list of actions to
360      * be computed next time someone asks for them.
361      */

362     private class Listener implements ContextListener {
363         public void attributeChanged(AttributeEvent evt) {
364             if (LOGGABLE) log.log("attributeChanged("+evt+") called on listener from " + SubMenuCache.this);
365             instance = null;
366         }
367         
368         public void bindingChanged(BindingEvent evt) {
369             if (LOGGABLE) log.log("bindingChanged("+evt+") called on listener from " + SubMenuCache.this);
370             instance = null;
371         }
372         
373         public void subcontextChanged(SubcontextEvent evt) {
374             if (LOGGABLE) log.log("subcontextChanged("+evt+") called on listener from " + SubMenuCache.this);
375             instance = null;
376         }
377     }
378
379 }
380
Popular Tags