/*
* 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-2003 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.openide.windows;
import java.io.IOException;
import java.util.*;
import org.openide.loaders.DataObject;
import org.openide.util.io.NbMarshalledObject;
import org.openide.util.NbBundle;
/** A top component which may be cloned.
* Typically cloning is harmless, i.e. the data contents (if any)
* of the component are the same, and the new component is merely
* a different presentation.
* Also, a list of all cloned components is kept.
*
* @author Jaroslav Tulach
*/
public abstract class CloneableTopComponent extends TopComponent
implements java.io.Externalizable, TopComponent.Cloneable {
/** generated Serialized Version UID */
static final long serialVersionUID = 4893753008783256289L;
/** reference with list of components */
private Ref ref;
/** Create a cloneable top component.
*/
public CloneableTopComponent () {
}
/** Create a cloneable top component associated with a data object.
* @param obj the data object
* @see TopComponent#TopComponent(DataObject)
*/
public CloneableTopComponent (DataObject obj) {
super (obj);
}
/** Clone the top component and register the clone.
* @return the new component
*/
public final Object clone () {
return cloneComponent ();
}
/** Clone the top component and register the clone.
* Simply calls createClonedObject () and registers the component to
* Ref.
*
* @return the new cloneable top component
*/
public final CloneableTopComponent cloneTopComponent() {
CloneableTopComponent top = createClonedObject ();
// register the component if it has not been registered before
top.setReference (getReference ());
return top;
}
/** Clone the top component and register the clone.
* @return the new component
*/
public final TopComponent cloneComponent() {
return cloneTopComponent ();
}
/** Called from {@link #clone} to actually create a new component from this one.
* The default implementation only clones the object by calling {@link Object#clone}.
* Subclasses may leave this as is, assuming they have no special needs for the cloned
* data besides copying it from one object to the other. If they do, the superclass
* method should be called, and the returned object modified appropriately.
* @return a copy of this object
*/
protected CloneableTopComponent createClonedObject () {
try {
// clones the component using serialization
NbMarshalledObject o = new NbMarshalledObject (this);
CloneableTopComponent top = (CloneableTopComponent)o.get ();
return top;
} catch (IOException ex) {
ex.printStackTrace();
throw new InternalError ();
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
throw new InternalError ();
}
}
/** Get a list of all components which are clone-sisters of this one.
*
* @return the clone registry for this component's group
*/
public synchronized final Ref getReference () {
if (ref == null) {
ref = new Ref (this);
}
return ref;
}
/** Changes the reference to which this components belongs.
* @param another the new reference this component should belong
*/
public synchronized final void setReference (Ref another) {
if (another == EMPTY) {
throw new IllegalArgumentException(
NbBundle.getBundle(CloneableTopComponent.class).getString("EXC_CannotAssign")
);
}
if (ref != null) {
// Remove from old ref, we are going to belong to 'another' reference.
ref.removeComponent(this);
}
// Register with the new reference.
another.register (this);
// Finally set the field.
ref = another;
}
/** Called when this component is about to close.
* The default implementation just unregisters the clone from its clone list.
* <p>If this is the last component in its clone group, then
* {@link #closeLast} is called to clean up.
*
* @return <CODE>true</CODE> if there are still clone sisters left, or this was the last in its group
* but {@link #closeLast} returned <code>true</code>
*/
public boolean canClose (Workspace workspace, boolean last) {
if (last) {
return getReference ().unregister (this);
}
return true;
}
/** Called when the last component in a clone group is closing.
* The default implementation just returns <code>true</code>.
* Subclasses may specify some hooks to run.
* @return <CODE>true</CODE> if the component is ready to be
* closed, <CODE>false</CODE> to cancel
*/
protected boolean closeLast () {
return true;
}
public void readExternal (java.io.ObjectInput oi)
throws java.io.IOException, java.lang.ClassNotFoundException {
super.readExternal (oi);
if (serialVersion != 0) {
// since serialVersion > 0
// the reference object is also stored
Ref ref = (Ref)oi.readObject ();
if (ref != null) {
setReference (ref);
}
}
}
public void writeExternal (java.io.ObjectOutput oo)
throws java.io.IOException {
super.writeExternal (oo);
oo.writeObject (ref);
}
// say what? --jglick
/* Empty set that should save work with testing like
* <pre>
* if (ref == null || ref.isEmpty ()) {
* CloneableTopComponent c = new CloneableTopComponent (obj);
* ref = c.getReference ();
* }
* </pre>
* Instead one can always set <CODE>ref = Ref.EMPTY</CODE> and test only if
* <CODE>ref.isEmpty</CODE> returns <CODE>true</CODE>.
*/
/** Empty clone-sister list.
*/
public static final Ref EMPTY = new Ref ();
/** Keeps track of a group of sister clones.
* <P>
* <B>Warning:</B>
* For proper use
* subclasses should have method readResolve () and implement it
* in right way to deal with separate serialization of TopComponent.
*/
public static class Ref implements java.io.Serializable {
/** generated Serialized Version UID */
static final long serialVersionUID = 5543148876020730556L;
/** manipulation lock */
private static final Object LOCK = new Object ();
/** Set of registered components. */
private transient /*final*/ Set componentSet = new HashSet(7);
/** Default constructor for creating empty reference.
*/
protected Ref () {
}
/** Constructor.
* @param c the component to refer to
*/
private Ref (CloneableTopComponent c) {
synchronized(LOCK) {
componentSet.add (c);
}
}
/** Enumeration of all registered components.
* @return enumeration of CloneableTopComponent
*/
public Enumeration getComponents () {
Set components;
synchronized (LOCK) {
components = new HashSet(componentSet);
}
return java.util.Collections.enumeration(components);
}
/** Test whether there is any component in this set.
* @return <CODE>true</CODE> if the reference set is empty
*/
public boolean isEmpty () {
synchronized (LOCK) {
return componentSet.isEmpty();
}
}
/** Retrieve an arbitrary component from the set.
* @return some component from the list of registered ones
* @exception NoSuchElementException if the set is empty
* @deprecated Use {@link #getArbitraryComponent} instead.
* It doesn't throw a runtime exception.
*/
public CloneableTopComponent getAnyComponent () {
synchronized (LOCK) {
return (CloneableTopComponent)componentSet.iterator().next();
}
}
/** Gets arbitrary component from the set.
* @return arbitratry <code>CloneableTopComponent</code> from the set
* or <code>null</code> if the set is empty
* @since 3.41 */
public CloneableTopComponent getArbitraryComponent() {
synchronized(LOCK) {
Iterator it = componentSet.iterator();
if(it.hasNext()) {
return (CloneableTopComponent)it.next();
} else {
return null;
}
}
}
/** Register new component.
* @param c the component to register
*/
private final void register (CloneableTopComponent c) {
synchronized (LOCK) {
componentSet.add (c);
}
}
/** Unregister the component. If this is the last asks if it is
* allowed to unregister it.
*
* @param c the component to unregister
* @return true if the component agreed to be unregister
*/
private final boolean unregister (CloneableTopComponent c) {
int componentCount;
synchronized(LOCK) {
componentCount = componentSet.size();
}
if (componentCount > 1 || c.closeLast()) {
removeComponent(c);
return true;
} else {
return false;
}
}
private void removeComponent(CloneableTopComponent c) {
synchronized (LOCK) {
componentSet.remove(c);
}
}
/** Adds also initializing of <code>componentSet</code> field. */
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
synchronized(LOCK) {
componentSet = new HashSet(7);
}
}
} // end of Ref
}