001/*
002 * JPPF.
003 * Copyright (C) 2005-2018 JPPF Team.
004 * http://www.jppf.org
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.jppf.utils;
019
020
021import java.io.*;
022import java.net.*;
023import java.util.*;
024
025import org.jppf.utils.configuration.*;
026
027/**
028 * Extension of the <code>java.util.Properties</code> class to handle the conversion of string values to other types.
029 * @author Laurent Cohen
030 */
031public class TypedProperties extends AbstractTypedProperties {
032  /**
033   * Explicit serialVersionUID.
034   */
035  private static final long serialVersionUID = 1L;
036
037  /**
038   * Default constructor.
039   */
040  public TypedProperties() {
041  }
042
043  /**
044   * Initialize this object with a set of existing properties.
045   * This will copy into the present object all map entries such that both key and value are strings.
046   * @param map the properties to be copied. No reference to this parameter is kept in this TypedProperties object.
047   */
048  public TypedProperties(final Map<?, ?> map) {
049    super(map);
050  }
051
052  /**
053   * Get the string value of a property with a specified name.
054   * @param key the name of the property to look for.
055   * @return the value of the property as a string, or null if it is not found.
056   */
057  public String getString(final String key) {
058    return getProperty(key, null);
059  }
060
061  /**
062   * Get the string value of a property with a specified name.
063   * @param key the name of the property to look for.
064   * @param defValue a default value to return if the property is not found.
065   * @return the value of the property as a string, or the default value if it is not found.
066   */
067  public String getString(final String key, final String defValue) {
068    return getProperty(key, defValue);
069  }
070
071  /**
072   * Set a property with the specified String value.
073   * @param key the name of the property to set.
074   * @param value the value to set on the property.
075   * @return this {@code TypedProperties} object.
076   */
077  public TypedProperties setString(final String key, final String value) {
078    setProperty(key, value);
079    return this;
080  }
081
082  /**
083   * Get the integer value of a property with a specified name.
084   * @param key the name of the property to look for.
085   * @return the value of the property as an int, or zero if it is not found.
086   */
087  public int getInt(final String key) {
088    return getInt(key, 0);
089  }
090
091  /**
092   * Get the integer value of a property with a specified name.
093   * @param key the name of the property to look for.
094   * @param defValue a default value to return if the property is not found.
095   * @return the value of the property as an int, or the default value if it is not found.
096   */
097  public int getInt(final String key, final int defValue) {
098    return PropertiesHelper.toInt(getProperty(key, null), defValue);
099  }
100
101  /**
102   * Set a property with the specified int value.
103   * @param key the name of the property to set.
104   * @param value the value to set on the property.
105   * @return this {@code TypedProperties} object.
106   */
107  public TypedProperties setInt(final String key, final int value) {
108    return setString(key, Integer.toString(value));
109  }
110
111  /**
112   * Get the long integer value of a property with a specified name.
113   * @param key the name of the property to look for.
114   * @return the value of the property as a long, or zero if it is not found.
115   */
116  public long getLong(final String key) {
117    return getLong(key, 0L);
118  }
119
120  /**
121   * Get the long integer value of a property with a specified name.
122   * @param key the name of the property to look for.
123   * @param defValue a default value to return if the property is not found.
124   * @return the value of the property as a long, or the default value if it is not found.
125   */
126  public long getLong(final String key, final long defValue) {
127    return PropertiesHelper.toLong(getProperty(key, null), defValue);
128  }
129
130  /**
131   * Set a property with the specified long value.
132   * @param key the name of the property to set.
133   * @param value the value to set on the property.
134   * @return this {@code TypedProperties} object.
135   */
136  public TypedProperties setLong(final String key, final long value) {
137    return setString(key, Long.toString(value));
138  }
139
140  /**
141   * Get the single precision value of a property with a specified name.
142   * @param key the name of the property to look for.
143   * @return the value of the property as a float, or zero if it is not found.
144   */
145  public float getFloat(final String key) {
146    return getFloat(key, 0f);
147  }
148
149  /**
150   * Get the single precision value of a property with a specified name.
151   * @param key the name of the property to look for.
152   * @param defValue a default value to return if the property is not found.
153   * @return the value of the property as a float, or the default value if it is not found.
154   */
155  public float getFloat(final String key, final float defValue) {
156    return PropertiesHelper.toFloat(getProperty(key, null), defValue);
157  }
158
159  /**
160   * Set a property with the specified float value.
161   * @param key the name of the property to set.
162   * @param value the value to set on the property.
163   * @return this {@code TypedProperties} object.
164   */
165  public TypedProperties setFloat(final String key, final float value) {
166    return setString(key, Float.toString(value));
167  }
168
169  /**
170   * Get the double precision value of a property with a specified name.
171   * If the key is not found a default value of 0d is returned.
172   * @param key the name of the property to look for.
173   * @return the value of the property as a double, or zero if it is not found.
174   */
175  public double getDouble(final String key) {
176    return getDouble(key, 0d);
177  }
178
179  /**
180   * Get the double precision value of a property with a specified name.
181   * @param key the name of the property to look for.
182   * @param defValue a default value to return if the property is not found.
183   * @return the value of the property as a double, or the default value if it is not found.
184   */
185  public double getDouble(final String key, final double defValue) {
186    return PropertiesHelper.toDouble(getProperty(key, null), defValue);
187  }
188
189  /**
190   * Set a property with the specified double value.
191   * @param key the name of the property to set.
192   * @param value the value to set on the property.
193   * @return this {@code TypedProperties} object.
194   */
195  public TypedProperties setDouble(final String key, final double value) {
196    return setString(key, Double.toString(value));
197  }
198
199  /**
200   * Get the boolean value of a property with a specified name.
201   * If the key is not found a default value of false is returned.
202   * @param key the name of the property to look for.
203   * @return the value of the property as a boolean, or <code>false</code> if it is not found.
204   */
205  public boolean getBoolean(final String key) {
206    return getBoolean(key, false);
207  }
208
209  /**
210   * Get the boolean value of a property with a specified name.
211   * @param key the name of the property to look for.
212   * @param defValue a default value to return if the property is not found.
213   * @return the value of the property as a boolean, or the default value if it is not found.
214   */
215  public boolean getBoolean(final String key, final boolean defValue) {
216    boolean booleanVal = defValue;
217    final String val = getProperty(key, null);
218    if (val != null) booleanVal = Boolean.valueOf(val.trim()).booleanValue();
219    return booleanVal;
220  }
221
222  /**
223   * Set a property with the specified boolean value.
224   * @param key the name of the property to set.
225   * @param value the value to set on the property.
226   * @return this {@code TypedProperties} object.
227   */
228  public TypedProperties setBoolean(final String key, final boolean value) {
229    return setString(key, Boolean.toString(value));
230  }
231
232  /**
233   * Get the char value of a property with a specified name.
234   * If the key is not found a default value of ' ' is returned.
235   * @param key the name of the property to look for.
236   * @return the value of the property as a char, or the default value ' ' (space character) if it is not found.
237   */
238  public char getChar(final String key) {
239    return getChar(key, ' ');
240  }
241
242  /**
243   * Get the char value of a property with a specified name.
244   * If the value has more than one character, the first one will be used.
245   * @param key the name of the property to look for.
246   * @param defValue a default value to return if the property is not found.
247   * @return the value of the property as a char, or the default value if it is not found.
248   */
249  public char getChar(final String key, final char defValue) {
250    char charVal = defValue;
251    final String val = getProperty(key, null);
252    if ((val != null) && (val.length() > 0)) charVal = val.charAt(0);
253    return charVal;
254  }
255
256  /**
257   * Set a property with the specified char value.
258   * @param key the name of the property to set.
259   * @param value the value to set on the property.
260   * @return this {@code TypedProperties} object.
261   */
262  public TypedProperties setChar(final String key, final char value) {
263    return setString(key, Character.toString(value));
264  }
265
266  /**
267   * Get the value of the specified property as a {@link File}.
268   * @param key the name of the property to look up.
269   * @return an abstract file path based on the value of the property, or null if the property is not defined.
270   */
271  public File getFile(final String key) {
272    return getFile(key, null);
273  }
274
275  /**
276   * Get the value of the specified property as a {@link File}.
277   * @param key the name of the property to look up.
278   * @param defValue the value to return if the property is not found.
279   * @return an abstract file path based on the value of the property, or the default value if the property is not defined.
280   */
281  public File getFile(final String key, final File defValue) {
282    final String s = getProperty(key);
283    return (s == null) || s.trim().isEmpty() ? defValue : new File(s);
284  }
285
286  /**
287   * Set the value of the specified property as a {@link File}.
288   * @param key the name of the property to look up.
289   * @param value the file whose path to set as the property value.
290   * @return this {@code TypedProperties} object.
291   */
292  public TypedProperties setFile(final String key, final File value) {
293    if (value != null) setProperty(key, value.getPath());
294    return this;
295  }
296
297  /**
298   * Get the value of the specified property as an array of strings.
299   * @param key the name of the property to look up.
300   * @param delimiter the delimiter used to split the value into an array of strings. It MUST be a litteral string, i.e. not a regex.
301   * @return an abstract file path based on the value of the property, or null if the property is not defined.
302   * @since 5.2
303   */
304  public String[] getStringArray(final String key, final String delimiter) {
305    return getStringArray(key, delimiter, null);
306  }
307
308  /**
309   * Get the value of the specified property as an array of strings.
310   * @param key the name of the property to look up.
311   * @param delimiter the delimiter used to split the value into an array of strings. It MUST be a litteral string, i.e. not a regex.
312   * @param defValue the value to return if the property is not found.
313   * @return an abstract file path based on the value of the property, or the default value if the property is not defined.
314   * @since 5.2
315   */
316  public String[] getStringArray(final String key, final String delimiter, final String[] defValue) {
317    final String s = getProperty(key);
318    if ((s == null) || s.trim().isEmpty()) return defValue;
319    final String[] array = StringUtils.parseStringArray(s, delimiter, false);
320    return (array == null) ? defValue : array;
321  }
322
323  /**
324   * Set the value of the specified property as an array of strings.
325   * @param key the name of the property to look up.
326   * @param delimiter the delimiter that will be inserted between any two adjacent strings. It MUST be a litteral string, i.e. not a regex.
327   * @param value an array of strings.
328   * @return this {@code TypedProperties} object.
329   * @since 5.2
330   */
331  public TypedProperties setStringArray(final String key, final String delimiter, final String[] value) {
332    if (value != null) {
333      final StringBuilder sb = new StringBuilder();
334      for (int i=0; i<value.length; i++) {
335        if (i > 0) sb.append(delimiter);
336        if (value[i] != null) sb.append(value[i]);
337      }
338      setProperty(key, sb.toString());
339    }
340    return this;
341  }
342
343  /**
344   * Get the value of a property with the specified name as a set of a properties.
345   * @param key the name of the property to look for.
346   * Its value is the path to another properties file. Relative paths are evaluated against the current application directory.
347   * @return the value of the property as another set of properties, or null if it is not found.
348   */
349  public TypedProperties getProperties(final String key) {
350    return getProperties(key, null);
351  }
352
353  /**
354   * Get the value of a property with the specified name as a set of properties.
355   * @param key the name of the property to look for.
356   * Its value is the path to another properties file. Relative paths are evaluated against the current application directory.
357   * @param def a default value to return if the property is not found.
358   * @return the value of the property as another set of properties, or the default value if it is not found.
359   */
360  public TypedProperties getProperties(final String key, final TypedProperties def) {
361    try (InputStream is = FileUtils.getFileInputStream(getString(key))) {
362      final TypedProperties res = new TypedProperties();
363      res.load(is);
364      return res;
365    } catch(@SuppressWarnings("unused") final IOException e) {
366      return def;
367    }
368  }
369
370  /**
371   * Get the value of a property with the specified name as an {@link InetAddress}.
372   * @param key the name of the property to retrieve.
373   * @return the property as an {@link InetAddress} instance, or null if the property is not defined or the host doesn't exist.
374   */
375  public InetAddress getInetAddress(final String key) {
376    return getInetAddress(key, null);
377  }
378
379  /**
380   * Get the value of a property with the specified name as an {@link InetAddress}.
381   * @param key the name of the property to retrieve.
382   * @param def the default value to use if the property is not defined.
383   * @return the property as an {@link InetAddress} instance, or the specified default value if the property is not defined.
384   */
385  public InetAddress getInetAddress(final String key, final InetAddress def) {
386    final String val = getString(key);
387    if (val == null) return def;
388    try {
389      return InetAddress.getByName(val);
390    } catch(@SuppressWarnings("unused") final UnknownHostException e) {
391      return def;
392    }
393  }
394
395  /**
396   * Extract the properties that pass the specified filter.
397   * @param filter the filter to use, if {@code null} then all properties are returned.
398   * @return a new {@code TypedProperties} object containing only the properties matching the filter.
399   */
400  public TypedProperties filter(final Filter filter) {
401    final TypedProperties result = new TypedProperties();
402    for (String key: stringPropertyNames()) {
403      final String value = getProperty(key);
404      if ((filter == null) || filter.accepts(key, value)) result.put(key, value);
405    }
406    return result;
407  }
408
409  /**
410   * A filter for {@code TypedProperties} objects.
411   */
412  public interface Filter {
413    /**
414     * Determine whether this filter accepts a property with the specirfied name and value.
415     * @param name the name of the property.
416     * @param value the value of the property.
417     * @return {@code true} if the property is accepted, {@code false} otherwise.
418     */
419    boolean accepts(String name, String value);
420  }
421
422  /**
423   * Set the value of a predefined property.
424   * @param <T> the type of the property.
425   * @param property the property whose value to set.
426   * @param value the value to set.
427   * @return the value of the property according to its type.
428   * @since 5.2
429   */
430  public <T> TypedProperties set(final JPPFProperty<T> property, final T value) {
431    setProperty(property.getName(), property.toString(value));
432    return this;
433  }
434
435  /**
436   * Set the value of a predefined parametrized property.
437   * @param <T> the type of the property.
438   * @param property the property whose value to set.
439   * @param value the value to set.
440   * @param parameters the values to replace the property's parameters with.
441   * @return the value of the property according to its type.
442   * @since 6.0
443   */
444  public <T> TypedProperties set(final JPPFProperty<T> property, final T value, final String...parameters) {
445    setProperty(property.resolveName(parameters), property.toString(value));
446    return this;
447  }
448
449  /**
450   * Get the value of a predefined property.
451   * @param <T> the type of the property.
452   * @param property the property whose value to retrieve.
453   * @return the value of the property according to its type.
454   * @since 5.2
455   */
456  public <T> T get(final JPPFProperty<T> property) {
457    if (this.containsKey(property.getName())) return property.valueOf(getProperty(property.getName()));
458    final String[] aliases = property.getAliases();
459    if ((aliases != null) && (aliases.length > 0)) {
460      for (String alias: aliases) {
461        if (this.containsKey(alias)) return property.valueOf(getProperty(alias));
462      }
463    }
464    return property.getDefaultValue();
465  }
466
467  /**
468   * Get the value of a predefined parametrized property.
469   * @param <T> the type of the property.
470   * @param property the property whose value to retrieve.
471   * @param parameters the values to replace the property's parameters with.
472   * @return the value of the property according to its type.
473   * @since 6.0
474   */
475  public <T> T get(final JPPFProperty<T> property, final String...parameters) {
476    String name = property.resolveName(parameters);
477    if (this.containsKey(name)) return property.valueOf(getProperty(name));
478    final String[] aliases = property.getAliases();
479    if ((aliases != null) && (aliases.length > 0)) {
480      for (String alias: aliases) {
481        name = property.resolveName(alias, parameters);
482        if (this.containsKey(name)) return property.valueOf(getProperty(name));
483      }
484    }
485    return property.getDefaultValue();
486  }
487
488  /**
489   * Remove the specified predefined property.
490   * @param <T> the type of the property.
491   * @param property the property whose value to retrieve.
492   * @return the old value of the property, or {@code null} if it wasn't defined.
493   * @since 5.2
494   */
495  public <T> T remove(final JPPFProperty<T> property) {
496    final Object o = remove(property.getName());
497    if (!(o instanceof String)) return null; 
498    return property.valueOf((String) o);
499  }
500
501  /**
502   * Determine whether this set of properties contains the specified property.
503   * The lookkup is performed on the property's name first, then on its aliases if the name is not found.
504   * @param property the property to look for.
505   * @return {@code true} if the property or one of its aliases is found, {@code false} otherwise.
506   * @since 5.2
507   */
508  public boolean containsProperty(final JPPFProperty<?> property) {
509    if (getProperty(property.getName()) != null) return true;
510    if (property.getAliases() != null) {
511      for (String name: property.getAliases()) {
512        if (getProperty(name) != null) return true;
513      }
514    }
515    return false;
516  }
517
518  /**
519   * Remove the specified predefined parametrized property.
520   * @param <T> the type of the property.
521   * @param property the property whose value to retrieve.
522   * @param parameters the values to replace the property's parameters with.
523   * @return the old value of the property, or {@code null} if it wasn't defined.
524   * @since 6.0
525   */
526  public <T> T remove(final JPPFProperty<T> property, final String...parameters) {
527    final Object o = remove(property.resolveName(parameters));
528    if (!(o instanceof String)) return null; 
529    return property.valueOf((String) o);
530  }
531
532  @Override
533  public void store(final Writer writer, final String comments) throws IOException {
534    PropertiesHelper.store(this, writer);
535  }
536}