001: /*
002: * Copyright (c) 2005-2008 Substance Kirill Grouchnikov. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of Substance Kirill Grouchnikov nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030: package org.jvnet.substance.utils;
031:
032: import java.awt.Component;
033: import java.io.*;
034: import java.net.URL;
035: import java.util.*;
036:
037: import javax.swing.JToggleButton;
038: import javax.swing.ListCellRenderer;
039:
040: /**
041: * This class is used to decide whether the a highlight theme should be applied
042: * on a specific component.
043: *
044: * <p>
045: * At runtime, the manager is populated from
046: * <code>META-INF/substance.highlight.properties</code> resources found in
047: * classpath. This allows providing additional application-specific components
048: * to be registered to use highlight theme. Each such resource should contain a
049: * list of fully qualified class names, one class name per line. The class name
050: * may be a "leaf" class (such as {@link JToggleButton}), or can be a parent
051: * class / interface (such as {@link ListCellRenderer}). In the later case, the
052: * highlight theme will be used on all classes that extend such class /
053: * implement such interface.
054: * </p>
055: *
056: * <p>
057: * In addition, the {@link #toUseHighlightTheme(Component)} API can be used to
058: * register additional application-specific components.
059: * </p>
060: *
061: * @author Kirill Grouchnikov
062: */
063: public class SubstanceHighlightManager {
064: /**
065: * Contains {@link Class} instances. If a class instance is in this set, all
066: * components of this class (directly, via extension or via inheritance)
067: * will use highlight theme.
068: */
069: protected Set<Class<?>> useHighlightThemeOn;
070:
071: /**
072: * Contains {@link Class} instances. This serves as a cache to speed up the
073: * subsequent processing.
074: *
075: * @see #toUseHighlightTheme(Component)
076: */
077: protected Set<Class<?>> cache;
078:
079: /**
080: * The singleton instance.
081: */
082: protected static SubstanceHighlightManager instance;
083:
084: /**
085: * Returns the singleton instance.
086: *
087: * @return The singleton instance.
088: */
089: public static synchronized SubstanceHighlightManager getInstance() {
090: if (instance == null) {
091: instance = new SubstanceHighlightManager();
092: instance.populate();
093: }
094: return instance;
095: }
096:
097: /**
098: * Creates a new empty manager.
099: */
100: private SubstanceHighlightManager() {
101: this .useHighlightThemeOn = new HashSet<Class<?>>();
102: this .cache = new HashSet<Class<?>>();
103: }
104:
105: /**
106: * Populates the {@link #useHighlightThemeOn} set. The classpath is scanned
107: * for all resources that match the name
108: * <code>META-INF/substance.highlight.properties</code>.
109: *
110: * @see #populateFrom(URL)
111: */
112: public void populate() {
113: // the following is fix by Dag Joar and Christian Schlichtherle
114: // for application running with -Xbootclasspath VM flag. In this case,
115: // the using MyClass.class.getClassLoader() would return null,
116: // but the context class loader will function properly
117: // that classes will be properly loaded regardless of whether the lib is
118: // added to the system class path, the extension class path and
119: // regardless of the class loader architecture set up by some
120: // frameworks.
121: ClassLoader cl = SubstanceCoreUtilities
122: .getClassLoaderForResources();
123: try {
124: Enumeration<?> rs = cl
125: .getResources("META-INF/substance.highlight.properties");
126: while (rs.hasMoreElements()) {
127: URL rUrl = (URL) rs.nextElement();
128: this .populateFrom(rUrl);
129: }
130: } catch (IOException ioe) {
131: }
132: }
133:
134: /**
135: * Populates the {@link #useHighlightThemeOn} set from the specific URL
136: * resource. The resource should contain a list of fully qualified class
137: * names, one class name per line. The class name may be a "leaf" class
138: * (such as {@link JToggleButton}), or can be a parent class / interface
139: * (such as {@link ListCellRenderer}). In the later case, the highlight
140: * theme will be used on all classes that extend such class / implement such
141: * interface.
142: *
143: * @param url
144: * Resource URL.
145: */
146: protected void populateFrom(URL url) {
147: InputStream is = null;
148: BufferedReader br = null;
149: try {
150: is = url.openStream();
151: br = new BufferedReader(new InputStreamReader(is));
152: while (true) {
153: String line = br.readLine();
154: if (line == null)
155: break;
156: line = line.trim();
157: if (line.length() == 0)
158: continue;
159: this .addToUseHighlightTheme(line);
160: }
161: } catch (IOException ioe) {
162: } finally {
163: if (is != null) {
164: try {
165: is.close();
166: } catch (IOException ioe) {
167: }
168: }
169: if (br != null) {
170: try {
171: br.close();
172: } catch (IOException ioe) {
173: }
174: }
175: }
176: }
177:
178: /**
179: * Adds the specified class to the set of components that should use
180: * highlight theme.
181: *
182: * @param clazz
183: * Component class.
184: */
185: public void addToUseHighlightTheme(Class<?> clazz) {
186: this .useHighlightThemeOn.add(clazz);
187: }
188:
189: /**
190: * Adds the specified class name to the set of components that should use
191: * highlight theme.
192: *
193: * @param className
194: * Component class name.
195: */
196: public void addToUseHighlightTheme(String className) {
197: try {
198: this .useHighlightThemeOn.add(Class.forName(className));
199: } catch (Exception exc) {
200: }
201: }
202:
203: /**
204: * Returns indication whether the specified component should use highlight
205: * theme. The component hierarchy is scanned from the component up. At each
206: * stage, the current ascendant is checked against all entries in the
207: * {@link #useHighlightThemeOn} set. If the class of the current ascendant
208: * matches one of the entries, <code>true</code> is returned. Otherwise,
209: * <code>false</code> is returned (no ascendant found matching any of the
210: * entries in the {@link #useHighlightThemeOn} set).
211: *
212: * <p>
213: * The implementation uses the {@link #cache} tp speed up the processing.
214: * When a component class is determined to use highlight theme, its class is
215: * put in this cache. The implementation first consults this cache - if the
216: * component class is found, <code>true</code> is returned. Otherwise, the
217: * above algorithm is performed.
218: *
219: * @param comp
220: * Component.
221: * @return <code>true</code> if the specified component should use
222: * highlight theme, <code>false</code> otherwise.
223: */
224: public synchronized boolean toUseHighlightTheme(Component comp) {
225: Component currComp = comp;
226: while (currComp != null) {
227: Class<?> currClazz = currComp.getClass();
228: if (cache.contains(currClazz))
229: return true;
230: for (Iterator<Class<?>> it = this .useHighlightThemeOn
231: .iterator(); it.hasNext();) {
232: Class<?> usehHighlightThemeClazz = it.next();
233: if (usehHighlightThemeClazz.isAssignableFrom(currClazz)) {
234: cache.add(currClazz);
235: return true;
236: }
237: }
238: currComp = currComp.getParent();
239: }
240: return false;
241: }
242: }
|