1 /*
2  * Copyright (c) 1996, 2017, 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.io.IOException;
29 import java.util.Arrays;
30 import java.util.HashSet;
31 import java.util.Objects;
32 import java.util.Set;
33 
34 import sun.datatransfer.DataFlavorUtil;
35 
36 /**
37  * A class that implements a mechanism to transfer data using cut/copy/paste
38  * operations.
39  * <p>
40  * {@link FlavorListener}s may be registered on an instance of the Clipboard
41  * class to be notified about changes to the set of {@link DataFlavor}s
42  * available on this clipboard (see {@link #addFlavorListener}).
43  *
44  * @author Amy Fowler
45  * @author Alexander Gerasimov
46  * @see java.awt.Toolkit#getSystemClipboard
47  * @see java.awt.Toolkit#getSystemSelection
48  * @since 1.1
49  */
50 public class Clipboard {
51 
52     String name;
53 
54     /**
55      * The owner of the clipboard.
56      */
57     protected ClipboardOwner owner;
58 
59     /**
60      * Contents of the clipboard.
61      */
62     protected Transferable contents;
63 
64     /**
65      * An aggregate of flavor listeners registered on this local clipboard.
66      *
67      * @since 1.5
68      */
69     private Set<FlavorListener> flavorListeners;
70 
71     /**
72      * A set of {@code DataFlavor}s that is available on this local clipboard.
73      * It is used for tracking changes of {@code DataFlavor}s available on this
74      * clipboard.
75      *
76      * @since 1.5
77      */
78     private Set<DataFlavor> currentDataFlavors;
79 
80     /**
81      * Creates a clipboard object.
82      *
83      * @param  name for the clipboard
84      * @see java.awt.Toolkit#getSystemClipboard
85      */
Clipboard(String name)86     public Clipboard(String name) {
87         this.name = name;
88     }
89 
90     /**
91      * Returns the name of this clipboard object.
92      *
93      * @return the name of this clipboard object
94      * @see java.awt.Toolkit#getSystemClipboard
95      */
getName()96     public String getName() {
97         return name;
98     }
99 
100     /**
101      * Sets the current contents of the clipboard to the specified transferable
102      * object and registers the specified clipboard owner as the owner of the
103      * new contents.
104      * <p>
105      * If there is an existing owner different from the argument {@code owner},
106      * that owner is notified that it no longer holds ownership of the clipboard
107      * contents via an invocation of {@code ClipboardOwner.lostOwnership()} on
108      * that owner. An implementation of {@code setContents()} is free not to
109      * invoke {@code lostOwnership()} directly from this method. For example,
110      * {@code lostOwnership()} may be invoked later on a different thread. The
111      * same applies to {@code FlavorListener}s registered on this clipboard.
112      * <p>
113      * The method throws {@code IllegalStateException} if the clipboard is
114      * currently unavailable. For example, on some platforms, the system
115      * clipboard is unavailable while it is accessed by another application.
116      *
117      * @param  contents the transferable object representing the clipboard
118      *         content
119      * @param  owner the object which owns the clipboard content
120      * @throws IllegalStateException if the clipboard is currently unavailable
121      * @see java.awt.Toolkit#getSystemClipboard
122      */
setContents(Transferable contents, ClipboardOwner owner)123     public synchronized void setContents(Transferable contents, ClipboardOwner owner) {
124         final ClipboardOwner oldOwner = this.owner;
125         final Transferable oldContents = this.contents;
126 
127         this.owner = owner;
128         this.contents = contents;
129 
130         if (oldOwner != null && oldOwner != owner) {
131             DataFlavorUtil.getDesktopService().invokeOnEventThread(() ->
132                     oldOwner.lostOwnership(Clipboard.this, oldContents));
133         }
134         fireFlavorsChanged();
135     }
136 
137     /**
138      * Returns a transferable object representing the current contents of the
139      * clipboard. If the clipboard currently has no contents, it returns
140      * {@code null}. The parameter Object requestor is not currently used. The
141      * method throws {@code IllegalStateException} 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      * Returns an array of {@code DataFlavor}s in which the current contents of
156      * this clipboard can be provided. If there are no {@code DataFlavor}s
157      * available, this method returns a zero-length array.
158      *
159      * @return an array of {@code DataFlavor}s in which the current contents of
160      *         this clipboard can be provided
161      * @throws IllegalStateException if this clipboard is currently unavailable
162      * @since 1.5
163      */
getAvailableDataFlavors()164     public DataFlavor[] getAvailableDataFlavors() {
165         Transferable cntnts = getContents(null);
166         if (cntnts == null) {
167             return new DataFlavor[0];
168         }
169         return cntnts.getTransferDataFlavors();
170     }
171 
172     /**
173      * Returns whether or not the current contents of this clipboard can be
174      * provided in the specified {@code DataFlavor}.
175      *
176      * @param  flavor the requested {@code DataFlavor} for the contents
177      * @return {@code true} if the current contents of this clipboard can be
178      *         provided in the specified {@code DataFlavor}; {@code false}
179      *         otherwise
180      * @throws NullPointerException if {@code flavor} is {@code null}
181      * @throws IllegalStateException if this clipboard is currently unavailable
182      * @since 1.5
183      */
isDataFlavorAvailable(DataFlavor flavor)184     public boolean isDataFlavorAvailable(DataFlavor flavor) {
185         if (flavor == null) {
186             throw new NullPointerException("flavor");
187         }
188 
189         Transferable cntnts = getContents(null);
190         if (cntnts == null) {
191             return false;
192         }
193         return cntnts.isDataFlavorSupported(flavor);
194     }
195 
196     /**
197      * Returns an object representing the current contents of this clipboard in
198      * the specified {@code DataFlavor}. The class of the object returned is
199      * defined by the representation class of {@code flavor}.
200      *
201      * @param  flavor the requested {@code DataFlavor} for the contents
202      * @return an object representing the current contents of this clipboard in
203      *         the specified {@code DataFlavor}
204      * @throws NullPointerException if {@code flavor} is {@code null}
205      * @throws IllegalStateException if this clipboard is currently unavailable
206      * @throws UnsupportedFlavorException if the requested {@code DataFlavor} is
207      *         not available
208      * @throws IOException if the data in the requested {@code DataFlavor} can
209      *         not be retrieved
210      * @see DataFlavor#getRepresentationClass
211      * @since 1.5
212      */
getData(DataFlavor flavor)213     public Object getData(DataFlavor flavor)
214         throws UnsupportedFlavorException, IOException {
215         if (flavor == null) {
216             throw new NullPointerException("flavor");
217         }
218 
219         Transferable cntnts = getContents(null);
220         if (cntnts == null) {
221             throw new UnsupportedFlavorException(flavor);
222         }
223         return cntnts.getTransferData(flavor);
224     }
225 
226     /**
227      * Registers the specified {@code FlavorListener} to receive
228      * {@code FlavorEvent}s from this clipboard. If {@code listener} is
229      * {@code null}, no exception is thrown and no action is performed.
230      *
231      * @param  listener the listener to be added
232      * @see #removeFlavorListener
233      * @see #getFlavorListeners
234      * @see FlavorListener
235      * @see FlavorEvent
236      * @since 1.5
237      */
addFlavorListener(FlavorListener listener)238     public synchronized void addFlavorListener(FlavorListener listener) {
239         if (listener == null) {
240             return;
241         }
242 
243         if (flavorListeners == null) {
244             flavorListeners = new HashSet<>();
245             currentDataFlavors = getAvailableDataFlavorSet();
246         }
247 
248         flavorListeners.add(listener);
249     }
250 
251     /**
252      * Removes the specified {@code FlavorListener} so that it no longer
253      * receives {@code FlavorEvent}s from this {@code Clipboard}. This method
254      * performs no function, nor does it throw an exception, if the listener
255      * specified by the argument was not previously added to this
256      * {@code Clipboard}. If {@code listener} is {@code null}, no exception is
257      * thrown and no action is performed.
258      *
259      * @param  listener the listener to be removed
260      * @see #addFlavorListener
261      * @see #getFlavorListeners
262      * @see FlavorListener
263      * @see FlavorEvent
264      * @since 1.5
265      */
removeFlavorListener(FlavorListener listener)266     public synchronized void removeFlavorListener(FlavorListener listener) {
267         if (listener == null || flavorListeners == null) {
268             return;
269         }
270         flavorListeners.remove(listener);
271     }
272 
273     /**
274      * Returns an array of all the {@code FlavorListener}s currently registered
275      * on this {@code Clipboard}.
276      *
277      * @return all of this clipboard's {@code FlavorListener}s or an empty array
278      *         if no listeners are currently registered
279      * @see #addFlavorListener
280      * @see #removeFlavorListener
281      * @see FlavorListener
282      * @see FlavorEvent
283      * @since 1.5
284      */
getFlavorListeners()285     public synchronized FlavorListener[] getFlavorListeners() {
286         return flavorListeners == null ? new FlavorListener[0] :
287             flavorListeners.toArray(new FlavorListener[flavorListeners.size()]);
288     }
289 
290     /**
291      * Checks change of the {@code DataFlavor}s and, if necessary, notifies all
292      * listeners that have registered interest for notification on
293      * {@code FlavorEvent}s.
294      *
295      * @since 1.5
296      */
fireFlavorsChanged()297     private void fireFlavorsChanged() {
298         if (flavorListeners == null) {
299             return;
300         }
301 
302         Set<DataFlavor> prevDataFlavors = currentDataFlavors;
303         currentDataFlavors = getAvailableDataFlavorSet();
304         if (Objects.equals(prevDataFlavors, currentDataFlavors)) {
305             return;
306         }
307         flavorListeners.forEach(listener ->
308                 DataFlavorUtil.getDesktopService().invokeOnEventThread(() ->
309                         listener.flavorsChanged(new FlavorEvent(Clipboard.this))));
310     }
311 
312     /**
313      * Returns a set of {@code DataFlavor}s currently available on this
314      * clipboard.
315      *
316      * @return a set of {@code DataFlavor}s currently available on this
317      *         clipboard
318      * @since 1.5
319      */
getAvailableDataFlavorSet()320     private Set<DataFlavor> getAvailableDataFlavorSet() {
321         Set<DataFlavor> set = new HashSet<>();
322         Transferable contents = getContents(null);
323         if (contents != null) {
324             DataFlavor[] flavors = contents.getTransferDataFlavors();
325             if (flavors != null) {
326                 set.addAll(Arrays.asList(flavors));
327             }
328         }
329         return set;
330     }
331 }
332