Skip to content

Commit

Permalink
refactor argument formatter for general use (#101)
Browse files Browse the repository at this point in the history
  • Loading branch information
aaschmid committed Nov 4, 2017
1 parent 13a253d commit b6117fc
Show file tree
Hide file tree
Showing 20 changed files with 363 additions and 235 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.tngtech.junit.dataprovider.placeholder;

import java.lang.annotation.Annotation;
import java.util.Arrays;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import com.tngtech.junit.dataprovider.placeholder.argformat.ArgumentFormat;
import com.tngtech.junit.dataprovider.placeholder.argformat.ArgumentFormatter;
import com.tngtech.junit.dataprovider.placeholder.argformat.DefaultFormatter;

/**
* This abstract placeholder is able to format arguments of a dataprovider test as comma-separated {@link String}
Expand Down Expand Up @@ -30,118 +33,107 @@
* </tr>
* </table>
*/
public abstract class AbstractArgumentPlaceholder extends BasePlaceholder {
abstract class AbstractArgumentPlaceholder extends BasePlaceholder {

/**
* {@link String} representation of {@code null}
*/
protected static final String STRING_NULL = "<null>";
protected static class ParameterAndArgument {

/**
* {@link String} representation of {@code ""}
*/
protected static final String STRING_EMPTY = "<empty string>";
private final Class<?> parameterType;
private final Annotation[] parameterAnnotations;
private final Object argument;

/**
* {@link String} representation of an non-printable character
*/
protected static final String STRING_NON_PRINTABLE = "<np>";

protected AbstractArgumentPlaceholder(String placeholderRegex) {
super(placeholderRegex);
}
public ParameterAndArgument(Class<?> parameterType, Annotation[] parameterAnnotations, Object argument) {
this.parameterType = parameterType;
this.parameterAnnotations = parameterAnnotations;
this.argument = argument;
}

@SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "false positive if 'param.toString()' returns 'null'")
protected String format(Object param) {
if (param == null) {
return STRING_NULL;
public Class<?> getParameterType() {
return parameterType;
}

} else if (param.getClass().isArray()) {
if (param.getClass().getComponentType().isPrimitive()) {
return formatValuesOfPrimitiveArray(param);
}
return "[" + formatValuesOfArray((Object[]) param) + "]";
public Annotation[] getParameterAnnotations() {
return parameterAnnotations;
}

} else if (param instanceof String && ((String) param).isEmpty()) {
return STRING_EMPTY;
public Object getArgument() {
return argument;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((argument == null) ? 0 : argument.hashCode());
result = prime * result + Arrays.hashCode(parameterAnnotations);
result = prime * result + ((parameterType == null) ? 0 : parameterType.hashCode());
return result;
}

String result;
if (param instanceof String) {
result = (String) param;
} else {
result = param.toString();
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ParameterAndArgument other = (ParameterAndArgument) obj;
if (argument == null) {
if (other.argument != null) {
return false;
}
} else if (!argument.equals(other.argument)) {
return false;
}
if (!Arrays.equals(parameterAnnotations, other.parameterAnnotations)) {
return false;
}
if (parameterType == null) {
if (other.parameterType != null) {
return false;
}
} else if (!parameterType.equals(other.parameterType)) {
return false;
}
return true;
}
if (result == null) { // maybe null if "param.toString()" returns null
return STRING_NULL;

@Override
public String toString() {
return "ParameterAndArgument [parameterType=" + parameterType + ", parameterAnnotations="
+ Arrays.toString(parameterAnnotations) + ", argument=" + argument + "]";
}
result = result.replaceAll("\0", "\\\\0").replaceAll("\r", "\\\\r").replaceAll("\n", "\\\\n");
return replaceNonPrintableChars(result, STRING_NON_PRINTABLE);
}

private String formatValuesOfPrimitiveArray(Object primitiveArray) {
Class<?> componentType = primitiveArray.getClass().getComponentType();

if (boolean.class.equals(componentType)) {
return Arrays.toString((boolean[]) primitiveArray);

} else if (byte.class.equals(componentType)) {
return Arrays.toString((byte[]) primitiveArray);

} else if (char.class.equals(componentType)) {
return Arrays.toString((char[]) primitiveArray);

} else if (short.class.equals(componentType)) {
return Arrays.toString((short[]) primitiveArray);

} else if (int.class.equals(componentType)) {
return Arrays.toString((int[]) primitiveArray);

} else if (long.class.equals(componentType)) {
return Arrays.toString((long[]) primitiveArray);

} else if (float.class.equals(componentType)) {
return Arrays.toString((float[]) primitiveArray);

} else if (double.class.equals(componentType)) {
return Arrays.toString((double[]) primitiveArray);
}
throw new IllegalStateException("Called 'formatPrimitiveArray' on non-primitive array");
AbstractArgumentPlaceholder(String placeholderRegex) {
super(placeholderRegex);
}

private String formatValuesOfArray(Object[] array) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < array.length; i++) {
stringBuilder.append(format(array[i]));
if (i < array.length - 1) {
stringBuilder.append(", ");
protected String format(ParameterAndArgument parameterAndArgument) {
Annotation[] parameterAnnotations = parameterAndArgument.getParameterAnnotations();
Object argument = parameterAndArgument.getArgument();

Class<? extends ArgumentFormatter<?>> formatter = (Class) DefaultFormatter.class;
for (Annotation annotation : parameterAnnotations) {
if (ArgumentFormat.class.isInstance(annotation)) { // TODO meta annotation
formatter = ((ArgumentFormat) annotation).value();
break;
}
}
return stringBuilder.toString();
}

private String replaceNonPrintableChars(String input, String replacement) {
StringBuilder result = new StringBuilder();
for (int offset = 0; offset < input.length();) {
int codePoint = input.codePointAt(offset);
offset += Character.charCount(codePoint);

// Replace invisible control characters and unused code points
switch (Character.getType(codePoint)) {
case Character.CONTROL: // \p{Cc}
case Character.FORMAT: // \p{Cf}
case Character.PRIVATE_USE: // \p{Co}
case Character.SURROGATE: // \p{Cs}
case Character.UNASSIGNED: // \p{Cn}
result.append(replacement);
break;

default:
result.append(Character.toChars(codePoint));
break;
}
try {
return ((Class<ArgumentFormatter<Object>>) formatter).newInstance().format(argument);
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();

} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result.toString();
return null; // TODO
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.tngtech.junit.dataprovider.placeholder;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class AnnotationBasedArgumentPlaceholder extends AbstractArgumentPlaceholder {

public AnnotationBasedArgumentPlaceholder() {
super("%aa\\[(-?[0-9]+|-?[0-9]+\\.\\.-?[0-9]+)\\]");
}

@Override
protected String getReplacementFor(String placeholder, ReplacementData data) {
String subscript = placeholder.substring(4, placeholder.length() - 1);

int from = Integer.MAX_VALUE;
int to = Integer.MIN_VALUE;
if (subscript.contains("..")) {
String[] split = subscript.split("\\.\\.");

from = Integer.parseInt(split[0]);
to = Integer.parseInt(split[1]);
} else {
from = Integer.parseInt(subscript);
to = from;
}

List<Object> arguments = data.getArguments();
from = (from >= 0) ? from : arguments.size() + from;
to = (to >= 0) ? to + 1 : arguments.size() + to + 1;
return formatAll(getParametersAndArguments(data.getTestMethod(), arguments, from, to));
}

private List<ParameterAndArgument> getParametersAndArguments(Method testMethod, List<Object> arguments, int from, int to) {
Annotation[][] parameterAnnotations = testMethod.getParameterAnnotations();
Class<?>[] parameterTypes = testMethod.getParameterTypes();

List<ParameterAndArgument> result = new ArrayList<ParameterAndArgument>();
for (int idx = 0; idx < arguments.size() && idx < parameterTypes.length; idx++) { // TODO test!
result.add(new ParameterAndArgument(parameterTypes[idx], parameterAnnotations[idx], arguments.get(idx)));
}
return result;
}

/**
* Formats the given parameters and arguments to a comma-separated list of {@code $parameterName=$argumentName}.
* Arguments {@link String} representation are therefore treated specially.
*
* @param parameterAndArguments parameter types and annotation as well as arguments to be formatted
* @return the formatted {@link String} of the given parameters and arguments
*/
protected String formatAll(List<ParameterAndArgument> parameterAndArguments) {
StringBuilder stringBuilder = new StringBuilder();
for (int idx = 0; idx < parameterAndArguments.size(); idx++) {
ParameterAndArgument parameterAndArgument = parameterAndArguments.get(idx);

stringBuilder.append(format(parameterAndArgument));

if (idx < parameterAndArguments.size() - 1) {
stringBuilder.append(", ");
}
}
return stringBuilder.toString();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.tngtech.junit.dataprovider.placeholder;

import java.lang.annotation.Annotation;
import java.util.List;

/**
Expand Down Expand Up @@ -45,12 +46,16 @@ protected String getReplacementFor(String placeholder, ReplacementData data) {
*/
protected String formatAll(List<Object> arguments) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < arguments.size(); i++) {
stringBuilder.append(format(arguments.get(i)));
if (i < arguments.size() - 1) {
for (int idx = 0; idx < arguments.size(); idx++) {
stringBuilder.append(format(arguments.get(idx)));
if (idx < arguments.size() - 1) {
stringBuilder.append(", ");
}
}
return stringBuilder.toString();
}

protected String format(Object argument) {
return format(new ParameterAndArgument(null, new Annotation[0], argument));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.tngtech.junit.dataprovider.placeholder.argformat;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotate a test method parameter to provide a custom formatter for it.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.ANNOTATION_TYPE, ElementType.PARAMETER })
public @interface ArgumentFormat {
Class<? extends ArgumentFormatter<?>> value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.tngtech.junit.dataprovider.placeholder.argformat;

/**
* Interface for defining custom argument formatter using the {@link ArgumentFormat} annotation.
*
* @param <T> the type of the object to format
*/
public interface ArgumentFormatter<T> {
String format(T argument);
}
Loading

0 comments on commit b6117fc

Please sign in to comment.