/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2014-2015 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.jersey.tests.performance.tools;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.logging.Logger;
/**
* Abstract class for test data generation.
*
* <p>Creates the pattern for different generation strategies.
* Contains the (limited) logic for class graph walk-through.<p/>
* <p>Every field which should be populated must be annotated by
* {@link org.glassfish.jersey.tests.performance.tools.GenerateForTest}</p>
*
* @author Adam Lindenthal (adam.lindenthal at oracle.com)
*/
public abstract class TestValueGenerator {
private static final int MAX_RECURSION_LEVEL = 5;
private static final Logger log = Logger.getLogger(TestValueGenerator.class.getName());
/** returns testing data int value */
public abstract int getInt();
/** returns testing data char value */
public abstract char getChar();
/** returns testing data String value */
public abstract String getString();
/** returns testing data long value */
public abstract long getLong();
/** returns testing data float value */
public abstract float getFloat();
/** returns testing data double value */
public abstract double getDouble();
/** returns testing data byte value */
public abstract byte getByte();
/** returns testing data short value */
public abstract short getShort();
/** returns testing data boolean value */
public abstract boolean getBoolean();
/** returns testing data enum value */
public abstract <T> T getEnum(Class<T> enumType);
protected Object handlePrimitivesAndWrappers(Class<?> type) {
if (type.isAssignableFrom(String.class)) {
return getString();
}
if (type.isAssignableFrom(Integer.class) || type.isAssignableFrom(int.class)) {
return getInt();
}
if (type.isAssignableFrom(Character.class) || type.isAssignableFrom(char.class)) {
return getChar();
}
if (type.isAssignableFrom(Float.class) || type.isAssignableFrom(float.class)) {
return getFloat();
}
if (type.isAssignableFrom(Long.class) || type.isAssignableFrom(long.class)) {
return getLong();
}
if (type.isAssignableFrom(Double.class) || type.isAssignableFrom(double.class)) {
return getDouble();
}
if (type.isAssignableFrom(Byte.class) || type.isAssignableFrom(byte.class)) {
return getByte();
}
if (type.isAssignableFrom(Short.class) || type.isAssignableFrom(short.class)) {
return getShort();
}
if (type.isAssignableFrom(Boolean.class) || type.isAssignableFrom(boolean.class)) {
return getBoolean();
}
return null;
}
protected Object handleCollections(Class<?> type,
GenerateForTest annotation,
int recursionLevel) throws ReflectiveOperationException {
int testDataLength = annotation.length();
Class<?> collectionMemberType = annotation.collectionMemberType();
Class<?> collectionType = type;
if (collectionType.isInterface()) {
collectionType = annotation.implementingClass();
if (collectionType.equals(Object.class)) {
throw new IllegalArgumentException("Unable to instantiate collection - interface was used for the "
+ "declaration and parameter 'implementingClass' not set.");
}
}
Object collection = collectionType.newInstance();
for (int i = 0; i < testDataLength; i++) {
// recursively resolve value for collection members
Object o = getValueForType(collectionMemberType, null, recursionLevel + 1);
// and add it to the collection instance
Method addMethod = type.getDeclaredMethod("add", Object.class);
addMethod.invoke(collection, o);
}
return collection;
}
protected Object handleArrays(Class<?> type, GenerateForTest annotation, int recursionLevel)
throws ReflectiveOperationException {
int testDataLength = annotation.length();
Class<?> arrayMemberType = type.getComponentType();
Object array = Array.newInstance(arrayMemberType, testDataLength);
for (int i = 0; i < testDataLength; i++) {
Object o = getValueForType(arrayMemberType, null, recursionLevel + 1);
Array.set(array, i, o);
}
return array;
}
public Object getValueForType(Class<?> type, GenerateForTest annotation) throws ReflectiveOperationException {
return getValueForType(type, annotation, 0);
}
protected Object getValueForType(Class<?> type, GenerateForTest annotation, int recursionLevel)
throws ReflectiveOperationException {
// handle primitives and wrapper classes
Object primitiveOrWrapper = handlePrimitivesAndWrappers(type);
if (primitiveOrWrapper != null) {
return primitiveOrWrapper;
}
// Handle collections
if (Collection.class.isAssignableFrom(type)) {
if (annotation != null) {
return handleCollections(type, annotation, recursionLevel);
} else {
return null;
}
}
// handle maps (unsupported)
if (Map.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Maps are not supported.");
}
// handle enums
if (type.isEnum()) {
return getEnum(type);
}
// handle arrays
if (type.isArray()) {
if (annotation != null) {
return handleArrays(type, annotation, recursionLevel);
} else {
return null;
}
}
// after selecting-out "all" the other possibilities, we are probably handling a custom inner-bean
// create the inner type
if (recursionLevel == MAX_RECURSION_LEVEL) {
log.fine("Maximum recursion level (" + recursionLevel + ") reached. Ignoring the field.");
return null;
}
Object innerBean = type.newInstance();
Field[] fields = type.getDeclaredFields();
for (Field field : fields) {
GenerateForTest subFieldAnnotation = field.getAnnotation(GenerateForTest.class);
if (subFieldAnnotation != null) {
field.setAccessible(true);
// recursively gather the values for the containing fields and set it
field.set(innerBean, getValueForType(field.getType(), subFieldAnnotation, recursionLevel + 1));
}
}
return innerBean;
}
}