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:
042: package org.openide.windows;
043:
044: import java.io.Externalizable;
045: import java.io.IOException;
046: import java.io.ObjectInput;
047: import java.io.ObjectInputStream;
048: import java.io.ObjectOutput;
049: import java.io.Serializable;
050: import java.util.Collections;
051: import java.util.Enumeration;
052: import java.util.HashSet;
053: import java.util.Iterator;
054: import java.util.Set;
055: import org.openide.util.NbBundle;
056: import org.openide.util.io.NbMarshalledObject;
057:
058: /** A top component which may be cloned.
059: * Typically cloning is harmless, i.e. the data contents (if any)
060: * of the component are the same, and the new component is merely
061: * a different presentation.
062: * Also, a list of all cloned components is kept.
063: *
064: * @author Jaroslav Tulach
065: */
066: public abstract class CloneableTopComponent extends TopComponent
067: implements Externalizable, TopComponent.Cloneable {
068: /** generated Serialized Version UID */
069: static final long serialVersionUID = 4893753008783256289L;
070:
071: // say what? --jglick
072:
073: /* Empty set that should save work with testing like
074: * <pre>
075: * if (ref == null || ref.isEmpty ()) {
076: * CloneableTopComponent c = new CloneableTopComponent (obj);
077: * ref = c.getReference ();
078: * }
079: * </pre>
080: * Instead one can always set <CODE>ref = Ref.EMPTY</CODE> and test only if
081: * <CODE>ref.isEmpty</CODE> returns <CODE>true</CODE>.
082: */
083:
084: /** Empty clone-sister list.
085: */
086: public static final Ref EMPTY = new Ref();
087:
088: /** reference with list of components */
089: private Ref ref;
090:
091: /** Create a cloneable top component.
092: */
093: public CloneableTopComponent() {
094: }
095:
096: /** Clone the top component and register the clone.
097: * @return the new component
098: */
099: public final Object clone() {
100: return cloneComponent();
101: }
102:
103: /** Clone the top component and register the clone.
104: * Simply calls createClonedObject () and registers the component to
105: * Ref.
106: *
107: * @return the new cloneable top component
108: */
109: public final CloneableTopComponent cloneTopComponent() {
110: CloneableTopComponent top = createClonedObject();
111:
112: // register the component if it has not been registered before
113: top.setReference(getReference());
114:
115: return top;
116: }
117:
118: /** Clone the top component and register the clone.
119: * @return the new component
120: */
121: public final TopComponent cloneComponent() {
122: return cloneTopComponent();
123: }
124:
125: /** Called from {@link #clone} to actually create a new component from this one.
126: * The default implementation serializes and deserializes the original and
127: * returns the result.
128: * Subclasses may leave this as is, assuming they have no special needs for the cloned
129: * data besides copying it from one object to the other. If they do, the superclass
130: * method should be called, and the returned object modified appropriately.
131: * @return a copy of this object
132: */
133: protected CloneableTopComponent createClonedObject() {
134: try {
135: // clones the component using serialization
136: NbMarshalledObject o = new NbMarshalledObject(this );
137: return (CloneableTopComponent) o.get();
138: } catch (IOException ex) {
139: throw new AssertionError(ex);
140: } catch (ClassNotFoundException ex) {
141: throw new AssertionError(ex);
142: }
143: }
144:
145: /** Get a list of all components which are clone-sisters of this one.
146: *
147: * @return the clone registry for this component's group
148: */
149: public synchronized final Ref getReference() {
150: if (ref == null) {
151: ref = new Ref(this );
152: }
153:
154: return ref;
155: }
156:
157: /** Changes the reference to which this components belongs.
158: * @param another the new reference this component should belong
159: */
160: public synchronized final void setReference(Ref another) {
161: if (another == EMPTY) {
162: throw new IllegalArgumentException(NbBundle.getBundle(
163: CloneableTopComponent.class).getString(
164: "EXC_CannotAssign"));
165: }
166:
167: if (ref != null) {
168: // Remove from old ref, we are going to belong to 'another' reference.
169: ref.removeComponent(this );
170: }
171:
172: // Register with the new reference.
173: another.register(this );
174:
175: // Finally set the field.
176: ref = another;
177: }
178:
179: /** Overrides superclass method, adds unregistering from references.
180: * @see Ref */
181: protected void componentClosed() {
182: super .componentClosed();
183:
184: if (!isOpened()) {
185: getReference().unregister(this );
186: }
187: }
188:
189: /**
190: * Unregisters this component from its clone list.
191: * {@inheritDoc}
192: */
193: public boolean canClose() {
194: if (!isOpened()) {
195: return false;
196: }
197:
198: return getReference().unregister(this );
199: }
200:
201: @SuppressWarnings("deprecation")
202: public boolean canClose(Workspace workspace, boolean last) {
203: if (last) {
204: return getReference().unregister(this );
205: }
206:
207: return true;
208: }
209:
210: /** Called when the last component in a clone group is closing.
211: * The default implementation just returns <code>true</code>.
212: * Subclasses may specify some hooks to run.
213: * @return <CODE>true</CODE> if the component is ready to be
214: * closed, <CODE>false</CODE> to cancel
215: */
216: protected boolean closeLast() {
217: return true;
218: }
219:
220: public void readExternal(ObjectInput oi) throws IOException,
221: ClassNotFoundException {
222: super .readExternal(oi);
223:
224: if (serialVersion != 0) {
225: // since serialVersion > 0
226: // the reference object is also stored
227: Ref ref = (Ref) oi.readObject();
228:
229: if (ref != null) {
230: setReference(ref);
231: }
232: }
233: }
234:
235: public void writeExternal(ObjectOutput oo)
236: throws java.io.IOException {
237: super .writeExternal(oo);
238:
239: oo.writeObject(ref);
240: }
241:
242: /** Keeps track of a group of sister clones.
243: * <P>
244: * <B>Warning:</B>
245: * For proper use
246: * subclasses should have method readResolve () and implement it
247: * in right way to deal with separate serialization of TopComponent.
248: */
249: public static class Ref implements Serializable {
250: /** generated Serialized Version UID */
251: static final long serialVersionUID = 5543148876020730556L;
252:
253: /** manipulation lock */
254: private static final Object LOCK = new Object();
255:
256: /** Set of registered components. */
257: private transient/*final*/Set<CloneableTopComponent> componentSet = new HashSet<CloneableTopComponent>(
258: 7);
259:
260: /** Default constructor for creating empty reference.
261: */
262: protected Ref() {
263: }
264:
265: /** Constructor.
266: * @param c the component to refer to
267: */
268: private Ref(CloneableTopComponent c) {
269: synchronized (LOCK) {
270: componentSet.add(c);
271: }
272: }
273:
274: /** Enumeration of all registered components.
275: * @return enumeration of CloneableTopComponent
276: */
277: public Enumeration<CloneableTopComponent> getComponents() {
278: Set<CloneableTopComponent> components;
279:
280: synchronized (LOCK) {
281: components = new HashSet<CloneableTopComponent>(
282: componentSet);
283: }
284:
285: return Collections.enumeration(components);
286: }
287:
288: /** Test whether there is any component in this set.
289: * @return <CODE>true</CODE> if the reference set is empty
290: */
291: public boolean isEmpty() {
292: synchronized (LOCK) {
293: return componentSet.isEmpty();
294: }
295: }
296:
297: /** Retrieve an arbitrary component from the set.
298: * @return some component from the list of registered ones
299: * @exception NoSuchElementException if the set is empty
300: * @deprecated Use {@link #getArbitraryComponent} instead.
301: * It doesn't throw a runtime exception.
302: */
303: public CloneableTopComponent getAnyComponent() {
304: synchronized (LOCK) {
305: return componentSet.iterator().next();
306: }
307: }
308:
309: /** Gets arbitrary component from the set. Preferrably returns currently
310: * active component if found in the set.
311: * @return arbitratry <code>CloneableTopComponent</code> from the set
312: * or <code>null</code> if the set is empty
313: * @since 3.41 */
314: public CloneableTopComponent getArbitraryComponent() {
315: TopComponent activated = WindowManager.getDefault()
316: .getRegistry().getActivated();
317:
318: synchronized (LOCK) {
319: // prefer already active component
320: if (componentSet.contains(activated)) {
321: return (CloneableTopComponent) activated;
322: }
323:
324: Iterator<CloneableTopComponent> it = componentSet
325: .iterator();
326:
327: if (it.hasNext()) {
328: return it.next();
329: } else {
330: return null;
331: }
332: }
333: }
334:
335: /** Register new component.
336: * @param c the component to register
337: */
338: private final void register(CloneableTopComponent c) {
339: synchronized (LOCK) {
340: componentSet.add(c);
341: }
342: }
343:
344: /** Unregister the component. If this is the last asks if it is
345: * allowed to unregister it.
346: *
347: * @param c the component to unregister
348: * @return true if the component agreed to be unregister
349: */
350: private final boolean unregister(CloneableTopComponent c) {
351: int componentCount;
352:
353: synchronized (LOCK) {
354: if (!componentSet.contains(c)) {
355: return true;
356: }
357:
358: componentCount = componentSet.size();
359: }
360:
361: if ((componentCount > 1) || c.closeLast()) {
362: removeComponent(c);
363:
364: return true;
365: } else {
366: return false;
367: }
368: }
369:
370: private void removeComponent(CloneableTopComponent c) {
371: synchronized (LOCK) {
372: componentSet.remove(c);
373: }
374: }
375:
376: /** Adds also initializing of <code>componentSet</code> field. */
377: private void readObject(ObjectInputStream in)
378: throws IOException, ClassNotFoundException {
379: in.defaultReadObject();
380:
381: synchronized (LOCK) {
382: componentSet = new HashSet<CloneableTopComponent>(7);
383: }
384: }
385: }
386: // end of Ref
387: }
|