0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.openide.util;
0043:
0044: import java.beans.PropertyChangeListener;
0045: import java.beans.PropertyChangeSupport;
0046: import java.io.Externalizable;
0047: import java.io.IOException;
0048: import java.io.ObjectInput;
0049: import java.io.ObjectInputStream;
0050: import java.io.ObjectOutput;
0051: import java.io.ObjectOutputStream;
0052: import java.io.ObjectStreamException;
0053: import java.io.Serializable;
0054: import java.lang.ref.WeakReference;
0055: import java.lang.reflect.Method;
0056: import java.security.AccessController;
0057: import java.security.PrivilegedActionException;
0058: import java.security.PrivilegedExceptionAction;
0059: import java.util.HashMap;
0060: import java.util.HashSet;
0061: import java.util.Map;
0062: import java.util.Set;
0063: import java.util.WeakHashMap;
0064: import java.util.logging.Level;
0065: import java.util.logging.Logger;
0066:
0067: /** Shared object that allows different instances of the same class
0068: * to share common data.
0069: * <p>The data are shared only between instances of the same class (not subclasses).
0070: * Thus, such "variables" have neither instance nor static behavior.
0071: *
0072: * @author Ian Formanek, Jaroslav Tulach
0073: */
0074: public abstract class SharedClassObject extends Object implements
0075: Externalizable {
0076: /** serialVersionUID */
0077: private static final long serialVersionUID = 4527891234589143259L;
0078:
0079: /** property change support (PropertyChangeSupport) */
0080: private static final Object PROP_SUPPORT = new Object();
0081:
0082: /** Map (Class, DataEntry) that maps Classes to maps of any objects */
0083: private static final Map<Class, DataEntry> values = new WeakHashMap<Class, DataEntry>(
0084: 37);
0085:
0086: /** A set of all classes for which we are currently inside createInstancePrivileged.
0087: * If a SCO constructor is called when an instance of that class already exists, normally
0088: * this will print a warning. However it is common to create an instance inside a static
0089: * block; in this case the constructor is actually called twice. Only the first instance
0090: * is ever returned, but this set ensures that no warning is printed during creation of the
0091: * second instance (because it is nobody's fault and it will be handled OK).
0092: * Map from class name to nesting count.
0093: */
0094: private static final Map<String, Integer> instancesBeingCreated = new HashMap<String, Integer>(
0095: 7);
0096:
0097: /** Set of classes to not warn about any more.
0098: * Names only.
0099: */
0100: private static final Set<String> alreadyWarnedAboutDupes = new HashSet<String>();
0101: private static final Logger err = Logger
0102: .getLogger("org.openide.util.SharedClassObject"); // NOI18N
0103:
0104: /** data entry for this class */
0105: private final DataEntry dataEntry;
0106:
0107: /** Lock for the object */
0108: private Object lock;
0109:
0110: /** hard reference to primary instance of this class
0111: * This is here not to allow the finalization till at least
0112: * one object exists
0113: */
0114: private final SharedClassObject first;
0115:
0116: /** Stack trace indicating where the first instance was created.
0117: * This is only set on the first instance; and only with the error manager on.
0118: */
0119: private Throwable firstTrace = null;
0120:
0121: /** Set by {@link SystemOption}s through the special property, see {@link #putProperty}.
0122: * SystemOption needs special handling, e.g. it needs to be deserialized by the lookup
0123: * after its first instance is created in {@link #findObject} method, only
0124: * SystemOption can be reset.
0125: */
0126: private boolean systemOption = false;
0127:
0128: /** If set, this means we have a system option waiting to be loaded from lookup.
0129: * If anyone changes a property on it before this happens, the exception is filled in,
0130: * so we know when it is loaded that something went wrong.
0131: */
0132: private boolean waitingOnSystemOption = false;
0133: private IllegalStateException prematureSystemOptionMutation = null;
0134: private boolean inReadExternal = false;
0135:
0136: /** Check that addNotify, removeNotify, initialize call super sometime. */
0137: private boolean addNotifySuper;
0138:
0139: /** Check that addNotify, removeNotify, initialize call super sometime. */
0140: private boolean removeNotifySuper;
0141:
0142: /** Check that addNotify, removeNotify, initialize call super sometime. */
0143: private boolean initializeSuper;
0144:
0145: /** Create a shared object.
0146: * Typically shared-class constructors should not take parameters, since there
0147: * will conventionally be no instance variables.
0148: * @see #initialize
0149: */
0150: protected SharedClassObject() {
0151: synchronized (getLock()) {
0152: DataEntry de = values.get(getClass());
0153:
0154: //System.err.println("SCO create: " + this + " de=" + de);
0155: if (de == null) {
0156: de = new DataEntry();
0157: values.put(getClass(), de);
0158: }
0159:
0160: dataEntry = de;
0161: de.increase();
0162:
0163: // finds reference for the first object of the class
0164: first = de.first(this );
0165: }
0166:
0167: if (first != null) {
0168: if (first == this ) {
0169: // Could be a performance hit, so only do this when developing.
0170: if (err.isLoggable(Level.FINE)) {
0171: Throwable t = new Throwable(
0172: "First instance created here"); // NOI18N
0173: t.fillInStackTrace();
0174: first.firstTrace = t;
0175: }
0176: } else {
0177: String clazz = getClass().getName();
0178: boolean creating;
0179:
0180: synchronized (instancesBeingCreated) {
0181: creating = instancesBeingCreated.containsKey(clazz);
0182: }
0183:
0184: if (creating) {
0185: //System.err.println ("Nesting: " + getClass ().getName () + " " + instancesBeingCreated.get (clazz));
0186: } else {
0187: if (!alreadyWarnedAboutDupes.contains(clazz)) {
0188: alreadyWarnedAboutDupes.add(clazz);
0189:
0190: Exception e = new IllegalStateException(
0191: "Warning: multiple instances of shared class "
0192: + clazz + " created."); // NOI18N
0193:
0194: if (first.firstTrace != null) {
0195: err.log(Level.WARNING, "First stack trace",
0196: first.firstTrace);
0197: } else {
0198: err
0199: .warning("(Run with -J-Dorg.openide.util.SharedClassObject.level=0 for more details.)"); // NOI18N
0200: }
0201:
0202: err.log(Level.WARNING, null, e);
0203: }
0204: }
0205: }
0206: }
0207: }
0208:
0209: /* Calls a referenceLost to decrease the counter on the shared data.
0210: * This method is final so no descendant can override it, but
0211: * it calls the method unreferenced() that can be overriden to perform any
0212: * additional tasks on finalizing.
0213: */
0214: protected final void finalize() throws Throwable {
0215: referenceLost();
0216: }
0217:
0218: /** Indicate whether the shared data of the last existing instance of this class
0219: * should be cleared when that instance is finalized.
0220: *
0221: * Subclasses may perform additional tasks
0222: * on finalization if desired. This method should be overridden
0223: * in lieu of {@link #finalize}.
0224: * <p>The default implementation returns <code>true</code>.
0225: * Classes which have precious shared data may want to return <code>false</code>, so that
0226: * all instances may be finalized, after which new instances will pick up the same shared variables
0227: * without requiring a recalculation.
0228: *
0229: * @return <code>true</code> if all shared data should be cleared,
0230: * <code>false</code> if it should stay in memory
0231: */
0232: protected boolean clearSharedData() {
0233: return true;
0234: }
0235:
0236: /** Test whether the classes of the compared objects are the same.
0237: * @param obj the object to compare to
0238: * @return <code>true</code> if the classes are equal
0239: */
0240: public final boolean equals(Object obj) {
0241: return ((obj instanceof SharedClassObject) && (getClass()
0242: .equals(obj.getClass())));
0243: }
0244:
0245: /** Get a hashcode of the shared class.
0246: * @return the hash code
0247: */
0248: public final int hashCode() {
0249: return getClass().hashCode();
0250: }
0251:
0252: /** Obtain lock for synchronization on manipulation with this
0253: * class.
0254: * Can be used by subclasses when performing nonatomic writes, e.g.
0255: * @return an arbitrary synchronizable lock object
0256: */
0257: protected final Object getLock() {
0258: if (lock == null) {
0259: lock = getClass().getName().intern();
0260: }
0261:
0262: return lock;
0263: }
0264:
0265: /** Should be called from within a finalize method to manage references
0266: * to the shared data (when the last reference is lost, the object is
0267: * removed)
0268: */
0269: private void referenceLost() {
0270: //System.err.println ("SharedClassObject.referenceLost:");
0271: //System.err.println ("\tLock: " + getLock());
0272: //System.err.println ("\tDataEntry: " + dataEntry);
0273: //System.err.println ("\tValues: " + values.containsKey(getClass()));
0274: synchronized (getLock()) {
0275: if ((dataEntry == null) || (dataEntry.decrease() == 0)) {
0276: if (clearSharedData()) {
0277: // clears the data
0278: values.remove(getClass());
0279: }
0280: }
0281: }
0282:
0283: //System.err.println("\tValues after: " + values.containsKey(getClass()));
0284: }
0285:
0286: /** Set a shared variable.
0287: * Automatically {@link #getLock locks}.
0288: * @param key name of the property
0289: * @param value value for that property (may be null)
0290: * @return the previous value assigned to the property, or <code>null</code> if none
0291: */
0292: protected final Object putProperty(Object key, Object value) {
0293: if (key == null) {
0294: throw new NullPointerException(
0295: "Tried to pass null key (value=" + value
0296: + ") to putProperty"); // NOI18N
0297: }
0298:
0299: synchronized (getLock()) {
0300: if (waitingOnSystemOption && (key != PROP_SUPPORT)
0301: && (prematureSystemOptionMutation == null)
0302: && !dataEntry.isInInitialize() && !inReadExternal) {
0303: // See below in findObject. Note that if we are still in initialize(),
0304: // it is harmless to set default values of properties, and from readExternal()
0305: // it is expected.
0306: prematureSystemOptionMutation = new IllegalStateException(
0307: "...setting property here..."); // NOI18N
0308: }
0309:
0310: return dataEntry.getMap(this ).put(key, value);
0311:
0312: //return dataEntry.getMap().put (key, value);
0313: }
0314: }
0315:
0316: /** Set a shared variable available only for string names.
0317: * Automatically {@link #getLock locks}.
0318: * <p><strong>Important:</strong> remember that <code>SharedClassObject</code>s
0319: * are like singleton beans; when you use <code>putProperty</code> with a value
0320: * of <code>true</code>, or call {@link #firePropertyChange}, you must consider that
0321: * the property name should match the JavaBeans name for a natural (introspected) property
0322: * for the bean, if such a property uses this key. For example, if you have a method
0323: * <code>getFoo</code> which uses {@link #getProperty} and a method <code>setFoo</code>
0324: * which uses <code>putProperty(..., true)</code>, then the key used <em>must</em>
0325: * be named <code>foo</code> (assuming you did not override this name in a BeanInfo).
0326: * Otherwise various listeners may not be prepared for the property change and may just
0327: * ignore it. For example, the property sheet for a <a href="@org-openide-nodes@/org/openide/nodes/BeanNode.html">BeanNode</a> based on a
0328: * <code>SharedClassObject</code> which stores its properties using a misnamed key
0329: * will probably not refresh correctly.
0330: * @param key name of the property
0331: * @param value value for that property (may be null)
0332: * @param notify should all listeners be notified about property change?
0333: * @return the previous value assigned to the property, or <code>null</code> if none
0334: */
0335: protected final Object putProperty(String key, Object value,
0336: boolean notify) {
0337: Object previous = putProperty(key, value);
0338:
0339: if (notify) {
0340: firePropertyChange(key, previous, value);
0341: }
0342:
0343: return previous;
0344: }
0345:
0346: /** Get a shared variable.
0347: * Automatically {@link #getLock locks}.
0348: * @param key name of the property
0349: * @return value of the property, or <code>null</code> if none
0350: */
0351: protected final Object getProperty(Object key) {
0352: synchronized (getLock()) {
0353: //System.err.println("SCO: " + this + " get: " + key + " de=" + dataEntry);
0354: if ("org.openide.util.SharedClassObject.initialize"
0355: .equals(key)) { // NOI18N
0356:
0357: return dataEntry.isInInitialize() ? Boolean.TRUE : null;
0358: }
0359:
0360: return dataEntry.get(this , key);
0361: }
0362: }
0363:
0364: /** Initialize shared state.
0365: * Should use {@link #putProperty} to set up variables.
0366: * Subclasses should always call the super method.
0367: * <p>This method need <em>not</em> be called explicitly; it will be called once
0368: * the first time a given shared class is used (not for each instance!).
0369: */
0370: protected void initialize() {
0371: initializeSuper = true;
0372: }
0373:
0374: /** Adds the specified property change listener to receive property
0375: * change events from this object.
0376: * @param l the property change listener
0377: */
0378: public final void addPropertyChangeListener(PropertyChangeListener l) {
0379: boolean noListener;
0380:
0381: synchronized (getLock()) {
0382: // System.out.println ("added listener: " + l + " to: " + getClass ()); // NOI18N
0383: PropertyChangeSupport supp = (PropertyChangeSupport) getProperty(PROP_SUPPORT);
0384:
0385: if (supp == null) {
0386: // System.out.println ("Creating support"); // NOI18N
0387: putProperty(PROP_SUPPORT,
0388: supp = new PropertyChangeSupport(this ));
0389: }
0390:
0391: noListener = !supp.hasListeners(null);
0392: supp.addPropertyChangeListener(l);
0393: }
0394:
0395: if (noListener) {
0396: addNotifySuper = false;
0397: addNotify();
0398:
0399: if (!addNotifySuper) {
0400: // [PENDING] theoretical race condition for this warning if listeners are added
0401: // and removed very quickly from two threads, I guess, and addNotify() impl is slow
0402: String msg = "You must call super.addNotify() from "
0403: + getClass().getName() + ".addNotify()"; // NOI18N
0404: err.warning(msg);
0405: }
0406: }
0407: }
0408:
0409: /**
0410: * Removes the specified property change listener so that it
0411: * no longer receives property change events from this object.
0412: * @param l the property change listener
0413: */
0414: public final void removePropertyChangeListener(
0415: PropertyChangeListener l) {
0416: boolean callRemoved;
0417:
0418: synchronized (getLock()) {
0419: PropertyChangeSupport supp = (PropertyChangeSupport) getProperty(PROP_SUPPORT);
0420:
0421: if (supp == null) {
0422: return;
0423: }
0424:
0425: boolean hasListener = supp.hasListeners(null);
0426: supp.removePropertyChangeListener(l);
0427: callRemoved = hasListener && !supp.hasListeners(null);
0428: }
0429:
0430: if (callRemoved) {
0431: putProperty(PROP_SUPPORT, null); // clean the PCS, see #25417
0432: removeNotifySuper = false;
0433: removeNotify();
0434:
0435: if (!removeNotifySuper) {
0436: String msg = "You must call super.removeNotify() from "
0437: + getClass().getName() + ".removeNotify()"; // NOI18N
0438: err.warning(msg);
0439: }
0440: }
0441: }
0442:
0443: /** Notify subclasses that the first listener has been added to this object.
0444: * Subclasses should always call the super method.
0445: * The default implementation does nothing.
0446: */
0447: protected void addNotify() {
0448: addNotifySuper = true;
0449: }
0450:
0451: /** Notify subclasses that the last listener has been removed from this object.
0452: * Subclasses should always call the super method.
0453: * The default implementation does nothing.
0454: */
0455: protected void removeNotify() {
0456: removeNotifySuper = true;
0457: }
0458:
0459: /** Fire a property change event to all listeners.
0460: * @param name the name of the property
0461: * @param oldValue the old value
0462: * @param newValue the new value
0463: */
0464:
0465: // not final - SystemOption overrides it, e.g.
0466: protected void firePropertyChange(String name, Object oldValue,
0467: Object newValue) {
0468: PropertyChangeSupport supp = (PropertyChangeSupport) getProperty(PROP_SUPPORT);
0469:
0470: if (supp != null) {
0471: supp.firePropertyChange(name, oldValue, newValue);
0472: }
0473: }
0474:
0475: /** Writes nothing to the stream.
0476: * @param oo ignored
0477: */
0478: public void writeExternal(ObjectOutput oo) throws IOException {
0479: }
0480:
0481: /** Reads nothing from the stream.
0482: * @param oi ignored
0483: */
0484: public void readExternal(ObjectInput oi) throws IOException,
0485: ClassNotFoundException {
0486: }
0487:
0488: /** This method provides correct handling of serialization and deserialization.
0489: * When serialized the method writeExternal is used to store the state.
0490: * When deserialized first an instance is located by a call to findObject (clazz, true)
0491: * and then a method readExternal is called to read its state from stream.
0492: * <P>
0493: * This allows to have only one instance of the class in the system and work
0494: * only with it.
0495: *
0496: * @return write replace object that handles the described serialization/deserialization process
0497: */
0498: protected Object writeReplace() {
0499: return new WriteReplace(this );
0500: }
0501:
0502: /** Obtain an instance of the desired class, if there is one.
0503: * @param clazz the shared class to look for
0504: * @return the instance, or <code>null</code> if such does not exists
0505: */
0506: public static <T extends SharedClassObject> T findObject(
0507: Class<T> clazz) {
0508: return findObject(clazz, false);
0509: }
0510:
0511: /** Find an existing object, possibly creating a new one as needed.
0512: * To create a new instance the class must be public and have a public
0513: * default constructor.
0514: *
0515: * @param clazz the class of the object to find (must extend <code>SharedClassObject</code>)
0516: * @param create <code>true</code> if the object should be created if it does not yet exist
0517: * @return an instance, or <code>null</code> if there was none and <code>create</code> was <code>false</code>
0518: * @exception IllegalArgumentException if a new instance could not be created for some reason
0519: */
0520: public static <T extends SharedClassObject> T findObject(
0521: Class<T> clazz, boolean create) {
0522: // synchronizing on the same object as returned from getLock()
0523: synchronized (clazz.getName().intern()) {
0524: DataEntry de = values.get(clazz);
0525:
0526: // either null or the object
0527: SharedClassObject obj = (de == null) ? null : de.get();
0528: boolean created = false;
0529:
0530: if ((obj == null) && create) {
0531: // try to create new instance
0532: SetAccessibleAction action = new SetAccessibleAction(
0533: clazz);
0534:
0535: try {
0536: obj = AccessController.doPrivileged(action);
0537: } catch (PrivilegedActionException e) {
0538: Exception ex = e.getException();
0539: IllegalArgumentException newEx = new IllegalArgumentException(
0540: ex.toString());
0541: newEx.initCause(ex);
0542: throw newEx;
0543: }
0544:
0545: created = true;
0546: }
0547:
0548: de = values.get(clazz);
0549:
0550: if (de != null) {
0551: SharedClassObject obj2 = de.get();
0552:
0553: if ((obj != null) && (obj != obj2)) {
0554: // Tricked! The static initializer for the class called findObject on itself.
0555: // So we created two instances of it.
0556: // Returning only the first (that created by the static initializer, rather
0557: // than by us explicitly), to avoid duplication.
0558: //System.err.println ("Nesting #2: " + clazz.getName ());
0559: if ((obj2 == null) && create) {
0560: throw new IllegalStateException(
0561: "Inconsistent state: " + clazz); // NOI18N
0562: }
0563:
0564: return clazz.cast(obj2);
0565: }
0566: }
0567:
0568: if (created) {
0569: // This hack was created due to the remove of SystemOptions deserialization
0570: // from project open operation, all SystemOptions are deserialized at this place
0571: // the first time anybody asks for the option.
0572: // It's crutial to do this just for SystemOptions and not for any other SharedClassObject,
0573: // otherwise it can cause deadlocks.
0574: // Lookup in the active session is used to find serialized state of the option,
0575: // if such state exists it is deserialized before the object is returned from lookup.
0576: if (obj.isSystemOption()) {
0577: // Lookup will find serialized version of searched object and deserialize it
0578: final Lookup.Result<T> r = Lookup.getDefault()
0579: .lookup(new Lookup.Template<T>(clazz));
0580:
0581: if (r.allInstances().isEmpty()) {
0582: // #17711: folder lookup not yet initialized. Try to load the option later.
0583: // In the meantime the default state of the option will be available.
0584: // If any attempt is made to change the option, _and_ it is later loaded,
0585: // then we print a stack trace of the mutation for debugging (since the mutations
0586: // would get clobbered by loading the settings from layer or whatever).
0587: obj.waitingOnSystemOption = true;
0588:
0589: final SharedClassObject _obj = obj;
0590: final IllegalStateException start = new IllegalStateException(
0591: "Making a SystemOption here that is not in lookup..."); // NOI18N
0592:
0593: class SOLoader implements LookupListener {
0594: public void resultChanged(LookupEvent ev) {
0595: if (!r.allInstances().isEmpty()) {
0596: // Got it.
0597: r
0598: .removeLookupListener(SOLoader.this );
0599:
0600: synchronized (_obj.getLock()) {
0601: _obj.waitingOnSystemOption = false;
0602:
0603: if (_obj.prematureSystemOptionMutation != null) {
0604: warn(start);
0605: warn(_obj.prematureSystemOptionMutation);
0606: warn(new IllegalStateException(
0607: "...and maybe getting clobbered here, see #17711.")); // NOI18N
0608: _obj.prematureSystemOptionMutation = null;
0609: }
0610: }
0611: }
0612: }
0613: }
0614: r.addLookupListener(new SOLoader());
0615: }
0616: }
0617: }
0618:
0619: if ((obj == null) && create) {
0620: throw new IllegalStateException("Inconsistent state: "
0621: + clazz); // NOI18N
0622: }
0623:
0624: return clazz.cast(obj);
0625: }
0626: }
0627:
0628: /** checks whether we are instance of system option.
0629: */
0630: private boolean isSystemOption() {
0631: Class c = this .getClass();
0632:
0633: while (c != SharedClassObject.class) {
0634: if ("org.openide.options.SystemOption".equals(c.getName())) {
0635: return true; // NOI18N
0636: }
0637:
0638: c = c.getSuperclass();
0639: }
0640:
0641: return false;
0642: }
0643:
0644: // See above:
0645: private static void warn(Throwable t) {
0646: err.log(Level.WARNING, null, t);
0647: }
0648:
0649: static SharedClassObject createInstancePrivileged(
0650: Class<? extends SharedClassObject> clazz) throws Exception {
0651: java.lang.reflect.Constructor<? extends SharedClassObject> c = clazz
0652: .getDeclaredConstructor(new Class[0]);
0653: c.setAccessible(true);
0654:
0655: String name = clazz.getName();
0656: assert instancesBeingCreated != null;
0657:
0658: synchronized (instancesBeingCreated) {
0659: Integer i = instancesBeingCreated.get(name);
0660: instancesBeingCreated.put(name,
0661: (i == null) ? new Integer(1) : new Integer(i
0662: .intValue() + 1));
0663: }
0664:
0665: try {
0666: return c.newInstance(new Object[0]);
0667: } finally {
0668: synchronized (instancesBeingCreated) {
0669: Integer i = instancesBeingCreated.get(name);
0670:
0671: if (i.intValue() == 1) {
0672: instancesBeingCreated.remove(name);
0673: } else {
0674: instancesBeingCreated.put(name, new Integer(i
0675: .intValue() - 1));
0676: }
0677: }
0678:
0679: c.setAccessible(false);
0680: }
0681: }
0682:
0683: /** Is called by the infrastructure in cases when a clean instance is requested.
0684: * As instances of <code>SharedClassObject</code> are singletons, there is
0685: * no way how to create new instance that would not contain the same data
0686: * as previously existing one. This method allows all subclasses that care
0687: * about the ability to refresh the settings (like <code>SystemOption</code>s)
0688: * to be notified about the cleaning request and clean their settings themselves.
0689: * <p>
0690: * Default implementation does nothing.
0691: *
0692: * @since made protected in version 4.46
0693: */
0694: protected void reset() {
0695: }
0696:
0697: /** Class that is used as default write replace.
0698: */
0699: static final class WriteReplace extends Object implements
0700: Serializable {
0701: /** serialVersionUID */
0702: static final long serialVersionUID = 1327893248974327640L;
0703:
0704: /** the class */
0705: private Class<? extends SharedClassObject> clazz;
0706:
0707: /** class name, in case clazz could not be reloaded */
0708: private String name;
0709:
0710: /** shared instance */
0711: private transient SharedClassObject object;
0712:
0713: /** Constructor.
0714: * @param the instance
0715: */
0716: public WriteReplace(SharedClassObject object) {
0717: this .object = object;
0718: this .clazz = object.getClass();
0719: this .name = clazz.getName();
0720: }
0721:
0722: /** Write object.
0723: */
0724: private void writeObject(ObjectOutputStream oos)
0725: throws IOException {
0726: oos.defaultWriteObject();
0727:
0728: object.writeExternal(oos);
0729: }
0730:
0731: /** Read object.
0732: */
0733: private void readObject(ObjectInputStream ois)
0734: throws IOException, ClassNotFoundException {
0735: ois.defaultReadObject();
0736:
0737: if (clazz == null) {
0738: // Means that the class is no longer available in the restoring classloader.
0739: // Normal enough if the module has been uninstalled etc. #15654
0740: if (name != null) {
0741: throw new ClassNotFoundException(name);
0742: } else {
0743: // Compatibility with older WR's.
0744: throw new ClassNotFoundException();
0745: }
0746: }
0747:
0748: object = findObject(clazz, true);
0749: object.inReadExternal = true;
0750:
0751: try {
0752: object.readExternal(ois);
0753: } finally {
0754: object.inReadExternal = false;
0755: }
0756: }
0757:
0758: /** Read resolve to the read object.
0759: * We give chance to actual instance to do its own resolution as well. It
0760: * is necessary for achieving back compatability of certain types of settings etc.
0761: */
0762: private Object readResolve() throws ObjectStreamException {
0763: SharedClassObject resolved = object;
0764:
0765: Method resolveMethod = findReadResolveMethod(object
0766: .getClass());
0767:
0768: if (resolveMethod != null) {
0769: // invoke resolve method and accept its result
0770: try {
0771: // make readResolve accessible (it can have any access modifier)
0772: resolveMethod.setAccessible(true);
0773:
0774: return resolveMethod.invoke(object);
0775: } catch (Exception ex) {
0776: // checked or runtime does not matter - we must survive
0777: String banner = "Skipping " + object.getClass()
0778: + " resolution:"; //NOI18N
0779: err.log(Level.WARNING, banner, ex);
0780: } finally {
0781: resolveMethod.setAccessible(false);
0782: }
0783: }
0784:
0785: return resolved;
0786: }
0787:
0788: /** Tries to find readResolve method in given class. Finds
0789: * both public and non-public occurences of the method and
0790: * searches also in superclasses */
0791: private static Method findReadResolveMethod(Class clazz) {
0792: Method result = null;
0793:
0794: // try ANY-MODIFIER occurences; search also in superclasses
0795: for (Class<?> i = clazz; i != null; i = i.getSuperclass()) {
0796: try {
0797: result = accept(i.getDeclaredMethod("readResolve")); // NOI18N
0798:
0799: // get out of cycle if method found
0800: if (result != null) {
0801: break;
0802: }
0803: } catch (NoSuchMethodException exc) {
0804: // readResolve does not exist in current class
0805: }
0806: }
0807:
0808: return result;
0809: }
0810:
0811: /*
0812: * @return passed method if method matches exactly readResolve declaration as defined in
0813: * Serializetion specification otherwise null
0814: */
0815: private static Method accept(Method candidate) {
0816: if (candidate != null) {
0817: // check exceptions clause
0818: Class[] result = candidate.getExceptionTypes();
0819:
0820: if ((result.length == 1)
0821: && ObjectStreamException.class
0822: .equals(result[0])) {
0823: // returned value type
0824: if (Object.class.equals(candidate.getReturnType())) {
0825: return candidate;
0826: }
0827: }
0828: }
0829:
0830: return null;
0831: }
0832: }
0833:
0834: /** The inner class that encapsulates the shared data together with
0835: * a reference counter
0836: */
0837: static final class DataEntry extends Object {
0838: /** The data */
0839: private HashMap<Object, Object> map;
0840:
0841: /** The reference counter */
0842: private int count = 0;
0843:
0844: /** weak reference to an object of this class */
0845: private WeakReference<SharedClassObject> ref = new WeakReference<SharedClassObject>(
0846: null);
0847:
0848: /** inited? */
0849: private boolean initialized = false;
0850: private boolean initializeInProgress = false;
0851:
0852: /** #7479: if initialize() threw unchecked exception, keep it here */
0853: private Throwable invalid = null;
0854:
0855: public String toString() { // for debugging
0856:
0857: return "SCO.DataEntry[ref=" + ref.get() + ",count=" + count
0858: + ",initialized=" + initialized + ",invalid="
0859: + invalid + ",map=" + map + "]"; // NOI18N
0860: }
0861:
0862: /** initialize() is in progress? */
0863: boolean isInInitialize() {
0864: return initializeInProgress;
0865: }
0866:
0867: /** Returns the data
0868: * @param obj the requestor object
0869: * @return the data
0870: */
0871: Map<Object, Object> getMap(SharedClassObject obj) {
0872: ensureValid(obj);
0873:
0874: if (map == null) {
0875: // to signal invalid state
0876: map = new HashMap<Object, Object>();
0877: }
0878:
0879: if (!initialized) {
0880: initialized = true;
0881:
0882: // no data for this class yet
0883: tryToInitialize(obj);
0884: }
0885:
0886: return map;
0887: }
0888:
0889: /** Returns a value for given key
0890: * @param obj the requestor object
0891: * @return the data
0892: */
0893: Object get(SharedClassObject obj, Object key) {
0894: ensureValid(obj);
0895:
0896: Object ret;
0897:
0898: if (map == null) {
0899: // to signal invalid state
0900: map = new HashMap<Object, Object>();
0901: ret = null;
0902: } else {
0903: ret = map.get(key);
0904: }
0905:
0906: if ((ret == null) && !initialized) {
0907: if (key == PROP_SUPPORT) {
0908: return null;
0909: }
0910:
0911: initialized = true;
0912:
0913: // no data for this class yet
0914: tryToInitialize(obj);
0915: ret = map.get(key);
0916: }
0917:
0918: return ret;
0919: }
0920:
0921: /** Returns the data
0922: * @return the data
0923: */
0924: Map getMap() {
0925: ensureValid(get());
0926:
0927: if (map == null) {
0928: // to signal invalid state
0929: map = new HashMap<Object, Object>();
0930: }
0931:
0932: return map;
0933: }
0934:
0935: private void ensureValid(SharedClassObject obj)
0936: throws IllegalStateException {
0937: if (invalid != null) {
0938: String msg;
0939:
0940: if (obj != null) {
0941: msg = obj.toString();
0942: } else {
0943: msg = "<unknown object>"; // NOI18N
0944: }
0945:
0946: throw (IllegalStateException) new IllegalStateException(
0947: msg).initCause(invalid);
0948: }
0949: // else fine
0950: }
0951:
0952: private void tryToInitialize(SharedClassObject obj)
0953: throws IllegalStateException {
0954: initializeInProgress = true;
0955: obj.initializeSuper = false;
0956:
0957: try {
0958: obj.initialize();
0959: } catch (Exception e) {
0960: invalid = e;
0961: throw (IllegalStateException) new IllegalStateException(
0962: invalid.toString() + " from " + obj)
0963: .initCause(invalid); // NOI18N
0964: } catch (LinkageError e) {
0965: invalid = e;
0966: throw (IllegalStateException) new IllegalStateException(
0967: invalid.toString() + " from " + obj)
0968: .initCause(invalid); // NOI18N
0969: } finally {
0970: initializeInProgress = false;
0971: }
0972:
0973: if (!obj.initializeSuper) {
0974: String msg = "You must call super.initialize() from "
0975: + obj.getClass().getName() + ".initialize()"; // NOI18N
0976: err.warning(msg);
0977: }
0978: }
0979:
0980: /** Increases the counter (thread safe)
0981: * @return new counter value
0982: */
0983: int increase() {
0984: return ++count;
0985: }
0986:
0987: /** Dereases the counter (thread safe)
0988: * @return new counter value
0989: */
0990: int decrease() {
0991: return --count;
0992: }
0993:
0994: /** Request for first object. If there is none, use the requestor
0995: * @param obj requestor
0996: * @return the an object of this type
0997: */
0998: SharedClassObject first(SharedClassObject obj) {
0999: SharedClassObject s = ref.get();
1000:
1001: if (s == null) {
1002: ref = new WeakReference<SharedClassObject>(obj);
1003:
1004: return obj;
1005: } else {
1006: return s;
1007: }
1008: }
1009:
1010: /** @return shared object or null
1011: */
1012: public SharedClassObject get() {
1013: return ref.get();
1014: }
1015:
1016: /** Reset map of values. */
1017: public void reset(SharedClassObject obj) {
1018: SharedClassObject s = get();
1019:
1020: if ((s != null) && (s != obj)) {
1021: return;
1022: }
1023:
1024: invalid = null;
1025: getMap().clear();
1026:
1027: initialized = true;
1028: tryToInitialize(obj);
1029: }
1030: }
1031:
1032: static final class SetAccessibleAction implements
1033: PrivilegedExceptionAction<SharedClassObject> {
1034: Class<? extends SharedClassObject> klass;
1035:
1036: SetAccessibleAction(Class<? extends SharedClassObject> klass) {
1037: this .klass = klass;
1038: }
1039:
1040: public SharedClassObject run() throws Exception {
1041: return createInstancePrivileged(klass);
1042: }
1043: }
1044: }
|