1 /*
   2  * Copyright (c) 1996, 2015, 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 package java.awt;
  26 
  27 import java.beans.ConstructorProperties;
  28 import java.io.InputStream;
  29 import java.net.URL;
  30 import java.security.AccessController;
  31 import java.security.PrivilegedExceptionAction;
  32 import java.util.Hashtable;
  33 import java.util.Properties;
  34 import java.util.StringTokenizer;
  35 
  36 import sun.awt.AWTAccessor;
  37 import sun.util.logging.PlatformLogger;
  38 
  39 /**
  40  * A class to encapsulate the bitmap representation of the mouse cursor.
  41  *
  42  * @see Component#setCursor
  43  * @author      Amy Fowler
  44  */
  45 public class Cursor implements java.io.Serializable {
  46 
  47     /**
  48      * The default cursor type (gets set if no cursor is defined).
  49      */
  50     public static final int     DEFAULT_CURSOR                  = 0;
  51 
  52     /**
  53      * The crosshair cursor type.
  54      */
  55     public static final int     CROSSHAIR_CURSOR                = 1;
  56 
  57     /**
  58      * The text cursor type.
  59      */
  60     public static final int     TEXT_CURSOR                     = 2;
  61 
  62     /**
  63      * The wait cursor type.
  64      */
  65     public static final int     WAIT_CURSOR                     = 3;
  66 
  67     /**
  68      * The south-west-resize cursor type.
  69      */
  70     public static final int     SW_RESIZE_CURSOR                = 4;
  71 
  72     /**
  73      * The south-east-resize cursor type.
  74      */
  75     public static final int     SE_RESIZE_CURSOR                = 5;
  76 
  77     /**
  78      * The north-west-resize cursor type.
  79      */
  80     public static final int     NW_RESIZE_CURSOR                = 6;
  81 
  82     /**
  83      * The north-east-resize cursor type.
  84      */
  85     public static final int     NE_RESIZE_CURSOR                = 7;
  86 
  87     /**
  88      * The north-resize cursor type.
  89      */
  90     public static final int     N_RESIZE_CURSOR                 = 8;
  91 
  92     /**
  93      * The south-resize cursor type.
  94      */
  95     public static final int     S_RESIZE_CURSOR                 = 9;
  96 
  97     /**
  98      * The west-resize cursor type.
  99      */
 100     public static final int     W_RESIZE_CURSOR                 = 10;
 101 
 102     /**
 103      * The east-resize cursor type.
 104      */
 105     public static final int     E_RESIZE_CURSOR                 = 11;
 106 
 107     /**
 108      * The hand cursor type.
 109      */
 110     public static final int     HAND_CURSOR                     = 12;
 111 
 112     /**
 113      * The move cursor type.
 114      */
 115     public static final int     MOVE_CURSOR                     = 13;
 116 
 117     /**
 118       * @deprecated As of JDK version 1.7, the {@link #getPredefinedCursor(int)}
 119       * method should be used instead.
 120       */
 121     @Deprecated
 122     protected static Cursor predefined[] = new Cursor[14];
 123 
 124     /**
 125      * This field is a private replacement for 'predefined' array.
 126      */
 127     private final static Cursor[] predefinedPrivate = new Cursor[14];
 128 
 129     /* Localization names and default values */
 130     static final String[][] cursorProperties = {
 131         { "AWT.DefaultCursor", "Default Cursor" },
 132         { "AWT.CrosshairCursor", "Crosshair Cursor" },
 133         { "AWT.TextCursor", "Text Cursor" },
 134         { "AWT.WaitCursor", "Wait Cursor" },
 135         { "AWT.SWResizeCursor", "Southwest Resize Cursor" },
 136         { "AWT.SEResizeCursor", "Southeast Resize Cursor" },
 137         { "AWT.NWResizeCursor", "Northwest Resize Cursor" },
 138         { "AWT.NEResizeCursor", "Northeast Resize Cursor" },
 139         { "AWT.NResizeCursor", "North Resize Cursor" },
 140         { "AWT.SResizeCursor", "South Resize Cursor" },
 141         { "AWT.WResizeCursor", "West Resize Cursor" },
 142         { "AWT.EResizeCursor", "East Resize Cursor" },
 143         { "AWT.HandCursor", "Hand Cursor" },
 144         { "AWT.MoveCursor", "Move Cursor" },
 145     };
 146 
 147     /**
 148      * The chosen cursor type initially set to
 149      * the <code>DEFAULT_CURSOR</code>.
 150      *
 151      * @serial
 152      * @see #getType()
 153      */
 154     int type = DEFAULT_CURSOR;
 155 
 156     /**
 157      * The type associated with all custom cursors.
 158      */
 159     public static final int     CUSTOM_CURSOR                   = -1;
 160 
 161     /*
 162      * hashtable, resource prefix, filename, and properties for custom cursors
 163      * support
 164      */
 165     private static final Hashtable<String,Cursor> systemCustomCursors = new Hashtable<>(1);
 166     private static final String RESOURCE_PREFIX = "/sun/awt/resources/cursors/";
 167     private static final String PROPERTIES_FILE = RESOURCE_PREFIX + "cursors.properties";
 168 
 169     private static       Properties systemCustomCursorProperties = null;
 170 
 171     private static final String CURSOR_DOT_PREFIX = "Cursor.";
 172     private static final String DOT_FILE_SUFFIX = ".File";
 173     private static final String DOT_HOTSPOT_SUFFIX = ".HotSpot";
 174     private static final String DOT_NAME_SUFFIX = ".Name";
 175 
 176     /*
 177      * JDK 1.1 serialVersionUID
 178      */
 179     private static final long serialVersionUID = 8028237497568985504L;
 180 
 181     private static final PlatformLogger log = PlatformLogger.getLogger("java.awt.Cursor");
 182 
 183     static {
 184         /* ensure that the necessary native libraries are loaded */
 185         Toolkit.loadLibraries();
 186         if (!GraphicsEnvironment.isHeadless()) {
 187             initIDs();
 188         }
 189 
 190         AWTAccessor.setCursorAccessor(
 191             new AWTAccessor.CursorAccessor() {
 192                 public long getPData(Cursor cursor) {
 193                     return cursor.pData;
 194                 }
 195 
 196                 public void setPData(Cursor cursor, long pData) {
 197                     cursor.pData = pData;
 198                 }
 199 
 200                 public int getType(Cursor cursor) {
 201                     return cursor.type;
 202                 }
 203             });
 204     }
 205 
 206     /**
 207      * Initialize JNI field and method IDs for fields that may be
 208      * accessed from C.
 209      */
 210     private static native void initIDs();
 211 
 212     /**
 213      * Hook into native data.
 214      */
 215     private transient long pData;
 216 
 217     private transient Object anchor = new Object();
 218 
 219     static class CursorDisposer implements sun.java2d.DisposerRecord {
 220         volatile long pData;
 221         public CursorDisposer(long pData) {
 222             this.pData = pData;
 223         }
 224         public void dispose() {
 225             if (pData != 0) {
 226                 finalizeImpl(pData);
 227             }
 228         }
 229     }
 230     transient CursorDisposer disposer;
 231     private void setPData(long pData) {
 232         this.pData = pData;
 233         if (GraphicsEnvironment.isHeadless()) {
 234             return;
 235         }
 236         if (disposer == null) {
 237             disposer = new CursorDisposer(pData);
 238             // anchor is null after deserialization
 239             if (anchor == null) {
 240                 anchor = new Object();
 241             }
 242             sun.java2d.Disposer.addRecord(anchor, disposer);
 243         } else {
 244             disposer.pData = pData;
 245         }
 246     }
 247 
 248     /**
 249      * The user-visible name of the cursor.
 250      *
 251      * @serial
 252      * @see #getName()
 253      */
 254     protected String name;
 255 
 256     /**
 257      * Returns a cursor object with the specified predefined type.
 258      *
 259      * @param type the type of predefined cursor
 260      * @return the specified predefined cursor
 261      * @throws IllegalArgumentException if the specified cursor type is
 262      *         invalid
 263      */
 264     static public Cursor getPredefinedCursor(int type) {
 265         if (type < Cursor.DEFAULT_CURSOR || type > Cursor.MOVE_CURSOR) {
 266             throw new IllegalArgumentException("illegal cursor type");
 267         }
 268         Cursor c = predefinedPrivate[type];
 269         if (c == null) {
 270             predefinedPrivate[type] = c = new Cursor(type);
 271         }
 272         // fill 'predefined' array for backwards compatibility.
 273         if (predefined[type] == null) {
 274             predefined[type] = c;
 275         }
 276         return c;
 277     }
 278 
 279     /**
 280      * Returns a system-specific custom cursor object matching the
 281      * specified name.  Cursor names are, for example: "Invalid.16x16"
 282      *
 283      * @param name a string describing the desired system-specific custom cursor
 284      * @return the system specific custom cursor named
 285      * @exception HeadlessException if
 286      * <code>GraphicsEnvironment.isHeadless</code> returns true
 287      * @exception AWTException in case of erroneous retrieving of the cursor
 288      */
 289     static public Cursor getSystemCustomCursor(final String name)
 290         throws AWTException, HeadlessException {
 291         GraphicsEnvironment.checkHeadless();
 292         Cursor cursor = systemCustomCursors.get(name);
 293 
 294         if (cursor == null) {
 295             synchronized(systemCustomCursors) {
 296                 if (systemCustomCursorProperties == null)
 297                     loadSystemCustomCursorProperties();
 298             }
 299 
 300             String prefix = CURSOR_DOT_PREFIX + name;
 301             String key    = prefix + DOT_FILE_SUFFIX;
 302 
 303             if (!systemCustomCursorProperties.containsKey(key)) {
 304                 if (log.isLoggable(PlatformLogger.Level.FINER)) {
 305                     log.finer("Cursor.getSystemCustomCursor(" + name + ") returned null");
 306                 }
 307                 return null;
 308             }
 309 
 310             final String fileName =
 311                 systemCustomCursorProperties.getProperty(key);
 312 
 313             final String localized = systemCustomCursorProperties.getProperty(
 314                     prefix + DOT_NAME_SUFFIX, name);
 315 
 316             String hotspot = systemCustomCursorProperties.getProperty(prefix + DOT_HOTSPOT_SUFFIX);
 317 
 318             if (hotspot == null)
 319                 throw new AWTException("no hotspot property defined for cursor: " + name);
 320 
 321             StringTokenizer st = new StringTokenizer(hotspot, ",");
 322 
 323             if (st.countTokens() != 2)
 324                 throw new AWTException("failed to parse hotspot property for cursor: " + name);
 325 
 326             final Point hotPoint;
 327             try {
 328                 hotPoint = new Point(Integer.parseInt(st.nextToken()),
 329                                      Integer.parseInt(st.nextToken()));
 330             } catch (NumberFormatException nfe) {
 331                 throw new AWTException("failed to parse hotspot property for cursor: " + name);
 332             }
 333 
 334             try {
 335                 final Toolkit toolkit = Toolkit.getDefaultToolkit();
 336                 final String file = RESOURCE_PREFIX + fileName;
 337 
 338                 cursor = AccessController.doPrivileged(
 339                         (PrivilegedExceptionAction<Cursor>) () -> {
 340                             URL url = Cursor.class.getResource(file);
 341                             Image image = toolkit.getImage(url);
 342                             return toolkit.createCustomCursor(image, hotPoint,
 343                                                               localized);
 344                         });
 345             } catch (Exception e) {
 346                 throw new AWTException(
 347                     "Exception: " + e.getClass() + " " + e.getMessage() +
 348                     " occurred while creating cursor " + name);
 349             }
 350 
 351             if (cursor == null) {
 352                 if (log.isLoggable(PlatformLogger.Level.FINER)) {
 353                     log.finer("Cursor.getSystemCustomCursor(" + name + ") returned null");
 354                 }
 355             } else {
 356                 systemCustomCursors.put(name, cursor);
 357             }
 358         }
 359 
 360         return cursor;
 361     }
 362 
 363     /**
 364      * Return the system default cursor.
 365      *
 366      * @return the default cursor
 367      */
 368     static public Cursor getDefaultCursor() {
 369         return getPredefinedCursor(Cursor.DEFAULT_CURSOR);
 370     }
 371 
 372     /**
 373      * Creates a new cursor object with the specified type.
 374      * @param type the type of cursor
 375      * @throws IllegalArgumentException if the specified cursor type
 376      * is invalid
 377      */
 378     @ConstructorProperties({"type"})
 379     public Cursor(int type) {
 380         if (type < Cursor.DEFAULT_CURSOR || type > Cursor.MOVE_CURSOR) {
 381             throw new IllegalArgumentException("illegal cursor type");
 382         }
 383         this.type = type;
 384 
 385         // Lookup localized name.
 386         name = Toolkit.getProperty(cursorProperties[type][0],
 387                                    cursorProperties[type][1]);
 388     }
 389 
 390     /**
 391      * Creates a new custom cursor object with the specified name.<p>
 392      * Note:  this constructor should only be used by AWT implementations
 393      * as part of their support for custom cursors.  Applications should
 394      * use Toolkit.createCustomCursor().
 395      * @param name the user-visible name of the cursor.
 396      * @see java.awt.Toolkit#createCustomCursor
 397      */
 398     protected Cursor(String name) {
 399         this.type = Cursor.CUSTOM_CURSOR;
 400         this.name = name;
 401     }
 402 
 403     /**
 404      * Returns the type for this cursor.
 405      *
 406      * @return the cursor type
 407      */
 408     public int getType() {
 409         return type;
 410     }
 411 
 412     /**
 413      * Returns the name of this cursor.
 414      * @return    a localized description of this cursor.
 415      * @since     1.2
 416      */
 417     public String getName() {
 418         return name;
 419     }
 420 
 421     /**
 422      * Returns a string representation of this cursor.
 423      * @return    a string representation of this cursor.
 424      * @since     1.2
 425      */
 426     public String toString() {
 427         return getClass().getName() + "[" + getName() + "]";
 428     }
 429 
 430     /*
 431      * load the cursor.properties file
 432      */
 433     private static void loadSystemCustomCursorProperties() throws AWTException {
 434         synchronized(systemCustomCursors) {
 435             systemCustomCursorProperties = new Properties();
 436 
 437             try {
 438                 AccessController.doPrivileged(
 439                         (PrivilegedExceptionAction<Object>) () -> {
 440                             try (InputStream is = Cursor.class
 441                                     .getResourceAsStream(PROPERTIES_FILE)) {
 442                                 systemCustomCursorProperties.load(is);
 443                             }
 444                             return null;
 445                         });
 446             } catch (Exception e) {
 447                 systemCustomCursorProperties = null;
 448                  throw new AWTException("Exception: " + e.getClass() + " " +
 449                    e.getMessage() + " occurred while loading: " +
 450                    PROPERTIES_FILE);
 451             }
 452         }
 453     }
 454 
 455     private native static void finalizeImpl(long pData);
 456 }