/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.persistence.jest;
import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.apache.openjpa.kernel.Filters;
/**
* A factory for a specific type of objects registered by a key.
* The client registers a type indexed by name.
* The client can get a new instance of the registered type.
* The requested registered type <em>not</em> necessarily have to have a no-arg
* constructor. The constructor arguments can be passed during
* {@link #newInstance(Class, Object...) new instance} request. Based on the
* arguments, a matching constructor, if any, is located and invoked.
*
* <K> type of key for this registry
* <T> base type of the objects to construct
*
* @author Pinaki Poddar
*
*/
public class PrototypeFactory<K,T> {
private Map<K, Class<? extends T>> _registry = new TreeMap<K, Class<? extends T>>();
/**
* Register the given class with the given key.
*
* @param key a non-null key.
* @param prototype a type.
*/
public void register(K key, Class<? extends T> prototype) {
_registry.put(key, prototype);
}
/**
* Create a new instance of the type {@linkplain #register(Object, Class) registered} before
* with the given key, if any.
* The given arguments are used to identify a constructor of the registered type and
* passed to the constructor of the registered type.
*
* @param key a key to identify a registered type.
* @param args arguments to pass to the constructor of the type.
*
* @return null if no type has been registered against the given key.
*/
public T newInstance(K key, Object... args) {
return _registry.containsKey(key) ? newInstance(_registry.get(key), args) : null;
}
/**
* Gets the keys registered in this factory.
*
* @return immutable set of registered keys.
*/
public Set<K> getRegisteredKeys() {
return Collections.unmodifiableSet(_registry.keySet());
}
private T newInstance(Class<? extends T> type, Object... args) {
try {
return findConstructor(type, getConstructorParameterTypes(args)).newInstance(args);
} catch (Exception e) {
throw new RuntimeException();
}
}
Class<?>[] getConstructorParameterTypes(Object... args) {
if (args == null || args.length == 0) {
return new Class<?>[0];
}
Class<?>[] types = new Class<?>[args.length];
for (int i = 0; i < args.length; i++) {
types[i] = args[i] == null ? Object.class : args[i].getClass();
}
return types;
}
/**
* Finds a constructor of the given class with given argument types.
*/
Constructor<? extends T> findConstructor(Class<? extends T> cls, Class<?>[] types) {
try {
return cls.getConstructor(types);
} catch (Exception e) {
Constructor<?>[] constructors = cls.getConstructors();
for (Constructor<?> cons : constructors) {
Class<?>[] paramTypes = cons.getParameterTypes();
boolean match = false;
if (paramTypes.length == types.length) {
for (int i = 0; i < paramTypes.length; i++) {
match = paramTypes[i].isAssignableFrom(Filters.wrap(types[i]));
if (!match)
break;
}
}
if (match) {
return (Constructor<? extends T>)cons;
}
}
}
throw new RuntimeException();//_loc.get("fill-ctor-none", cls, Arrays.toString(types)).getMessage());
}
}