001package ball.util;
002/*-
003 * ##########################################################################
004 * Utilities
005 * $Id: PropertiesImpl.java 6199 2020-06-15 21:18:10Z ball $
006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-util/trunk/src/main/java/ball/util/PropertiesImpl.java $
007 * %%
008 * Copyright (C) 2008 - 2020 Allen D. Ball
009 * %%
010 * Licensed under the Apache License, Version 2.0 (the "License");
011 * you may not use this file except in compliance with the License.
012 * You may obtain a copy of the License at
013 *
014 *      http://www.apache.org/licenses/LICENSE-2.0
015 *
016 * Unless required by applicable law or agreed to in writing, software
017 * distributed under the License is distributed on an "AS IS" BASIS,
018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
019 * See the License for the specific language governing permissions and
020 * limitations under the License.
021 * ##########################################################################
022 */
023import java.io.File;
024import java.io.FileInputStream;
025import java.io.IOException;
026import java.io.InputStream;
027import java.io.InputStreamReader;
028import java.io.OutputStream;
029import java.io.OutputStreamWriter;
030import java.lang.reflect.Method;
031import java.nio.charset.Charset;
032import java.util.Properties;
033
034import static ball.util.Converter.convertTo;
035import static java.nio.charset.StandardCharsets.UTF_8;
036
037/**
038 * {@link Properties} implementation that overrides
039 * {@link #load(InputStream)} and {@link #store(OutputStream,String)}
040 * methods to specify the {@link Charset} (as {@code UTF-8}).
041 *
042 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
043 * @version $Revision: 6199 $
044 */
045public class PropertiesImpl extends Properties {
046    private static final long serialVersionUID = -5034894719756448226L;
047
048    /**
049     * UTF-8
050     */
051    protected static final Charset CHARSET = UTF_8;
052
053    /**
054     * See {@link Properties#Properties()}.
055     */
056    public PropertiesImpl() { this(null); }
057
058    /**
059     * See {@link Properties#Properties(Properties)}.
060     *
061     * @param   defaults        A {@link Properties} that contains default
062     *                          values for any keys not found in this
063     *                          {@link Properties}.
064     */
065    public PropertiesImpl(Properties defaults) { super(defaults); }
066
067    /**
068     * See {@link #PropertiesImpl(Properties)}.
069     *
070     * @param   defaults        The default {@link Properties}.
071     * @param   file            The {@link File} to load
072     *                          (may be {@code null}).
073     *
074     * @throws  IOException     If {@code file} is not null and cannot be
075     *                          read.
076     */
077    public PropertiesImpl(Properties defaults, File file) throws IOException {
078        this(defaults);
079
080        if (file != null) {
081            try (InputStream in = new FileInputStream(file)) {
082                load(in);
083            }
084        }
085    }
086
087    /**
088     * See {@link #PropertiesImpl(Properties)}.
089     *
090     * @param   defaults        The default {@link Properties}.
091     * @param   resource        The name of the {@code resource} to load
092     *                          (may be {@code null}).
093     *
094     * @throws  IOException     If {@code resource} is not null and cannot be
095     *                          read.
096     */
097    public PropertiesImpl(Properties defaults,
098                          String resource) throws IOException {
099        this(defaults);
100
101        if (resource != null) {
102            try (InputStream in = getClass().getResourceAsStream(resource)) {
103                load(in);
104            }
105        }
106    }
107
108    /**
109     * Method to configure an {@link Object} properties with values in
110     * {@link.this} {@link PropertiesImpl}.  (An {@link Object} "setter"
111     * does not have to return {@code void} to be invoked.)
112     *
113     * @param   object          The {@link Object} to configure.
114     *
115     * @return  The argument {@link Object}.
116     *
117     * @throws  Exception       If any problem is encountered.
118     */
119    public Object configure(Object object) throws Exception {
120        return configure(this, object);
121    }
122
123    @Override
124    public void load(InputStream in) throws IOException { load(this, in); }
125
126    @Override
127    public void store(OutputStream out, String comment) throws IOException {
128        store(this, out, comment);
129    }
130
131    protected static void load(Properties properties,
132                               InputStream in) throws IOException {
133        properties.load(new InputStreamReader(in, CHARSET));
134    }
135
136    protected static void store(Properties properties,
137                                OutputStream out,
138                                String comment) throws IOException {
139        OutputStreamWriter writer = new OutputStreamWriter(out, CHARSET);
140
141        properties.store(writer, comment);
142        writer.flush();
143    }
144
145    /**
146     * See {@link #configure(Object)}.
147     *
148     * @param   properties      The {@link Properties} with the
149     *                          configuration parameters..
150     * @param   object          The {@link Object} to configure.
151     *
152     * @return  The argument {@link Object}.
153     *
154     * @throws  Exception       If any problem is encountered.
155     */
156    public static Object configure(Properties properties,
157                                   Object object) throws Exception {
158        for (String key : properties.stringPropertyNames()) {
159            Object value = properties.get(key);
160            Method method =
161                getSetMethod(object,
162                             key, (value != null) ? value.getClass() : null);
163
164            if (method != null) {
165                value = convertTo(value, method.getParameterTypes()[0]);
166                method.invoke(object, value);
167            }
168        }
169
170        return object;
171    }
172
173    private static Method getSetMethod(Object object,
174                                       String property, Class<?> parameter) {
175        Method method = null;
176        String name =
177            "set"
178            + property.substring(0, 1).toUpperCase()
179            + property.substring(1);
180
181        if (method == null) {
182            if (parameter != null) {
183                try {
184                    method = object.getClass().getMethod(name, parameter);
185                } catch (Exception exception) {
186                }
187            }
188        }
189
190        if (method == null) {
191            for (Method m : object.getClass().getMethods()) {
192                if (m.getName().equals(name)
193                    && (! m.isVarArgs())
194                    && m.getParameterTypes().length == 1) {
195                    method = m;
196                    break;
197                }
198            }
199        }
200
201        return method;
202    }
203}