1 /*
   2  * Copyright (c) 1996, 2006, 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 currentDataFlavors;
  75 
  76     /**
  77      * Creates a clipboard object.
  78      *
  79      * @see java.awt.Toolkit#getSystemClipboard
  80      */
  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      */
  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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 312     private void fireFlavorsChanged() {
 313         if (flavorListeners == null) {
 314             return;
 315         }
 316         Set 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      */
 342     private Set getAvailableDataFlavorSet() {
 343         Set 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 }