/*
*
* * Copyright 2010, Unitils.org
* *
* * 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 org.unitils.mock.report.impl;
import org.unitils.core.util.ObjectFormatter;
import org.unitils.mock.core.proxy.ProxyInvocation;
import java.lang.reflect.Field;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.apache.commons.lang.StringUtils.rightPad;
import static org.apache.commons.lang.StringUtils.uncapitalize;
import static org.unitils.util.ReflectionUtils.getAllFields;
import static org.unitils.util.ReflectionUtils.getFieldValue;
/**
* A base class for reports about proxy invocations.
*
* @author Tim Ducheyne
* @author Filip Neven
* @author Kenny Claes
*/
public abstract class ProxyInvocationsReport {
/**
* The maximum depth (child objects) of objects to display in the reports.
*/
public static int OBJECT_FORMATTER_MAX_RECURSION_DEPT = 3;
/**
* The maximum nr of elements for arrays and collections to display in the reports
*/
public static int OBJECT_FORMATTER_MAX_NR_ARRAY_OR_COLLECTION_ELEMENTS = 15;
/**
* The maximum length of an inline value
*/
public static int MAX_INLINE_PARAMETER_LENGTH = 20;
protected ObjectFormatter objectFormatter = new ObjectFormatter(OBJECT_FORMATTER_MAX_RECURSION_DEPT, OBJECT_FORMATTER_MAX_NR_ARRAY_OR_COLLECTION_ELEMENTS);
protected Map<Object, String> testedObjectFieldValuesAndNames;
public ProxyInvocationsReport(Object testedObject) {
testedObjectFieldValuesAndNames = getFieldValuesAndNames(testedObject);
}
/**
* Creates a string representation of the details of the given invocation. This will give information about
* where the invocation occurred.
*
* @param proxyInvocation The invocation to format, not null
* @return The string representation, not null
*/
protected String formatInvokedAt(ProxyInvocation proxyInvocation) {
return " ..... at " + proxyInvocation.getInvokedAt();
}
/**
* Formats the given value. If the value is small enough (lenght <=20), the value itself is returned, else
* a name is returned generated by the {@link #createLargeValueName} method.
* <p/>
* E.g. string1, myClass1
*
* @param valueAtInvocationTime The value to format, not null
* @param value The value to format by reference, not null
* @param type The type of the large value, not null
* @param currentLargeObjects The current the large values, not null
* @param allLargeObjects All large values per value, not null
* @param largeObjectNameIndexes The current indexes to use for the large value names (per value type), not null
* @return The value or the replaced name, not null
*/
protected String formatValue(Object valueAtInvocationTime, Object value, Class<?> type, List<FormattedObject> currentLargeObjects, Map<Object, FormattedObject> allLargeObjects, Map<Class<?>, Integer> largeObjectNameIndexes) {
if (allLargeObjects.containsKey(valueAtInvocationTime)) {
FormattedObject formattedObject = allLargeObjects.get(valueAtInvocationTime);
currentLargeObjects.add(formattedObject);
return formattedObject.getName();
}
String objectRepresentation = formatObject(valueAtInvocationTime);
String valueName = testedObjectFieldValuesAndNames.get(value);
if (valueName == null) {
if (objectRepresentation.length() <= MAX_INLINE_PARAMETER_LENGTH) {
// The object representation is small enough to be shown inline
return objectRepresentation;
}
// The object representation is to large to be shown inline. Generate a name for it, which can be shown as a replacement.
if (allLargeObjects.containsKey(value)) {
// reuse the same value name for the same instance
FormattedObject formattedObject = allLargeObjects.get(value);
valueName = formattedObject.getName();
} else {
valueName = createLargeValueName(type, largeObjectNameIndexes);
}
}
FormattedObject formattedObject = new FormattedObject(valueName, objectRepresentation);
allLargeObjects.put(valueAtInvocationTime, formattedObject);
allLargeObjects.put(value, formattedObject);
currentLargeObjects.add(formattedObject);
return valueName;
}
/**
* Creates a name to replace a large value.
* The name is derived from the given type and index. E.g. string1, myClass1
*
* @param type The type of the large value, not null
* @param largeObjectNameIndexes The current indexes per type, not null
* @return The name, not null
*/
protected String createLargeValueName(Class<?> type, Map<Class<?>, Integer> largeObjectNameIndexes) {
Integer index = largeObjectNameIndexes.get(type);
if (index == null) {
index = 0;
}
largeObjectNameIndexes.put(type, ++index);
String result = uncapitalize(type.getSimpleName());
return result + index;
}
/**
* Formats the invocation number, and adds spaces to make sure everything is formatted
* nicely on the same line width.
*
* @param invocationIndex The index of the invcation
* @param totalInvocationNumber The total number of invocations.
* @return The formatted invocation number
*/
protected String formatInvocationIndex(int invocationIndex, int totalInvocationNumber) {
int padSize = String.valueOf(totalInvocationNumber).length() + 2;
return rightPad(invocationIndex + ".", padSize);
}
/**
* @param object The object
* @return A string representation of the object, not null
*/
protected String formatObject(Object object) {
return objectFormatter.format(object);
}
/**
* Gets all the field values in the given test object with their corresponding field names.
*
* @param testedObject The test object
* @return The values and names in an identity map, empty if tested object is null
*/
protected Map<Object, String> getFieldValuesAndNames(Object testedObject) {
Map<Object, String> result = new IdentityHashMap<Object, String>();
if (testedObject == null) {
return result;
}
Set<Field> fields = getAllFields(testedObject.getClass());
for (Field field : fields) {
Object value = getFieldValue(testedObject, field);
if (value != null) {
result.put(value, field.getName());
}
}
return result;
}
/**
* Class for representing a value that was too large to be displayed inline.
*/
protected static class FormattedObject {
/* The name used as inline replacement */
private String name;
/* The actual string representation of the value */
private String representation;
/**
* Creates a large value
*
* @param name The name used as inline replacement, not null
* @param representation The actual string representation of the value, not null
*/
public FormattedObject(String name, String representation) {
this.name = name;
this.representation = representation;
}
/**
* @return The name used as inline replacement, not null
*/
public String getName() {
return name;
}
/**
* @return The actual string representation of the value, not null
*/
public String getRepresentation() {
return representation;
}
}
}