001/*
002 * JPPF.
003 * Copyright (C) 2005-2019 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.util.*;
023
024import org.jppf.utils.configuration.*;
025
026/**
027 * Extension of the <code>java.util.Properties</code> class to handle the conversion of string values to other types.
028 * @author Laurent Cohen
029 * @since 6.0
030 */
031public abstract class AbstractTypedProperties extends Properties {
032  /**
033   * Explicit serialVersionUID.
034   */
035  private static final long serialVersionUID = 1L;
036  /**
037   * The default (initial) properties, if any.
038   */
039  private AbstractTypedProperties initialProps;
040
041  /**
042   * Default constructor.
043   */
044  public AbstractTypedProperties() {
045  }
046
047  /**
048   * Initialize this object with a set of existing properties.
049   * This will copy into the present object all map entries such that both key and value are strings.
050   * @param map the properties to be copied. No reference to this parameter is kept in this TypedProperties object.
051   */
052  public AbstractTypedProperties(final Map<?, ?> map) {
053    if (map != null) {
054      map.forEach((key, value) -> {
055        if ((key instanceof String) && (value instanceof String)) setProperty((String) key, (String) value);
056      });
057    }
058  }
059
060  /**
061   * Load the properties from the specified reader.
062   * The properties are first loaded, then includes are resolved, variable substitutions are resolved, and finally scripted values are computed.
063   * @param <T> the type of object returned by this method.
064   * @param reader the reader to read the properties from.
065   * @return this {@code TypedProperties} object.
066   * @throws IOException if any error occurs.
067   */
068  @SuppressWarnings("unchecked")
069  public synchronized <T extends AbstractTypedProperties> T loadAndResolve(final Reader reader) throws IOException {
070    new PropertiesLoader().load(this, reader);
071    new SubstitutionsHandler().resolve(this);
072    new ScriptHandler().process(this);
073    return (T) this;
074  }
075
076  /**
077   * Convert this set of properties into a string.
078   * @return a representation of this object as a string.
079   */
080  public String asString() {
081    return asString(null);
082  }
083
084  /**
085   * Convert this set of properties into a string.
086   * @param delimiter the delimiter between two properties definitions.
087   * @return a representation of this object as a string.
088   */
089  public String asString(final String delimiter) {
090    String result = "";
091    try (final Writer writer = new StringWriter()) {
092      store(writer, null);
093      result = writer.toString();
094    } catch(final Exception e) {
095      return String.format("error converting properties to string: %s", e);
096    }
097    return (delimiter == null) ? result : result.replace("\n", delimiter);
098  }
099
100  /**
101   * Populate this propertis object from a string source.
102   * @param <T> the type of object returned by this method.
103   * @param source the source to read the properties from.
104   * @return this properties object.
105   */
106  @SuppressWarnings("unchecked")
107  public <T extends AbstractTypedProperties> T fromString(final String source) {
108    return fromString(source, null);
109  }
110
111  /**
112   * Populate this propertis object from a string source.
113   * @param <T> the type of object returned by this method.
114   * @param source the source to read the properties from.
115   * @param delimiter the delimiter between two properties definitions.
116   * @return this properties object.
117   */
118  @SuppressWarnings("unchecked")
119  public <T extends AbstractTypedProperties> T fromString(final String source, final String delimiter) {
120    clear();
121    try (final Reader reader = new StringReader((delimiter == null) ? source : source.replace(delimiter, "\n"))) {
122      loadAndResolve(reader);
123    } catch (@SuppressWarnings("unused") final Exception e) {
124    }
125    return (T) this;
126  }
127
128  /**
129   * 
130   * @return the default (initial) properties, if any.
131   * @exclude
132   */
133  public AbstractTypedProperties getDefaults() {
134    return initialProps;
135  }
136
137  /**
138   * @param defaults the default (initial) properties, if any.
139   * @exclude
140   */
141  public void setDefaults(final AbstractTypedProperties defaults) {
142    this.initialProps = defaults;
143  }
144
145  /**
146   * @exclude
147   */
148  public void reset() {
149    if (initialProps != null) {
150      clear();
151      putAll(initialProps);
152    }
153  }
154
155  @Override
156  public synchronized String toString() {
157    final Set<String> set = new TreeSet<>();
158    forEach((k, v) -> {
159      if ((k instanceof String) && (v instanceof String)) set.add((String) k + '=' + v);
160    });
161    final StringBuilder sb = new StringBuilder("{");
162    int count = 0;
163    for (final String s: set) {
164      if (count > 0) sb.append(", ");
165      sb.append(s);
166      count++;
167    }
168    return sb.append('}').toString();
169  }
170}