1 | |
|
2 | |
|
3 | |
|
4 | |
|
5 | |
|
6 | |
|
7 | |
|
8 | |
|
9 | |
|
10 | |
|
11 | |
|
12 | |
|
13 | |
|
14 | |
|
15 | |
|
16 | |
|
17 | |
|
18 | |
|
19 | |
|
20 | |
|
21 | |
|
22 | |
|
23 | |
|
24 | |
|
25 | |
|
26 | |
package com.sun.javafx.runtime.util; |
27 | |
|
28 | |
import java.io.BufferedInputStream; |
29 | |
import java.io.BufferedReader; |
30 | |
import java.io.InputStream; |
31 | |
import java.io.InputStreamReader; |
32 | |
import java.io.IOException; |
33 | |
import java.io.Reader; |
34 | |
import java.net.URL; |
35 | |
import java.net.URLConnection; |
36 | |
import java.nio.charset.Charset; |
37 | |
import java.security.AccessController; |
38 | |
import java.security.PrivilegedActionException; |
39 | |
import java.security.PrivilegedExceptionAction; |
40 | |
import java.util.Arrays; |
41 | |
import java.util.Collections; |
42 | |
import java.util.List; |
43 | |
import java.util.Locale; |
44 | |
import java.util.Enumeration; |
45 | |
import java.util.HashSet; |
46 | |
import java.util.Set; |
47 | |
import java.util.concurrent.ConcurrentMap; |
48 | |
import java.util.concurrent.ConcurrentHashMap; |
49 | |
import java.util.logging.Level; |
50 | |
import java.util.logging.Logger; |
51 | |
|
52 | |
import com.sun.javafx.runtime.util.backport.ResourceBundle; |
53 | |
import com.sun.javafx.runtime.util.backport.ResourceBundleEnumeration; |
54 | |
|
55 | 92 | class FXPropertyResourceBundle extends ResourceBundle { |
56 | |
|
57 | |
private static final String CHARTAG = "@charset \""; |
58 | 8 | private static final List<String> FORMAT_FXPROPERTIES |
59 | |
= Collections.unmodifiableList(Arrays.asList("javafx.properties")); |
60 | |
private ConcurrentMap<String, Object> lookup; |
61 | 8 | private static Logger logger = null; |
62 | |
|
63 | |
|
64 | |
private static final int CRETURN = 0x000d; |
65 | |
private static final int NEWLINE = 0x000a; |
66 | |
private static final int FSLASH = 0x002f; |
67 | |
private static final int DQUOTE = 0x0022; |
68 | |
private static final int SQUOTE = 0x0027; |
69 | |
private static final int EQUAL = 0x003d; |
70 | |
private static final int BSLASH = 0x005c; |
71 | |
private static final int SUBST = 0xfffd; |
72 | |
private static final int BOM = 0xfeff; |
73 | |
|
74 | |
|
75 | 8 | private static final Locale ROOTLOCALE = new Locale(""); |
76 | |
|
77 | |
public FXPropertyResourceBundle(InputStream is, String resourceName) |
78 | |
throws IOException { |
79 | 13 | this(getReader(is), resourceName); |
80 | 13 | } |
81 | |
|
82 | |
public FXPropertyResourceBundle(Reader reader, String resourceName) |
83 | 13 | throws IOException { |
84 | 13 | lookup = new ConcurrentHashMap<String, Object>(); |
85 | 13 | initialize(reader, resourceName); |
86 | 13 | } |
87 | |
|
88 | |
@Override |
89 | |
public boolean containsKey(String key) { |
90 | 0 | if (key == null) { |
91 | 0 | throw new NullPointerException(); |
92 | |
} |
93 | 0 | return true; |
94 | |
} |
95 | |
|
96 | |
@Override |
97 | |
protected Object handleGetObject(String key) { |
98 | 45 | if (key == null) { |
99 | 0 | throw new NullPointerException(); |
100 | |
} |
101 | 45 | return lookup.get(key); |
102 | |
} |
103 | |
|
104 | |
@Override |
105 | |
public Enumeration<String> getKeys() { |
106 | 0 | ResourceBundle parent = this.parent; |
107 | 0 | return new ResourceBundleEnumeration(lookup.keySet(), |
108 | |
(parent != null) ? parent.getKeys() : null); |
109 | |
} |
110 | |
|
111 | |
@Override |
112 | |
protected Set<String> handleKeySet() { |
113 | 4 | return lookup.keySet(); |
114 | |
} |
115 | |
|
116 | |
private void initialize(Reader reader, String resourceName) throws IOException { |
117 | 13 | BufferedReader br = new BufferedReader(reader); |
118 | |
int c; |
119 | 13 | int lineNum = 1; |
120 | 13 | StringBuilder sb = new StringBuilder(); |
121 | 13 | String key = null; |
122 | 13 | boolean foundEqual = false; |
123 | 13 | boolean firstChar = true; |
124 | 13 | int quote = 0; |
125 | |
|
126 | 2067 | while ((c = getCodePoint(br)) != -1) { |
127 | 2054 | switch (c) { |
128 | |
case CRETURN: |
129 | |
|
130 | 10 | br.mark(8); |
131 | 10 | if (getCodePoint(br) != NEWLINE) { |
132 | 0 | br.reset(); |
133 | |
} |
134 | |
|
135 | |
case NEWLINE: |
136 | 56 | lineNum ++; |
137 | 56 | if (quote != 0) { |
138 | 6 | sb.appendCodePoint(NEWLINE); |
139 | |
} |
140 | |
break; |
141 | |
|
142 | |
case FSLASH: |
143 | 30 | if (quote != 0) { |
144 | 17 | sb.appendCodePoint(c); |
145 | |
} else { |
146 | 13 | lineNum += skipComments(br, resourceName); |
147 | |
} |
148 | 13 | break; |
149 | |
|
150 | |
case SQUOTE: |
151 | |
case DQUOTE: |
152 | 141 | if (quote == 0) { |
153 | 70 | if ((key == null && foundEqual) || |
154 | |
(key != null && !foundEqual)) { |
155 | 0 | logPropertySyntaxError(c, lineNum, resourceName); |
156 | 0 | break; |
157 | |
} |
158 | |
|
159 | |
|
160 | 70 | quote = c; |
161 | 71 | } else if (c != SQUOTE && c != DQUOTE) { |
162 | |
|
163 | 0 | sb.appendCodePoint(c); |
164 | 71 | } else if (quote != c) { |
165 | |
|
166 | 1 | sb.appendCodePoint(c); |
167 | |
} else { |
168 | |
|
169 | 70 | quote = 0; |
170 | 70 | if (!foundEqual && key == null) { |
171 | |
try { |
172 | 35 | key = convertEscapes(sb.toString()); |
173 | 0 | } catch (IllegalArgumentException e) { |
174 | 0 | logPropertySyntaxError(e.getMessage(), lineNum, resourceName); |
175 | 35 | } |
176 | 35 | sb.setLength(0); |
177 | 35 | } else if (foundEqual && key != null) { |
178 | |
try { |
179 | 35 | lookup.put(key, convertEscapes(sb.toString())); |
180 | 0 | } catch (IllegalArgumentException e) { |
181 | 0 | logPropertySyntaxError(e.getMessage(), lineNum, resourceName); |
182 | 35 | } |
183 | 35 | sb.setLength(0); |
184 | 35 | key = null; |
185 | 35 | foundEqual = false; |
186 | |
} else { |
187 | 0 | logPropertySyntaxError(c, lineNum, resourceName); |
188 | |
} |
189 | |
} |
190 | 0 | break; |
191 | |
|
192 | |
case EQUAL: |
193 | 35 | if (quote != 0) { |
194 | 0 | sb.appendCodePoint(c); |
195 | |
} else { |
196 | 35 | if (foundEqual) { |
197 | 0 | logPropertySyntaxError(c, lineNum, resourceName); |
198 | |
} else { |
199 | 35 | if (key == null) { |
200 | 0 | logPropertySyntaxError(c, lineNum, resourceName); |
201 | |
} else { |
202 | 35 | foundEqual = true; |
203 | |
} |
204 | |
} |
205 | |
} |
206 | 35 | break; |
207 | |
|
208 | |
case BSLASH: |
209 | 16 | if (quote != 0) { |
210 | 16 | sb.appendCodePoint(c); |
211 | |
|
212 | 16 | sb.appendCodePoint(getCodePoint(br)); |
213 | |
} else { |
214 | 0 | logPropertySyntaxError(c, lineNum, resourceName); |
215 | |
} |
216 | 0 | break; |
217 | |
|
218 | |
case BOM: |
219 | 0 | if (firstChar) { |
220 | |
|
221 | 0 | firstChar = false; |
222 | |
} else { |
223 | 0 | logPropertySyntaxError(c, lineNum, resourceName); |
224 | |
} |
225 | 0 | break; |
226 | |
|
227 | |
default: |
228 | 1776 | if (quote != 0) { |
229 | 1704 | sb.appendCodePoint(c); |
230 | 72 | } else if (Character.isWhitespace(c) || c == SUBST) { |
231 | 1 | break; |
232 | |
} else { |
233 | 0 | logPropertySyntaxError(c, lineNum, resourceName); |
234 | |
} |
235 | 0 | break; |
236 | |
} |
237 | |
} |
238 | |
|
239 | 13 | br.close(); |
240 | 13 | } |
241 | |
|
242 | |
private int getCodePoint(BufferedReader br) throws IOException { |
243 | 2471 | int c = br.read(); |
244 | 2471 | if (Character.isHighSurrogate((char)c)) { |
245 | 0 | return Character.toCodePoint((char)c, (char)br.read()); |
246 | |
} else { |
247 | 2471 | return c; |
248 | |
} |
249 | |
} |
250 | |
|
251 | |
private int skipComments(BufferedReader br, String resourceName) throws IOException { |
252 | 13 | int newlines = 0; |
253 | |
|
254 | 13 | switch ((char)getCodePoint(br)) { |
255 | |
case '*': |
256 | |
|
257 | |
while (true) { |
258 | 30 | int i = getCodePoint(br); |
259 | 30 | if ((char)i == '\n') { |
260 | 2 | newlines ++; |
261 | 28 | } else if ((char)i == '*') { |
262 | 2 | if ((char)getCodePoint(br) == '/') { |
263 | 2 | break; |
264 | |
} |
265 | 26 | } else if (i == -1) { |
266 | |
|
267 | 0 | log(Level.WARNING, |
268 | |
"non-closing comment at the end of "+resourceName); |
269 | 0 | break; |
270 | |
} |
271 | 28 | } |
272 | |
break; |
273 | |
|
274 | |
case '/': |
275 | |
|
276 | |
while (true) { |
277 | 333 | int i = getCodePoint(br); |
278 | 333 | if ((char)i == '\n') { |
279 | 11 | newlines ++; |
280 | 11 | break; |
281 | 322 | } else if (i == -1) { |
282 | 0 | break; |
283 | |
} |
284 | 322 | } |
285 | |
break; |
286 | |
} |
287 | |
|
288 | 13 | return newlines; |
289 | |
} |
290 | |
|
291 | |
|
292 | |
|
293 | |
|
294 | |
|
295 | |
|
296 | |
|
297 | |
|
298 | |
|
299 | |
|
300 | |
|
301 | |
|
302 | |
|
303 | |
|
304 | |
|
305 | |
private static String convertEscapes(String str) { |
306 | |
|
307 | 70 | int x= str.indexOf('\\'); |
308 | 70 | if (x == -1) { |
309 | 60 | return str; |
310 | |
} |
311 | |
|
312 | 10 | StringBuilder sb = new StringBuilder(); |
313 | 10 | if (x != 0) { |
314 | 9 | sb.append(str, 0, x); |
315 | |
} |
316 | 10 | int len = str.length(); |
317 | |
try { |
318 | 172 | while (x < len) { |
319 | 162 | char c = str.charAt(x++); |
320 | 162 | if (c != '\\') { |
321 | 146 | sb.append(c); |
322 | 146 | continue; |
323 | |
} |
324 | |
|
325 | 16 | int top = x - 1; |
326 | 16 | c = str.charAt(x++); |
327 | 16 | int n = -1; |
328 | 16 | switch (c) { |
329 | |
case 'u': |
330 | 1 | n = 0; |
331 | 5 | for (int i = 0; i < 4; i++) { |
332 | 4 | c = str.charAt(x++); |
333 | 4 | if (('0' <= c && c <= '9') || |
334 | |
('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')) { |
335 | 4 | n = (n << 4) + Character.digit(c, 16); |
336 | |
} else { |
337 | 0 | throw new IllegalArgumentException("illegal escape sequence '" |
338 | |
+ str.substring(top, x) + "'"); |
339 | |
} |
340 | |
} |
341 | 1 | break; |
342 | |
case 'b': |
343 | 0 | n = '\b'; |
344 | 0 | break; |
345 | |
case 't': |
346 | 1 | n = '\t'; |
347 | 1 | break; |
348 | |
case 'n': |
349 | 0 | n = '\n'; |
350 | 0 | break; |
351 | |
case 'f': |
352 | 0 | n = '\f'; |
353 | 0 | break; |
354 | |
case 'r': |
355 | 0 | n = '\r'; |
356 | 0 | break; |
357 | |
case '"': |
358 | 8 | n = '"'; |
359 | 8 | break; |
360 | |
case '\'': |
361 | 4 | n = '\''; |
362 | 4 | break; |
363 | |
case '\\': |
364 | 1 | n = c; |
365 | 1 | break; |
366 | |
case '0': case '1': case '2': case '3': |
367 | |
case '4': case '5': case '6': case '7': |
368 | 1 | n = Character.digit(c, 8); |
369 | 1 | char leadChar = c; |
370 | 1 | if (x < len) { |
371 | 1 | c = str.charAt(x); |
372 | 1 | if ('0' <= c && c <= '7') { |
373 | 1 | n = (n << 3) + Character.digit(c, 8); |
374 | 1 | if (++x < len) { |
375 | 1 | c = str.charAt(x); |
376 | 1 | if (leadChar <= '3' && '0' <= c && c <= '7') { |
377 | 1 | n = (n << 3) + Character.digit(c, 8); |
378 | 1 | x++; |
379 | |
} |
380 | |
} |
381 | |
} |
382 | |
} |
383 | |
break; |
384 | |
default: |
385 | 0 | throw new IllegalArgumentException("illegal escape sequence '" |
386 | |
+ str.substring(top, x) + "'"); |
387 | |
} |
388 | 16 | if (n != -1) { |
389 | 16 | sb.append((char) n); |
390 | |
} |
391 | 16 | } |
392 | 0 | } catch (StringIndexOutOfBoundsException e) { |
393 | 0 | throw new IllegalArgumentException("illegal escape sequence: " + str); |
394 | 10 | } |
395 | 10 | return sb.toString(); |
396 | |
} |
397 | |
|
398 | |
private static Reader getReader(InputStream is) throws IOException { |
399 | 13 | Charset charset = null; |
400 | 13 | BufferedInputStream bis = new BufferedInputStream(is); |
401 | 13 | bis.mark(256); |
402 | 13 | byte[] ba = new byte[CHARTAG.length()]; |
403 | 13 | if (bis.read(ba, 0, CHARTAG.length()) == ba.length) { |
404 | 13 | String possibleCharsetTag = new String(ba, "UTF-8"); |
405 | 13 | if (possibleCharsetTag.equals(CHARTAG)) { |
406 | 4 | StringBuilder sb = new StringBuilder(); |
407 | |
byte b; |
408 | 4 | boolean found = false; |
409 | |
while (true) { |
410 | 52 | b = (byte)bis.read(); |
411 | |
|
412 | 52 | if (b == '\r' || b == '\n') { |
413 | 4 | if (!found) { |
414 | 0 | log(Level.WARNING, |
415 | |
"Incorrect format in @charset tag"); |
416 | |
} |
417 | |
break; |
418 | |
} |
419 | |
|
420 | 48 | if (b != '"') { |
421 | 44 | sb.append((char)b); |
422 | |
} else { |
423 | 4 | found = true; |
424 | 4 | if ((char)bis.read() == ';') { |
425 | |
|
426 | |
try { |
427 | 4 | charset = Charset.forName(sb.toString()); |
428 | 0 | } catch (Exception e) { |
429 | 0 | log(Level.WARNING, |
430 | |
"charset '" + sb.toString() + "' was not available"); |
431 | 4 | } |
432 | |
} else { |
433 | 0 | log(Level.WARNING, |
434 | |
"Incorrect format in @charset tag"); |
435 | |
} |
436 | |
} |
437 | |
} |
438 | 4 | } else { |
439 | 9 | bis.reset(); |
440 | |
} |
441 | |
} |
442 | |
|
443 | 13 | if (charset == null) { |
444 | 9 | charset = Charset.forName("UTF-8"); |
445 | |
} |
446 | |
|
447 | 13 | return new InputStreamReader(bis, charset); |
448 | |
} |
449 | |
|
450 | |
private static class FxEchoBackResourceBundle extends ResourceBundle { |
451 | 8 | private static final Set<String> keyset = new HashSet<String>(); |
452 | 8 | static final FxEchoBackResourceBundle INSTANCE = new FxEchoBackResourceBundle(); |
453 | |
|
454 | 8 | private FxEchoBackResourceBundle() { |
455 | 8 | } |
456 | |
|
457 | |
@Override |
458 | |
public boolean containsKey(String key) { |
459 | 0 | return true; |
460 | |
} |
461 | |
|
462 | |
@Override |
463 | |
protected Object handleGetObject(String key) { |
464 | 12 | if (key == null) { |
465 | 0 | throw new NullPointerException(); |
466 | |
} |
467 | 12 | return key; |
468 | |
} |
469 | |
|
470 | |
@Override |
471 | |
public Enumeration<String> getKeys() { |
472 | 0 | return new ResourceBundleEnumeration(keyset, null); |
473 | |
} |
474 | |
|
475 | |
@Override protected Set<String> handleKeySet() { |
476 | 8 | return keyset; |
477 | |
} |
478 | |
} |
479 | |
|
480 | |
static class FXPropertiesControl extends ResourceBundle.Control { |
481 | 8 | static final FXPropertiesControl INSTANCE = new FXPropertiesControl(); |
482 | |
|
483 | 8 | private FXPropertiesControl() { |
484 | 8 | } |
485 | |
|
486 | |
@Override |
487 | |
public List<String> getFormats(String baseName) { |
488 | 52 | if (baseName == null) { |
489 | 0 | throw new NullPointerException(); |
490 | |
} |
491 | |
|
492 | 52 | return FXPropertyResourceBundle.FORMAT_FXPROPERTIES; |
493 | |
} |
494 | |
|
495 | |
@Override |
496 | |
public Locale getFallbackLocale(String baseName, Locale locale) { |
497 | 8 | if (baseName == null || locale == null) { |
498 | 0 | throw new NullPointerException(); |
499 | |
} |
500 | 8 | return null; |
501 | |
} |
502 | |
|
503 | |
@Override |
504 | |
public ResourceBundle newBundle(String baseName, Locale locale, String format, |
505 | |
ClassLoader classLoader, boolean reloadFlag) |
506 | |
throws IllegalAccessException, InstantiationException, IOException { |
507 | 40 | if (locale.equals(ROOTLOCALE)) { |
508 | 13 | return FxEchoBackResourceBundle.INSTANCE; |
509 | |
} |
510 | |
|
511 | 27 | String bundleName = toBundleName(baseName, locale); |
512 | 27 | ResourceBundle bundle = null; |
513 | 27 | final String resourceName = toResourceName(bundleName, "fxproperties"); |
514 | 27 | final ClassLoader loader = classLoader; |
515 | 27 | final boolean reload = reloadFlag; |
516 | 27 | InputStream stream = null; |
517 | |
try { |
518 | 27 | stream = AccessController.doPrivileged( |
519 | 54 | new PrivilegedExceptionAction<InputStream>() { |
520 | |
public InputStream run() throws IOException { |
521 | 27 | InputStream is = null; |
522 | 27 | if (reload) { |
523 | 0 | URL url = loader.getResource(resourceName); |
524 | 0 | if (url != null) { |
525 | 0 | URLConnection connection = url.openConnection(); |
526 | 0 | if (connection != null) { |
527 | |
|
528 | |
|
529 | 0 | connection.setUseCaches(false); |
530 | 0 | is = connection.getInputStream(); |
531 | |
} |
532 | |
} |
533 | 0 | } else { |
534 | 27 | is = loader.getResourceAsStream(resourceName); |
535 | |
} |
536 | 27 | return is; |
537 | |
} |
538 | |
}); |
539 | 0 | } catch (PrivilegedActionException e) { |
540 | 0 | throw (IOException) e.getException(); |
541 | 27 | } |
542 | |
|
543 | 27 | if (stream != null) { |
544 | |
try { |
545 | 13 | bundle = new FXPropertyResourceBundle(stream, resourceName); |
546 | |
} finally { |
547 | 13 | stream.close(); |
548 | 13 | } |
549 | |
} |
550 | |
|
551 | 27 | return bundle; |
552 | |
} |
553 | |
} |
554 | |
|
555 | |
private static void logPropertySyntaxError(int c, int lineNum, String resourceName) { |
556 | 0 | logPropertySyntaxError(String.format("'%c' (U+%04X) is incorrectly placed", c, c), |
557 | |
lineNum, resourceName); |
558 | 0 | } |
559 | |
|
560 | |
private static void logPropertySyntaxError(String message, int lineNum, String resourceName) { |
561 | 0 | logPropertySyntaxError(String.format("%s in line %d of %s", message, lineNum, resourceName)); |
562 | 0 | } |
563 | |
|
564 | |
private static void logPropertySyntaxError(String message) { |
565 | 0 | log(Level.WARNING, message); |
566 | 0 | throw new IllegalArgumentException(message); |
567 | |
} |
568 | |
|
569 | |
private static void log(Level l, String msg) { |
570 | 0 | if (logger == null) { |
571 | 0 | logger = Logger.getLogger("com.sun.javafx.runtime.util.FXPropertyResourceBundle"); |
572 | |
} |
573 | |
|
574 | 0 | logger.log(l, msg); |
575 | 0 | } |
576 | |
} |