/*
* Copyright (C) 2013,2014 The Cat Hive Developers.
*
* Licensed 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 com.cathive.fx.inject.core;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javafx.util.Builder;
import javafx.util.StringConverter;
/**
* Abstract base class for all component builders that use
* dependency injection framework to construct instances.
* <p>
* Currently, the logic to find appropriate setter methods a and look up the
* StringConverters in a Map seems a duplicate of what has already been implemented
* in the JavaFX runtime.
* A better solution may follow in the future.
*
* @param <T> Type of the class to be constructed.
*
* @author Benjamin P. Jung
* @since 1.1.0
*/
public abstract class FXMLComponentBuilder<T> extends AbstractMap<String, Object> implements Builder<T> {
private final Class<T> componentClass;
private final Map<String, Object> componentProperties = new HashMap<>();
/**
* Constructs a new component builder for the given class.
* @param componentClass
* Class to be used when constructing new instances.
*/
protected FXMLComponentBuilder(final Class<T> componentClass) {
super();
this.componentClass = componentClass;
}
@Override
public Object put(final String key, final Object value) {
componentProperties.put(key, value);
return null;
}
@Override
public Set<Map.Entry<String, Object>> entrySet() {
throw new UnsupportedOperationException();
}
@Override
public T build() {
final T component = getInstance(this.componentClass);
for (final String key : componentProperties.keySet()) {
final Object value = componentProperties.get(key);
final String setterName = String.format("set%s%s", key.substring(0, 1).toUpperCase(), key.substring(1));
try {
Method setterMethod = null;
for (Method method : componentClass.getMethods()) {
if (method.getName().equals(setterName)) {
setterMethod = method;
break;
}
}
if (setterMethod == null) {
throw new IllegalStateException(String.format("No setter for field '%s' could be found.", key));
}
final Class<?> paramType = setterMethod.getParameterTypes()[0];
Object paramValue;
if (Enum.class.isAssignableFrom(paramType)) {
paramValue = paramType.getMethod("valueOf", String.class).invoke(setterMethod, value);
} else {
final StringConverter<?> stringConverter = getStringConverter(paramType);
paramValue = stringConverter.fromString((String) value);
}
setterMethod.invoke(component, paramValue);
} catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
return component;
}
/**
* Returns an instance of the requested class from the dependency injection framework being used.
*
* @param clazz Class to be constructed via DI container.
* @return An instance of the requested class that has been fetched
* via the concrete implementation of DI container being used.
*/
protected abstract T getInstance(Class<T> clazz);
/**
* Retrieves a {@link javafx.util.StringConverter} instance for the given class.
* @param valueClass
* Class to be used when looking for a matching string converter.
* @return
* A string converter that can handle values of the given class.
*/
private StringConverter<?> getStringConverter(final Class<?> valueClass) {
Class<? extends StringConverter<?>> aClass = StringConverterRetriever.retrieveConverterFor(valueClass);
if (aClass == null) {
throw new IllegalArgumentException(String.format("Can't find StringConverter for class '%s'.", valueClass.getName()));
}
try {
return aClass.newInstance();
} catch (final InstantiationException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
}