/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.options; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import org.openide.ErrorManager; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.SharedClassObject; /** Base class for all system options. * Provides methods for adding * and working with property change and guarantees * that all instances of the same class will share these listeners. * <P> * When a new option is created, it should subclass * <CODE>SystemOption</CODE>, add <em>static</em> variables to it that will hold * the values of properties, and write non-static setters/getters that will * notify all listeners about property changes via * {@link #firePropertyChange}. * <p>JavaBeans introspection is used to find the properties, * so it is possible to use {@link BeanInfo}. * * @author Jaroslav Tulach */ public abstract class SystemOption extends SharedClassObject implements HelpCtx.Provider { /** generated Serialized Version UID */ static final long serialVersionUID = 558589201969066966L; /** property to indicate that the option is currently loading its data */ private static final Object PROP_LOADING = new Object (); /** property to indicate that the option is currently loading its data */ private static final Object PROP_STORING = new Object (); /** Default constructor. */ public SystemOption() { // SystemOption must declare this property in order to be correctly deserialized // by SharedClassObject.findObject function putProperty ("netbeans.systemoption.hack", null); // NOI18N } /** Fire a property change event to all listeners. Delays * this loading when readExternal is active till it finishes. * * @param name the name of the property * @param oldValue the old value * @param newValue the new value */ protected void firePropertyChange ( String name, Object oldValue, Object newValue ) { if (getProperty (PROP_LOADING) != null) { // somebody is loading, assign any object different than // this to indicate that firing should occure putProperty (PROP_LOADING, PROP_LOADING); // but do not fire the change now return; } super.firePropertyChange (name, oldValue, newValue); } /** Write all properties of this object (or subclasses) to an object output. * @param out the output stream * @exception IOException on error */ public void writeExternal (ObjectOutput out) throws IOException { try { // gets info about all properties that were added by subclass BeanInfo info = org.openide.util.Utilities.getBeanInfo (getClass (), SystemOption.class); PropertyDescriptor[] desc = info.getPropertyDescriptors (); putProperty (PROP_STORING, this); Object[] param = new Object[0]; synchronized (getLock ()) { // write all properties that have getter to stream for (int i = 0; i < desc.length; i++) { // skip readonly Properties if (desc[i].getWriteMethod () == null) { continue; } String propName = desc[i].getName(); Object value = getProperty(propName); boolean fromRead; // JST: this code handles the case when somebody needs to store // different value then is the value of get/set method. // in such case value (from getProperty) is not of the type // of the getter/setter and is used instead of the value from getXXXX Method read = desc[i].getReadMethod(); if (read == null) { continue; } if (value == null || isInstance(desc[i].getPropertyType(), value)) { fromRead = true; try { value = read.invoke (this, param); } catch (InvocationTargetException ex) { // exception thrown IOException ne = new IOException (NbBundle.getMessage ( SystemOption.class, "EXC_InGetter", getClass (), desc[i].getName () )); ErrorManager.getDefault ().annotate (ne, ex); throw ne; } catch (IllegalAccessException ex) { // exception thrown IOException ne = new IOException (NbBundle.getMessage ( SystemOption.class, "EXC_InGetter", getClass (), desc[i].getName () )); ErrorManager.getDefault ().annotate (ne, ex); throw ne; } } else { fromRead = false; } // writes name of the property out.writeObject (propName); // writes its value out.writeObject (value); // from getter or stored prop? out.writeObject(fromRead ? Boolean.TRUE : Boolean.FALSE); } } } catch (IntrospectionException ex) { // if we cannot found any info about properties } finally { putProperty (PROP_STORING, null); } // write null to signal end of properties out.writeObject (null); } /** Returns true if the object is assignable to the class. * Also if the class is primitive and the object is of the matching wrapper type. */ private static boolean isInstance(Class c, Object o) { return c.isInstance(o) || (c == Byte.TYPE && (o instanceof Byte)) || (c == Short.TYPE && (o instanceof Short)) || (c == Integer.TYPE && (o instanceof Integer)) || (c == Long.TYPE && (o instanceof Long)) || (c == Float.TYPE && (o instanceof Float)) || (c == Double.TYPE && (o instanceof Double)) || (c == Boolean.TYPE && (o instanceof Boolean)) || (c == Character.TYPE && (o instanceof Character)); } /** Read all properties of this object (or subclasses) from an object input. * If there is a problem setting the value of any property, that property will be ignored; * other properties should still be set. * @param in the input stream * @exception IOException on error * @exception ClassNotFound if a class used to restore the system option is not found */ public void readExternal (ObjectInput in) throws IOException, ClassNotFoundException { // hashtable that maps names of properties to setter methods HashMap map = new HashMap (); try { synchronized (getLock ()) { // indicate that we are loading files putProperty (PROP_LOADING, this); try { // gets info about all properties that were added by subclass BeanInfo info = org.openide.util.Utilities.getBeanInfo (getClass (), SystemOption.class); PropertyDescriptor[] desc = info.getPropertyDescriptors (); // write all properties that have getter to stream for (int i = 0; i < desc.length; i++) { Method m = desc[i].getWriteMethod (); /*if (m == null) { System.out.println ("HOW HOW HOW HOWHOWHOWHOWHWO: " + desc[i].getName() + " XXX " + getClass()); throw new IOException (new MessageFormat (NbBundle.getBundle (SystemOption.class).getString ("EXC_InSetter")). format (new Object[] {getClass (), desc[i].getName ()}) ); } */ map.put (desc[i].getName (), m ); } } catch (IntrospectionException ex) { // if we cannot found any info about properties // leave the hashtable empty and only read stream till null is found ErrorManager.getDefault().notify ( ErrorManager.INFORMATIONAL, ex); } String preread = null; do { // read the name of property String name; if (preread != null) { name = preread; preread = null; } else { name = (String)in.readObject(); } // break if the end of property stream is found if (name == null) break; // read the value of property Object value = in.readObject (); // read flag - use the setter method or store as property? Object useMethodObject = in.readObject(); boolean useMethod; boolean nullRead = false; // this should be last processed property? if (useMethodObject == null) { useMethod = true; nullRead = true; } else if (useMethodObject instanceof String) { useMethod = true; preread = (String) useMethodObject; } else { useMethod = ((Boolean) useMethodObject).booleanValue(); } if (useMethod) { // set the value Method write = (Method)map.get (name); if (write != null) { // if you have where to set the value try { write.invoke (this, new Object[] { value }); } catch (Exception ex) { ErrorManager.getDefault ().notify ( ErrorManager.INFORMATIONAL, ex ); } } } else { putProperty(name, value, false); } if (nullRead) { break; } } while (true); } } finally { // get current state if (this != getProperty (PROP_LOADING)) { // some changes should be fired // loading finished putProperty (PROP_LOADING, null); firePropertyChange (null, null, null); } else { // loading finished putProperty (PROP_LOADING, null); } } } protected boolean clearSharedData () { return false; } /** * Get the name of this system option. * The default implementation just uses the {@link #displayName display name}. * @return the name */ public final String getName () { return displayName (); } /** * Get the display name of this system option. * @return the display name */ public abstract String displayName (); /** Get context help for this system option. * @return context help */ public HelpCtx getHelpCtx () { return new HelpCtx (SystemOption.class); } /** Allows subclasses to test whether the change of a property * is invoked from readExternal method or by external change invoked * by any other program. * * @return true if the readExternal method is in progress */ protected final boolean isReadExternal () { return getProperty (PROP_LOADING) != null; } /** Allows subclasses to test whether the getter of a property * is invoked from writeExternal method or by any other part of the program. * * @return true if the writeExternal method is in progress */ protected final boolean isWriteExternal () { return getProperty (PROP_STORING) != null; } }