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.util.datatransfer;
043:
044: import java.awt.datatransfer.DataFlavor;
045: import java.awt.datatransfer.Transferable;
046: import java.awt.datatransfer.UnsupportedFlavorException;
047: import java.io.IOException;
048: import java.util.HashSet;
049: import java.util.LinkedHashMap;
050: import javax.swing.event.EventListenerList;
051: import org.openide.util.NbBundle;
052:
053: /** Provides additional operations on
054: * a transferable.
055: *
056: * @author Jaroslav Tulach
057: */
058: public class ExTransferable extends Object implements Transferable {
059: /** An implementation of <code>Transferable</code> that contains no data. */
060: public static final Transferable EMPTY = new Empty();
061:
062: /** Flavor for transfer of multiple objects.
063: */
064: public static final DataFlavor multiFlavor;
065: static {
066: try {
067: multiFlavor = new DataFlavor(
068: "application/x-java-openide-multinode;class=org.openide.util.datatransfer.MultiTransferObject", // NOI18N
069: NbBundle.getBundle(ExTransferable.class).getString(
070: "transferFlavorsMultiFlavorName"),
071: MultiTransferObject.class.getClassLoader());
072: } catch (ClassNotFoundException e) {
073: throw new AssertionError(e);
074: }
075: }
076:
077: /** hash map that assigns objects to dataflavors (DataFlavor, Single) */
078: private LinkedHashMap<DataFlavor, Single> map;
079:
080: /** listeners */
081: private EventListenerList listeners;
082:
083: /** Creates new support.
084: * @param t transferable to to copy values from
085: * @param o clipobard owner (or null)
086: */
087: private ExTransferable(final Transferable t) {
088: map = new LinkedHashMap<DataFlavor, Single>();
089:
090: final DataFlavor[] df = t.getTransferDataFlavors();
091:
092: if (df != null) {
093: for (int i = 0; i < df.length; i++) {
094: try {
095: final int fi = i;
096: map.put(df[i], new Single(df[i]) {
097: public Object getData() throws IOException,
098: UnsupportedFlavorException {
099: return t.getTransferData(df[fi]);
100: }
101: });
102: } catch (Exception ex) {
103: // ignore if the data cannot be retrived
104: }
105: }
106: }
107: }
108:
109: /** Add a new flavor with its data.
110: * @param single the single transferable to use
111: */
112: public void put(Single single) {
113: map.put(single.flavor, single);
114: }
115:
116: /** Remove a flavor from the supported set.
117: * @param flavor the flavor to remove
118: */
119: public void remove(DataFlavor flavor) {
120: map.remove(flavor);
121: }
122:
123: /* Get supported flavors.
124: * @return the flavors
125: */
126: public DataFlavor[] getTransferDataFlavors() {
127: return map.keySet().toArray(new DataFlavor[0]);
128: }
129:
130: /* Is this flavor supported?
131: * @param flavor flavor to test
132: * @return <code>true</code> if this flavor is supported
133: */
134: public boolean isDataFlavorSupported(DataFlavor flavor) {
135: return map.containsKey(flavor);
136: }
137:
138: /* Get the transferable data for this flavor.
139: * @param flavor the flavor
140: * @return the data
141: * @throws IOException currently not thrown
142: * @throws UnsupportedFlavorException if that flavor is not supported
143: */
144: public Object getTransferData(DataFlavor flavor)
145: throws UnsupportedFlavorException, IOException {
146: Single o = map.get(flavor);
147:
148: if (o == null) {
149: throw new UnsupportedFlavorException(flavor);
150: }
151:
152: return o.getTransferData(flavor);
153: }
154:
155: /** Method to create a new extended transferable from a plain transferable.
156: * If the given transferable is already <code>ExTransferable</code>, then it
157: * is returned as is.
158: * Otherwise the data is copied.
159: *
160: * @param t transferable to create support for
161: * @return extended transferable
162: */
163: public static ExTransferable create(Transferable t) {
164: // [PENDING] check should probably be: if (t.getClass() == ExTransferable.class)
165: // (in case for some weird reason someone subclasses ExTransferable)
166: if (t instanceof ExTransferable) {
167: return (ExTransferable) t;
168: }
169:
170: return new ExTransferable(t);
171: }
172:
173: /** Adds a listener to watch the life-cycle of this object.
174: *
175: * @param l the listener
176: */
177: public synchronized final void addTransferListener(
178: TransferListener l) {
179: if (listeners == null) {
180: listeners = new EventListenerList();
181: }
182:
183: listeners.add(TransferListener.class, l);
184: }
185:
186: /** Removes a listener.
187: */
188: public synchronized final void removeTransferListener(
189: TransferListener l) {
190: if (listeners != null) {
191: listeners.remove(TransferListener.class, l);
192: }
193: }
194:
195: /** Fires notification to all listeners about
196: * accepting the drag.
197: * @param action one of java.awt.dnd.DnDConstants.ACTION_*
198: */
199: final void fireAccepted(int action) {
200: if (listeners == null) {
201: return;
202: }
203:
204: Object[] arr = listeners.getListenerList();
205:
206: for (int i = arr.length - 1; i >= 0; i -= 2) {
207: ((TransferListener) arr[i]).accepted(action);
208: }
209: }
210:
211: /** Fires notification to all listeners about
212: * accepting the drag.
213: */
214: final void fireRejected() {
215: if (listeners == null) {
216: return;
217: }
218:
219: Object[] arr = listeners.getListenerList();
220:
221: for (int i = arr.length - 1; i >= 0; i -= 2) {
222: ((TransferListener) arr[i]).rejected();
223: }
224: }
225:
226: /** Fires notification to all listeners about
227: * accepting the drag.
228: */
229: final void fireOwnershipLost() {
230: if (listeners == null) {
231: return;
232: }
233:
234: Object[] arr = listeners.getListenerList();
235:
236: for (int i = arr.length - 1; i >= 0; i -= 2) {
237: ((TransferListener) arr[i]).ownershipLost();
238: }
239: }
240:
241: /** Support for transferable owner with only one data flavor.
242: * Subclasses need only implement {@link #getData}.
243: */
244: public static abstract class Single extends Object implements
245: Transferable {
246: /** the supported data flavor */
247: private DataFlavor flavor;
248:
249: /** Constructor.
250: * @param flavor flavor of the data
251: */
252: public Single(DataFlavor flavor) {
253: this .flavor = flavor;
254: }
255:
256: /* Flavors that are supported.
257: * @return array with <CODE>contextFlavor</CODE>
258: * @see TransferFlavors.contextFlavor
259: */
260: public DataFlavor[] getTransferDataFlavors() {
261: return new DataFlavor[] { flavor };
262: }
263:
264: /* Is the flavor supported?
265: * @param flavor flavor to test
266: * @return true if this flavor is supported
267: */
268: public boolean isDataFlavorSupported(DataFlavor flavor) {
269: return this .flavor.equals(flavor);
270: }
271:
272: /* Creates transferable data for this flavor.
273: */
274: public Object getTransferData(DataFlavor flavor)
275: throws UnsupportedFlavorException, IOException {
276: if (!this .flavor.equals(flavor)) {
277: throw new UnsupportedFlavorException(flavor);
278: }
279:
280: return getData();
281: }
282:
283: /** Abstract method to override to provide the right data for this
284: * transferable.
285: *
286: * @return the data
287: * @throws IOException when an I/O error occurs
288: * @throws UnsupportedFlavorException if the flavor is not supported
289: */
290: protected abstract Object getData() throws IOException,
291: UnsupportedFlavorException;
292: }
293:
294: /** Transferable object for multiple transfer.
295: * It allows several types of data
296: * to be combined into one clipboard element.
297: *
298: * @author Jaroslav Tulach
299: */
300: public static class Multi extends Object implements Transferable {
301: /** supported flavors list */
302: private static final DataFlavor[] flavorList = { multiFlavor };
303:
304: /** object that is about to be return as result of transfer */
305: private MultiTransferObject transferObject;
306:
307: /** Constructor taking a list of <code>Transferable</code> objects.
308: *
309: * @param trans array of transferable objects
310: */
311: public Multi(Transferable[] trans) {
312: transferObject = new TransferObjectImpl(trans);
313: }
314:
315: /** Get supported flavors.
316: * @return only one flavor, {@link #multiFlavor}
317: */
318: public DataFlavor[] getTransferDataFlavors() {
319: return flavorList;
320: }
321:
322: /** Is this flavor supported?
323: * @param flavor the flavor
324: * @return <code>true</code> only if the flavor is {@link #multiFlavor}
325: */
326: public boolean isDataFlavorSupported(DataFlavor flavor) {
327: return flavor.equals(multiFlavor);
328: }
329:
330: /** Get transfer data.
331: * @param flavor the flavor ({@link #multiFlavor})
332: * @return {@link MultiTransferObject} that represents data in this object
333: * @exception UnsupportedFlavorException when the flavor is not supported
334: * @exception IOException when it is not possible to read data
335: */
336: public Object getTransferData(DataFlavor flavor)
337: throws UnsupportedFlavorException, IOException {
338: if (!isDataFlavorSupported(flavor)) {
339: throw new UnsupportedFlavorException(flavor);
340: }
341:
342: return transferObject;
343: }
344:
345: /** Class implementing MultiTransferObject interface. */
346: static class TransferObjectImpl implements MultiTransferObject {
347: /** transferable objects */
348: private Transferable[] trans;
349:
350: /** Creates new object from transferable objects.
351: * @param trans array of transferable objects
352: */
353: public TransferObjectImpl(Transferable[] trans) {
354: this .trans = trans;
355: }
356:
357: /** Number of transfered elements.
358: * @return number of elements
359: */
360: public int getCount() {
361: return trans.length;
362: }
363:
364: /** @return Transferable at the specific index */
365: public Transferable getTransferableAt(int index) {
366: return trans[index];
367: }
368:
369: /** Test whether data flavor is supported by index-th item.
370: *
371: * @param index the index of
372: * @param flavor flavor to test
373: * @return <CODE>true</CODE> if flavor is supported by all elements
374: */
375: public boolean isDataFlavorSupported(int index,
376: DataFlavor flavor) {
377: try {
378: return trans[index].isDataFlavorSupported(flavor);
379: } catch (Exception e) {
380: return false;
381:
382: // patch to get the Netbeans start under Solaris
383: // [PENDINGbeta]
384: }
385: }
386:
387: /** Test whether each transfered item supports at least one of these
388: * flavors. Each item can support different flavor.
389: * @param array array of flavors
390: */
391: public boolean areDataFlavorsSupported(DataFlavor[] array) {
392: HashSet<DataFlavor> flav = new HashSet<DataFlavor>();
393:
394: for (int i = 0; i < array.length; i++) {
395: flav.add(array[i]);
396: }
397:
398: // cycles through all transferable objects and scans their content
399: // to find out if each supports at least one requested flavor
400: outer: for (int i = 0; i < trans.length; i++) {
401: // insert all flavors of the first object into array
402: DataFlavor[] flavors = trans[i]
403: .getTransferDataFlavors();
404:
405: if (flavors == null) {
406: return false;
407: }
408:
409: // loop through rest of Transferable objects
410: for (int j = 0; j < flavors.length; j++) {
411: if (flav.contains(flavors[j])) {
412: // this flavor is supported
413: continue outer;
414: }
415: }
416:
417: // for this transferable no flavor is supported
418: return false;
419: }
420:
421: return true;
422: }
423:
424: /** Gets list of all supported flavors for i-th element.
425: * @param i the element to find flavors for
426: * @return array of supported flavors
427: */
428: public DataFlavor[] getTransferDataFlavors(int i) {
429: return trans[i].getTransferDataFlavors();
430: }
431:
432: /**
433: * @param indx index of element to work with
434: * @param flavor one needs to obtain
435: * @return object for the flavor of the i-th element
436: */
437: public Object getTransferData(int indx, DataFlavor flavor)
438: throws UnsupportedFlavorException, IOException {
439: return trans[indx].getTransferData(flavor);
440: }
441:
442: /** Compute common flavors.
443: * @param t array of transferable objects
444: * @return array of common flavors
445: * /
446: private static DataFlavor[] computeCommonFlavors (Transferable[] t) {
447: if (t.length == 0) {
448: // no flavor is supported => return empty array
449: return new DataFlavor[] { };
450: }
451:
452: // insert all flavors of the first object into array
453: DataFlavor[] flavors = t[0].getTransferDataFlavors ();
454: // number of non null elements in flavors array
455: int flavorsCount = (flavors == null)? 0 : flavors.length;
456: int flavorsLength = flavorsCount; // non-changing length of the original flavors array
457:
458: // loop through rest of Transferable objects
459: for (int i = 1; i < t.length; i++) {
460: // loop through array
461: for (int j = 0; j < flavorsLength; j++) {
462: // if the flavor is not supported
463: boolean supported = false;
464: try {
465: supported = t[i].isDataFlavorSupported (flavors[j]);
466: } catch (Exception e) {
467: // patch to get the Netbeans start under Solaris
468: // [PENDINGbeta]
469: }
470: if (flavors[j] != null && !supported) {
471: // then clear it
472: flavors[j] = null;
473: flavorsCount--;
474: }
475: }
476: }
477:
478: // create resulting array
479: DataFlavor[] result = new DataFlavor[flavorsLength];
480: for (int i = 0, j = 0; i < flavorsLength; i++) {
481: if (flavors[i] != null) {
482: // add it to the result
483: result[j++] = flavors[i];
484: }
485: }
486:
487: return result;
488: }
489: */
490: }
491: }
492:
493: /** TransferableOwnerEmpty is TransferableOwner that contains no data.
494: *
495: * @author Jaroslav Tulach
496: */
497: private static class Empty extends Object implements Transferable {
498: /** Package private constructor to allow only one instance from TransferableOwner.
499: */
500: Empty() {
501: }
502:
503: /** Flavors that are supported.
504: * @return empty array
505: */
506: public DataFlavor[] getTransferDataFlavors() {
507: return new DataFlavor[] {};
508: }
509:
510: /** Does not support any flavor
511: * @param flavor flavor to test
512: * @return false
513: */
514: public boolean isDataFlavorSupported(DataFlavor flavor) {
515: return false;
516: }
517:
518: /** Creates transferable data for this flavor.
519: * @exception UnsupportedFlavorException does not support any flavor
520: */
521: public Object getTransferData(DataFlavor flavor)
522: throws UnsupportedFlavorException, IOException {
523: throw new UnsupportedFlavorException(flavor);
524: }
525: }
526: }
|