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