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