/*************************GO-LICENSE-START*********************************
* Copyright 2014 ThoughtWorks, Inc.
*
* 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.
*************************GO-LICENSE-END***********************************/
package com.thoughtworks.go.config;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import com.thoughtworks.go.config.parser.GoConfigFieldTypeConverter;
import com.thoughtworks.go.config.registry.ConfigElementImplementationRegistry;
import com.thoughtworks.go.util.ConfigUtil;
import org.jdom2.Element;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.beans.TypeMismatchException;
import static com.thoughtworks.go.util.ExceptionUtils.bomb;
import static com.thoughtworks.go.util.ExceptionUtils.bombIf;
import static com.thoughtworks.go.util.ExceptionUtils.bombIfNull;
import static com.thoughtworks.go.util.ExceptionUtils.bombUnless;
import static com.thoughtworks.go.util.ObjectUtil.nullSafeEquals;
import static java.text.MessageFormat.format;
public class GoConfigFieldWriter {
private final ConfigUtil configUtil = new ConfigUtil("magic");
private Field configField;
private final Object value;
private SimpleTypeConverter typeConverter;
private ConfigCache configCache;
private final ConfigElementImplementationRegistry registry;
public GoConfigFieldWriter(Field declaredField, Object value, SimpleTypeConverter converter, ConfigCache configCache, final ConfigElementImplementationRegistry registry) {
this.configField = declaredField;
this.value = value;
this.configField.setAccessible(true);
this.typeConverter = converter;
this.configCache = configCache;
this.registry = registry;
}
public GoConfigFieldWriter(Field declaredField, Object value, ConfigCache configCache, final ConfigElementImplementationRegistry registry) {
this(declaredField, value, new GoConfigFieldTypeConverter(), configCache, registry);
}
public Field getConfigField() {
return configField;
}
public String value() {
if (isAttribute()) {
return ConfigCache.annotationFor(configField, ConfigAttribute.class).value();
}
throw bomb("Unknown type for field " + configField.getName());
}
public void setValueIfNotNull(Element e, Object o) {
configField.setAccessible(true);
if (isSubtag()) {
setFieldIfNotNull(configField, o, parseSubtag(e, configField));
} else if (isAttribute()) {
setValueFromElementIfAppropriate(e, o);
bombIfNullAndNotAllowed(configField, o, configField.getAnnotation(ConfigAttribute.class));
} else if (isConfigValue()) {
setFieldIfNotNull(configField, o, e.getText());
}
}
private void setValueFromElementIfAppropriate(Element e, Object o) {
final String val = parseAttribute(e, configField);
try {
setFieldIfNotNull(configField, o, val);
} catch (TypeMismatchException e1) {
final String message = format("Could not set value [{0}] on Field [{1}] of type [{2}] ", val,
configField.getName(), configField.getType());
throw new RuntimeException(message, e1);
}
}
private void bombIfNullAndNotAllowed(Field field, Object instance, ConfigAttribute attribute) {
try {
if (!attribute.allowNull()) {
bombIfNull(field.get(instance),
"Field '" + field.getName() + "' is still set to null. "
+ "Must give a default value.");
}
} catch (IllegalAccessException e) {
throw bomb("Error getting configField: " + field.getName(), e);
}
}
private void setFieldIfNotNull(Field field, Object instance, Object val) {
try {
if (val != null) {
Object convertedValue = typeConverter.convertIfNecessary(val, configField.getType());
field.set(instance, convertedValue);
}
} catch (IllegalAccessException e) {
throw bomb("Error setting configField: " + field.getName(), e);
}
}
@SuppressWarnings("unchecked")
private Collection parseCollection(Element e, Class<?> collectionType) {
ConfigCollection collection = collectionType.getAnnotation(ConfigCollection.class);
Class<?> type = collection.value();
Object o = newInstance(collectionType);
bombUnless(o instanceof Collection,
"Must be some sort of list. Was: " + collectionType.getName());
Collection baseCollection = (Collection) o;
for (Element childElement : (List<Element>) e.getChildren()) {
if (isInCollection(childElement, type)) {
baseCollection.add(parseType(childElement, type));
}
}
bombIf(baseCollection.size() < collection.minimum(),
"Required at least " + collection.minimum() + " subelements to '" + e.getName() + "'. "
+ "Found " + baseCollection.size() + ".");
return baseCollection;
}
private boolean isInCollection(Element e, Class<?> type) {
if (type.isInterface() && type.isAnnotationPresent(ConfigInterface.class)) {
for (Class<?> implementation : registry.implementersOf(type)) {
if (configTag(implementation).equals(e.getName())) {
return true;
}
}
}
return configTag(type).equals(e.getName());
}
//TODO this is duplicated from magical cruiseconfig loader
private void parseFields(Element e, Object o) {
for (GoConfigFieldWriter field : new GoConfigClassWriter(o.getClass(), configCache, registry).getAllFields(o)) {
field.setValueIfNotNull(e, o);
}
}
private Class<?> typeToGenerate(Element e, Class<?> type) {
if (type.isInterface() && type.isAnnotationPresent(ConfigInterface.class)) {
for (Class<?> implementation : registry.implementersOf(type)) {
if (configTag(implementation).equals(e.getName())) {
return implementation;
}
}
} else {
if (configTag(type).equals(e.getName())) {
return type;
}
}
throw bomb("Unable to determine type to generate.\n"
+ "Type: " + type.getName() + "\n"
+ "Element: " + configUtil.elementOutput(e));
}
public boolean isSubtag() {
return ConfigCache.isAnnotationPresent(configField, ConfigSubtag.class);
}
boolean isImplicitCollection() {
return isSubtag() && isConfigCollection(configField.getType()) && !ConfigCache.isAnnotationPresent(configField.getType(), ConfigTag.class);
}
boolean isConfigCollection() {
return isSubtag() && isConfigCollection(configField.getType());
}
private boolean isConfigCollection(Class<?> type) {
return ConfigCache.isAnnotationPresent(type, ConfigCollection.class);
}
public boolean isAttribute() {
return ConfigCache.isAnnotationPresent(configField, ConfigAttribute.class);
}
public boolean isConfigValue() {
return ConfigCache.isAnnotationPresent(configField, ConfigValue.class);
}
private boolean optionalAndMissingTag(Element e, Field field) {
ConfigTag tag = configTag(field.getType());
boolean optional = field.getAnnotation(ConfigSubtag.class).optional();
boolean isMissingElement = !configUtil.hasChild(e, tag);
if(!optional && isMissingElement) {
throw bomb("Non optional tag '" + tag + "' is not in config file. Found: " + configUtil.elementOutput(e));
}
return optional && isMissingElement;
}
private boolean optionalAndMissingAttribute(Element e, ConfigAttribute attribute) {
boolean optional = attribute.optional();
boolean isMissingAttribute = !configUtil.hasAttribute(e, attribute.value());
if(!optional && isMissingAttribute) {
throw bomb("Non optional attribute '" + attribute.value() + "' is not in element: " + configUtil.elementOutput(e));
}
return optional && isMissingAttribute;
}
private Object newInstance(Class<?> type) {
try {
return type.newInstance();
} catch (Exception e) {
throw bomb("Error creating new instance of class " + type.getName(), e);
}
}
private ConfigTag configTag(Class<?> type) {
bombIf(!ConfigCache.isAnnotationPresent(type, ConfigTag.class), "Invalid type '" + type + "' to autoload. Must have ConfigTag annotation.");
return ConfigCache.annotationFor(type, ConfigTag.class);
}
private String parseAttribute(Element e, Field field) {
ConfigAttribute attribute = ConfigCache.annotationFor(field, ConfigAttribute.class);
if (optionalAndMissingAttribute(e, attribute)) {
return null;
}
return configUtil.getAttribute(e, attribute.value());
}
private Object parseSubtag(Element e, Field field) {
Class<?> type = field.getType();
ConfigTag tag = configTag(type);
if (optionalAndMissingTag(e, field)) {
return null;
}
Element subElement = configUtil.getChild(e, tag);
return parseType(subElement, type);
}
private Object parseType(Element e, Class<?> type) {
Object o = isConfigCollection(type) ? parseCollection(e, type) : newInstance(typeToGenerate(e, type));
parseFields(e, o);
return o;
}
public String toString() {
return "<" + this.getClass().getName() + ": " + this.configField.getName() + ">";
}
public boolean isDefault(GoConfigClassWriter configClass) {
Object defaultObject = configClass.defaultField(configField);
return nullSafeEquals(getValue(), defaultObject);
}
private boolean isConfigInterface() {
return ConfigCache.isAnnotationPresent(configField.getType(), ConfigInterface.class);
}
public Object getValue() {
return value;
}
}