Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve converters #613

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/fitnesse/fixtures/PageBuilder.java
Expand Up @@ -20,6 +20,8 @@ public PageBuilder() {
}

public void line(String line) {
if (line == null)
line = "";
if (line.startsWith("\\"))
line = line.substring(1);
line = line.replace("&bar;", "|");
Expand Down
25 changes: 13 additions & 12 deletions src/fitnesse/fixtures/PageCreator.java
Expand Up @@ -10,7 +10,7 @@
public class PageCreator extends ColumnFixture {
public String pageName;
public String pageContents;
public String pageAttributes = "";
public String pageAttributes;

public boolean valid() throws Exception {
if (pageContents != null) {
Expand All @@ -20,7 +20,7 @@ public boolean valid() throws Exception {
WikiPage root = FitnesseFixtureContext.context.getRootPage();
WikiPagePath pagePath = PathParser.parse(pageName);
WikiPage thePage = WikiPageUtil.addPage(root, pagePath, pageContents);
if (!"".equals(pageAttributes)) {
if (pageAttributes != null && pageAttributes.length() > 0) {
PageData data = thePage.getData();
setAttributes(data);
thePage.commit(data);
Expand All @@ -30,15 +30,17 @@ public boolean valid() throws Exception {
}

private void setAttributes(PageData data) throws Exception {
StringTokenizer tokenizer = new StringTokenizer(pageAttributes, ",");
while (tokenizer.hasMoreTokens()) {
String nameValuePair = tokenizer.nextToken();
int equals = nameValuePair.indexOf("=");
if (equals < 0)
throw new Exception("Attribute must have form name=value");
String name = nameValuePair.substring(0, equals);
String value = nameValuePair.substring(equals + 1);
data.setAttribute(name, value);
if (pageAttributes != null) {
StringTokenizer tokenizer = new StringTokenizer(pageAttributes, ",");
while (tokenizer.hasMoreTokens()) {
String nameValuePair = tokenizer.nextToken();
int equals = nameValuePair.indexOf("=");
if (equals < 0)
throw new Exception("Attribute must have form name=value");
String name = nameValuePair.substring(0, equals);
String value = nameValuePair.substring(equals + 1);
data.setAttribute(name, value);
}
}
}

Expand All @@ -54,4 +56,3 @@ public void setPageAttributes(String pageAttributes) {
this.pageAttributes = pageAttributes;
}
}

6 changes: 4 additions & 2 deletions src/fitnesse/slim/Converter.java
Expand Up @@ -7,7 +7,9 @@
* strings. Each derivative of this interface corresponds to a particular type.
*/
public interface Converter<T> {
public String toString(T o);
public String toString(T o);

T fromString(String arg);
final String NULL_VALUE = null;

T fromString(String arg);
}
56 changes: 31 additions & 25 deletions src/fitnesse/slim/ConverterSupport.java
@@ -1,33 +1,39 @@
package fitnesse.slim;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import fitnesse.slim.converters.ConverterRegistry;

class ConverterSupport {

public static Object[] convertArgs(Object[] args, Class<?>[] argumentTypes) {
Object[] convertedArgs = new Object[args.length];
for (int i = 0; i < argumentTypes.length; i++) {
convertedArgs[i] = convertArg(args[i], argumentTypes[i]);
}
return convertedArgs;
}

@SuppressWarnings("unchecked")
private static <T> T convertArg(Object arg, Class<T> argumentType)
throws SlimError {
if (arg == null || (argumentType.isInstance(arg) && !(String.class.equals(argumentType)))) {
// arg may be a List or an instance that comes from the variable store
// But String arguments should always pass through the registered String Converter
return (T) arg;
}
Converter<T> converter = ConverterRegistry.getConverterForClass(argumentType);
if (converter != null) {
return converter.fromString(arg.toString());
}
throw new SlimError(String.format(
"message:<<%s %s.>>",
SlimServer.NO_CONVERTER_FOR_ARGUMENT_NUMBER,
argumentType.getName()));
}
public static Object[] convertArgs(Object[] args, Type[] argumentTypes) {
Object[] convertedArgs = new Object[args.length];
for (int i = 0; i < argumentTypes.length; i++) {
if (argumentTypes[i] instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) argumentTypes[i];
convertedArgs[i] = convertArg(args[i], (Class<?>) parameterizedType.getRawType(), (ParameterizedType) argumentTypes[i]);
} else
convertedArgs[i] = convertArg(args[i], (Class<?>) argumentTypes[i], null);
}
return convertedArgs;
}

@SuppressWarnings("unchecked")
private static <T> T convertArg(Object arg, Class<T> argumentType, ParameterizedType argumentParameterizedType) throws SlimError {

if (arg == null || (argumentType.isInstance(arg) && String.class != argumentType)) {
// arg may be an instance that comes from the variable store
// But String arguments should always pass through the registered String Converter
return (T) arg;
}

Converter<T> converter = ConverterRegistry.getConverterForClass(argumentType, argumentParameterizedType);
if (converter != null) {
return converter.fromString(arg.toString());
}

throw new SlimError(String.format("message:<<%s %s.>>", SlimServer.NO_CONVERTER_FOR_ARGUMENT_NUMBER, argumentType.getName()));
}

}
5 changes: 3 additions & 2 deletions src/fitnesse/slim/MethodExecutor.java
Expand Up @@ -4,6 +4,7 @@

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

public abstract class MethodExecutor {
public MethodExecutor() {
Expand Down Expand Up @@ -33,8 +34,8 @@ protected MethodExecutionResult invokeMethod(Object instance, Method method, Obj
}

protected Object[] convertArgs(Method method, Object args[]) {
Class<?>[] argumentTypes = method.getParameterTypes();
return ConverterSupport.convertArgs(args, argumentTypes);
Type[] argumentParameterTypes = method.getGenericParameterTypes();
return ConverterSupport.convertArgs(args, argumentParameterTypes);
}

protected Object callMethod(Object instance, Method method, Object[] convertedArgs) throws Throwable {
Expand Down
32 changes: 0 additions & 32 deletions src/fitnesse/slim/converters/BooleanArrayConverter.java

This file was deleted.

18 changes: 10 additions & 8 deletions src/fitnesse/slim/converters/BooleanConverter.java
Expand Up @@ -2,21 +2,23 @@
// Released under the terms of the CPL Common Public License version 1.0.
package fitnesse.slim.converters;

import fitnesse.util.StringUtils;

import fitnesse.slim.Converter;

public class BooleanConverter implements Converter<Boolean> {
public static final String TRUE = "true";
public static final String FALSE = "false";
public static final String YES = "yes";

public Boolean fromString(String arg) {
return (
arg.equalsIgnoreCase("true") ||
arg.equalsIgnoreCase("yes") ||
arg.equals(TRUE)
);
public String toString(Boolean o) {
return o != null ? o.booleanValue() ? TRUE : FALSE : NULL_VALUE;
}

public String toString(Boolean o) {
return o ? TRUE : FALSE;
public Boolean fromString(String arg) {
if (StringUtils.isBlank(arg))
return null;

return arg.equalsIgnoreCase(TRUE) || arg.equalsIgnoreCase(YES);
}
}
6 changes: 4 additions & 2 deletions src/fitnesse/slim/converters/CharConverter.java
Expand Up @@ -3,13 +3,15 @@
package fitnesse.slim.converters;

import fitnesse.slim.Converter;
import fitnesse.util.StringUtils;

public class CharConverter implements Converter<Character> {

public String toString(Character o) {
return o.toString();
return o != null ? o.toString() : NULL_VALUE;
}

public Character fromString(String arg) {
return arg.toCharArray()[0];
return !StringUtils.isBlank(arg) ? arg.charAt(0) : null;
}
}
142 changes: 87 additions & 55 deletions src/fitnesse/slim/converters/ConverterRegistry.java
Expand Up @@ -2,68 +2,100 @@

import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import fitnesse.slim.Converter;

public class ConverterRegistry {

final static Map<Class<?>, Converter<?>> converters = new HashMap<Class<?>, Converter<?>>();

static {
addStandardConverters();
}

@SuppressWarnings("unchecked")
protected static void addStandardConverters() {
addConverter(void.class, new VoidConverter());
addConverter(String.class, new StringConverter());
addConverter(int.class, new IntConverter());
addConverter(double.class, new DoubleConverter());
addConverter(Integer.class, new IntConverter());
addConverter(Double.class, new DoubleConverter());
addConverter(char.class, new CharConverter());
addConverter(boolean.class, new BooleanConverter());
addConverter(Boolean.class, new BooleanConverter());
addConverter(Date.class, new DateConverter());
addConverter(List.class, new ListConverter());
addConverter(Integer[].class, new IntegerArrayConverter());
addConverter(int[].class, new IntegerArrayConverter());
addConverter(String[].class, new StringArrayConverter());
addConverter(boolean[].class, new BooleanArrayConverter());
addConverter(Boolean[].class, new BooleanArrayConverter());
addConverter(double[].class, new DoubleArrayConverter());
addConverter(Double[].class, new DoubleArrayConverter());
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T> Converter<T> getConverterForClass(Class<? extends T> clazz) {
if (converters.containsKey(clazz))
return (Converter<T>) converters.get(clazz);

PropertyEditor pe = PropertyEditorManager.findEditor(clazz);
// com.sun.beans.EnumEditor and sun.beans.EnumEditor seem to be used in
// different usages.
if (Enum.class.isAssignableFrom(clazz)
&& (pe == null || "EnumEditor".equals(pe.getClass().getSimpleName())))
return new EnumConverter(clazz);

if (pe != null) {
return new PropertyEditorConverter<T>(pe);
}
return null;
}

public static <T> void addConverter(Class<? extends T> clazz,
Converter<T> converter) {
converters.put(clazz, converter);
}

public static Map<Class<?>, Converter<?>> getConverters() {
return Collections.unmodifiableMap(converters);
}
private final static Map<Class<?>, Converter<?>> converters = new HashMap<Class<?>, Converter<?>>();

static {
addStandardConverters();
}

protected static void addStandardConverters() {
addConverter(void.class, new VoidConverter());

addConverter(String.class, new StringConverter());
addConverter(Date.class, new DateConverter());

addConverter(Double.class, new DoubleConverter());
addConverter(double.class, new PrimitiveDoubleConverter());

addConverter(Integer.class, new IntConverter());
addConverter(int.class, new PrimitiveIntConverter());

addConverter(Character.class, new CharConverter());
addConverter(char.class, new PrimitiveCharConverter());

addConverter(Boolean.class, new BooleanConverter());
addConverter(boolean.class, new PrimitiveBooleanConverter());
}

public static <T> Converter<T> getConverterForClass(Class<? extends T> clazz) {
Converter<T> converter = (Converter<T>) getConverterForClass(clazz, null);
return converter;
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T> Converter<T> getConverterForClass(Class<? extends T> clazz, ParameterizedType typedClazz) {

//use converter set in registry
if (converters.containsKey(clazz)) {
return (Converter<T>) converters.get(clazz);
}

//use property editor
PropertyEditor pe = PropertyEditorManager.findEditor(clazz);
if (pe != null && !"EnumEditor".equals(pe.getClass().getSimpleName())) {
// com.sun.beans.EnumEditor and sun.beans.EnumEditor seem to be used in different usages.
return new PropertyEditorConverter<T>(pe);
}

//for enum, use generic enum converter
if (clazz.isEnum()) {
return new GenericEnumConverter(clazz);
}

//for array, use generic array converter
if (clazz.isArray()) {
Class<?> componentType = clazz.getComponentType();
Converter<?> converterForClass = getConverterForClassOrStringConverter(componentType);
return new GenericArrayConverter(componentType, converterForClass);
}

//for collection, use generic collection converter
if (Collection.class.isAssignableFrom(clazz)) {
Class<?> componentType = typedClazz != null ? (Class<?>) typedClazz.getActualTypeArguments()[0] : String.class;
Converter<?> converterForClass = getConverterForClassOrStringConverter(componentType);
return new GenericCollectionConverter(clazz, converterForClass);
}

return null;
}

public static <T> void addConverter(Class<? extends T> clazz, Converter<T> converter) {
converters.put(clazz, converter);
}

public static Map<Class<?>, Converter<?>> getConverters() {
return Collections.unmodifiableMap(converters);
}

/*
* PRIVATE
*/
public static Converter<?> getConverterForClassOrStringConverter(Class<?> clazz) {
Converter<?> converter = getConverterForClass(clazz);
if (converter == null) {
converter = getConverterForClass(String.class);
}
return converter;
}
}