1 /*
2  * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package java.awt.datatransfer;
27 
28 import java.awt.EventQueue;
29 
30 import java.util.Set;
31 import java.util.HashSet;
32 import java.util.Arrays;
33 
34 import java.io.IOException;
35 
36 import sun.awt.EventListenerAggregate;
37 
38 /**
39  * A class that implements a mechanism to transfer data using
40  * cut/copy/paste operations.
41  * <p>
42  * {@link FlavorListener}s may be registered on an instance of the
43  * Clipboard class to be notified about changes to the set of
44  * {@link DataFlavor}s available on this clipboard (see
45  * {@link #addFlavorListener}).
46  *
47  * @see java.awt.Toolkit#getSystemClipboard
48  * @see java.awt.Toolkit#getSystemSelection
49  *
50  * @author      Amy Fowler
51  * @author      Alexander Gerasimov
52  */
53 public class Clipboard {
54 
55     String name;
56 
57     protected ClipboardOwner owner;
58     protected Transferable contents;
59 
60     /**
61      * An aggregate of flavor listeners registered on this local clipboard.
62      *
63      * @since 1.5
64      */
65     private EventListenerAggregate flavorListeners;
66 
67     /**
68      * A set of <code>DataFlavor</code>s that is available on
69      * this local clipboard. It is used for tracking changes
70      * of <code>DataFlavor</code>s available on this clipboard.
71      *
72      * @since 1.5
73      */
74     private Set<DataFlavor> currentDataFlavors;
75 
76     /**
77      * Creates a clipboard object.
78      *
79      * @see java.awt.Toolkit#getSystemClipboard
80      */
Clipboard(String name)81     public Clipboard(String name) {
82         this.name = name;
83     }
84 
85     /**
86      * Returns the name of this clipboard object.
87      *
88      * @see java.awt.Toolkit#getSystemClipboard
89      */
getName()90     public String getName() {
91         return name;
92     }
93 
94     /**
95      * Sets the current contents of the clipboard to the specified
96      * transferable object and registers the specified clipboard owner
97      * as the owner of the new contents.
98      * <p>
99      * If there is an existing owner different from the argument
100      * <code>owner</code>, that owner is notified that it no longer
101      * holds ownership of the clipboard contents via an invocation
102      * of <code>ClipboardOwner.lostOwnership()</code> on that owner.
103      * An implementation of <code>setContents()</code> is free not
104      * to invoke <code>lostOwnership()</code> directly from this method.
105      * For example, <code>lostOwnership()</code> may be invoked later on
106      * a different thread. The same applies to <code>FlavorListener</code>s
107      * registered on this clipboard.
108      * <p>
109      * The method throws <code>IllegalStateException</code> if the clipboard
110      * is currently unavailable. For example, on some platforms, the system
111      * clipboard is unavailable while it is accessed by another application.
112      *
113      * @param contents the transferable object representing the
114      *                 clipboard content
115      * @param owner the object which owns the clipboard content
116      * @throws IllegalStateException if the clipboard is currently unavailable
117      * @see java.awt.Toolkit#getSystemClipboard
118      */
setContents(Transferable contents, ClipboardOwner owner)119     public synchronized void setContents(Transferable contents, ClipboardOwner owner) {
120         final ClipboardOwner oldOwner = this.owner;
121         final Transferable oldContents = this.contents;
122 
123         this.owner = owner;
124         this.contents = contents;
125 
126         if (oldOwner != null && oldOwner != owner) {
127             EventQueue.invokeLater(new Runnable() {
128                 public void run() {
129                     oldOwner.lostOwnership(Clipboard.this, oldContents);
130                 }
131             });
132         }
133         fireFlavorsChanged();
134     }
135 
136     /**
137      * Returns a transferable object representing the current contents
138      * of the clipboard.  If the clipboard currently has no contents,
139      * it returns <code>null</code>. The parameter Object requestor is
140      * not currently used.  The method throws
141      * <code>IllegalStateException</code> if the clipboard is currently
142      * unavailable.  For example, on some platforms, the system clipboard is
143      * unavailable while it is accessed by another application.
144      *
145      * @param requestor the object requesting the clip data  (not used)
146      * @return the current transferable object on the clipboard
147      * @throws IllegalStateException if the clipboard is currently unavailable
148      * @see java.awt.Toolkit#getSystemClipboard
149      */
getContents(Object requestor)150     public synchronized Transferable getContents(Object requestor) {
151         return contents;
152     }
153 
154 
155     /**
156      * Returns an array of <code>DataFlavor</code>s in which the current
157      * contents of this clipboard can be provided. If there are no
158      * <code>DataFlavor</code>s available, this method returns a zero-length
159      * array.
160      *
161      * @return an array of <code>DataFlavor</code>s in which the current
162      *         contents of this clipboard can be provided
163      *
164      * @throws IllegalStateException if this clipboard is currently unavailable
165      *
166      * @since 1.5
167      */
getAvailableDataFlavors()168     public DataFlavor[] getAvailableDataFlavors() {
169         Transferable cntnts = getContents(null);
170         if (cntnts == null) {
171             return new DataFlavor[0];
172         }
173         return cntnts.getTransferDataFlavors();
174     }
175 
176     /**
177      * Returns whether or not the current contents of this clipboard can be
178      * provided in the specified <code>DataFlavor</code>.
179      *
180      * @param flavor the requested <code>DataFlavor</code> for the contents
181      *
182      * @return <code>true</code> if the current contents of this clipboard
183      *         can be provided in the specified <code>DataFlavor</code>;
184      *         <code>false</code> otherwise
185      *
186      * @throws NullPointerException if <code>flavor</code> is <code>null</code>
187      * @throws IllegalStateException if this clipboard is currently unavailable
188      *
189      * @since 1.5
190      */
isDataFlavorAvailable(DataFlavor flavor)191     public boolean isDataFlavorAvailable(DataFlavor flavor) {
192         if (flavor == null) {
193             throw new NullPointerException("flavor");
194         }
195 
196         Transferable cntnts = getContents(null);
197         if (cntnts == null) {
198             return false;
199         }
200         return cntnts.isDataFlavorSupported(flavor);
201     }
202 
203     /**
204      * Returns an object representing the current contents of this clipboard
205      * in the specified <code>DataFlavor</code>.
206      * The class of the object returned is defined by the representation
207      * class of <code>flavor</code>.
208      *
209      * @param flavor the requested <code>DataFlavor</code> for the contents
210      *
211      * @return an object representing the current contents of this clipboard
212      *         in the specified <code>DataFlavor</code>
213      *
214      * @throws NullPointerException if <code>flavor</code> is <code>null</code>
215      * @throws IllegalStateException if this clipboard is currently unavailable
216      * @throws UnsupportedFlavorException if the requested <code>DataFlavor</code>
217      *         is not available
218      * @throws IOException if the data in the requested <code>DataFlavor</code>
219      *         can not be retrieved
220      *
221      * @see DataFlavor#getRepresentationClass
222      *
223      * @since 1.5
224      */
getData(DataFlavor flavor)225     public Object getData(DataFlavor flavor)
226         throws UnsupportedFlavorException, IOException {
227         if (flavor == null) {
228             throw new NullPointerException("flavor");
229         }
230 
231         Transferable cntnts = getContents(null);
232         if (cntnts == null) {
233             throw new UnsupportedFlavorException(flavor);
234         }
235         return cntnts.getTransferData(flavor);
236     }
237 
238 
239     /**
240      * Registers the specified <code>FlavorListener</code> to receive
241      * <code>FlavorEvent</code>s from this clipboard.
242      * If <code>listener</code> is <code>null</code>, no exception
243      * is thrown and no action is performed.
244      *
245      * @param listener the listener to be added
246      *
247      * @see #removeFlavorListener
248      * @see #getFlavorListeners
249      * @see FlavorListener
250      * @see FlavorEvent
251      * @since 1.5
252      */
addFlavorListener(FlavorListener listener)253     public synchronized void addFlavorListener(FlavorListener listener) {
254         if (listener == null) {
255             return;
256         }
257         if (flavorListeners == null) {
258             currentDataFlavors = getAvailableDataFlavorSet();
259             flavorListeners = new EventListenerAggregate(FlavorListener.class);
260         }
261         flavorListeners.add(listener);
262     }
263 
264     /**
265      * Removes the specified <code>FlavorListener</code> so that it no longer
266      * receives <code>FlavorEvent</code>s from this <code>Clipboard</code>.
267      * This method performs no function, nor does it throw an exception, if
268      * the listener specified by the argument was not previously added to this
269      * <code>Clipboard</code>.
270      * If <code>listener</code> is <code>null</code>, no exception
271      * is thrown and no action is performed.
272      *
273      * @param listener the listener to be removed
274      *
275      * @see #addFlavorListener
276      * @see #getFlavorListeners
277      * @see FlavorListener
278      * @see FlavorEvent
279      * @since 1.5
280      */
removeFlavorListener(FlavorListener listener)281     public synchronized void removeFlavorListener(FlavorListener listener) {
282         if (listener == null || flavorListeners == null) {
283             return;
284         }
285         flavorListeners.remove(listener);
286     }
287 
288     /**
289      * Returns an array of all the <code>FlavorListener</code>s currently
290      * registered on this <code>Clipboard</code>.
291      *
292      * @return all of this clipboard's <code>FlavorListener</code>s or an empty
293      *         array if no listeners are currently registered
294      * @see #addFlavorListener
295      * @see #removeFlavorListener
296      * @see FlavorListener
297      * @see FlavorEvent
298      * @since 1.5
299      */
getFlavorListeners()300     public synchronized FlavorListener[] getFlavorListeners() {
301         return flavorListeners == null ? new FlavorListener[0] :
302                 (FlavorListener[])flavorListeners.getListenersCopy();
303     }
304 
305     /**
306      * Checks change of the <code>DataFlavor</code>s and, if necessary,
307      * notifies all listeners that have registered interest for notification
308      * on <code>FlavorEvent</code>s.
309      *
310      * @since 1.5
311      */
fireFlavorsChanged()312     private void fireFlavorsChanged() {
313         if (flavorListeners == null) {
314             return;
315         }
316         Set<DataFlavor> prevDataFlavors = currentDataFlavors;
317         currentDataFlavors = getAvailableDataFlavorSet();
318         if (prevDataFlavors.equals(currentDataFlavors)) {
319             return;
320         }
321         FlavorListener[] flavorListenerArray =
322                 (FlavorListener[])flavorListeners.getListenersInternal();
323         for (int i = 0; i < flavorListenerArray.length; i++) {
324             final FlavorListener listener = flavorListenerArray[i];
325             EventQueue.invokeLater(new Runnable() {
326                 public void run() {
327                     listener.flavorsChanged(new FlavorEvent(Clipboard.this));
328                 }
329             });
330         }
331     }
332 
333     /**
334      * Returns a set of <code>DataFlavor</code>s currently available
335      * on this clipboard.
336      *
337      * @return a set of <code>DataFlavor</code>s currently available
338      *         on this clipboard
339      *
340      * @since 1.5
341      */
getAvailableDataFlavorSet()342     private Set<DataFlavor> getAvailableDataFlavorSet() {
343         Set<DataFlavor> set = new HashSet<>();
344         Transferable contents = getContents(null);
345         if (contents != null) {
346             DataFlavor[] flavors = contents.getTransferDataFlavors();
347             if (flavors != null) {
348                 set.addAll(Arrays.asList(flavors));
349             }
350         }
351         return set;
352     }
353 }
354