001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.openide.options;
042:
043: import org.openide.util.HelpCtx;
044: import org.openide.util.NbBundle;
045: import org.openide.util.SharedClassObject;
046:
047: import java.beans.BeanInfo;
048: import java.beans.IntrospectionException;
049: import java.beans.PropertyDescriptor;
050:
051: import java.io.IOException;
052: import java.io.ObjectInput;
053: import java.io.ObjectOutput;
054:
055: import java.lang.reflect.InvocationTargetException;
056: import java.lang.reflect.Method;
057:
058: import java.util.HashMap;
059: import java.util.Map;
060: import java.util.logging.Level;
061: import java.util.logging.Logger;
062: import org.openide.util.Exceptions;
063:
064: /**
065: * Base class for all system options.
066: * Provides methods for adding
067: * and working with property change and guarantees
068: * that all instances of the same class will share these listeners.
069: * <P>
070: * When a new option is created, it should subclass
071: * <CODE>SystemOption</CODE>, add <em>static</em> variables to it that will hold
072: * the values of properties, and write non-static setters/getters that will
073: * notify all listeners about property changes via
074: * {@link #firePropertyChange}.
075: * <p>JavaBeans introspection is used to find the properties,
076: * so it is possible to use {@link BeanInfo}.
077: *
078: * @author Jaroslav Tulach
079: * @deprecated Use {@link org.openide.util.NbPreferences} instead.
080: */
081: public abstract class SystemOption extends SharedClassObject implements
082: HelpCtx.Provider {
083: /** generated Serialized Version UID */
084: static final long serialVersionUID = 558589201969066966L;
085:
086: /** property to indicate that the option is currently loading its data */
087: private static final Object PROP_LOADING = new Object();
088:
089: /** property to indicate that the option is currently loading its data */
090: private static final Object PROP_STORING = new Object();
091:
092: /** property that holds a Map<String,Object> that stores old values */
093: private static final Object PROP_ORIGINAL_VALUES = new Object();
094:
095: /** this represent null in the map in PROP_ORIGINAL_VALUES */
096: private static final Object NULL = new Object();
097:
098: /** Default constructor. */
099: public SystemOption() {
100: }
101:
102: /** Fire a property change event to all listeners. Delays
103: * this loading when readExternal is active till it finishes.
104: *
105: * @param name the name of the property
106: * @param oldValue the old value
107: * @param newValue the new value
108: */
109: protected void firePropertyChange(String name, Object oldValue,
110: Object newValue) {
111: if ((name != null)
112: && (getProperty("org.openide.util.SharedClassObject.initialize") == null)) { // NOI18N
113:
114: Map originalValues = (Map) getProperty(PROP_ORIGINAL_VALUES);
115:
116: if (originalValues == null) {
117: originalValues = new HashMap();
118: putProperty(PROP_ORIGINAL_VALUES, originalValues);
119: }
120:
121: if (originalValues.get(name) == null) {
122: if (getProperty(name) == null) {
123: // this is supposed to be setter
124: originalValues.put(name, new Box(oldValue));
125: } else {
126: // regular usage of putProperty (....);
127: originalValues.put(name, (oldValue == null) ? NULL
128: : oldValue);
129: }
130: }
131: }
132:
133: if (getProperty(PROP_LOADING) != null) {
134: // somebody is loading, assign any object different than
135: // this to indicate that firing should occure
136: putProperty(PROP_LOADING, PROP_LOADING);
137:
138: // but do not fire the change now
139: return;
140: }
141:
142: super .firePropertyChange(name, oldValue, newValue);
143: }
144:
145: /** Implements the reset by setting back all properties that were
146: * modified. A <em>modified property</em> has fired a
147: * <code>PropertyChangeEvent</code> with
148: * non-null name and non-null old value. The name and value are
149: * remembered and this method sets them back to original value.
150: * <p>
151: * Subclasses are free to override this method and reimplement the
152: * reset by themselves.
153: *
154: * @since 4.46
155: */
156: protected void reset() {
157: synchronized (getLock()) {
158: Map m = (Map) getProperty(PROP_ORIGINAL_VALUES);
159:
160: if ((m == null) || m.isEmpty()) {
161: return;
162: }
163:
164: java.util.Iterator it = m.entrySet().iterator();
165: WHILE: while (it.hasNext()) {
166: Map.Entry e = (Map.Entry) it.next();
167:
168: if (e.getValue() instanceof Box) {
169: Object value = ((Box) e.getValue()).value;
170:
171: try {
172: // gets info about all properties that were added by subclass
173: BeanInfo info = org.openide.util.Utilities
174: .getBeanInfo(getClass(),
175: SystemOption.class);
176: PropertyDescriptor[] desc = info
177: .getPropertyDescriptors();
178:
179: for (int i = 0; i < desc.length; i++) {
180: if (e.getKey().equals(desc[i].getName())) {
181: // our property
182: Method write = desc[i].getWriteMethod();
183:
184: if (write != null) {
185: write.invoke(this ,
186: new Object[] { value });
187: }
188:
189: continue WHILE;
190: }
191: }
192: } catch (InvocationTargetException ex) {
193: // exception thrown
194: Logger.getLogger(SystemOption.class.getName())
195: .log(Level.WARNING, null, ex);
196: } catch (IllegalAccessException ex) {
197: Logger.getLogger(SystemOption.class.getName())
198: .log(Level.WARNING, null, ex);
199: } catch (IntrospectionException ex) {
200: Logger.getLogger(SystemOption.class.getName())
201: .log(Level.WARNING, null, ex);
202: }
203: } else {
204: putProperty(e.getKey(),
205: (e.getValue() == NULL) ? null : e
206: .getValue());
207: }
208: }
209:
210: // reset all remembered values
211: putProperty(PROP_ORIGINAL_VALUES, null);
212: }
213:
214: super .firePropertyChange(null, null, null);
215: }
216:
217: /** Write all properties of this object (or subclasses) to an object output.
218: * @param out the output stream
219: * @exception IOException on error
220: */
221: public void writeExternal(ObjectOutput out) throws IOException {
222: try {
223: // gets info about all properties that were added by subclass
224: BeanInfo info = org.openide.util.Utilities.getBeanInfo(
225: getClass(), SystemOption.class);
226: PropertyDescriptor[] desc = info.getPropertyDescriptors();
227:
228: putProperty(PROP_STORING, this );
229:
230: Object[] param = new Object[0];
231:
232: synchronized (getLock()) {
233: // write all properties that have getter to stream
234: for (int i = 0; i < desc.length; i++) {
235: // skip readonly Properties
236: if (desc[i].getWriteMethod() == null) {
237: continue;
238: }
239: String propName = desc[i].getName();
240: Object value = getProperty(propName);
241: boolean fromRead;
242: // JST: this code handles the case when somebody needs to store
243: // different value then is the value of get/set method.
244: // in such case value (from getProperty) is not of the type
245: // of the getter/setter and is used instead of the value from getXXXX
246: Method read = desc[i].getReadMethod();
247:
248: if (read == null) {
249: continue;
250: }
251: if ((value == null)
252: || isInstance(desc[i].getPropertyType(),
253: value)) {
254: fromRead = true;
255: try {
256: value = read.invoke(this , param);
257: } catch (InvocationTargetException ex) {
258: throw (IOException) new IOException(
259: NbBundle.getMessage(
260: SystemOption.class,
261: "EXC_InGetter", getClass(),
262: desc[i].getName()))
263: .initCause(ex);
264: } catch (IllegalAccessException ex) {
265: throw (IOException) new IOException(
266: NbBundle.getMessage(
267: SystemOption.class,
268: "EXC_InGetter", getClass(),
269: desc[i].getName()))
270: .initCause(ex);
271: }
272: } else {
273: fromRead = false;
274: }
275: // writes name of the property
276: out.writeObject(propName);
277: // writes its value
278: out.writeObject(value);
279: // from getter or stored prop?
280: out.writeObject(fromRead ? Boolean.TRUE
281: : Boolean.FALSE);
282: }
283: }
284: } catch (IntrospectionException ex) {
285: // if we cannot found any info about properties
286: } finally {
287: putProperty(PROP_STORING, null);
288: }
289:
290: // write null to signal end of properties
291: out.writeObject(null);
292: }
293:
294: /** Returns true if the object is assignable to the class.
295: * Also if the class is primitive and the object is of the matching wrapper type.
296: */
297: private static boolean isInstance(Class c, Object o) {
298: return c.isInstance(o)
299: || ((c == Byte.TYPE) && (o instanceof Byte))
300: || ((c == Short.TYPE) && (o instanceof Short))
301: || ((c == Integer.TYPE) && (o instanceof Integer))
302: || ((c == Long.TYPE) && (o instanceof Long))
303: || ((c == Float.TYPE) && (o instanceof Float))
304: || ((c == Double.TYPE) && (o instanceof Double))
305: || ((c == Boolean.TYPE) && (o instanceof Boolean))
306: || ((c == Character.TYPE) && (o instanceof Character));
307: }
308:
309: /** Read all properties of this object (or subclasses) from an object input.
310: * If there is a problem setting the value of any property, that property will be ignored;
311: * other properties should still be set.
312: * @param in the input stream
313: * @exception IOException on error
314: * @exception ClassNotFound if a class used to restore the system option is not found
315: */
316: public void readExternal(ObjectInput in) throws IOException,
317: ClassNotFoundException {
318: // hashtable that maps names of properties to setter methods
319: HashMap map = new HashMap();
320:
321: try {
322: synchronized (getLock()) {
323: // indicate that we are loading files
324: putProperty(PROP_LOADING, this );
325:
326: try {
327: // gets info about all properties that were added by subclass
328: BeanInfo info = org.openide.util.Utilities
329: .getBeanInfo(getClass(), SystemOption.class);
330: PropertyDescriptor[] desc = info
331: .getPropertyDescriptors();
332:
333: // write all properties that have getter to stream
334: for (int i = 0; i < desc.length; i++) {
335: Method m = desc[i].getWriteMethod();
336:
337: /*if (m == null) {
338: System.out.println ("HOW HOW HOW HOWHOWHOWHOWHWO: " + desc[i].getName() + " XXX " + getClass());
339: throw new IOException (new MessageFormat (NbBundle.getBundle (SystemOption.class).getString ("EXC_InSetter")).
340: format (new Object[] {getClass (), desc[i].getName ()})
341: );
342: } */
343: map.put(desc[i].getName(), m);
344: }
345: } catch (IntrospectionException ex) {
346: // if we cannot found any info about properties
347: // leave the hashtable empty and only read stream till null is found
348: Logger.getLogger(SystemOption.class.getName()).log(
349: Level.WARNING, null, ex);
350: }
351:
352: String preread = null;
353:
354: do {
355: // read the name of property
356: String name;
357:
358: if (preread != null) {
359: name = preread;
360: preread = null;
361: } else {
362: name = (String) in.readObject();
363: }
364:
365: // break if the end of property stream is found
366: if (name == null) {
367: break;
368: }
369:
370: // read the value of property
371: Object value = in.readObject();
372:
373: // read flag - use the setter method or store as property?
374: Object useMethodObject = in.readObject();
375: boolean useMethod;
376: boolean nullRead = false; // this should be last processed property?
377:
378: if (useMethodObject == null) {
379: useMethod = true;
380: nullRead = true;
381: } else if (useMethodObject instanceof String) {
382: useMethod = true;
383: preread = (String) useMethodObject;
384: } else {
385: useMethod = ((Boolean) useMethodObject)
386: .booleanValue();
387: }
388:
389: if (useMethod) {
390: // set the value
391: Method write = (Method) map.get(name);
392:
393: if (write != null) {
394: // if you have where to set the value
395: try {
396: write.invoke(this ,
397: new Object[] { value });
398: } catch (Exception ex) {
399: String msg = "Cannot call " + write
400: + " for property "
401: + getClass().getName() + "."
402: + name; // NOI18N
403: Exceptions.attachMessage(ex, msg);
404: Logger.getLogger(
405: SystemOption.class.getName())
406: .log(Level.WARNING, null, ex);
407: }
408: }
409: } else {
410: putProperty(name, value, false);
411: }
412:
413: if (nullRead) {
414: break;
415: }
416: } while (true);
417: }
418: } finally {
419: // get current state
420: if (this != getProperty(PROP_LOADING)) {
421: // some changes should be fired
422: // loading finished
423: putProperty(PROP_LOADING, null);
424: firePropertyChange(null, null, null);
425: } else {
426: // loading finished
427: putProperty(PROP_LOADING, null);
428: }
429: }
430: }
431:
432: protected boolean clearSharedData() {
433: return false;
434: }
435:
436: /**
437: * Get the name of this system option.
438: * The default implementation just uses the {@link #displayName display name}.
439: * @return the name
440: */
441: public final String getName() {
442: return displayName();
443: }
444:
445: /**
446: * Get the display name of this system option.
447: * @return the display name
448: */
449: public abstract String displayName();
450:
451: /** Get context help for this system option.
452: * @return context help
453: */
454: public HelpCtx getHelpCtx() {
455: return new HelpCtx(SystemOption.class);
456: }
457:
458: /** Allows subclasses to test whether the change of a property
459: * is invoked from readExternal method or by external change invoked
460: * by any other program.
461: *
462: * @return true if the readExternal method is in progress
463: */
464: protected final boolean isReadExternal() {
465: return getProperty(PROP_LOADING) != null;
466: }
467:
468: /** Allows subclasses to test whether the getter of a property
469: * is invoked from writeExternal method or by any other part of the program.
470: *
471: * @return true if the writeExternal method is in progress
472: */
473: protected final boolean isWriteExternal() {
474: return getProperty(PROP_STORING) != null;
475: }
476:
477: /** A wrapper object to indicate that a setter should be called
478: * when reseting to default.
479: */
480: private static final class Box extends Object {
481: public Object value;
482:
483: public Box(Object v) {
484: this .value = v;
485: }
486: }
487: // end of Box
488: }
|