org.hellojavaer.testcase.generator.TestCaseGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.hellojavaer.testcase.generator.TestCaseGenerator.java

Source

/*
 * Copyright 2015-2016 the original author or authors.
 *
 * 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.hellojavaer.testcase.generator;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.Stack;

import org.springframework.beans.BeanUtils;

/**
 * 
 * @author <a href="mailto:hellojavaer@gmail.com">zoukaiming</a>
 */
public class TestCaseGenerator {

    private static long RANDOM_SEED = 175934207561840L;
    private static Date START_TIME;
    private static Character[] CHAR_RANGE = null;
    private static Character[] STRING_RANGE = null;

    static {
        List<Character> charRange = new ArrayList<Character>();
        for (int i = 0; i < 10; i++) {
            charRange.add((char) (i + '0'));
        }
        for (int i = 0; i < 26; i++) {
            charRange.add((char) (i + 'A'));
        }
        for (int i = 0; i < 26; i++) {
            charRange.add((char) (i + 'a'));
        }
        CHAR_RANGE = charRange.toArray(new Character[charRange.size()]);

        List<Character> stringRange = new ArrayList<Character>();
        for (int i = 0; i < 10; i++) {
            stringRange.add((char) (i + '0'));
        }
        for (int i = 0; i < 26; i++) {
            stringRange.add((char) (i + 'A'));
        }
        STRING_RANGE = stringRange.toArray(new Character[stringRange.size()]);

        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        try {
            START_TIME = df.parse("2015-01-01 00:00:00:000");
        } catch (ParseException e) {
            System.err.print(e.getMessage());
        }

        Random random = new Random(RANDOM_SEED);
        List<Character> list = Arrays.asList(STRING_RANGE);
        Collections.shuffle(list, random);
        list.toArray(STRING_RANGE);
    }

    public static <T> List<T> generate(Class<T> clazz, String[] uniqueFields, int count) {
        return generate(clazz, uniqueFields, count, null, 0);
    }

    public static <T> List<T> generate(Class<T> clazz, String[] uniqueFields, int count, String[] excludeFields) {
        return generate(clazz, uniqueFields, count, excludeFields, 0);
    }

    @SuppressWarnings("rawtypes")
    public static <T> List<T> generate(Class<T> clazz, String[] uniqueFields, int count, String[] excludeFields,
            int recursiveCycleLimit) {
        List<String> excludeFieldList = null;
        if (excludeFields != null && excludeFields.length > 0) {
            excludeFieldList = Arrays.asList(excludeFields);
        }
        if (recursiveCycleLimit <= 0) {
            recursiveCycleLimit = 0;
        }
        checkBeanValidity(clazz, excludeFieldList, recursiveCycleLimit);

        List<T> list = new ArrayList<T>(count);
        ControlParam cp = new ControlParam();
        cp.setExcludeFieldList(excludeFieldList);
        cp.setNumIndex(1);
        cp.setRandom(new Random(RANDOM_SEED));
        cp.setStrIndex(100000);
        cp.setStrStep(1111L);
        cp.setCharIndex(0);
        cp.setRecursiveCycleLimit(recursiveCycleLimit);
        Calendar time = Calendar.getInstance();
        time.setTime(START_TIME);
        cp.setTime(time);
        if (uniqueFields != null && uniqueFields.length > 0) {
            cp.setUniqueFieldList(Arrays.asList(uniqueFields));
        }

        for (int i = 0; i < count; i++) {
            Stack<Class> stack = new Stack<Class>();
            stack.push(clazz);
            list.add(produceBean(clazz, cp, stack));
        }
        return list;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private static <T> void checkBeanValidity(Class<T> clazz, List<String> excludeFieldList,
            int recursiveCycleLimit) {
        PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(clazz);
        boolean validBean = false;
        for (PropertyDescriptor pd : pds) {
            if (pd.getWriteMethod() != null && pd.getReadMethod() != null && //
                    (excludeFieldList != null && !excludeFieldList.contains(pd.getName())
                            || excludeFieldList == null) //
            ) {
                validBean = true;
                // just set write ,read for user control
                if (!Modifier.isPublic(pd.getWriteMethod().getDeclaringClass().getModifiers())) {
                    pd.getWriteMethod().setAccessible(true);
                }

                Class fieldClazz = pd.getPropertyType();
                if (!TypeUtil.isBaseType(fieldClazz)) {
                    try {
                        // ????
                        if (recursiveCycleLimit == 0 && fieldClazz == clazz) {
                            throw new RuntimeException("recursive cycle limit is 0! field[" + pd.getName()
                                    + "] may cause recursive, please add this field[" + pd.getName()
                                    + "] to exclude list or set recursiveCycleLimit more than 0 .");
                        } else if (!fieldClazz.isAssignableFrom(clazz)) {
                            checkBeanValidity(fieldClazz, null, 999999999);
                        }
                    } catch (Exception e) {
                        throw new RuntimeException("Unknown Class " + fieldClazz.getName() + " for field "
                                + pd.getName() + " ! please add this field[" + pd.getName() + "] to exclude list.",
                                e);
                    }
                }
            }
        }
        if (!validBean) {
            throw new RuntimeException(
                    "Invalid Bean Class[" + clazz.getName() + "], for it has not getter setter methods!");
        }
    }

    private static class ControlParam {

        private long numIndex;
        private Random random;
        private long strIndex;
        private long strStep;
        private int charIndex;
        private Calendar time;
        private List<String> excludeFieldList;
        private int recursiveCycleLimit;

        private List<String> uniqueFieldList;

        public long getNumIndex() {
            return numIndex;
        }

        public void setNumIndex(long numIndex) {
            this.numIndex = numIndex;
        }

        public Random getRandom() {
            return random;
        }

        public void setRandom(Random random) {
            this.random = random;
        }

        public long getStrIndex() {
            return strIndex;
        }

        public void setStrIndex(long strIndex) {
            this.strIndex = strIndex;
        }

        public int getCharIndex() {
            return charIndex;
        }

        public void setCharIndex(int charIndex) {
            this.charIndex = charIndex;
        }

        public Calendar getTime() {
            return time;
        }

        public void setTime(Calendar time) {
            this.time = time;
        }

        public List<String> getExcludeFieldList() {
            return excludeFieldList;
        }

        public void setExcludeFieldList(List<String> excludeFieldList) {
            this.excludeFieldList = excludeFieldList;
        }

        public int getRecursiveCycleLimit() {
            return recursiveCycleLimit;
        }

        public void setRecursiveCycleLimit(int recursiveCycleLimit) {
            this.recursiveCycleLimit = recursiveCycleLimit;
        }

        public List<String> getUniqueFieldList() {
            return uniqueFieldList;
        }

        public void setUniqueFieldList(List<String> uniqueFieldList) {
            this.uniqueFieldList = uniqueFieldList;
        }

        public long getStrStep() {
            return strStep;
        }

        public void setStrStep(long strStep) {
            this.strStep = strStep;
        }

    }

    @SuppressWarnings("rawtypes")
    private static class TypeUtil {

        private static final Set<Class> baseTypeCache = new HashSet<Class>();
        private static final Set<Class> numTypeCache = new HashSet<Class>();
        static {
            baseTypeCache.add(byte.class);
            baseTypeCache.add(short.class);
            baseTypeCache.add(int.class);
            baseTypeCache.add(long.class);
            baseTypeCache.add(float.class);
            baseTypeCache.add(double.class);
            baseTypeCache.add(char.class);
            baseTypeCache.add(boolean.class);

            baseTypeCache.add(Byte.class);
            baseTypeCache.add(Short.class);
            baseTypeCache.add(Integer.class);
            baseTypeCache.add(Long.class);
            baseTypeCache.add(Float.class);
            baseTypeCache.add(Double.class);
            baseTypeCache.add(Character.class);
            baseTypeCache.add(Boolean.class);

            baseTypeCache.add(String.class);
            baseTypeCache.add(Date.class);

            numTypeCache.add(byte.class);
            numTypeCache.add(short.class);
            numTypeCache.add(int.class);
            numTypeCache.add(long.class);
            numTypeCache.add(float.class);
            numTypeCache.add(double.class);

            numTypeCache.add(Byte.class);
            numTypeCache.add(Short.class);
            numTypeCache.add(Integer.class);
            numTypeCache.add(Long.class);
            numTypeCache.add(Float.class);
            numTypeCache.add(Double.class);
        }

        public static boolean isBaseType(Class clazz) {
            // if (fieldClazz.isPrimitive() // 8 ??
            // || fieldClazz == Byte.class || fieldClazz == Short.class
            // || fieldClazz == Integer.class
            // || fieldClazz == Long.class || fieldClazz == Float.class
            // || fieldClazz == Double.class
            // || fieldClazz == Boolean.class || fieldClazz == Character.class// 8 ??
            // || fieldClazz == Date.class || fieldClazz == String.class // 
            // || fieldClazz.isEnum() // 
            // )
            return clazz.isEnum() || baseTypeCache.contains(clazz);
        }

        public static boolean isNumberType(Class clazz) {
            return numTypeCache.contains(clazz);
        }
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private static <T> T produceBean(Class<T> clazz, ControlParam countrolParam, Stack<Class> parseClassList) {
        try {
            T item = clazz.newInstance();
            for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(clazz)) {
                Method writeMethod = pd.getWriteMethod();
                if (writeMethod == null || pd.getReadMethod() == null || //
                        countrolParam.getExcludeFieldList() != null
                                && countrolParam.getExcludeFieldList().contains(pd.getName())//
                ) {//
                    continue;
                }
                Class fieldClazz = pd.getPropertyType();
                Long numIndex = countrolParam.getNumIndex();
                // int enumIndex = countrolParam.getEnumIndex();
                Random random = countrolParam.getRandom();
                long strIndex = countrolParam.getStrIndex();
                int charIndex = countrolParam.getCharIndex();
                Calendar time = countrolParam.getTime();
                if (TypeUtil.isBaseType(fieldClazz)) {
                    if (TypeUtil.isNumberType(fieldClazz)) {
                        if (fieldClazz == Byte.class) {
                            writeMethod.invoke(item, Byte.valueOf((byte) (numIndex & 0x7F)));
                        } else if (fieldClazz == Short.class) {
                            writeMethod.invoke(item, Short.valueOf((short) (numIndex & 0x7FFF)));
                        } else if (fieldClazz == Integer.class) {
                            writeMethod.invoke(item, Integer.valueOf((int) (numIndex & 0x7FFFFFFF)));
                        } else if (fieldClazz == Long.class) {
                            writeMethod.invoke(item, Long.valueOf((long) numIndex));
                        } else if (fieldClazz == Float.class) {
                            writeMethod.invoke(item, Float.valueOf((float) numIndex));
                        } else if (fieldClazz == Double.class) {
                            writeMethod.invoke(item, Double.valueOf((double) numIndex));
                        } else if (fieldClazz == byte.class) {//
                            writeMethod.invoke(item, (byte) (numIndex & 0x7F));
                        } else if (fieldClazz == short.class) {
                            writeMethod.invoke(item, (short) (numIndex & 0x7FFF));
                        } else if (fieldClazz == int.class) {
                            writeMethod.invoke(item, (int) (numIndex & 0x7FFFFFFF));
                        } else if (fieldClazz == long.class) {
                            writeMethod.invoke(item, (long) numIndex);
                        } else if (fieldClazz == float.class) {
                            writeMethod.invoke(item, (float) numIndex);
                        } else if (fieldClazz == double.class) {
                            writeMethod.invoke(item, (double) numIndex);
                        }
                        numIndex++;
                        if (numIndex < 0) {
                            numIndex &= 0x7FFFFFFFFFFFFFFFL;
                        }
                        countrolParam.setNumIndex(numIndex);
                    } else if (fieldClazz == boolean.class) {
                        writeMethod.invoke(item, random.nextBoolean());
                    } else if (fieldClazz == Boolean.class) {
                        writeMethod.invoke(item, Boolean.valueOf(random.nextBoolean()));
                    } else if (fieldClazz == char.class) {
                        writeMethod.invoke(item, CHAR_RANGE[charIndex]);
                        charIndex++;
                        if (charIndex >= CHAR_RANGE.length) {
                            charIndex = 0;
                        }
                        countrolParam.setCharIndex(charIndex);
                    } else if (fieldClazz == Character.class) {
                        writeMethod.invoke(item, Character.valueOf(CHAR_RANGE[charIndex]));
                        charIndex++;
                        if (charIndex >= CHAR_RANGE.length) {
                            charIndex = 0;
                        }
                        countrolParam.setCharIndex(charIndex);
                    } else if (fieldClazz == String.class) {
                        if (countrolParam.getUniqueFieldList() != null
                                && countrolParam.getUniqueFieldList().contains(pd.getName())) {
                            StringBuilder sb = new StringBuilder();
                            convertNum(strIndex, STRING_RANGE, countrolParam.getRandom(), sb);
                            writeMethod.invoke(item, sb.toString());
                            strIndex += countrolParam.getStrStep();
                            if (strIndex < 0) {
                                strIndex &= 0x7FFFFFFFFFFFFFFFL;
                            }
                            countrolParam.setStrIndex(strIndex);
                        } else {
                            writeMethod.invoke(item, String.valueOf(CHAR_RANGE[charIndex]));
                            charIndex++;
                            if (charIndex >= CHAR_RANGE.length) {
                                charIndex = 0;
                            }
                            countrolParam.setCharIndex(charIndex);
                        }

                    } else if (fieldClazz == Date.class) {
                        writeMethod.invoke(item, time.getTime());
                        time.add(Calendar.DAY_OF_YEAR, 1);
                    } else if (fieldClazz.isEnum()) {
                        int index = random.nextInt(fieldClazz.getEnumConstants().length);
                        writeMethod.invoke(item, fieldClazz.getEnumConstants()[index]);
                    } else {
                        //
                        throw new RuntimeException("out of countrol Class " + fieldClazz.getName());
                    }
                } else {
                    parseClassList.push(fieldClazz);
                    // TODO ?
                    Set<Class> set = new HashSet<Class>(parseClassList);
                    if (parseClassList.size() - set.size() <= countrolParam.getRecursiveCycleLimit()) {
                        Object bean = produceBean(fieldClazz, countrolParam, parseClassList);
                        writeMethod.invoke(item, bean);
                    }
                    parseClassList.pop();
                }
            }
            return item;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static void convertNum(long num, Character[] baseChar, Random random, StringBuilder result) {
        if (num <= 0 && result.length() == 0) {
            result.insert(0, baseChar[0]);
        } else if (num > 0) {
            int i = (int) (num % baseChar.length);
            Character ch = random.nextBoolean() ? baseChar[i] : Character.toLowerCase(baseChar[i]);
            result.insert(0, ch);
            convertNum(num / baseChar.length, baseChar, random, result);
        }
    }
}