1 /* Copyright (c) 2001-2016, The HSQL Development Group 2 * All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are met: 6 * CrashCrash7 * Redistributions of source code must retain the above copyright notice, this 8 * list of conditions and the following disclaimer. 9 * 10 * Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 14 * Neither the name of the HSQL Development Group nor the names of its 15 * contributors may be used to endorse or promote products derived from this 16 * software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, 22 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 23 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 24 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 32 package org.hsqldb.persist; 33 34 import java.io.FileNotFoundException; 35 import java.io.InputStream; 36 import java.io.OutputStream; 37 import java.util.Enumeration; 38 import java.util.Properties; 39 40 import org.hsqldb.error.Error; 41 import org.hsqldb.error.ErrorCode; 42 import org.hsqldb.lib.ArrayUtil; 43 import org.hsqldb.lib.FileAccess; 44 import org.hsqldb.lib.FileUtil; 45 import org.hsqldb.lib.HashMap; 46 import org.hsqldb.map.ValuePool; 47 48 /** 49 * Wrapper for java.util.Properties to limit values to Specific types and 50 * allow saving and loading.<p> 51 * 52 * @author Fred Toussi (fredt@users dot sourceforge.net) 53 * @version 2.3.4 54 * @since 1.7.0 55 */ 56 public class HsqlProperties { 57 58 // 59 public static final int ANY_ERROR = 0; 60 public static final int NO_VALUE_FOR_KEY = 1; 61 protected String fileName; 62 protected String fileExtension = ""; 63 protected Properties stringProps; 64 protected int[] errorCodes = ValuePool.emptyIntArray; 65 protected String[] errorKeys = ValuePool.emptyStringArray; 66 protected boolean resource = false; 67 protected FileAccess fa; 68 protected HashMap metaData; 69 70 public HsqlProperties() { 71 stringProps = new Properties(); 72 fileName = null; 73 } 74 75 public HsqlProperties(String fileName) { 76 this(fileName, ".properties"); 77 } 78 79 public HsqlProperties(String fileName, String fileExtension) { 80 81 stringProps = new Properties(); 82 this.fileName = fileName; 83 this.fileExtension = fileExtension; 84 fa = FileUtil.getFileUtil(); 85 } 86 87 public HsqlProperties(HashMap meta, String fileName, FileAccess accessor, 88 boolean b) { 89 90 stringProps = new Properties(); 91 this.fileName = fileName; 92 this.fileExtension = ".properties"; 93 fa = accessor; 94 metaData = meta; 95 } 96 97 public HsqlProperties(Properties props) { 98 stringProps = props; 99 } 100 101 public void setFileName(String name) { 102 fileName = name; 103 } 104 105 public String setProperty(String key, int value) { 106 return setProperty(key, Integer.toString(value)); 107 } 108 109 public String setProperty(String key, boolean value) { 110 return setProperty(key, String.valueOf(value)); 111 } 112 113 public String setProperty(String key, String value) { 114 return (String) stringProps.put(key, value); 115 } 116 117 public String setPropertyIfNotExists(String key, String value) { 118 119 value = getProperty(key, value); 120 121 return setProperty(key, value); 122 } 123 124 public Properties getProperties() { 125 return stringProps; 126 } 127 128 public String getProperty(String key) { 129 return stringProps.getProperty(key); 130 } 131 132 public String getProperty(String key, String defaultValue) { 133 return stringProps.getProperty(key, defaultValue); 134 } 135 136 public int getIntegerProperty(String key, int defaultValue) { 137 return getIntegerProperty(stringProps, key, defaultValue); 138 } 139 140 public static int getIntegerProperty(Properties props, String key, 141 int defaultValue) { 142 143 String prop = props.getProperty(key); 144 145 try { 146 if (prop != null) { 147 prop = prop.trim(); 148 defaultValue = Integer.parseInt(prop); 149 } 150 } catch (NumberFormatException e) {} 151 152 return defaultValue; 153 } 154 155 public boolean isPropertyTrue(String key) { 156 return isPropertyTrue(key, false); 157 } 158 159 public boolean isPropertyTrue(String key, boolean defaultValue) { 160 161 String value = stringProps.getProperty(key); 162 163 if (value == null) { 164 return defaultValue; 165 } 166 167 value = value.trim(); 168 169 return value.toLowerCase().equals("true"); 170 } 171 172 public void removeProperty(String key) { 173 stringProps.remove(key); 174 } 175 176 public void addProperties(Properties props) { 177 178 if (props == null) { 179 return; 180 } 181 182 Enumeration keys = props.propertyNames(); 183 184 while (keys.hasMoreElements()) { 185 String key = (String) keys.nextElement(); 186 String value = props.getProperty(key); 187 188 this.stringProps.put(key, value); 189 } 190 } 191 192 public void addProperties(HsqlProperties props) { 193 194 if (props == null) { 195 return; 196 } 197 198 addProperties(props.stringProps); 199 } 200 201 // oj@openoffice.org 202 public boolean propertiesFileExists() { 203 204 if (fileName == null) { 205 return false; 206 } 207 208 String propFilename = fileName + fileExtension; 209 210 return fa.isStreamElement(propFilename); 211 } 212 213 public boolean load() throws Exception { 214 215 if (fileName == null || fileName.length() == 0) { 216 throw new FileNotFoundException( 217 Error.getMessage(ErrorCode.M_HsqlProperties_load)); 218 } 219 220 if (!propertiesFileExists()) { 221 return false; 222 } 223 224 InputStream fis = null; 225 String propsFilename = fileName + fileExtension; 226 227 // oj@openoffice.org 228 try { 229 fis = fa.openInputStreamElement(propsFilename); 230 231 stringProps.load(fis); 232 } finally { 233 if (fis != null) { 234 fis.close(); 235 } 236 } 237 238 return true; 239 } 240 241 /** 242 * Saves the properties. 243 */ 244 public void save() throws Exception { 245 246 if (fileName == null || fileName.length() == 0) { 247 throw new java.io.FileNotFoundException( 248 Error.getMessage(ErrorCode.M_HsqlProperties_load)); 249 } 250 251 String filestring = fileName + fileExtension; 252 253 save(filestring); 254 } 255 256 /** 257 * Saves the properties using JDK2 method if present, otherwise JDK1. 258 */ 259 public void save(String fileString) throws Exception { 260 261 // oj@openoffice.org 262 fa.createParentDirs(fileString); 263 fa.removeElement(fileString); 264 265 OutputStream fos = fa.openOutputStreamElement(fileString); 266 FileAccess.FileSync outDescriptor = fa.getFileSync(fos); 267 String name = HsqlDatabaseProperties.PRODUCT_NAME + " " 268 + HsqlDatabaseProperties.THIS_FULL_VERSION; 269 270 stringProps.store(fos, name); 271 fos.flush(); 272 outDescriptor.sync(); 273 fos.close(); 274 275 outDescriptor = null; 276 fos = null; 277 } 278 279 /** 280 * Adds the error code and the key to the list of errors. This list 281 * is populated during construction or addition of elements and is used 282 * outside this class to act upon the errors. 283 */ 284 protected void addError(int code, String key) { 285 286 errorCodes = (int[]) ArrayUtil.resizeArray(errorCodes, 287 errorCodes.length + 1); 288 errorKeys = (String[]) ArrayUtil.resizeArray(errorKeys, 289 errorKeys.length + 1); 290 errorCodes[errorCodes.length - 1] = code; 291 errorKeys[errorKeys.length - 1] = key; 292 } 293 294 /** 295 * Creates and populates an HsqlProperties Object from the arguments 296 * array of a Main method. Properties are in the form of "-key value" 297 * pairs. Each key is prefixed with the type argument and a dot before 298 * being inserted into the properties Object. <p> 299 * 300 * "--help" is treated as a key with no value and not inserted. 301 */ 302 public static HsqlProperties argArrayToProps(String[] arg, String type) { 303 304 HsqlProperties props = new HsqlProperties(); 305 306 for (int i = 0; i < arg.length; i++) { 307 String p = arg[i]; 308 309 if (p.equals("--help") || p.equals("-help")) { 310 props.addError(NO_VALUE_FOR_KEY, p.substring(1)); 311 } else if (p.startsWith("--")) { 312 String value = i + 1 < arg.length ? arg[i + 1] 313 : ""; 314 315 props.setProperty(type + "." + p.substring(2), value); 316 317 i++; 318 } else if (p.charAt(0) == '-') { 319 String value = i + 1 < arg.length ? arg[i + 1] 320 : ""; 321 322 props.setProperty(type + "." + p.substring(1), value); 323 324 i++; 325 } 326 } 327 328 return props; 329 } 330 331 /** 332 * Creates and populates a new HsqlProperties Object using a string 333 * such as "key1=value1;key2=value2". <p> 334 * 335 * The string that represents the = sign above is specified as pairsep 336 * and the one that represents the semicolon is specified as delimiter, 337 * allowing any string to be used for either.<p> 338 * 339 * Leading / trailing spaces around the keys and values are discarded.<p> 340 * 341 * The string is parsed by (1) subdividing into segments by delimiter 342 * (2) subdividing each segment in two by finding the first instance of 343 * the pairsep (3) trimming each pair of segments from step 2 and 344 * inserting into the properties object.<p> 345 * 346 * Each key is prefixed with the type argument and a dot before being 347 * inserted.<p> 348 * 349 * Any key without a value is added to the list of errors. 350 */ 351 public static HsqlProperties delimitedArgPairsToProps(String s, 352 String pairsep, String dlimiter, String type) { 353 354 HsqlProperties props = new HsqlProperties(); 355 int currentpair = 0; 356 357 while (true) { 358 int nextpair = s.indexOf(dlimiter, currentpair); 359 360 if (nextpair == -1) { 361 nextpair = s.length(); 362 } 363 364 // find value within the segment 365 int valindex = s.substring(0, nextpair).indexOf(pairsep, 366 currentpair); 367 368 if (valindex == -1) { 369 props.addError(NO_VALUE_FOR_KEY, 370 s.substring(currentpair, nextpair).trim()); 371 } else { 372 String key = s.substring(currentpair, valindex).trim(); 373 String value = s.substring(valindex + pairsep.length(), 374 nextpair).trim(); 375 376 if (type != null) { 377 key = type + "." + key; 378 } 379 380 props.setProperty(key, value); 381 } 382 383 if (nextpair == s.length()) { 384 break; 385 } 386 387 currentpair = nextpair + dlimiter.length(); 388 } 389 390 return props; 391 } 392 393 public Enumeration propertyNames() { 394 return stringProps.propertyNames(); 395 } 396 397 public boolean isEmpty() { 398 return stringProps.isEmpty(); 399 } 400 401 public String[] getErrorKeys() { 402 return errorKeys; 403 } 404 405 public void validate() {} 406 407 // column number mappings 408 public static final int indexName = 0; 409 public static final int indexType = 1; 410 public static final int indexClass = 2; 411 public static final int indexIsRange = 3; 412 public static final int indexDefaultValue = 4; 413 public static final int indexRangeLow = 5; 414 public static final int indexRangeHigh = 6; 415 public static final int indexValues = 7; 416 public static final int indexLimit = 9; 417 418 public static Object[] getMeta(String name, int type) { 419 420 Object[] row = new Object[indexLimit]; 421 422 row[indexName] = name; 423 row[indexType] = ValuePool.getInt(type); 424 row[indexClass] = "Long"; 425 row[indexDefaultValue] = Long.valueOf(0); 426 427 return row; 428 } 429 430 public static Object[] getMeta(String name, int type, 431 String defaultValue) { 432 433 Object[] row = new Object[indexLimit]; 434 435 row[indexName] = name; 436 row[indexType] = ValuePool.getInt(type); 437 row[indexClass] = "String"; 438 row[indexDefaultValue] = defaultValue; 439 440 return row; 441 } 442 443 public static Object[] getMeta(String name, int type, 444 boolean defaultValue) { 445 446 Object[] row = new Object[indexLimit]; 447 448 row[indexName] = name; 449 row[indexType] = ValuePool.getInt(type); 450 row[indexClass] = "Boolean"; 451 row[indexDefaultValue] = defaultValue ? Boolean.TRUE 452 : Boolean.FALSE; 453 454 return row; 455 } 456 457 public static Object[] getMeta(String name, int type, int defaultValue, 458 int[] values) { 459 460 Object[] row = new Object[indexLimit]; 461 462 row[indexName] = name; 463 row[indexType] = ValuePool.getInt(type); 464 row[indexClass] = "Integer"; 465 row[indexDefaultValue] = ValuePool.getInt(defaultValue); 466 row[indexValues] = values; 467 468 return row; 469 } 470 471 public static Object[] getMeta(String name, int type, int defaultValue, 472 int rangeLow, int rangeHigh) { 473 474 Object[] row = new Object[indexLimit]; 475 476 row[indexName] = name; 477 row[indexType] = ValuePool.getInt(type); 478 row[indexClass] = "Integer"; 479 row[indexDefaultValue] = ValuePool.getInt(defaultValue); 480 row[indexIsRange] = Boolean.TRUE; 481 row[indexRangeLow] = ValuePool.getInt(rangeLow); 482 row[indexRangeHigh] = ValuePool.getInt(rangeHigh); 483 484 return row; 485 } 486 487 /** 488 * Performs any range checking for property and return an error message 489 */ 490 public static String validateProperty(String key, String value, 491 Object[] meta) { 492 493 if (meta[indexClass].equals("Boolean")) { 494 value = value.toLowerCase(); 495 496 if (value.equals("true") || value.equals("false")) { 497 return null; 498 } 499 500 return "invalid boolean value for property: " + key; 501 } 502 503 if (meta[indexClass].equals("String")) { 504 return null; 505 } 506 507 if (meta[indexClass].equals("Long")) { 508 return null; 509 } 510 511 if (meta[indexClass].equals("Integer")) { 512 try { 513 int number = Integer.parseInt(value); 514 515 if (Boolean.TRUE.equals(meta[indexIsRange])) { 516 int low = ((Integer) meta[indexRangeLow]).intValue(); 517 int high = ((Integer) meta[indexRangeHigh]).intValue(); 518 519 if (number < low || high < number) { 520 return "value outside range for property: " + key; 521 } 522 } 523 524 if (meta[indexValues] != null) { 525 int[] values = (int[]) meta[indexValues]; 526 527 if (ArrayUtil.find(values, number) == -1) { 528 return "value not supported for property: " + key; 529 } 530 } 531 } catch (NumberFormatException e) { 532 return "invalid integer value for property: " + key; 533 } 534 535 return null; 536 } 537 538 return null; 539 } 540 541 public int getPropertyWithinRange(String name, int number) { 542 543 Object[] meta = (Object[]) metaData.get(name); 544 545 if (meta == null) { 546 return number; 547 } 548 549 if (meta[indexClass].equals("Integer")) { 550 if (Boolean.TRUE.equals(meta[indexIsRange])) { 551 int low = ((Integer) meta[indexRangeLow]).intValue(); 552 int high = ((Integer) meta[indexRangeHigh]).intValue(); 553 554 if (number < low) { 555 return low; 556 } else if (high < number) { 557 return high; 558 } 559 } 560 561 if (meta[indexValues] != null) { 562 int[] values = (int[]) meta[indexValues]; 563 564 if (ArrayUtil.find(values, number) == -1) { 565 return values[0]; 566 } 567 } 568 } 569 570 return number; 571 } 572 573 public boolean validateProperty(String name, int number) { 574 575 Object[] meta = (Object[]) metaData.get(name); 576 577 if (meta == null) { 578 return false; 579 } 580 581 if (meta[indexClass].equals("Integer")) { 582 if (Boolean.TRUE.equals(meta[indexIsRange])) { 583 int low = ((Integer) meta[indexRangeLow]).intValue(); 584 int high = ((Integer) meta[indexRangeHigh]).intValue(); 585 586 if (number < low || high < number) { 587 return false; 588 } 589 } 590 591 if (meta[indexValues] != null) { 592 int[] values = (int[]) meta[indexValues]; 593 594 if (ArrayUtil.find(values, number) == -1) { 595 return false; 596 } 597 } 598 599 return true; 600 } 601 602 return false; 603 } 604 605 public String toString() { 606 607 StringBuffer sb; 608 609 sb = new StringBuffer(); 610 611 sb.append('['); 612 613 int len = stringProps.size(); 614 Enumeration en = stringProps.propertyNames(); 615 616 for (int i = 0; i < len; i++) { 617 String key = (String) en.nextElement(); 618 619 sb.append(key); 620 sb.append('='); 621 sb.append(stringProps.get(key)); 622 623 if (i + 1 < len) { 624 sb.append(','); 625 sb.append(' '); 626 } 627 628 sb.append(']'); 629 } 630 631 return sb.toString(); 632 } 633 } 634