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