Coverage Report - com.sun.javafx.runtime.util.FXPropertyResourceBundle
 
Classes in this File Line Coverage Branch Coverage Complexity
FXPropertyResourceBundle
73%
151/208
61%
94/154
0
FXPropertyResourceBundle$FXPropertiesControl
85%
22/26
70%
7/10
0
FXPropertyResourceBundle$FXPropertiesControl$1
42%
5/12
17%
1/6
0
FXPropertyResourceBundle$FxEchoBackResourceBundle
70%
7/10
50%
1/2
0
 
 1  
 /*
 2  
  * Copyright 2008 Sun Microsystems, Inc.  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.  Sun designates this
 8  
  * particular file as subject to the "Classpath" exception as provided
 9  
  * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 22  
  * CA 95054 USA or visit www.sun.com if you need additional information or
 23  
  * have any questions.
 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  
     // code point literals
 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  
     // to be removed if we discard JDK 5 support
 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;  // quoting character used for a literal
 125  
 
 126  2067
         while ((c = getCodePoint(br)) != -1) {
 127  2054
             switch (c) {
 128  
             case CRETURN:
 129  
                 // normalize '\r' and "\r\n" to '\n'
 130  10
                 br.mark(8);
 131  10
                 if (getCodePoint(br) != NEWLINE) {
 132  0
                     br.reset();
 133  
                 }
 134  
                 // fall through
 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  
                     // start of a literal
 160  70
                     quote = c;
 161  71
                 } else if (c != SQUOTE && c != DQUOTE) {
 162  
                     // a normal character in a literal
 163  0
                     sb.appendCodePoint(c);
 164  71
                 } else if (quote != c) {
 165  
                     // the other quote character in a literal
 166  1
                     sb.appendCodePoint(c);
 167  
                 } else {
 168  
                     // closing of a quote
 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  
                     // append the next character no matter what
 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  
                     // ignore BOM at the beginning
 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  
             // skip till we find a corresponding "*/"
 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  
                     // non-closing comment causes an error
 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  
             // skip till we find a new line or end of the file
 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  
      * Converts escape sequences (e.g., "\u0020") in the given <code>str</code> to
 293  
      * their Unicode values and returns a String containing the converted Unicode values.
 294  
      * The conversion follows the spec in JLS 3.0 3.3 Unicode Escapes and 3.10.6 Escape
 295  
      * Sequences for Character and String Literals.
 296  
      * 
 297  
      * @param str a <code>String</code> to be converted
 298  
      * @return a <code>String</code> containing converted escapes.
 299  
      *         If the given <code>str</code> doesn't include any escape sequences,
 300  
      *         <code>str</code> is returned.
 301  
      * @exception NullPointerException if <code>str</code> is null.
 302  
      * @exception IllegalArgumentException if <code>str</code> contains any invalid
 303  
      *            escape sequences.
 304  
      */
 305  
     private static String convertEscapes(String str) {
 306  
         // Quickly check if str has any backslash.
 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  
                             // conforms to the CSS encoding declaration
 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  
                                             // Disable caches to get fresh data for
 528  
                                             // reloading.
 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  
 }