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