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 26package java.awt.datatransfer; 27 28import java.io.IOException; 29import java.util.Arrays; 30import java.util.HashSet; 31import java.util.Objects; 32import java.util.Set; 33 34import 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 */ 50public 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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