public class RandomData {

    private static class GeneratorWithType<Type> {
        final Class<Type> generatedClass;
        final Generator<Type> generator;

        public GeneratorWithType(Class<Type> generatedClass, Generator<Type> generator) {
            this.generatedClass = generatedClass;
            this.generator = generator;
        }
    }

    private List<GeneratorWithType> generators = new ArrayList<>();
    private Map<Class, OnObjectGeneratedListener> postListeners = new HashMap<>();
    private Random random = new Random();

    public RandomData() {
        addGenerator(boolean.class, () -> false);
        addGenerator(int.class, () -> 0);
        addGenerator(float.class, () -> 0.0f);
        addGenerator(String.class, () -> "");
        addGenerator(Drawable.class, ShapeDrawable::new);
        addGenerator(Date.class, Date::new);
        addGenerator(List.class, ArrayList::new);
        addGenerator(Map.class, HashMap::new);
        addGenerator(Set.class, HashSet::new);
    }

    /**
     * Add a generator to generate values for fields.
     * The field type doesn't have to exactly match the provided type - it has to be 'assignable'.
     * For example Float generators will work fine with float and Double fields.
     * Generators work for constructor parameters and writable, non-static fields.
     *
     * @param aClass    a type to handle using the provided generator
     * @param generator a generator
     * @param <Type>
     */
    public <Type> void addGenerator(@NonNull Class<Type> aClass, @NonNull Generator<Type> generator) {
        generators.add(0, new GeneratorWithType<>(aClass, generator));
    }

    /**
     * Add a simple generator to generate values for fields.
     * The field type doesn't have to exactly match the provided type - it has to be 'assignable'.
     * For example Float generators will work fine with float and Double fields.
     * Generators work for constructor parameters and writable, non-static fields.
     *
     * @param aClass    a type to handle using the provided generator
     * @param generator a generator
     * @param <Type>
     */
    public <Type> void addGenerator(@NonNull Class<Type> aClass, @NonNull SimpleGenerator<Type> generator) {
        generators.add(0, new GeneratorWithType<>(aClass, new Generator<Type>() {
            @Override
            public Type next(@Nullable DataContext context) {
                return generator.next();
            }
        }));
    }

    private <Type> Type makeInstance(@NonNull Class<Type> aClass, DataContext context) throws InstantiationException {
        try {
            return aClass.newInstance();
        } catch (IllegalAccessException | IllegalArgumentException | InstantiationException e) {
        }
        List<Constructor> constructors = new ArrayList<>(Arrays.asList(aClass.getConstructors()));
        Collections.sort(constructors, (o1, o2) -> o2.getParameterTypes().length - o1.getParameterTypes().length);
        for (Constructor constructor : constructors) {
            Class[] parameterTypes = constructor.getParameterTypes();
            Target[] parameters = new Target[parameterTypes.length];
            Annotation[][] annotations = constructor.getParameterAnnotations();
            for (int i = 0; i < parameterTypes.length; i++)
                parameters[i] = new Target(annotations[i], parameterTypes[i], aClass);
            try {
                Object[] params = new Object[parameters.length];
                for (int i = 0; i < params.length; i++) {
                    Target target = parameters[i];
                    for (GeneratorWithType g : generators) {
                        if (ClassUtils.isAssignable(g.generatedClass, target.getType(), true) && g.generator.match(target)) {
                            params[i] = g.generator.next(context);
                            break;
                        }
                    }
                }
                return (Type) constructor.newInstance(params);
            } catch (IllegalAccessException e) {
            } catch (InvocationTargetException e) {
            }
        }

        throw new InstantiationException("Unable to instantiate an object of type " + aClass.getName());
    }

    /**
     * Generate an object using provided generators and parameter factories. Non-parameterless constructors
     * are supported. RandomData fills all non-final and non-static fields using reflection. Constructors
     * are used in the following order: 0-parameter, data, first longest, other
     *
     * @param aClass a type of an object to generate
     * @param <Type>
     * @return the generated object
     */
    @NonNull
    public <Type> Type generate(@NonNull Class<Type> aClass) {
        return generate(aClass, new DataContext());
    }

    @NonNull
    public <Type> Type generate(@NonNull Class<Type> aClass, DataContext context) {
        Type instance;
        try {
            instance = makeInstance(aClass, context);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        }

        fill(instance, context);
        OnObjectGeneratedListener<Type> listener2 = postListeners.get(aClass);
        if (listener2 != null)
            listener2.onObjectGenerated(instance);
        return instance;
    }

    public <Type> Type[] generateArray(Class<Type> aClass, int size) {
        return generateArray(aClass, size, new DataContext());
    }

    public <Type> Type[] generateArray(Class<Type> aClass, int size, DataContext context) {
        Type[] array = (Type[]) Array.newInstance(aClass, size);
        fillArray(array, context);
        return array;
    }

    public <Type> List<Type> generateList(Class<Type> aClass, int size) {
        return generateList(aClass, size, new DataContext());
    }

    public <Type> List<Type> generateList(Class<Type> aClass, int size, DataContext context) {
        List<Type> list = new ArrayList<>();
        for (int i = 0; i < size; i++)
            list.add(generate(aClass, context));
        return list;
    }

    public void fill(@NonNull Object target) {
        fill(target, new DataContext());
    }

    public void fill(@NonNull Object target, DataContext context) {
        context.save();
        if (target.getClass().isArray()) {
            fillArray((Object[]) target, context);
        } else if (List.class.isAssignableFrom(target.getClass())) {
            fillIterable((List) target, context);
        } else if (!(target instanceof Iterable)) {
            fillObject(target, context);
        }
        context.restore();
    }

    private void fillObject(Object target, DataContext context) {
        Class c = target.getClass();
        for (Field f : c.getDeclaredFields()) {
            f.setAccessible(true);
            if ((f.getModifiers() & Modifier.FINAL) != 0)
                continue;
            if ((f.getModifiers() & Modifier.STATIC) != 0)
                continue;
            if (f.getAnnotation(Ignore.class) == null)
                fillValue(target, f, context);
        }
    }

    private void fillValue(Object obj, Field field, DataContext context) {
        Target target = new Target(field);
        if (field.getType().isArray()) {
            RandomSize annotation = field.getAnnotation(RandomSize.class);
            int size;
            if (annotation != null) {
                size = random.nextInt(annotation.max() - annotation.min()) + annotation.min();
            } else {
                size = random.nextInt(RandomSize.DEFAULT_MAX - RandomSize.DEFAULT_MIN) + RandomSize.DEFAULT_MIN;
            }
            try {
                field.set(obj, generateArray(target.getType().getComponentType(), size, context));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        } else if (target.getType().isAssignableFrom(List.class)) {
            RandomSize annotation = field.getAnnotation(RandomSize.class);
            int size;
            if (annotation != null) {
                size = random.nextInt(annotation.max() - annotation.min()) + annotation.min();
            } else {
                size = random.nextInt(RandomSize.DEFAULT_MAX - RandomSize.DEFAULT_MIN) + RandomSize.DEFAULT_MIN;
            }
            try {
                field.set(obj, generateList(target.getComponentType(), size, context));
            } catch (IllegalAccessException | NullPointerException e) {
                e.printStackTrace();
            }
        } else {
            for (GeneratorWithType g : generators) {
                if (ClassUtils.isAssignable(g.generatedClass, target.getType(), true) && g.generator.match(target)) {
                    try {
                        field.set(obj, g.generator.next(context));
                        return;
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
            if (target.getType().isPrimitive() || target.getType().equals(String.class))
                return;
            try {
                field.set(obj, generate(target.getType(), context));
                return;
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            try {
                Object object = field.get(obj);
                if (object != null)
                    fill(object, context);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    private void fillArray(Object[] target, DataContext context) {
        for (int i = 0; i < target.length; i++) {
            if (target[i] == null) {
                target[i] = generate(target.getClass().getComponentType(), context);
            } else {
                fill(target[i], context);
            }
        }
    }

    private void fillIterable(Iterable target, DataContext context) {
        for (Object o : target)
            fill(o, context);
    }

    public void reset() {
        for (GeneratorWithType g : generators)
            g.generator.reset();
    }

    public <Type> void addOnObjectGeneratedListener(Class<Type> aClass, OnObjectGeneratedListener listener) {
        postListeners.put(aClass, listener);
    }

    public <Type> void removeOnObjectGeneratedListener(Class<Type> aClass) {
        postListeners.remove(aClass);
    }
} public class DateGenerator extends Generator<Date> {
    private Random random = new Random();
    private static final int WEEK = 7 * 24 * 60 * 60 * 1000;
    private long startDate;
    private long endDate;

    public DateGenerator() {
        endDate = new Date().getTime();
        startDate = endDate - WEEK;
    }

    public DateGenerator(Date startDate, Date endDate) {
        this.startDate = Math.min(startDate.getTime(), endDate.getTime());
        this.endDate = Math.max(startDate.getTime(), endDate.getTime());
    }

    @Override
    protected Matcher getDefaultMatcher() {
        return f -> f.getName().equals("date");
    }

    @Override
    public Date next(DataContext context) {
        return new Date(startDate + random.nextInt((int) (endDate - startDate)));
    }
} public class DrawableImageGenerator extends Generator<Drawable> {
    private Context context;
    private Random random = new Random();
    private boolean preventDuplicates;

    private Set<Integer> used = new HashSet<>();

    private int[] images = {
            R.drawable.randomdata_background0, R.drawable.randomdata_background1, R.drawable.randomdata_background2, R.drawable.randomdata_background3, R.drawable.randomdata_background4
    };

    public DrawableImageGenerator(Context context) {
        this.context = context;
        preventDuplicates = true;
    }

    public DrawableImageGenerator(Context context, boolean preventDuplicates) {
        this.context = context;
        this.preventDuplicates = preventDuplicates;
    }

    @Override
    public Drawable next(DataContext dataContext) {
        return context.getResources().getDrawable(next2());
    }

    private int next2() {
        int resId;
        if (preventDuplicates) {
            if (used.size() == images.length)
                used.clear();
            do {
                resId = images[random.nextInt(images.length)];
            }
            while (used.contains(resId));
            used.add(resId);
        } else {
            resId = images[random.nextInt(images.length)];
        }
        return resId;
    }

    @Override
    public void reset() {
        used.clear();
    }
} public class EnumGenerator<Type extends Enum<Type>> extends Generator<Type> {
    private Class<Type> type;
    private Random random = new Random();

    public EnumGenerator(Class<Type> type) {
        this.type = type;
    }

    @Override
    public Type next(@Nullable DataContext context) {
        return type.getEnumConstants()[random.nextInt(type.getEnumConstants().length)];
    }
} public class TextGenerator extends Generator<String> {
    private static String[] loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin nibh augue, suscipit a, scelerisque sed, lacinia in, mi. Cras vel lorem. Etiam pellentesque aliquet tellus. Phasellus pharetra nulla ac diam. Quisque semper justo at risus. Donec venenatis, turpis vel hendrerit interdum, dui ligula ultricies purus, sed posuere libero dui id orci. Nam congue, pede vitae dapibus aliquet, elit magna vulputate arcu, vel tempus metus leo non est. Etiam sit amet lectus quis est congue mollis. Phasellus congue lacus eget neque. Phasellus ornare, ante vitae consectetuer consequat, purus sapien ultricies dolor, et mollis pede metus eget nisi. Praesent sodales velit quis augue. Cras suscipit, urna at aliquam rhoncus, urna quam viverra nisi, in interdum massa nibh nec erat. ".split("\\. ");
    private int sentences;
    private boolean useSequence;
    private int sequence;

    public TextGenerator() {
        sentences = 1;
        useSequence = true;
    }

    public TextGenerator(int sentences, boolean useSequence) {
        this.sentences = sentences;
        this.useSequence = useSequence;
    }

    @Override
    protected Matcher getDefaultMatcher() {
        return f -> f.getName() != null && (f.getName().contains("description") || f.getName().contains("text"));
    }

    @Override
    public String next(DataContext context) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < sentences; i++) {
            builder.append(loremIpsum[(i + sequence) % loremIpsum.length]).append(". ");
            if (i % loremIpsum.length == loremIpsum.length - 1 && sentences > loremIpsum.length)
                builder.append("\n\n");
        }
        if (useSequence)
            sequence += sentences;
        return builder.toString();
    }

    @Override
    public void reset() {
        sequence = 0;
    }
} public class StringAmountGenerator extends AmountGenerator<String> {

    public StringAmountGenerator() {
        super();
    }

    public StringAmountGenerator(int max, boolean useCommon) {
        super(max, useCommon);
    }

    @Override
    protected Matcher getDefaultMatcher() {
        return f -> f.getName() != null && f.getName().contains("amount");
    }

    @Override
    public String next(DataContext context) {
        Currency currency = Currency.getInstance(Locale.getDefault());
        return String.format(Locale.getDefault(), "%s %.2f", currency.getSymbol(), next2(context));
    }
} public class StringCardNumberGenerator extends Generator<String> {
    private static class BIN {
        String name;
        int number;
        int length;

        BIN(String name, int number, int length) {
            this.name = name;
            this.number = number;
            this.length = length;
        }
    }

    private static BIN[] bins = {
            new BIN("MasterCard", 51, 16),
            new BIN("MasterCard", 52, 16),
            new BIN("MasterCard", 53, 16),
            new BIN("MasterCard", 54, 16),
            new BIN("MasterCard", 55, 16),
            new BIN("VISA", 4, 16),
            new BIN("American Express", 34, 15),
            new BIN("American Express", 37, 15)
    };

    private Random random = new Random();

    @Override
    protected Matcher getDefaultMatcher() {
        return f -> f.getName() != null && f.getName().contains("number");
    }

    @Override
    public String next(DataContext context) {
        BIN bin = bins[random.nextInt(bins.length)];
        StringBuilder builder = new StringBuilder();
        builder.append(bin.number);
        while (builder.length() < bin.length - 1) {
            builder.append(random.nextInt(10));
        }
        builder.append(getLuhnDigit(builder.toString()));
        for (int i = 0; i < bin.length; i += 4)
            builder.insert(bin.length - i, " ");
        return builder.toString();
    }

    private int getLuhnDigit(String number) {
        int sum = 0;
        for (int i = 0; i < number.length(); i++) {
            int digit = number.charAt(i) - '0';

            if ((i % 2) == 0) {
                digit = digit * 2;
                if (digit > 9) {
                    digit = (digit / 10) + (digit % 10);
                }
            }
            sum += digit;
        }

        int mod = sum % 10;
        return (mod == 0) ? 0 : 10 - mod;
    }
} public class DrawableAvatarGenerator extends Generator<Drawable> {
    private Context context;
    private Random random = new Random();
    private Gender gender;
    private boolean preventDuplicates;

    private Set<Integer> femaleUsed = new HashSet<>();
    private Set<Integer> maleUsed = new HashSet<>();

    private int[] female = {
            R.drawable.randomdata_woman0, R.drawable.randomdata_woman1, R.drawable.randomdata_woman2, R.drawable.randomdata_woman3, R.drawable.randomdata_woman4, R.drawable.randomdata_woman5, R.drawable.randomdata_woman6, R.drawable.randomdata_woman7, R.drawable.randomdata_woman8, R.drawable.randomdata_woman9
    };

    private int[] male = {
            R.drawable.randomdata_man0, R.drawable.randomdata_man1, R.drawable.randomdata_man2, R.drawable.randomdata_man3, R.drawable.randomdata_man4, R.drawable.randomdata_man5, R.drawable.randomdata_man6, R.drawable.randomdata_man7, R.drawable.randomdata_man8, R.drawable.randomdata_man9
    };

    public DrawableAvatarGenerator(Context context) {
        this.context = context;
        gender = Gender.Both;
        preventDuplicates = true;
    }

    public DrawableAvatarGenerator(Context context, Gender gender, boolean preventDuplicates) {
        this.context = context;
        this.gender = gender;
        this.preventDuplicates = preventDuplicates;
    }

    @Override
    protected Matcher getDefaultMatcher() {
        return f -> f.getName() != null && f.getName().contains("avatar");
    }

    @Override
    public Drawable next(DataContext dataContext) {
        return context.getResources().getDrawable(next2(dataContext));
    }

    private int next2(DataContext dataContext) {
        Gender g = dataContext != null ? dataContext.get(PersonDataContext.GENDER) : null;
        if (g == null && dataContext != null)
            dataContext.set(PersonDataContext.GENDER, g = gender == Gender.Both ? random.nextBoolean() ? Gender.Female : Gender.Male : gender == Gender.Female ? Gender.Female : Gender.Male);
        int resId;
        if (preventDuplicates) {
            if (g == Gender.Male && maleUsed.size() == male.length)
                maleUsed.clear();
            if (g == Gender.Female && femaleUsed.size() == female.length)
                femaleUsed.clear();
            do {
                resId = next3(g);
            }
            while (g == Gender.Male && maleUsed.contains(resId) || g == Gender.Female && femaleUsed.contains(resId));
            if (g == Gender.Male) {
                maleUsed.add(resId);
            } else {
                femaleUsed.add(resId);
            }
        } else {
            resId = next3(g);
        }
        return resId;
    }

    private int next3(Gender gender) {
        return gender == Gender.Female ? female[random.nextInt(female.length)] : male[random.nextInt(male.length)];
    }

    @Override
    public void reset() {
        maleUsed.clear();
        femaleUsed.clear();
    }
} public enum Gender {
    Female, Male, Both
} public class StringNameGenerator extends Generator<String> {
    private StringFirstNameGenerator firstNameGenerator;
    private StringLastNameGenerator lastNameGenerator;

    public StringNameGenerator() {
        firstNameGenerator = new StringFirstNameGenerator();
        lastNameGenerator = new StringLastNameGenerator();
    }

    public StringNameGenerator(Gender gender) {
        firstNameGenerator = new StringFirstNameGenerator(gender, true, true);
        lastNameGenerator = new StringLastNameGenerator();
    }

    public StringNameGenerator(Gender gender, boolean preventDuplicates, boolean allowTwoNames) {
        firstNameGenerator = new StringFirstNameGenerator(gender, preventDuplicates, allowTwoNames);
        lastNameGenerator = new StringLastNameGenerator();
    }

    @Override
    protected Matcher getDefaultMatcher() {
        return f -> f.getName() != null && f.getName().contains("name");
    }

    @Override
    public String next(DataContext context) {
        String name = context != null ? context.get(DataContext.NAME) : null;
        if (name == null) {
            name = firstNameGenerator.next(context) + " " + lastNameGenerator.next(context);
            if (context != null)
                context.set(DataContext.NAME, name);
        }
        return name;
    }

    @Override
    public void reset() {
        firstNameGenerator.reset();
        lastNameGenerator.reset();
    }

} public class DateToStringTransformer implements Transformer<Date, String> {
    private SimpleDateFormat format;

    public DateToStringTransformer() {
        format = new SimpleDateFormat("HH:mm, dd MMM");
    }

    public DateToStringTransformer(SimpleDateFormat format) {
        this.format = format;
    }

    @Override
    public String transform(Date date) {
        return format.format(date);
    }
}
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import carbon.component.AvatarTextSubtext2DateRow;
import carbon.component.DefaultAvatarTextSubtextDateItem;
import carbon.component.DefaultHeaderItem;
import carbon.component.HeaderRow;
import carbon.recycler.RowListAdapter;
import carbon.widget.RecyclerView;
import tk.zielony.randomdata.RandomData;
import tk.zielony.randomdata.common.DateGenerator;
import tk.zielony.randomdata.common.DrawableImageGenerator;
import tk.zielony.randomdata.common.EnumGenerator;
import tk.zielony.randomdata.common.TextGenerator;
import tk.zielony.randomdata.finance.StringAmountGenerator;
import tk.zielony.randomdata.finance.StringCardNumberGenerator;
import tk.zielony.randomdata.person.DrawableAvatarGenerator;
import tk.zielony.randomdata.person.Gender;
import tk.zielony.randomdata.person.StringNameGenerator;
import tk.zielony.randomdata.transformer.DateToStringTransformer;
package tk.zielony.randomdata.test;





public class MainActivity extends AppCompatActivity {
    RowListAdapter<Serializable> adapter;
    private RandomData randomData;
    private SwipeRefreshLayout swipeRefresh;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recycler = findViewById(R.id.recycler);
        recycler.setLayoutManager(new LinearLayoutManager(this));
        adapter = new RowListAdapter<>();
        adapter.putFactory(DefaultAvatarTextSubtextDateItem.class, AvatarTextSubtext2DateRow::new);
        adapter.putFactory(DefaultHeaderItem.class, HeaderRow::new);
        adapter.putFactory(CreditCardItem.class, CreditCardRow::new);
        recycler.setAdapter(adapter);

        swipeRefresh = findViewById(R.id.swipeRefresh);
        swipeRefresh.setOnRefreshListener(this::fillItems);

        swipeRefresh.setRefreshing(true);
        fillItems();
    }

    private void fillItems() {
        new Thread() {
            @Override
            public void run() {
                List<Serializable> items;
                if (Math.random() > 0.5) {
                    randomData = new RandomData();
                    randomData.addGenerator(String.class, new StringNameGenerator(Gender.Both).withMatcher(f -> "text".equals(f.getName()) && f.getDeclaringClass().equals(DefaultAvatarTextSubtextDateItem.class)));