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.beans.ConstructorProperties;
022import java.lang.reflect.Constructor;
023import java.lang.reflect.Field;
024import java.lang.reflect.InvocationTargetException;
025import java.lang.reflect.Member;
026import java.lang.reflect.Method;
027import java.util.Arrays;
028import java.util.Map;
029import java.util.TreeMap;
030import java.util.TreeSet;
031
032import static java.util.Comparator.comparing;
033import static java.lang.reflect.Modifier.isStatic;
034import static java.lang.reflect.Modifier.isPublic;
035
036/**
037 * {@link Factory} base class.
038 *
039 * @param       <T>             The type of {@link Object} this
040 *                              {@link Factory} will produce.
041 *
042 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
043 */
044public class Factory<T> extends TreeMap<Class<?>[],Member> {
045    private static final long serialVersionUID = -5733222257965875050L;
046
047    /** @serial */ private final Class<? extends T> type;
048    /** @serial */ private final Object factory;
049
050    /**
051     * Sole public constructor.
052     *
053     * @param   type            The {@link Class} of {@link Object} this
054     *                          {@link Factory} will produce.
055     *
056     * @throws  NullPointerException
057     *                          If {@code type} is {@code null}.
058     */
059    @ConstructorProperties({ "type" })
060    public Factory(Class<? extends T> type) { this(type, null); }
061
062    /**
063     * Construct a {@link Factory} by wrapping a factory instance.
064     *
065     * @param   type            The {@link Class} of {@link Object} this
066     *                          {@link Factory} will produce.
067     * @param   factory         An {@link Object} factory for this
068     *                          {@code type} (may be {@code null}).
069     *
070     * @throws  NullPointerException
071     *                          If {@code type} is {@code null}.
072     */
073    @ConstructorProperties({ "type", "factory" })
074    protected Factory(Class<? extends T> type, Object factory) {
075        super(comparing(Arrays::toString));
076
077        this.type = type;
078        this.factory = factory;
079
080        CandidateSet set = new CandidateSet(type);
081
082        if (factory != null) {
083            Arrays.stream(factory.getClass().getMethods())
084                .filter(t -> isPublic(t.getModifiers()))
085                .filter(t -> type.isAssignableFrom(t.getReturnType()))
086                .filter(t -> set.contains(t.getName()))
087                .forEach(t -> putIfAbsent(t.getParameterTypes(), t));
088        }
089
090        Arrays.stream(type.getMethods())
091            .filter(t -> (isPublic(t.getModifiers())
092                          && isStatic(t.getModifiers())))
093            .filter(t -> type.isAssignableFrom(t.getReturnType()))
094            .filter(t -> (set.contains(t.getName())))
095            .forEach(t -> putIfAbsent(t.getParameterTypes(), t));
096
097        Arrays.stream(type.getConstructors())
098            .filter(t -> isPublic(t.getModifiers()))
099            .forEach(t -> putIfAbsent(t.getParameterTypes(), t));
100    }
101
102    /**
103     * Method to get the type ({@link Class}) of objects produced by this
104     * {@link Factory}.
105     *
106     * @return  The type of {@link Object} produced by this
107     *          {@link Factory}.
108     */
109    public Class<? extends T> getType() { return type; }
110
111    /**
112     * Method to get the underlying factory {@link Object}.
113     *
114     * @return  The underlying factory {@link Object} or {@code null} if
115     *          there is none.
116     */
117    public Object getFactory() { return factory; }
118
119    /**
120     * Method to get an {@link Object} instance.  This method will first
121     * attempt to find and invoke a static factory method.  If no factory
122     * method is found, it will then attempt to construct a new instance.
123     *
124     * @param   parameters      The parameter types to use to search for the
125     *                          {@link Object} static factory method or
126     *                          constructor.
127     * @param   arguments       The arguments to the {@link Object} static
128     *                          factory method or constructor.
129     *
130     * @return  The {@link Object} instance.
131     *
132     * @throws  IllegalAccessException
133     *                          If the specified {@link Constructor} or
134     *                          {@link Method} enforces Java language
135     *                          access control and the underlying
136     *                          {@link Constructor} or {@link Method} is
137     *                          inaccessible.
138     * @throws  IllegalArgumentException
139     *                          If the number of actual and formal parameters
140     *                          differ; if an unwrapping conversion for
141     *                          primitive arguments fails; or if, after
142     *                          possible unwrapping, a parameter value cannot
143     *                          be converted to the corresponding formal
144     *                          parameter type by a method invocation
145     *                          conversion; if this {@link Constructor} or
146     *                          {@link Method} pertains to an
147     *                          {@link Enum} type.
148     * @throws  InstantiationException
149     *                          If the underlying {@link Constructor} or
150     *                          {@link Method} represents an abstract
151     *                          {@link Class}.
152     * @throws  InvocationTargetException
153     *                          If the underlying {@link Constructor} or
154     *                          {@link Method} fails for some reason.
155     * @throws  NoSuchMethodException
156     *                          If the specified {@link Constructor} or
157     *                          {@link Method} does not exist.
158     */
159    public T getInstance(Class<?>[] parameters, Object... arguments) throws IllegalAccessException,
160                                                                            IllegalArgumentException,
161                                                                            InstantiationException,
162                                                                            InvocationTargetException,
163                                                                            NoSuchMethodException {
164        return apply(getFactoryMethod(parameters), arguments);
165    }
166
167    /**
168     * Method to get an {@link Object} instance.  This method will first
169     * attempt to find and invoke a static factory method.  If no factory
170     * method is found, it will then attempt to construct a new instance.
171     *
172     * @param   arguments       The arguments to the {@link Object} static
173     *                          factory method or constructor.
174     *
175     * @return  The {@link Object} instance.
176     *
177     * @throws  IllegalAccessException
178     *                          If the specified {@link Constructor} or
179     *                          {@link Method} enforces Java language
180     *                          access control and the underlying
181     *                          {@link Constructor} or {@link Method} is
182     *                          inaccessible.
183     * @throws  IllegalArgumentException
184     *                          If the number of actual and formal parameters
185     *                          differ; if an unwrapping conversion for
186     *                          primitive arguments fails; or if, after
187     *                          possible unwrapping, a parameter value cannot
188     *                          be converted to the corresponding formal
189     *                          parameter type by a method invocation
190     *                          conversion; if this {@link Constructor} or
191     *                          {@link Method} pertains to an
192     *                          {@link Enum} type.
193     * @throws  InstantiationException
194     *                          If the underlying {@link Constructor} or
195     *                          {@link Method} represents an abstract
196     *                          {@link Class}.
197     * @throws  InvocationTargetException
198     *                          If the underlying {@link Constructor} or
199     *                          {@link Method} fails for some reason.
200     * @throws  NoSuchMethodException
201     *                          If the specified {@link Constructor} or
202     *                          {@link Method} does not exist.
203     */
204    public T getInstance(Object... arguments) throws IllegalAccessException,
205                                                     IllegalArgumentException,
206                                                     InstantiationException,
207                                                     InvocationTargetException,
208                                                     NoSuchMethodException {
209        return getInstance(typesOf(arguments), arguments);
210    }
211
212    /**
213     * Method to determine if there is a factory {@link Member} (factory
214     * {@link Method}, static {@link Method}, or {@link Constructor}) to
215     * manufacture or get an {@link Object}.
216     *
217     * @param   parameters      The {@link Constructor} or {@link Method}
218     *                          parameter list.
219     *
220     * @return  {@code true} if there is such a {@link Member};
221     *          {@code false} otherwise.
222     */
223    public boolean hasFactoryMethodFor(Class<?>... parameters) {
224        boolean hasMember = false;
225
226        try {
227            getFactoryMethod(parameters);
228            hasMember = (get(parameters) != null);
229        } catch (NoSuchMethodException exception) {
230            hasMember = false;
231        }
232
233        return hasMember;
234    }
235
236    /**
237     * Method to get a factory {@link Member} (factory {@link Method},
238     * static {@link Method}, or {@link Constructor}) to manufacture or get
239     * an {@link Object}.
240     *
241     * @param   parameters      The {@link Constructor} or {@link Method}
242     *                          parameter list.
243     *
244     * @return  The factory {@link Member}.
245     *
246     * @throws  NoSuchMethodException
247     *                          If the specified {@link Constructor} or
248     *                          {@link Method} does not exist.
249     */
250    public Member getFactoryMethod(Class<?>... parameters) throws NoSuchMethodException {
251        if (! containsKey(parameters)) {
252            put(parameters, getType().getConstructor(parameters));
253        }
254
255        return get(parameters);
256    }
257
258    /**
259     * Method to apply a factory {@link Member} (factory {@link Method},
260     * static {@link Method}, or {@link Constructor}) to manufacture or get
261     * an {@link Object}.
262     *
263     * @param   member          The {@link Member} ({@link Constructor} or
264     *                          static {@link Method}) to invoke.
265     * @param   arguments       The array of arguments to apply.
266     *
267     * @return  The {@link Object} instance.
268     *
269     * @throws  IllegalAccessException
270     *                          If the specified {@link Constructor} or
271     *                          {@link Method} enforces Java language
272     *                          access control and the underlying
273     *                          {@link Constructor} or {@link Method} is
274     *                          inaccessible.
275     * @throws  InstantiationException
276     *                          If the underlying {@link Constructor} or
277     *                          {@link Method} represents an abstract
278     *                          {@link Class}.
279     * @throws  InvocationTargetException
280     *                          If the underlying {@link Constructor} or
281     *                          {@link Method} fails for some reason.
282     */
283    public T apply(Member member, Object... arguments) throws IllegalAccessException,
284                                                              InstantiationException, InvocationTargetException {
285        Object object = null;
286
287        if (member instanceof Method) {
288            object = ((Method) member).invoke(factory, arguments);
289        } else if (member instanceof Constructor) {
290            object = ((Constructor) member).newInstance(arguments);
291        } else if (member instanceof Field) {
292            object = ((Field) member).get(null);
293        } else {
294            throw new IllegalArgumentException("member=" + member);
295        }
296
297        return getType().cast(object);
298    }
299
300    @Override
301    public Member get(Object key) {
302        Member value = null;
303
304        if (key instanceof Class<?>[]) {
305            if (! super.containsKey(key)) {
306                for (Map.Entry<Class<?>[],Member> entry : entrySet()) {
307                    if (isApplicable(entry.getKey(), (Class<?>[]) key)) {
308                        value = entry.getValue();
309                        break;
310                    }
311                }
312
313                super.put((Class<?>[]) key, value);
314            }
315
316            value = super.get(key);
317        }
318
319        return value;
320    }
321
322    /**
323     * Convenience method to get the types of an argument array.
324     *
325     * @param   arguments       The argument array.
326     *
327     * @return  An array of types ({@link Class}s).
328     */
329    protected static Class<?>[] typesOf(Object... arguments) {
330        Class<?>[] types = new Class<?>[arguments.length];
331
332        for (int i = 0; i < types.length; i += 1) {
333            types[i] = arguments[i].getClass();
334        }
335
336        return types;
337    }
338
339    /**
340     * Method to determine if an argument of the specified types may be
341     * applied to a method or constructor with the specified parameters.
342     *
343     * @see Class#isAssignableFrom(Class)
344     *
345     * @param   parameters      The parameter types.
346     * @param   arguments       The argument types.
347     *
348     * @return  {@code true} if the length of the argument array is the same
349     *          as the length of the parameter array and each parameter is
350     *          assignable from its corresponding argument; {@code false}
351     *          otherwise.
352     */
353    protected static boolean isApplicable(Class<?>[] parameters, Class<?>... arguments) {
354        boolean match = (parameters.length == arguments.length);
355
356        for (int i = 0; match && i < arguments.length; i += 1) {
357            match &= parameters[i].isAssignableFrom(arguments[i]);
358        }
359
360        return match;
361    }
362
363    private class CandidateSet extends TreeSet<String> {
364        private static final long serialVersionUID = -7927801377734740425L;
365
366        public CandidateSet(Class<? extends T> type) {
367            super(Arrays.asList("compile",
368                                "create",
369                                "decode",
370                                "forName",
371                                "getDefault",
372                                "getDefaultInstance",
373                                "getInstance",
374                                "getObjectInstance",
375                                "new" + type.getSimpleName(),
376                                "newInstance",
377                                "valueOf"));
378
379            if (! (type.isAssignableFrom(Boolean.class)
380                   || type.isAssignableFrom(Integer.class)
381                   || type.isAssignableFrom(Long.class))) {
382                add("get" + type.getSimpleName());
383            }
384        }
385    }
386}