001package co.codewizards.cloudstore.core.objectfactory; 002 003import static java.util.Objects.*; 004 005import java.lang.reflect.Constructor; 006import java.lang.reflect.InvocationTargetException; 007import java.util.Collections; 008import java.util.HashMap; 009import java.util.LinkedList; 010import java.util.List; 011import java.util.Map; 012import java.util.ServiceLoader; 013 014/** 015 * Factory for objects. 016 * <p> 017 * Instead of invoking {@code new MySomething()}, devs can import {@link ObjectFactoryUtil}<code>.*</code> 018 * statically and then invoke {@link ObjectFactoryUtil#createObject(Class) createObject(MySomething.class)}. 019 * Thus allowing downstream projects to extend the system by providing a replacement-class. For example, if the 020 * replacement-class {@code MyOther} was registered for {@code MySomething}, the method 021 * {@code createObject(MySomething.class)} would return an instance of {@code MyOther} instead of 022 * {@code MySomething}. 023 * <p> 024 * However, it is urgently recommended <i>not</i> to use this approach, whenever it is possible to use a better solution, 025 * preferably a well-defined service (=> {@link ServiceLoader}). There are situations, e.g. data-model-classes 026 * (a.k.a. entities), where services are not possible and the {@code ObjectFactory} is the perfect solution. 027 * <p> 028 * In order to register a sub-class as replacement for a certain base-class, implementors have to provide a 029 * {@link ClassExtension} and register it using the {@link ServiceLoader}-mechanism. 030 * <p> 031 * Important: You should usually <i>not</i> need to access this class directly! Use {@link ObjectFactoryUtil} instead 032 * (statically import its methods). 033 * 034 * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co 035 */ 036public class ObjectFactory { 037 038 private final Map<Class<?>, ClassExtension<?>> baseClass2ClassExtension; 039 040 private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class[0]; 041 042 private static final class Holder { 043 public static final ObjectFactory instance = new ObjectFactory(); 044 } 045 046 /** 047 * Gets the singleton instance of this {@code ObjectFactory}. 048 * <p> 049 * <b>Important:</b> You should normally <i>not</i> invoke this method directly, but use {@link ObjectFactoryUtil} 050 * instead. 051 * @return the {@code ObjectFactory} instance; never <code>null</code>. 052 */ 053 public static ObjectFactory getInstance() { 054 return Holder.instance; 055 } 056 057 protected ObjectFactory() { 058 final Map<Class<?>, ClassExtension<?>> baseClass2ClassExtension = new HashMap<Class<?>, ClassExtension<?>>(); 059 for (final ClassExtension<?> classExtension : ServiceLoader.load(ClassExtension.class)) { 060 final ClassExtension<?> old = baseClass2ClassExtension.get(classExtension.getBaseClass()); 061 if (old == null || old.getPriority() < classExtension.getPriority()) 062 baseClass2ClassExtension.put(classExtension.getBaseClass(), classExtension); 063 else if (old.getPriority() == classExtension.getPriority()) 064 throw new IllegalStateException("Multiple ClassExtensions registered on the base-class %s with the same priority!"); 065 } 066 this.baseClass2ClassExtension = Collections.unmodifiableMap(baseClass2ClassExtension); 067 } 068 069 public <T> Class<? extends T> getExtendingClass(final Class<T> clazz) { 070 Class<? extends T> c = clazz; 071 ClassExtension<? extends T> classExtension; 072 while (null != (classExtension = getClassExtension(c))) { 073 c = classExtension.getExtendingClass(); 074 } 075 return c; 076 } 077 078 @SuppressWarnings("unchecked") 079 public <T> ClassExtension<T> getClassExtension(final Class<T> clazz) { 080 return (ClassExtension<T>) baseClass2ClassExtension.get(clazz); 081 } 082 083 public <T> T createObject(final Class<T> clazz) { 084 return createObject(clazz, (Class<?>[]) null, (Object[]) null); 085 } 086 087 public <T> T createObject(final Class<T> clazz, final Object ... parameters) { 088 return createObject(clazz, (Class<?>[]) null, parameters); 089 } 090 091 public <T> T createObject(final Class<T> clazz, Class<?>[] parameterTypes, final Object ... parameters) { 092 requireNonNull(clazz, "clazz"); 093 if (parameterTypes != null && parameters != null) { 094 if (parameterTypes.length != parameters.length) 095 throw new IllegalArgumentException(String.format( 096 "parameterTypes.length != parameters.length :: %s != %s", parameterTypes.length, parameters.length)); 097 } 098 099 if (parameterTypes == null && (parameters == null || parameters.length == 0)) 100 parameterTypes = EMPTY_CLASS_ARRAY; 101 102 final Class<? extends T> c = getExtendingClass(clazz); 103 104 Constructor<? extends T> constructor; 105 if (parameterTypes == null && parameters != null) 106 constructor = getMatchingConstructor(c, parameters); 107 else { 108 for (int i = 0; i < parameterTypes.length; ++i) { 109 if (parameterTypes[i] == null) 110 throw new IllegalArgumentException(String.format("parameterTypes[%s] == null", i)); 111 } 112 try { 113 constructor = c.getDeclaredConstructor(parameterTypes); 114 } catch (final NoSuchMethodException e) { 115 throw new RuntimeException(e); 116 } 117 } 118 119 constructor.setAccessible(true); 120 121 try { 122 final T instance = constructor.newInstance(parameters); 123 return instance; 124 } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { 125 throw new RuntimeException(e); 126 } 127 } 128 129 private <T> Constructor<T> getMatchingConstructor(final Class<T> clazz, final Object[] parameters) { 130 requireNonNull(clazz, "clazz"); 131 requireNonNull(parameters, "parameters"); 132 final Constructor<?>[] constructors = clazz.getDeclaredConstructors(); 133 final List<Constructor<T>> constructorsWithSameNumberOfArguments = new LinkedList<Constructor<T>>(); 134 for (final Constructor<?> constructor : constructors) { 135 if (constructor.getParameterTypes().length == parameters.length) { 136 @SuppressWarnings("unchecked") 137 final 138 Constructor<T> con = (Constructor<T>) constructor; 139 constructorsWithSameNumberOfArguments.add(con); 140 } 141 } 142 143 if (constructorsWithSameNumberOfArguments.isEmpty()) 144 throw new RuntimeException(new NoSuchMethodException(String.format("The class %s does not have any constructor with %s arguments.", clazz.getName(), parameters.length))); 145 146 if (constructorsWithSameNumberOfArguments.size() == 1) 147 return constructorsWithSameNumberOfArguments.get(0); 148 149 throw new UnsupportedOperationException(String.format("The class %s has multiple constructors with %s arguments. This is NOT YET SUPPORTED!", clazz.getName(), parameters.length)); 150 } 151}