1 /*
2  * Copyright (c) 1996, 2018, 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.security.AccessController;
30 import java.security.PrivilegedAction;
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 static final 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}.
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 */
Toolkit.loadLibraries()185         Toolkit.loadLibraries();
186         if (!GraphicsEnvironment.isHeadless()) {
initIDs()187             initIDs();
188         }
189 
AWTAccessor.setCursorAccessor( new AWTAccessor.CursorAccessor() { public long getPData(Cursor cursor) { return cursor.pData; } public void setPData(Cursor cursor, long pData) { cursor.pData = pData; } public int getType(Cursor cursor) { return cursor.type; } })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      */
initIDs()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;
CursorDisposer(long pData)221         public CursorDisposer(long pData) {
222             this.pData = pData;
223         }
dispose()224         public void dispose() {
225             if (pData != 0) {
226                 finalizeImpl(pData);
227             }
228         }
229     }
230     transient CursorDisposer disposer;
setPData(long pData)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      */
getPredefinedCursor(int type)264     public static 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} returns true
287      * @exception AWTException in case of erroneous retrieving of the cursor
288      */
getSystemCustomCursor(final String name)289     public static 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             final Toolkit toolkit = Toolkit.getDefaultToolkit();
334             final String file = RESOURCE_PREFIX + fileName;
335             final InputStream in = AccessController.doPrivileged(
336                     (PrivilegedAction<InputStream>) () -> {
337                         return Cursor.class.getResourceAsStream(file);
338                     });
339             try (in) {
340                 Image image = toolkit.createImage(in.readAllBytes());
341                 cursor = toolkit.createCustomCursor(image, hotPoint, localized);
342             } catch (Exception e) {
343                 throw new AWTException(
344                     "Exception: " + e.getClass() + " " + e.getMessage() +
345                     " occurred while creating cursor " + name);
346             }
347 
348             if (cursor == null) {
349                 if (log.isLoggable(PlatformLogger.Level.FINER)) {
350                     log.finer("Cursor.getSystemCustomCursor(" + name + ") returned null");
351                 }
352             } else {
353                 systemCustomCursors.put(name, cursor);
354             }
355         }
356 
357         return cursor;
358     }
359 
360     /**
361      * Return the system default cursor.
362      *
363      * @return the default cursor
364      */
getDefaultCursor()365     public static Cursor getDefaultCursor() {
366         return getPredefinedCursor(Cursor.DEFAULT_CURSOR);
367     }
368 
369     /**
370      * Creates a new cursor object with the specified type.
371      * @param type the type of cursor
372      * @throws IllegalArgumentException if the specified cursor type
373      * is invalid
374      */
375     @ConstructorProperties({"type"})
Cursor(int type)376     public Cursor(int type) {
377         if (type < Cursor.DEFAULT_CURSOR || type > Cursor.MOVE_CURSOR) {
378             throw new IllegalArgumentException("illegal cursor type");
379         }
380         this.type = type;
381 
382         // Lookup localized name.
383         name = Toolkit.getProperty(cursorProperties[type][0],
384                                    cursorProperties[type][1]);
385     }
386 
387     /**
388      * Creates a new custom cursor object with the specified name.<p>
389      * Note:  this constructor should only be used by AWT implementations
390      * as part of their support for custom cursors.  Applications should
391      * use Toolkit.createCustomCursor().
392      * @param name the user-visible name of the cursor.
393      * @see java.awt.Toolkit#createCustomCursor
394      */
Cursor(String name)395     protected Cursor(String name) {
396         this.type = Cursor.CUSTOM_CURSOR;
397         this.name = name;
398     }
399 
400     /**
401      * Returns the type for this cursor.
402      *
403      * @return the cursor type
404      */
getType()405     public int getType() {
406         return type;
407     }
408 
409     /**
410      * Returns the name of this cursor.
411      * @return    a localized description of this cursor.
412      * @since     1.2
413      */
getName()414     public String getName() {
415         return name;
416     }
417 
418     /**
419      * Returns a string representation of this cursor.
420      * @return    a string representation of this cursor.
421      * @since     1.2
422      */
toString()423     public String toString() {
424         return getClass().getName() + "[" + getName() + "]";
425     }
426 
427     /*
428      * load the cursor.properties file
429      */
loadSystemCustomCursorProperties()430     private static void loadSystemCustomCursorProperties() throws AWTException {
431         synchronized(systemCustomCursors) {
432             systemCustomCursorProperties = new Properties();
433 
434             try {
435                 AccessController.doPrivileged(
436                         (PrivilegedExceptionAction<Object>) () -> {
437                             try (InputStream is = Cursor.class
438                                     .getResourceAsStream(PROPERTIES_FILE)) {
439                                 systemCustomCursorProperties.load(is);
440                             }
441                             return null;
442                         });
443             } catch (Exception e) {
444                 systemCustomCursorProperties = null;
445                  throw new AWTException("Exception: " + e.getClass() + " " +
446                    e.getMessage() + " occurred while loading: " +
447                    PROPERTIES_FILE);
448             }
449         }
450     }
451 
finalizeImpl(long pData)452     private static native void finalizeImpl(long pData);
453 }
454