TestValueGenerator.java

/*
 * Copyright (c) 2014, 2019 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

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
 */
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;
    }

}