| Line | Hits | Source |
|---|---|---|
| 1 | /* | |
| 2 | Chrysalis Web Framework [http://chrysalis.sourceforge.net] | |
| 3 | Copyright (c) 2002, 2003, 2004, Paul Strack | |
| 4 | ||
| 5 | All rights reserved. | |
| 6 | ||
| 7 | Redistribution and use in source and binary forms, with or without | |
| 8 | modification, are permitted provided that the following conditions are met: | |
| 9 | ||
| 10 | 1. Redistributions of source code must retain the above copyright notice, this | |
| 11 | list of conditions and the following disclaimer. | |
| 12 | ||
| 13 | 2. Redistributions in binary form must reproduce the above copyright notice, | |
| 14 | this list of conditions and the following disclaimer in the documentation | |
| 15 | and/or other materials provided with the distribution. | |
| 16 | ||
| 17 | 3. Neither the name of the copyright holder nor the names of its contributors | |
| 18 | may be used to endorse or promote products derived from this software without | |
| 19 | specific prior written permission. | |
| 20 | ||
| 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |
| 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | |
| 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
| 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
| 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |
| 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 31 | */ | |
| 32 | ||
| 33 | package org.chwf.config; | |
| 34 | ||
| 35 | import java.io.IOException; | |
| 36 | import java.io.InputStream; | |
| 37 | import java.util.Collections; | |
| 38 | import java.util.Locale; | |
| 39 | import java.util.Map; | |
| 40 | import java.util.Properties; | |
| 41 | import java.util.SortedMap; | |
| 42 | import java.util.TreeMap; | |
| 43 | import java.util.WeakHashMap; | |
| 44 | ||
| 45 | import javax.xml.parsers.SAXParser; | |
| 46 | import javax.xml.parsers.SAXParserFactory; | |
| 47 | ||
| 48 | import org.chwf.util.SequencedHashMap; | |
| 49 | ||
| 50 | /** | |
| 51 | * @author Paul Strack | |
| 52 | */ | |
| 53 | 0 | public abstract class ConfigFactory { |
| 54 | ||
| 55 | /** Property for location of extra configuration: "extra.xml.config". */ | |
| 56 | static final String EXTRA_XML_CONFIG = "extra.xml.config"; | |
| 57 | ||
| 58 | /** | |
| 59 | * A map for storing Config objects. This map holds its | |
| 60 | * contents via weak references so that references can be | |
| 61 | * deleted if application memory becomes an issue. The | |
| 62 | * deleted Config object will be reloaded when requested. | |
| 63 | */ | |
| 64 | 2 | static final Map CONFIG_MAP = new WeakHashMap(); |
| 65 | ||
| 66 | /** Constant for empty string. */ | |
| 67 | static final String EMPTY_STRING = ""; | |
| 68 | ||
| 69 | /** Constant for underscore. */ | |
| 70 | static final char UNDERSCORE = '_'; | |
| 71 | ||
| 72 | /** Character delimiting sub-names in configuration keys. */ | |
| 73 | static final char KEY_DELIMITER = '.'; | |
| 74 | ||
| 75 | /** File extension for properties. */ | |
| 76 | static final String PROPERTY_FILE_EXTENSION = ".properties"; | |
| 77 | ||
| 78 | /** File extension for xml. */ | |
| 79 | static final String XML_FILE_EXTENSION = ".xml"; | |
| 80 | ||
| 81 | /** Base file name for package-level resources. */ | |
| 82 | static final String PACKAGE_RESOURCE = "chrysalis"; | |
| 83 | ||
| 84 | /** Length of package resource name. */ | |
| 85 | 1 | static final int PACKAGE_RESOURCE_LENGTH = PACKAGE_RESOURCE.length(); |
| 86 | ||
| 87 | /** Limit to number of rounds of property expansion. */ | |
| 88 | static final int EXPANSION_LIMIT = 100; | |
| 89 | ||
| 90 | /** String used to mark start of property expansion point. */ | |
| 91 | static final String EXPANSION_START = "${"; | |
| 92 | ||
| 93 | /** String used to mark end of property expansion point. */ | |
| 94 | static final String EXPANSION_END = "}"; | |
| 95 | ||
| 96 | /** File separator abbreviation. */ | |
| 97 | static final String FILE_SEPARATOR_ABBREVIATION = "/"; | |
| 98 | ||
| 99 | /** Path separator abbrevation. */ | |
| 100 | static final String PATH_SEPARATOR_ABBREVIATION = ":"; | |
| 101 | ||
| 102 | /** File separator system property key. */ | |
| 103 | static final String FILE_SEPARATOR = "file.separator"; | |
| 104 | ||
| 105 | /** Path separator system property key. */ | |
| 106 | static final String PATH_SEPARATOR = "path.separator"; | |
| 107 | ||
| 108 | /** Empty sorted map singleton. */ | |
| 109 | 1 | static final SortedMap EMPTY_SORTED_MAP = |
| 110 | Collections.unmodifiableSortedMap(new TreeMap()); | |
| 111 | ||
| 112 | /** | |
| 113 | * <p>Factory method for Config class.</p> | |
| 114 | * | |
| 115 | * @param contextClass The context class for the configuration. | |
| 116 | * @return The Config object for the given class. | |
| 117 | * @throws ConfigurationException For errors in loading configuration data. | |
| 118 | */ | |
| 119 | public static Config getConfig(Class contextClass) | |
| 120 | throws ConfigurationException { | |
| 121 | 367 | return getConfig(contextClass.getName(), null); |
| 122 | } | |
| 123 | ||
| 124 | /** | |
| 125 | * <p>Factory method for Config class.</p> | |
| 126 | * | |
| 127 | * @param contextClass The context class for the configuration. | |
| 128 | * @param locale The locale. | |
| 129 | * @return The Config object for the given class. | |
| 130 | * @throws ConfigurationException For errors in loading configuration data. | |
| 131 | */ | |
| 132 | public static Config getConfig(Class contextClass, Locale locale) | |
| 133 | throws ConfigurationException { | |
| 134 | 420 | return getConfig(contextClass.getName(), locale); |
| 135 | } | |
| 136 | ||
| 137 | /** | |
| 138 | * Factory method for Config class. The resource name should be the location | |
| 139 | * of the file in the classpath, with "." separating directories rather than | |
| 140 | * "/". If you use "/" or your operating systems path separator, they will be | |
| 141 | * replaced by ".". The resource name should omit the file extension | |
| 142 | * (".properties" or ".xml"). This factory method can be used for resources | |
| 143 | * that are not associated with any particular class.<p> | |
| 144 | * | |
| 145 | * @param resource The name of the resource. | |
| 146 | * @return The Config object for the given resource. | |
| 147 | * @throws ConfigurationException For errors in loading configuration data. | |
| 148 | */ | |
| 149 | public static Config getConfig(String resource) | |
| 150 | throws ConfigurationException { | |
| 151 | 5 | return getConfig(resource, null); |
| 152 | } | |
| 153 | ||
| 154 | /** | |
| 155 | * Factory method for Config class. As {@link #getConfig(java.lang.String)}, | |
| 156 | * but include a locale for internationalization.<p> | |
| 157 | * | |
| 158 | * @param resource The name of the resource. | |
| 159 | * @param locale The locale. | |
| 160 | * @return The Config object for the given resource. | |
| 161 | * @throws ConfigurationException For errors in loading configuration data. | |
| 162 | */ | |
| 163 | public static Config getConfig(String resource, Locale locale) | |
| 164 | throws ConfigurationException { | |
| 165 | ||
| 166 | 4870 | resource = resource.replace('/', '.'); |
| 167 | 4870 | Config config = null; |
| 168 | 4870 | String localizedResource = resource + deriveLocaleName(locale); |
| 169 | ||
| 170 | // Synchronize on the config map for thread safety | |
| 171 | 4870 | synchronized (CONFIG_MAP) { |
| 172 | 4870 | config = (Config) CONFIG_MAP.get(localizedResource); |
| 173 | 4870 | if (config == null) { |
| 174 | 3544 | config = new ConfigImpl(resource, locale); |
| 175 | 3542 | CONFIG_MAP.put(localizedResource, config); |
| 176 | } | |
| 177 | 4868 | } |
| 178 | 4868 | return config; |
| 179 | } | |
| 180 | ||
| 181 | /** | |
| 182 | * Derive name for Locale object. | |
| 183 | * | |
| 184 | * @param locale The locale | |
| 185 | * @return The name | |
| 186 | */ | |
| 187 | static String deriveLocaleName(Locale locale) { | |
| 188 | 9331 | if (locale == null) { |
| 189 | 6802 | return EMPTY_STRING; |
| 190 | } else { | |
| 191 | 2529 | if ((locale.getCountry() == null) |
| 192 | || locale.getCountry().equals(EMPTY_STRING)) { | |
| 193 | 1451 | return UNDERSCORE + locale.getLanguage(); |
| 194 | } else { | |
| 195 | 1078 | return UNDERSCORE |
| 196 | + locale.getLanguage() | |
| 197 | + UNDERSCORE | |
| 198 | + locale.getCountry(); | |
| 199 | } | |
| 200 | } | |
| 201 | } | |
| 202 | ||
| 203 | /** | |
| 204 | * Derive parent config (up the package hierarchy) from resource and locale. | |
| 205 | * | |
| 206 | * @param resource The resource name. | |
| 207 | * @param locale The locale. | |
| 208 | * @return The parent config. | |
| 209 | */ | |
| 210 | static Config deriveParentConfig(String resource, Locale locale) { | |
| 211 | 3525 | if (resource.equals(PACKAGE_RESOURCE)) { |
| 212 | 534 | return TERMINAL_CONFIG; |
| 213 | 2991 | } else if (resource.endsWith(PACKAGE_RESOURCE)) { |
| 214 | 2275 | String parentResource = deriveParentResourceName(resource); |
| 215 | 2275 | return getConfig(parentResource, locale); |
| 216 | } else { | |
| 217 | 716 | String parentResource = stripLastName(resource) + PACKAGE_RESOURCE; |
| 218 | 716 | return getConfig(parentResource, locale); |
| 219 | } | |
| 220 | } | |
| 221 | ||
| 222 | /** | |
| 223 | * Derive config for parent locale from resource and locale. | |
| 224 | * | |
| 225 | * @param resource The resource name. | |
| 226 | * @param locale The locale. | |
| 227 | * @return The config for parent locale. | |
| 228 | */ | |
| 229 | static Config deriveParentLocale(String resource, Locale locale) { | |
| 230 | 3542 | if (locale == null) { |
| 231 | 2542 | return TERMINAL_CONFIG; |
| 232 | 1000 | } else if (locale.getCountry().equals(EMPTY_STRING)) { |
| 233 | 556 | return getConfig(resource, null); |
| 234 | 444 | } else if (locale.getVariant().equals(EMPTY_STRING)) { |
| 235 | 444 | locale = new Locale(locale.getLanguage(), EMPTY_STRING); |
| 236 | 444 | return getConfig(resource, locale); |
| 237 | } else { | |
| 238 | 0 | locale = new Locale(locale.getLanguage(), locale.getVariant()); |
| 239 | 0 | return getConfig(resource, locale); |
| 240 | } | |
| 241 | } | |
| 242 | ||
| 243 | /** | |
| 244 | * Derive parent resource name for package resource (by chopping off the | |
| 245 | * last value). | |
| 246 | * | |
| 247 | * @param resource The resource name. | |
| 248 | * @return The parent resource name. | |
| 249 | */ | |
| 250 | static String deriveParentResourceName(String resource) { | |
| 251 | 2275 | int length = resource.length() - PACKAGE_RESOURCE_LENGTH - 1; |
| 252 | 2275 | String stripped = resource.substring(0, length); |
| 253 | 2275 | return stripLastName(stripped) + PACKAGE_RESOURCE; |
| 254 | } | |
| 255 | ||
| 256 | //----------------------------------------------------------------- | |
| 257 | // Static initialization methods | |
| 258 | //----------------------------------------------------------------- | |
| 259 | ||
| 260 | /** | |
| 261 | * Load config data for resource. | |
| 262 | * | |
| 263 | * @param resource The resource name. | |
| 264 | * @return The resource data. | |
| 265 | */ | |
| 266 | static Map initData(String resource) { | |
| 267 | 3544 | Map data = new SequencedHashMap(); |
| 268 | 3544 | data.putAll(loadPropertyFileData(resource)); |
| 269 | 3544 | loadXMLFileData(resource, data); |
| 270 | 3544 | loadExtraXMLData(resource, data); |
| 271 | 3544 | trimWhitespace(data); |
| 272 | 3544 | expandProperties(resource, data); |
| 273 | 3542 | if (data.isEmpty()) { |
| 274 | 2976 | return Collections.EMPTY_MAP; // Singleton to reduce memory usage |
| 275 | } else { | |
| 276 | 566 | return data; |
| 277 | } | |
| 278 | } | |
| 279 | ||
| 280 | /** | |
| 281 | * Load config data from property file and inserts it into the map. | |
| 282 | * | |
| 283 | * @param resource The resource name. | |
| 284 | * @return The resource data. | |
| 285 | */ | |
| 286 | static Map loadPropertyFileData(String resource) { | |
| 287 | 3544 | String file = deriveFile(resource, PROPERTY_FILE_EXTENSION); |
| 288 | 3544 | InputStream is = getStream(file); |
| 289 | 3544 | if (is != null) { |
| 290 | 386 | Properties props = new Properties(); |
| 291 | try { | |
| 292 | 386 | props.load(is); |
| 293 | 386 | } catch (IOException ex) { |
| 294 | 0 | handleFileError(file, ex); |
| 295 | 0 | } finally { |
| 296 | 0 | try { |
| 297 | 386 | is.close(); |
| 298 | 386 | } catch (IOException ex) { |
| 299 | 0 | handleFileError(file, ex); |
| 300 | 386 | } |
| 301 | } | |
| 302 | 386 | return props; |
| 303 | } else { | |
| 304 | 3158 | return Collections.EMPTY_MAP; |
| 305 | } | |
| 306 | } | |
| 307 | ||
| 308 | /** | |
| 309 | * Load config data from xml file and inserts it into the map. | |
| 310 | * | |
| 311 | * @param resource The resource name. | |
| 312 | * @param data The map to which resource data is added. | |
| 313 | */ | |
| 314 | static void loadXMLFileData(String resource, Map data) { | |
| 315 | 3544 | String file = deriveFile(resource, XML_FILE_EXTENSION); |
| 316 | 3544 | InputStream is = getStream(file); |
| 317 | 3544 | if (is != null) { |
| 318 | try { | |
| 319 | 186 | SAXParser sp = SAXParserFactory.newInstance().newSAXParser(); |
| 320 | 186 | XMLDigester handler = new XMLDigester(); |
| 321 | 186 | sp.parse(is, handler); |
| 322 | 186 | data.putAll(handler.getData()); |
| 323 | 186 | } catch (Exception ex) { |
| 324 | 0 | handleFileError(file, ex); |
| 325 | 0 | } finally { |
| 326 | 0 | try { |
| 327 | 186 | is.close(); |
| 328 | 186 | } catch (IOException ex) { |
| 329 | 0 | handleFileError(file, ex); |
| 330 | 186 | } |
| 331 | } | |
| 332 | } | |
| 333 | 3544 | } |
| 334 | ||
| 335 | /** | |
| 336 | * Load extra xml data from special location. | |
| 337 | * | |
| 338 | * @param resource The resource name. | |
| 339 | * @param data The map to which resource data is added. | |
| 340 | */ | |
| 341 | static void loadExtraXMLData(String resource, Map data) { | |
| 342 | 3544 | String file = (String) data.get(EXTRA_XML_CONFIG); |
| 343 | 3544 | if (file == null) { |
| 344 | 3528 | return; |
| 345 | } | |
| 346 | 16 | InputStream is = getStream(file); |
| 347 | 16 | if (is != null) { |
| 348 | try { | |
| 349 | 16 | SAXParser sp = SAXParserFactory.newInstance().newSAXParser(); |
| 350 | 16 | XMLDigester handler = new XMLDigester(); |
| 351 | 16 | sp.parse(is, handler); |
| 352 | 16 | data.putAll(handler.getData()); |
| 353 | 16 | } catch (Exception ex) { |
| 354 | 0 | handleFileError(file, ex); |
| 355 | 0 | } finally { |
| 356 | 0 | try { |
| 357 | 16 | is.close(); |
| 358 | 16 | } catch (IOException ex) { |
| 359 | 0 | handleFileError(file, ex); |
| 360 | 16 | } |
| 361 | } | |
| 362 | } | |
| 363 | 16 | } |
| 364 | ||
| 365 | /** | |
| 366 | * Derive file path from resource name by replace key delimiters | |
| 367 | * with path delimiters and adding the file extension. | |
| 368 | * | |
| 369 | * @param resource The resource name. | |
| 370 | * @param extension The file extension. | |
| 371 | * @return The derived file path. | |
| 372 | */ | |
| 373 | static String deriveFile(String resource, String extension) { | |
| 374 | 7088 | return resource.replace(KEY_DELIMITER, '/') + extension; |
| 375 | } | |
| 376 | ||
| 377 | /** | |
| 378 | * Open input stream to file searching the classpath. | |
| 379 | * | |
| 380 | * @param file The relative file path. | |
| 381 | * @return A stream pointing the to file (or <code>null</code> if not found). | |
| 382 | */ | |
| 383 | static InputStream getStream(String file) { | |
| 384 | 7104 | ClassLoader loader = Thread.currentThread().getContextClassLoader(); |
| 385 | 7104 | return loader.getResourceAsStream(file); |
| 386 | } | |
| 387 | ||
| 388 | /** | |
| 389 | * Report file error. | |
| 390 | * | |
| 391 | * @param file The relative file path. | |
| 392 | * @param ex The original exception. | |
| 393 | * @throws ConfigurationException With information about the error. | |
| 394 | */ | |
| 395 | static void handleFileError(String file, Exception ex) | |
| 396 | throws ConfigurationException { | |
| 397 | 0 | throw new ConfigurationException( |
| 398 | ConfigurationException.MESSAGE_CONFIG_FILE_ERROR, | |
| 399 | new Object[] { file, ex.getMessage()}); | |
| 400 | } | |
| 401 | ||
| 402 | /** | |
| 403 | * Trim whitespace from data. | |
| 404 | * | |
| 405 | * @param data The data. | |
| 406 | */ | |
| 407 | static void trimWhitespace(Map data) { | |
| 408 | 3544 | Object[] keys = data.keySet().toArray(); |
| 409 | 11629 | for (int i = 0; i < keys.length; i++) { |
| 410 | 8085 | Object key = keys[i]; |
| 411 | 8085 | String value = (String) data.get(key); |
| 412 | 8085 | data.put(key, value.trim()); |
| 413 | } | |
| 414 | 3544 | } |
| 415 | ||
| 416 | /** | |
| 417 | * Expand all property values. | |
| 418 | * | |
| 419 | * @param resource The resource name. | |
| 420 | * @param data The map containing the data. | |
| 421 | * @throws ConfigurationException If there is an expansion overflow. | |
| 422 | */ | |
| 423 | static void expandProperties(String resource, Map data) | |
| 424 | throws ConfigurationException { | |
| 425 | // Use key array to avoid ConcurrentModificationException | |
| 426 | 3544 | Object[] keys = data.keySet().toArray(); |
| 427 | 11627 | for (int i = 0; i < keys.length; i++) { |
| 428 | 8085 | Object key = keys[i]; |
| 429 | try { | |
| 430 | 8085 | String oldValue = (String) data.get(key); |
| 431 | 8085 | String newValue = expandProperty(oldValue, data, 0); |
| 432 | 8083 | if (newValue != oldValue) { |
| 433 | 12 | data.put(key, newValue); |
| 434 | } | |
| 435 | 8083 | } catch (StackOverflowError e) { |
| 436 | 2 | throw new ConfigurationException( |
| 437 | ConfigurationException.MESSAGE_PROPERTY_EXPANSION_OVERFLOW, | |
| 438 | new Object[] { resource, key }); | |
| 439 | } | |
| 440 | } | |
| 441 | 3542 | } |
| 442 | ||
| 443 | /** | |
| 444 | * Expand single property value. | |
| 445 | * | |
| 446 | * @param value The property value being expanded. | |
| 447 | * @param data The map containing the data. | |
| 448 | * @param counter A running count of the expansion recursion depth. | |
| 449 | * @return The expanded property value. | |
| 450 | * @throws StackOverflowError If the expansion limit is exceeded. | |
| 451 | */ | |
| 452 | static String expandProperty(String value, Map data, int counter) | |
| 453 | throws StackOverflowError { | |
| 454 | 17878 | if (counter > EXPANSION_LIMIT) { |
| 455 | 0 | throw new StackOverflowError(); |
| 456 | } | |
| 457 | ||
| 458 | 17878 | int start = value.indexOf(EXPANSION_START); |
| 459 | 17878 | if (start < 0) { |
| 460 | 8083 | return value; |
| 461 | } | |
| 462 | 9795 | int end = value.indexOf(EXPANSION_END, start); |
| 463 | 9795 | if (end < 0) { |
| 464 | 0 | return value; |
| 465 | } | |
| 466 | ||
| 467 | 9795 | String prefix = value.substring(0, start); |
| 468 | 9795 | String suffix = value.substring(end + 1); |
| 469 | 9795 | String key = value.substring(start + 2, end); |
| 470 | ||
| 471 | 9795 | if (key.equals(FILE_SEPARATOR_ABBREVIATION)) { |
| 472 | 4 | key = FILE_SEPARATOR; |
| 473 | 9791 | } else if (key.equals(PATH_SEPARATOR_ABBREVIATION)) { |
| 474 | 0 | key = PATH_SEPARATOR; |
| 475 | } | |
| 476 | ||
| 477 | 9795 | String property = (String) data.get(key); |
| 478 | 9795 | if (property == null) { |
| 479 | 8 | property = System.getProperty(key); |
| 480 | } | |
| 481 | ||
| 482 | 9795 | value = prefix + property + suffix; |
| 483 | ||
| 484 | 9793 | return expandProperty(value, data, counter++); |
| 485 | } | |
| 486 | ||
| 487 | /** | |
| 488 | * Strip last value from name. | |
| 489 | * | |
| 490 | * @param name The name being stripped. | |
| 491 | * @return The stripped name. | |
| 492 | */ | |
| 493 | static String stripLastName(String name) { | |
| 494 | 12576 | return name.substring(0, name.lastIndexOf(KEY_DELIMITER) + 1); |
| 495 | } | |
| 496 | ||
| 497 | /** | |
| 498 | * Strip last value and period from name. | |
| 499 | * | |
| 500 | * @param name The name being stripped. | |
| 501 | * @return The stripped name. | |
| 502 | */ | |
| 503 | static String stripLastNameAndPeriod(String name) { | |
| 504 | 27768 | if (name.indexOf(KEY_DELIMITER) < 0) { |
| 505 | 9261 | return EMPTY_STRING; |
| 506 | } | |
| 507 | 18507 | return name.substring(0, name.lastIndexOf(KEY_DELIMITER)); |
| 508 | } | |
| 509 | ||
| 510 | /** Singleton that terminates property search by returning null. */ | |
| 511 | 1 | static final Config TERMINAL_CONFIG = new ConfigImpl() { |
| 512 | String getWithContext(String context, String key) { | |
| 513 | return null; | |
| 514 | } | |
| 515 | ||
| 516 | Map getMapWithContext(String context, String key) { | |
| 517 | return Collections.EMPTY_MAP; | |
| 518 | } | |
| 519 | ||
| 520 | public String getRawValue(String key) { | |
| 521 | return null; | |
| 522 | } | |
| 523 | ||
| 524 | public Map getOrderedMap(String key) { | |
| 525 | return Collections.EMPTY_MAP; | |
| 526 | } | |
| 527 | }; | |
| 528 | } |
|
this report was generated by version 1.0.5 of jcoverage. |
copyright © 2003, jcoverage ltd. all rights reserved. |