001: /*
002: * $RCSfile: PropertySourceImpl.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.1 $
009: * $Date: 2005/02/11 04:57:17 $
010: * $State: Exp $
011: */
012: package javax.media.jai;
013:
014: import java.io.IOException;
015: import java.io.ObjectInputStream;
016: import java.io.ObjectOutputStream;
017: import java.io.Serializable;
018: import java.util.Collections;
019: import java.util.HashSet;
020: import java.util.Hashtable;
021: import java.util.Iterator;
022: import java.util.List;
023: import java.util.Map;
024: import java.util.Set;
025: import com.sun.media.jai.util.PropertyUtil;
026: import javax.media.jai.util.CaselessStringKey;
027:
028: /**
029: * A utility implementation of the <code>PropertySource</code> interface.
030: * Properties are managed by three internal structures: one which maps
031: * property names to values, a second which maps property names to
032: * <code>PropertySource</code>s, and a third which tracks which entries
033: * in the name-value mapping derived their respective values from a
034: * <code>PropertySource</code> in the name-<code>PropertySource</code>
035: * mapping. The case of property names is retained for subsequent
036: * retrieval but is ignored when the names are used as keys.
037: *
038: * @see CaselessStringKey
039: * @see PropertySource
040: * @see WritablePropertySource
041: * @see WritablePropertySourceImpl
042: *
043: * @since JAI 1.1
044: */
045: // NB A class of this name existed in JAI 1.0.2 but that class was renamed
046: // to what is now PropertyEnvironment.
047: public class PropertySourceImpl implements PropertySource, Serializable {
048: /**
049: * Mapping of <code>CaselessStringKey</code>s to values.
050: * If this object is serialized, only those entries of which
051: * the value is serializable will be retained.
052: */
053: protected transient Map properties;
054:
055: /**
056: * Mapping of <code>CaselessStringKey</code>s to
057: * <code>PropertySource</code>s.
058: * If this object is serialized, only those entries of which
059: * the value is serializable will be retained.
060: */
061: protected transient Map propertySources;
062:
063: /**
064: * <code>CaselessStringKey</code>s corresponding to the keys of entries
065: * in <code>properties</code> which derived their respective
066: * values from a <code>PropertySource</code> in
067: * <code>propertySources</code>.
068: */
069: protected Set cachedPropertyNames;
070:
071: /**
072: * Constructs a <code>PropertySourceImpl</code> instance with
073: * no properties set.
074: */
075: protected PropertySourceImpl() {
076: properties = new Hashtable();
077: propertySources = new Hashtable();
078: cachedPropertyNames = Collections
079: .synchronizedSet(new HashSet());
080: }
081:
082: /**
083: * Constructs a <code>PropertySourceImpl</code> instance which
084: * will derive properties from one or both of the supplied parameters.
085: * The <code>propertyMap</code> and <code>propertySource</code> parameters
086: * will be used to initialize the name-value and
087: * name-<code>PropertySource</code> mappings, respectively.
088: * Entries in the <code>propertyMap</code> object will be assumed
089: * to be properties if the key is a <code>String</code> or a
090: * <code>CaselessStringKey</code>. The <code>propertySource</code>
091: * object will be queried for the names of properties that it emits
092: * but requests for associated values will not be made at this time
093: * so as to to defer any calculation that such requests might provoke.
094: * The case of property names will be retained but will be ignored
095: * insofar as the name is used as a key to the property value.
096: *
097: * @param propertyMap A <code>Map</code> from which to copy properties
098: * which have keys which are either <code>String</code>s or
099: * <code>CaselessStringKey</code>s.
100: * @param propertySource A <code>PropertySource</code> from which to
101: * derive properties.
102: *
103: * @exception IllegalArgumentException if <code>propertyMap</code>
104: * and <code>propertySource</code> are both <code>null</code>
105: * and this constructor is not being invoked from within a
106: * subclass constructor. When invoked from a subclass
107: * constructor both parameters may be <code>null</code>.
108: */
109: public PropertySourceImpl(Map propertyMap,
110: PropertySource propertySource) {
111: this ();
112:
113: // If both parameters are null throw an exception if this constructor
114: // is not invoked from within a subclass constructor.
115: if (propertyMap == null && propertySource == null) {
116: boolean throwException = false;
117: try {
118: Class rootClass = Class
119: .forName("javax.media.jai.PropertySourceImpl");
120: throwException = this .getClass().equals(rootClass);
121: } catch (Exception e) {
122: // Ignore it.
123: }
124: if (throwException) {
125: throw new IllegalArgumentException(JaiI18N
126: .getString("Generic0"));
127: }
128: }
129:
130: if (propertyMap != null) {
131: Iterator keys = propertyMap.keySet().iterator();
132: while (keys.hasNext()) {
133: Object key = keys.next();
134: if (key instanceof String) {
135: properties.put(new CaselessStringKey((String) key),
136: propertyMap.get(key));
137: } else if (key instanceof CaselessStringKey) {
138: properties.put((CaselessStringKey) key, propertyMap
139: .get(key));
140: }
141: }
142: }
143:
144: if (propertySource != null) {
145: String[] names = propertySource.getPropertyNames();
146: if (names != null) {
147: int length = names.length;
148: for (int i = 0; i < length; i++) {
149: propertySources.put(
150: new CaselessStringKey(names[i]),
151: propertySource);
152: }
153: }
154: }
155: }
156:
157: /**
158: * Returns an array of <code>String</code>s recognized as names by
159: * this property source. The case of the property names is retained.
160: * If no properties are available, <code>null</code> will be returned.
161: *
162: * @return an array of <code>String</code>s giving the valid
163: * property names or <code>null</code>.
164: */
165: public String[] getPropertyNames() {
166: synchronized (properties) {
167: if (properties.size() + propertySources.size() == 0) {
168: return null;
169: }
170:
171: // Create a set from the property name-value mapping.
172: Set propertyNames = Collections
173: .synchronizedSet(new HashSet(properties.keySet()));
174:
175: // Add all names not already in the set.
176: propertyNames.addAll(propertySources.keySet());
177:
178: // Copy names to an array.
179: int length = propertyNames.size();
180: String[] names = new String[length];
181: Iterator elements = propertyNames.iterator();
182: int index = 0;
183: while (elements.hasNext() && index < length) { // redundant test
184: names[index++] = ((CaselessStringKey) elements.next())
185: .getName();
186: }
187:
188: return names;
189: }
190: }
191:
192: /**
193: * Returns an array of <code>String</code>s recognized as names by
194: * this property source that begin with the supplied prefix. If
195: * no property names match, <code>null</code> will be returned.
196: * The comparison is done in a case-independent manner.
197: *
198: * @return an array of <code>String</code>s giving the valid
199: * property names.
200: *
201: * @exception IllegalArgumentException if <code>prefix</code>
202: * is <code>null</code>.
203: */
204: public String[] getPropertyNames(String prefix) {
205: return PropertyUtil
206: .getPropertyNames(getPropertyNames(), prefix);
207: }
208:
209: /**
210: * Returns the class expected to be returned by a request for
211: * the property with the specified name. If this information
212: * is unavailable, <code>null</code> will be returned.
213: * This method queries only the name-value mapping so as to avoid
214: * requesting a property value from a <code>PropertySource</code>
215: * to which the name might refer via the name-<code>PropertySource</code>
216: * mapping. If it is known from <code>getPropertyNames()</code> that
217: * the property is emitted by this <code>PropertySource</code> but this
218: * method returns <code>null</code>, then <code>getProperty()</code>
219: * will have to be invoked and the <code>Class</code> obtained from
220: * the property value itself.
221: *
222: * @param propertyName the name of the property, as a <code>String</code>.
223: *
224: * @return The <code>Class</code> expected to be returneded by a
225: * request for the value of this property or <code>null</code>.
226: *
227: * @exception IllegalArgumentException if <code>propertyName</code>
228: * is <code>null</code>.
229: */
230: public Class getPropertyClass(String propertyName) {
231: if (propertyName == null) {
232: throw new IllegalArgumentException(JaiI18N
233: .getString("Generic0"));
234: }
235: synchronized (properties) {
236: Class propertyClass = null;
237: Object value = properties.get(new CaselessStringKey(
238: propertyName));
239: if (value != null) {
240: propertyClass = value.getClass();
241: }
242: return propertyClass;
243: }
244: }
245:
246: /**
247: * Returns the value of a property. If the property name is not
248: * recognized, <code>java.awt.Image.UndefinedProperty</code> will
249: * be returned.
250: *
251: * <p> If the requested name is found in the name-value mapping,
252: * the corresponding value will be returned. Otherwise the
253: * name-<code>PropertySource</code> mapping will be queried and the
254: * value will be derived from the found <code>PropertySource</code>,
255: * if any. If the value is derived from a <code>PropertySource</code>,
256: * a record will be kept of this and this property will be referred to
257: * as a "cached property".
258: *
259: * @param propertyName the name of the property, as a <code>String</code>.
260: *
261: * @return the value of the property, as an
262: * <code>Object</code>, or the value
263: * <code>java.awt.Image.UndefinedProperty</code>.
264: *
265: * @exception IllegalArgumentException if <code>propertyName</code>
266: * is <code>null</code>.
267: */
268: public Object getProperty(String propertyName) {
269: if (propertyName == null) {
270: throw new IllegalArgumentException(JaiI18N
271: .getString("Generic0"));
272: }
273:
274: synchronized (properties) {
275: CaselessStringKey key = new CaselessStringKey(propertyName);
276:
277: // Try to retrieve from value mapping.
278: Object value = properties.get(key);
279:
280: if (value == null) {
281: // Try to retrieve from PropertySource mapping.
282: PropertySource propertySource = (PropertySource) propertySources
283: .get(key);
284: if (propertySource != null) {
285: value = propertySource.getProperty(propertyName);
286: if (value != java.awt.Image.UndefinedProperty) {
287: // Cache the value and flag it as such.
288: properties.put(key, value);
289: cachedPropertyNames.add(key);
290: }
291: } else { // No PropertySource: undefined property.
292: value = java.awt.Image.UndefinedProperty;
293: }
294: }
295:
296: return value;
297: }
298: }
299:
300: /**
301: * Copies into a <code>Map</code> all properties currently available
302: * via this <code>PropertySource</code>. All property values are
303: * copied by reference rather than by being cloned. The keys in the
304: * <code>Map</code> will be <code>String</code>s with the original
305: * property name case intact. Property values derived from the
306: * name-value mapping will take precedence. The names of properties
307: * whose values are derived via the name-<code>PropertySource</code>
308: * mapping will be recorded as "cached properties".
309: *
310: * @return A <code>Map</code> of all properties or <code>null</code> if
311: * none are defined.
312: */
313: public Map getProperties() {
314: if (properties.size() + propertySources.size() == 0) {
315: return null;
316: }
317:
318: synchronized (properties) {
319: Hashtable props = null;
320:
321: String[] propertyNames = getPropertyNames();
322: if (propertyNames != null) {
323: int length = propertyNames.length;
324: props = new Hashtable(properties.size());
325: for (int i = 0; i < length; i++) {
326: String name = propertyNames[i];
327: Object value = getProperty(name);
328: props.put(name, value);
329: }
330: }
331:
332: return props;
333: }
334: }
335:
336: /**
337: * Serialize a <code>Map</code> which contains serializable keys.
338: */
339: private static void writeMap(ObjectOutputStream out, Map map)
340: throws IOException {
341: // Create an empty Hashtable.
342: Hashtable table = new Hashtable();
343:
344: // Copy serializable properties to local table.
345: Iterator keys = map.keySet().iterator();
346: while (keys.hasNext()) {
347: Object key = keys.next();
348: Object value = map.get(key);
349: if (value instanceof Serializable) {
350: table.put(key, value);
351: }
352: }
353:
354: // Write serialized form to the stream.
355: out.writeObject(table);
356: }
357:
358: /**
359: * Serialize the PropertySourceImpl.
360: */
361: private void writeObject(ObjectOutputStream out) throws IOException {
362: // Write serializable fields.
363: out.defaultWriteObject();
364:
365: synchronized (properties) {
366: // Write serializable forms of name-value and
367: // name-PropertySource maps.
368: writeMap(out, properties);
369: writeMap(out, propertySources);
370: }
371: }
372:
373: /**
374: * Deserialize the PropertySourceImpl.
375: */
376: private void readObject(ObjectInputStream in) throws IOException,
377: ClassNotFoundException {
378: // Read serializable fields.
379: in.defaultReadObject();
380:
381: // Read serializable forms of name-value and name-PropertySource maps.
382: properties = (Map) in.readObject();
383: propertySources = (Map) in.readObject();
384:
385: // Clean up cached names list: delete names not in deserialized
386: // name-value map.
387: Iterator names = cachedPropertyNames.iterator();
388: Set propertyNames = properties.keySet();
389: while (names.hasNext()) {
390: if (!propertyNames.contains(names.next())) {
391: names.remove(); // remove name from cachedPropertyNames.
392: }
393: }
394: }
395: }
|