18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/test/java/example/fruit/oldskool/BananaBuilder.java:
--------------------------------------------------------------------------------
1 | package example.fruit.oldskool;
2 |
3 | import example.fruit.Banana;
4 |
5 | public class BananaBuilder implements Builder {
6 | private double ripeness = 0.0;
7 | private double curve = 0.5;
8 |
9 | private BananaBuilder() {}
10 |
11 | public static BananaBuilder aBanana() {
12 | return new BananaBuilder();
13 | }
14 |
15 | public Banana build() {
16 | Banana banana = new Banana(curve);
17 | banana.ripen(ripeness);
18 | return banana;
19 | }
20 |
21 | public BananaBuilder withRipeness(double ripeness){
22 | this.ripeness = ripeness;
23 | return this;
24 | }
25 |
26 | public BananaBuilder withCurve(double curve) {
27 | this.curve = curve;
28 | return this;
29 | }
30 |
31 | public BananaBuilder but() {
32 | return new BananaBuilder()
33 | .withRipeness(ripeness)
34 | .withCurve(curve);
35 | }
36 | }
--------------------------------------------------------------------------------
/src/test/java/example/fruit/makeiteasy/AppleCartMaker.java:
--------------------------------------------------------------------------------
1 | package example.fruit.makeiteasy;
2 |
3 | import com.natpryce.makeiteasy.Instantiator;
4 | import com.natpryce.makeiteasy.Property;
5 | import com.natpryce.makeiteasy.PropertyLookup;
6 | import example.fruit.Apple;
7 | import example.fruit.AppleCart;
8 |
9 | import static com.natpryce.makeiteasy.MakeItEasy.an;
10 | import static com.natpryce.makeiteasy.MakeItEasy.listOf;
11 | import static com.natpryce.makeiteasy.Property.newProperty;
12 | import static example.fruit.makeiteasy.FruitMakers.Apple;
13 |
14 |
15 | /**
16 | * An example of how to define builders for properties that are collections.
17 | */
18 | public class AppleCartMaker {
19 | public static final Property> apples = newProperty();
20 |
21 | public static final Instantiator AppleCart = new Instantiator() {
22 | @Override
23 | public AppleCart instantiate(PropertyLookup lookup) {
24 | return new AppleCart(lookup.valueOf(apples, listOf(an(Apple))));
25 | }
26 | };
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/natpryce/makeiteasy/sequence/ChainedSequence.java:
--------------------------------------------------------------------------------
1 | package com.natpryce.makeiteasy.sequence;
2 |
3 | import com.natpryce.makeiteasy.Donor;
4 |
5 | /**
6 | * A sequence of values, each of which is calculated from the previous value
7 | * in the sequence.
8 | *
9 | * @param the type of the value
10 | */
11 | public abstract class ChainedSequence implements Donor {
12 | private T prevValue = null;
13 |
14 | @Override
15 | public T value() {
16 | T result = (prevValue == null) ? firstValue() : valueAfter(prevValue);
17 | prevValue = result;
18 | return result;
19 | }
20 |
21 | /**
22 | * Returns the first value in the sequence.
23 | * The result must not be null.
24 | *
25 | * @return the first value.
26 | */
27 | abstract protected T firstValue();
28 |
29 | /**
30 | * Return the value after a given value.
31 | * The result must not be null.
32 | *
33 | * @param prevValue the previous value
34 | * @return the value after prevValue in this sequence.
35 | */
36 | abstract protected T valueAfter(T prevValue);
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/natpryce/makeiteasy/PropertyLookup.java:
--------------------------------------------------------------------------------
1 | package com.natpryce.makeiteasy;
2 |
3 | /**
4 | * Looks up property values.
5 | *
6 | * @param type type of object for which the properties apply.
7 | */
8 | public interface PropertyLookup {
9 | /**
10 | *
11 | * @param property the property for which a value will be returned
12 | * @param defaultValue the default value to use if no value can be found
13 | * @param the type of the value
14 | * @return the value for the given property, or defaultValue if no value can be found.
15 | */
16 | V valueOf(Property super T,V> property, V defaultValue);
17 |
18 | /**
19 | *
20 | * @param property the property for which a value will be returned
21 | * @param defaultValueDonor an object that can provide the default value to use if no value can be found
22 | * @param the type of the value
23 | * @return the value for the given property, or defaultValueDonor.value() if no value can be found.
24 | */
25 | V valueOf(Property super T,V> property, Donor extends V> defaultValueDonor);
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/java/example/fruit/makeiteasy/FruitBowlMaker.java:
--------------------------------------------------------------------------------
1 | package example.fruit.makeiteasy;
2 |
3 | import com.natpryce.makeiteasy.Instantiator;
4 | import com.natpryce.makeiteasy.Property;
5 | import com.natpryce.makeiteasy.PropertyLookup;
6 | import example.fruit.Fruit;
7 | import example.fruit.FruitBowl;
8 |
9 | import static com.natpryce.makeiteasy.MakeItEasy.*;
10 | import static com.natpryce.makeiteasy.Property.newProperty;
11 | import static example.fruit.makeiteasy.FruitMakers.Apple;
12 | import static example.fruit.makeiteasy.FruitMakers.Banana;
13 |
14 |
15 | /**
16 | * An example of how to define builders for properties that are collections.
17 | */
18 | public class FruitBowlMaker {
19 | public static final Property> contents = newProperty();
20 |
21 | public static final Instantiator FruitBowl = new Instantiator() {
22 | @Override
23 | public FruitBowl instantiate(PropertyLookup lookup) {
24 | return new FruitBowl(
25 | lookup.valueOf(contents, listOf(an(Apple), a(Banana)).value()));
26 | }
27 | };
28 | }
--------------------------------------------------------------------------------
/.idea/runConfigurations/All_Tests.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/test/java/example/fruit/makeiteasy/FruitMakers.java:
--------------------------------------------------------------------------------
1 | package example.fruit.makeiteasy;
2 |
3 | import com.natpryce.makeiteasy.Instantiator;
4 | import com.natpryce.makeiteasy.Property;
5 | import static com.natpryce.makeiteasy.Property.newProperty;
6 | import com.natpryce.makeiteasy.PropertyLookup;
7 | import example.fruit.Apple;
8 | import example.fruit.Banana;
9 | import example.fruit.Fruit;
10 |
11 |
12 | public class FruitMakers {
13 | public static final Property ripeness = newProperty();
14 |
15 | public static final Property leaves = newProperty();
16 |
17 | public static final Property curve = newProperty();
18 |
19 | public static final Instantiator Apple = new Instantiator() {
20 | @Override
21 | public Apple instantiate(PropertyLookup lookup) {
22 | Apple apple = new Apple(lookup.valueOf(leaves, 2));
23 | apple.ripen(lookup.valueOf(ripeness, 0.0));
24 | return apple;
25 | }
26 | };
27 |
28 | public static final Instantiator Banana = new Instantiator() {
29 | @Override
30 | public Banana instantiate(PropertyLookup lookup) {
31 | Banana banana = new Banana(lookup.valueOf(curve, 0.5));
32 | banana.ripen(lookup.valueOf(ripeness, 0.0));
33 | return banana;
34 | }
35 | };
36 | }
37 |
--------------------------------------------------------------------------------
/make-it-easy.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/test/java/example/fruit/makeiteasy/MakeItEasyExample.java:
--------------------------------------------------------------------------------
1 | package example.fruit.makeiteasy;
2 |
3 | import com.natpryce.makeiteasy.Maker;
4 | import example.fruit.*;
5 |
6 | import static com.natpryce.makeiteasy.MakeItEasy.*;
7 | import static example.fruit.makeiteasy.AppleCartMaker.AppleCart;
8 | import static example.fruit.makeiteasy.AppleCartMaker.apples;
9 | import static example.fruit.makeiteasy.FruitBowlMaker.FruitBowl;
10 | import static example.fruit.makeiteasy.FruitBowlMaker.contents;
11 | import static example.fruit.makeiteasy.FruitMakers.*;
12 | import static example.fruit.makeiteasy.TreeMaker.BananaTree;
13 | import static example.fruit.makeiteasy.TreeMaker.bananas;
14 |
15 |
16 | @SuppressWarnings({"UnusedDeclaration"})
17 | public class MakeItEasyExample {
18 | public static void howToMakeSimpleObjects() {
19 | Maker appleWith2Leaves = an(Apple, with(2, leaves));
20 | Maker ripeApple = appleWith2Leaves.but(with(ripeness, 0.9));
21 | Maker unripeApple = appleWith2Leaves.but(with(ripeness, 0.125));
22 |
23 | Apple apple1 = make(ripeApple);
24 | Apple apple2 = make(unripeApple);
25 |
26 | Banana defaultBanana = make(a(Banana));
27 | Banana straightBanana = make(a(Banana, with(curve, 0.0)));
28 | Banana squishyBanana = make(a(Banana, with(ripeness, 1.0)));
29 | }
30 |
31 | public static void howToMakeObjectsWithPropertiesThatAreCollections() {
32 | AppleCart cart = make(a(AppleCart, with(apples, listOf(
33 | an(Apple, with(ripeness, 0.5)),
34 | an(Apple, with(ripeness, 0.35))
35 | ))));
36 |
37 | FruitBowl bowl = make(a(FruitBowl, with(contents, listOf(
38 | an(Apple, with(2, leaves)),
39 | an(Apple, with(3, leaves)),
40 | a(Banana, with(ripeness, 0.25)),
41 | a(Banana, with(ripeness, 0.99))
42 | ))));
43 | }
44 |
45 | // Yuck! But Java does not do type inference properly.
46 | public static void howToMakeGenericObjects() {
47 | Tree bananaTree = make(a(BananaTree,
48 | with(bananas, setOf(a(Banana), a(Banana), a(Banana)))));
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/test/java/com/natpryce/makeiteasy/tests/SequenceTests.java:
--------------------------------------------------------------------------------
1 | package com.natpryce.makeiteasy.tests;
2 |
3 | import com.natpryce.makeiteasy.Donor;
4 | import org.junit.Test;
5 |
6 | import java.util.NoSuchElementException;
7 | import java.util.TreeSet;
8 |
9 | import static com.natpryce.makeiteasy.MakeItEasy.from;
10 | import static com.natpryce.makeiteasy.MakeItEasy.fromRepeating;
11 | import static java.util.Arrays.asList;
12 | import static org.hamcrest.Matchers.equalTo;
13 | import static org.hamcrest.junit.MatcherAssert.assertThat;
14 |
15 | public class SequenceTests {
16 | @Test
17 | public void sequenceFromCollection() {
18 | Donor names = from(new TreeSet<>(asList("Bob", "Alice", "Carol", "Dave")));
19 |
20 | assertThat(names.value(), equalTo("Alice"));
21 | assertThat(names.value(), equalTo("Bob"));
22 | assertThat(names.value(), equalTo("Carol"));
23 | assertThat(names.value(), equalTo("Dave"));
24 | }
25 |
26 | @Test(expected = NoSuchElementException.class)
27 | public void sequenceFailsIfNoMoreElementsInCollection() {
28 | Donor names = from(asList("A", "B"));
29 |
30 | assertThat(names.value(), equalTo("A"));
31 | assertThat(names.value(), equalTo("B"));
32 |
33 | names.value();
34 | }
35 |
36 | @Test
37 | public void repeatingSequenceFromCollection() {
38 | Donor names = fromRepeating(asList("A", "B"));
39 |
40 | assertThat(names.value(), equalTo("A"));
41 | assertThat(names.value(), equalTo("B"));
42 | assertThat(names.value(), equalTo("A"));
43 | assertThat(names.value(), equalTo("B"));
44 | }
45 |
46 | @Test
47 | public void sequenceFromVarargs() {
48 | Donor names = from("Alice", "Bob", "Carol", "Dave");
49 |
50 | assertThat(names.value(), equalTo("Alice"));
51 | assertThat(names.value(), equalTo("Bob"));
52 | assertThat(names.value(), equalTo("Carol"));
53 | assertThat(names.value(), equalTo("Dave"));
54 | }
55 |
56 | @Test
57 | public void repeatingSequenceFromVarargs() {
58 | Donor names = fromRepeating("A", "B");
59 |
60 | assertThat(names.value(), equalTo("A"));
61 | assertThat(names.value(), equalTo("B"));
62 | assertThat(names.value(), equalTo("A"));
63 | assertThat(names.value(), equalTo("B"));
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/src/test/java/example/fruit/makeiteasy/TreeMaker.java:
--------------------------------------------------------------------------------
1 | package example.fruit.makeiteasy;
2 |
3 | import com.natpryce.makeiteasy.Instantiator;
4 | import com.natpryce.makeiteasy.Property;
5 | import static com.natpryce.makeiteasy.Property.newProperty;
6 | import com.natpryce.makeiteasy.PropertyLookup;
7 | import example.fruit.Apple;
8 | import example.fruit.Banana;
9 | import example.fruit.Fruit;
10 | import example.fruit.Tree;
11 |
12 | import static java.util.Collections.emptyList;
13 | import java.util.List;
14 |
15 |
16 | /**
17 | * Java's broken type system makes it harder than it should be to define Properties and Instantiators
18 | * for generic types.
19 | *
20 | * To see the problem, inline the call to fruit() or emptyList() in the instantiator below.
21 | * As soon as the result of a generic method is passed as a parameter, instead of
22 | * assigned to a local variable or constant, the code stops compiling!
23 | *
24 | * The solution is to define local variables or constants to force the bindings of the property
25 | * type parameters, such as the apples and bananas constants. Or you can define properties and
26 | * instantiators for a specific instantiation of the generic type, as shown by the AppleTree
27 | * and BananaTree instantiators below.
28 | */
29 | @SuppressWarnings("UnusedDeclaration")
30 | public class TreeMaker {
31 | /* We must have a single instance of the fruit property for equals & hashCode to work properly...
32 | */
33 | private static final Property,?> fruit = newProperty();
34 |
35 | /* But because constants cannot have generic wildcards, we must expose the constant through a
36 | * generic method that forces the property to have the required static type
37 | */
38 | public static Property, Iterable extends F>> fruit() {
39 | //noinspection unchecked
40 | return (Property, Iterable extends F>>)fruit;
41 | }
42 |
43 | public static Instantiator> Tree() {
44 | return new Instantiator>() {
45 | @Override
46 | public Tree instantiate(PropertyLookup> lookup) {
47 | Property, Iterable extends F>> fruit = fruit();
48 | List noFruit = emptyList();
49 |
50 | return new Tree<>(lookup.valueOf(fruit, noFruit));
51 | }
52 | };
53 | }
54 |
55 | /* We have to define constants to explictly bind all the type parameters. Code that uses
56 | * the fruit() and Tree() factory functions in maker expressions won't compile even though it
57 | * is type-safe.
58 | */
59 | public static final Instantiator> AppleTree = Tree();
60 | public static final Property, Iterable extends Apple>> apples = fruit();
61 |
62 | public static final Instantiator> BananaTree = Tree();
63 | public static final Property, Iterable extends Banana>> bananas = fruit();
64 |
65 | /* I hate the Java type system.
66 | */
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/com/natpryce/makeiteasy/Maker.java:
--------------------------------------------------------------------------------
1 | package com.natpryce.makeiteasy;
2 |
3 |
4 | import org.pcollections.HashTreePMap;
5 | import org.pcollections.PMap;
6 |
7 |
8 | /**
9 | * Makes objects of a given type with a specified initial state.
10 | *
11 | * @param the type of object to make
12 | */
13 | public class Maker implements PropertyLookup, Donor {
14 | private final PMap, PropertyValue super T, ?>> values;
15 | private final Instantiator instantiator;
16 |
17 | /**
18 | * Creates a Maker for objects of a given type with a given initial state.
19 | *
20 | * @param instantiator creates the new objects
21 | * @param propertyValues define the initial state of the new objects
22 | */
23 | @SafeVarargs
24 | public Maker(Instantiator instantiator, PropertyValue super T, ?>... propertyValues) {
25 | this.instantiator = instantiator;
26 | this.values = byProperty(propertyValues);
27 | }
28 |
29 | @SafeVarargs
30 | private Maker(Maker that, PropertyValue super T, ?>... propertyValues) {
31 | this.instantiator = that.instantiator;
32 | this.values = that.values.plusAll(byProperty(propertyValues));
33 | }
34 |
35 | private static PMap, PropertyValue super T, ?>> byProperty(PropertyValue super T, ?>[] propertyValues) {
36 | PMap, PropertyValue super T, ?>> propertyMap = HashTreePMap.empty();
37 |
38 | for (PropertyValue super T, ?> propertyValue : propertyValues) {
39 | propertyMap = propertyMap.plus(propertyValue.property(), propertyValue);
40 | }
41 |
42 | return propertyMap;
43 | }
44 |
45 | /**
46 | * Makes a new object.
47 | *
48 | * The {@link com.natpryce.makeiteasy.MakeItEasy#make(Maker) MakeItEasy.make} method
49 | * is syntactic sugar to make calls to this method read more naturally in most
50 | * contexts.
51 | *
52 | * @return a new object
53 | */
54 | public T make() {
55 | return instantiator.instantiate(this);
56 | }
57 |
58 | @Override
59 | public T value() {
60 | return make();
61 | }
62 |
63 | /**
64 | * Returns a new Maker for the same type of object and with the same initial state
65 | * except where overridden by the given propertyValues.
66 | *
67 | * @param propertyValues those initial properties of the new Make that will differ from this Maker
68 | * @return a new Maker
69 | */
70 | @SafeVarargs
71 | public final Maker but(PropertyValue super T, ?>... propertyValues) {
72 | return new Maker<>(this, propertyValues);
73 | }
74 |
75 | @Override
76 | public V valueOf(Property super T, V> property, V defaultValue) {
77 | return valueOf(property, new SameValueDonor<>(defaultValue));
78 | }
79 |
80 | @Override
81 | public V valueOf(Property super T, V> property, Donor extends V> defaultValue) {
82 | if (values.containsKey(property)) {
83 | //noinspection unchecked
84 | return (V) values.get(property).value();
85 | }
86 | else {
87 | return defaultValue.value();
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/java/com/natpryce/makeiteasy/MakeItEasy.java:
--------------------------------------------------------------------------------
1 | package com.natpryce.makeiteasy;
2 |
3 | import com.natpryce.makeiteasy.sequence.ElementsSequence;
4 |
5 | import java.util.*;
6 |
7 | import static java.util.Arrays.asList;
8 |
9 |
10 | /**
11 | * Syntactic sugar for using Make It Easy test-data builders.
12 | */
13 | public class MakeItEasy {
14 | @SafeVarargs
15 | public static Maker a(Instantiator instantiator, PropertyValue super T, ?> ... propertyProviders) {
16 | return new Maker<>(instantiator, propertyProviders);
17 | }
18 |
19 | @SafeVarargs
20 | public static Maker an(Instantiator instantiator, PropertyValue super T, ?> ... propertyProviders) {
21 | return new Maker<>(instantiator, propertyProviders);
22 | }
23 |
24 | public static PropertyValue with(Property property, W value) {
25 | return new PropertyValue<>(property, new SameValueDonor(value));
26 | }
27 |
28 | public static PropertyValue with(W value, Property property) {
29 | return new PropertyValue<>(property, new SameValueDonor(value));
30 | }
31 |
32 | public static PropertyValue with(Property property, Donor valueDonor) {
33 | return new PropertyValue<>(property, valueDonor);
34 | }
35 |
36 | public static PropertyValue with(Donor valueDonor, Property property) {
37 | return new PropertyValue<>(property, valueDonor);
38 | }
39 |
40 | public static PropertyValue withNull(Property property) {
41 | return new PropertyValue<>(property, new SameValueDonor(null));
42 | }
43 |
44 | @SafeVarargs
45 | public static Donor theSame(Instantiator instantiator, PropertyValue super T, ?> ... propertyProviders) {
46 | return theSame(an(instantiator, propertyProviders));
47 | }
48 |
49 | public static Donor theSame(Donor originalDonor) {
50 | return new SameValueDonor<>(originalDonor.value());
51 | }
52 |
53 | public static T make(Maker maker) {
54 | return maker.value();
55 | }
56 |
57 | @SafeVarargs
58 | public static Donor> listOf(Donor extends T>... donors) {
59 | return new NewCollectionDonor, T>(donors) {
60 | protected List newCollection() { return new ArrayList<>(); }
61 | };
62 | }
63 |
64 | @SafeVarargs
65 | public static Donor> setOf(Donor extends T>... donors) {
66 | return new NewCollectionDonor, T>(donors) {
67 | protected Set newCollection() { return new HashSet<>(); }
68 | };
69 | }
70 |
71 | @SafeVarargs
72 | public static > Donor> sortedSetOf(Donor extends T>... donors) {
73 | return new NewCollectionDonor, T>(donors) {
74 | protected SortedSet newCollection() { return new TreeSet<>(); }
75 | };
76 | }
77 |
78 | public static Donor from(final Iterable values) {
79 | return new ElementsSequence<>(values, Collections.emptyList());
80 | }
81 |
82 | @SafeVarargs
83 | public static Donor from(T ... values) {
84 | return from(asList(values));
85 | }
86 |
87 | public static Donor fromRepeating(Iterable values) {
88 | return new ElementsSequence<>(values, values);
89 | }
90 |
91 | @SafeVarargs
92 | public static Donor fromRepeating(T ... values) {
93 | return fromRepeating(asList(values));
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/test/java/com/natpryce/makeiteasy/tests/MakeItEasyTest.java:
--------------------------------------------------------------------------------
1 | package com.natpryce.makeiteasy.tests;
2 |
3 | import com.natpryce.makeiteasy.Instantiator;
4 | import com.natpryce.makeiteasy.Maker;
5 | import com.natpryce.makeiteasy.Property;
6 | import com.natpryce.makeiteasy.PropertyLookup;
7 | import org.junit.Test;
8 |
9 | import static com.natpryce.makeiteasy.MakeItEasy.*;
10 | import static com.natpryce.makeiteasy.Property.newProperty;
11 | import static org.hamcrest.Matchers.equalTo;
12 | import static org.hamcrest.Matchers.nullValue;
13 | import static org.hamcrest.junit.MatcherAssert.assertThat;
14 |
15 | public class MakeItEasyTest {
16 | public static class ThingToMake {
17 | public final String name;
18 | public final int age;
19 |
20 | public ThingToMake(String name, int age) {
21 | this.name = name;
22 | this.age = age;
23 | }
24 | }
25 |
26 | public static final Property name = newProperty();
27 | public static final Property age = newProperty();
28 |
29 | public static final Instantiator ThingToMake = new Instantiator() {
30 | @Override
31 | public ThingToMake instantiate(PropertyLookup lookup) {
32 | return new ThingToMake(lookup.valueOf(name, "Nemo"), lookup.valueOf(age, 99));
33 | }
34 | };
35 |
36 | @Test
37 | public void usesDefaultPropertyValuesIfNoPropertiesSpecified() {
38 | ThingToMake madeThing = make(a(ThingToMake));
39 |
40 | assertThat(madeThing.name, equalTo("Nemo"));
41 | assertThat(madeThing.age, equalTo(99));
42 | }
43 |
44 | @Test
45 | public void overridesDefaultValuesWithExplicitProperties() {
46 | ThingToMake madeThing = make(a(ThingToMake, with(name, "Bob"), with(age, 10)));
47 |
48 | assertThat(madeThing.name, equalTo("Bob"));
49 | assertThat(madeThing.age, equalTo(10));
50 |
51 | ThingToMake differentName = make(a(ThingToMake, with(name, "Bill")));
52 | assertThat(differentName.name, equalTo("Bill"));
53 | }
54 |
55 | @Test
56 | public void canSpecifyNullPropertyValue() {
57 | ThingToMake madeThing = make(a(ThingToMake, withNull(name)));
58 |
59 | assertThat(madeThing.name, nullValue());
60 | }
61 |
62 |
63 | public static class ThingContainer {
64 | public final ThingToMake thing;
65 |
66 | public ThingContainer(ThingToMake thing) {
67 | this.thing = thing;
68 | }
69 | }
70 |
71 | public static final Property thing = newProperty();
72 |
73 | public static final Instantiator ThingContainer = new Instantiator() {
74 | @Override
75 | public ThingContainer instantiate(PropertyLookup lookup) {
76 | return new ThingContainer(lookup.valueOf(thing, make(a(ThingToMake))));
77 | }
78 | };
79 |
80 | @Test
81 | public void canUseMakersToInitialisePropertyValues() {
82 | ThingContainer container = make(a(ThingContainer, with(thing, a(ThingToMake, with(name, "foo")))));
83 |
84 | assertThat(container.thing.name, equalTo("foo"));
85 | }
86 |
87 | @Test
88 | public void sharingDefinitionsAvoidsAliasingErrors() {
89 | Maker x99Maker = a(ThingToMake, with(name, "x"), with(age, 99));
90 | Maker x77Maker = x99Maker.but(with(age, 77));
91 |
92 | ThingToMake x99 = x99Maker.make();
93 | ThingToMake x77 = x77Maker.make();
94 |
95 | assertThat("x99.name", x99.name, equalTo("x"));
96 | assertThat("x77.name", x77.name, equalTo("x"));
97 |
98 | assertThat("x99.age", x99.age, equalTo(99));
99 | assertThat("x77.age", x77.age, equalTo(77));
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/test/java/example/donors/DonorExample.java:
--------------------------------------------------------------------------------
1 | package example.donors;
2 |
3 | import com.natpryce.makeiteasy.*;
4 | import com.natpryce.makeiteasy.sequence.ChainedSequence;
5 | import com.natpryce.makeiteasy.sequence.IndexedSequence;
6 | import org.junit.Test;
7 |
8 | import java.util.SortedSet;
9 | import java.util.TreeSet;
10 | import java.util.UUID;
11 |
12 | import static com.natpryce.makeiteasy.MakeItEasy.*;
13 | import static com.natpryce.makeiteasy.Property.newProperty;
14 | import static org.hamcrest.junit.MatcherAssert.assertThat;
15 | import static org.hamcrest.Matchers.equalTo;
16 | import static org.hamcrest.Matchers.not;
17 |
18 | public class DonorExample {
19 | public static class NamedThing {
20 | public final String name;
21 |
22 | public NamedThing(String name) {
23 | this.name = name;
24 | }
25 | }
26 |
27 | public static final Property name = newProperty();
28 |
29 | public static final Instantiator NamedThing = new Instantiator() {
30 | @Override
31 | public NamedThing instantiate(PropertyLookup lookup) {
32 | return new NamedThing(lookup.valueOf(name, "anonymous"));
33 | }
34 | };
35 |
36 | @Test
37 | public void allocatingUniqueNames() {
38 | class UUIDValue implements Donor {
39 | @Override
40 | public String value() {
41 | return UUID.randomUUID().toString();
42 | }
43 | }
44 |
45 | Maker aNamedThing = a(NamedThing, with(name, new UUIDValue()));
46 |
47 | NamedThing thing0 = make(aNamedThing);
48 | NamedThing thing1 = make(aNamedThing);
49 |
50 | assertThat(thing0.name, not(equalTo(thing1.name)));
51 | }
52 |
53 | @Test
54 | public void allocatingNamesByIndex() {
55 | class NameSequence extends IndexedSequence {
56 | @Override
57 | protected String valueAt(int index) {
58 | return Integer.toString(index);
59 | }
60 | }
61 |
62 | Maker aNamedThing = a(NamedThing, with(name, new NameSequence()));
63 |
64 | NamedThing thing0 = make(aNamedThing);
65 | NamedThing thing1 = make(aNamedThing);
66 |
67 | assertThat(thing0.name, equalTo("0"));
68 | assertThat(thing1.name, equalTo("1"));
69 | }
70 |
71 | @Test
72 | public void allocatingNamesByChain() {
73 | Maker aNamedThing = a(NamedThing, with(name, new ChainedSequence() {
74 | protected String firstValue() { return "X"; }
75 | protected String valueAfter(String prevValue) { return prevValue + "'"; }
76 | }));
77 |
78 | NamedThing thing0 = make(aNamedThing);
79 | NamedThing thing1 = make(aNamedThing);
80 | NamedThing thing2 = make(aNamedThing);
81 |
82 | assertThat(thing0.name, equalTo("X"));
83 | assertThat(thing1.name, equalTo("X'"));
84 | assertThat(thing2.name, equalTo("X''"));
85 | }
86 |
87 | @Test
88 | public void allocatingNamesFromACollection() {
89 | SortedSet names = new TreeSet<>();
90 | names.add("Bob");
91 | names.add("Alice");
92 | names.add("Carol");
93 | names.add("Dave");
94 |
95 | Maker aNamedThing = a(NamedThing, with(name, from(names)));
96 |
97 | NamedThing thing0 = make(aNamedThing);
98 | NamedThing thing1 = make(aNamedThing);
99 | NamedThing thing2 = make(aNamedThing);
100 | NamedThing thing3 = make(aNamedThing);
101 |
102 | assertThat(thing0.name, equalTo("Alice"));
103 | assertThat(thing1.name, equalTo("Bob"));
104 | assertThat(thing2.name, equalTo("Carol"));
105 | assertThat(thing3.name, equalTo("Dave"));
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/test/java/com/natpryce/makeiteasy/tests/PropertyValueSharingTest.java:
--------------------------------------------------------------------------------
1 | package com.natpryce.makeiteasy.tests;
2 |
3 | import com.natpryce.makeiteasy.Instantiator;
4 | import com.natpryce.makeiteasy.Maker;
5 | import com.natpryce.makeiteasy.Property;
6 | import com.natpryce.makeiteasy.PropertyLookup;
7 | import org.junit.Test;
8 |
9 | import java.util.Collections;
10 | import java.util.List;
11 |
12 | import static com.natpryce.makeiteasy.MakeItEasy.*;
13 | import static com.natpryce.makeiteasy.Property.newProperty;
14 | import static org.hamcrest.Matchers.not;
15 | import static org.hamcrest.Matchers.sameInstance;
16 | import static org.hamcrest.junit.MatcherAssert.assertThat;
17 |
18 | // See Issue 2.
19 | public class PropertyValueSharingTest {
20 | public class Identity {
21 | public final String name;
22 |
23 | public Identity(String name) {
24 | this.name = name;
25 | }
26 |
27 | @Override
28 | public String toString() {
29 | return "Identity[" + System.identityHashCode(this) + ": " + name + "]";
30 | }
31 | }
32 |
33 | public class Identified {
34 | public final Identity identity;
35 |
36 | public Identified(Identity identity) {
37 | this.identity = identity;
38 | }
39 | }
40 |
41 | public final Property name = newProperty();
42 |
43 | public final Instantiator Identity = new Instantiator() {
44 | @Override
45 | public Identity instantiate(PropertyLookup lookup) {
46 | return new Identity(lookup.valueOf(name, "default-name"));
47 | }
48 | };
49 |
50 | public final Property identity = newProperty();
51 |
52 | public final Instantiator Identified = new Instantiator() {
53 | @Override
54 | public Identified instantiate(PropertyLookup lookup) {
55 | return new Identified(lookup.valueOf(identity, new Identity("default-identity")));
56 | }
57 | };
58 |
59 | @Test
60 | public void aDistinctPropertyValueInstanceIsUsedForEachMadeObjectWhenPropertyIsDefinedWithAMaker() {
61 | Maker anIdentified = an(Identified,
62 | with(identity, a(Identity, with(name, "original-name"))));
63 |
64 | Identified x = make(anIdentified);
65 | Identified y = make(anIdentified);
66 |
67 | assertThat(x.identity, not(sameInstance(y.identity)));
68 | }
69 |
70 | @Test
71 | public void canExplicitlyDeclareThatDifferentInstancesHaveTheSamePropertyValueInstance() {
72 | Maker anIdentified = an(Identified,
73 | with(identity, theSame(Identity, with(name, "original-name"))));
74 |
75 | Identified x = make(anIdentified);
76 | Identified y = make(anIdentified);
77 |
78 | assertThat(x.identity, sameInstance(y.identity));
79 | }
80 |
81 | public class SecretAgent {
82 | public final List assumedIdentities;
83 |
84 | public SecretAgent(List assumedIdentities) {
85 | this.assumedIdentities = assumedIdentities;
86 | }
87 | }
88 |
89 | public final Instantiator SecretAgent = new Instantiator() {
90 | @Override
91 | public SecretAgent instantiate(PropertyLookup lookup) {
92 | return new SecretAgent(
93 | lookup.valueOf(assumedIdentities,
94 | Collections.emptyList()));
95 | }
96 | };
97 |
98 | public final Property> assumedIdentities = newProperty();
99 |
100 | @Test
101 | public void distinctCollectionElementsAreUsedForEachMadeObjectWhenElementsAreDefinedWithAMaker() {
102 | Maker anAgent = a(SecretAgent,
103 | with(assumedIdentities, listOf(
104 | an(Identity, with(name, "jason bourne")),
105 | an(Identity, with(name, "james bond")))));
106 |
107 | SecretAgent x = make(anAgent);
108 | SecretAgent y = make(anAgent);
109 |
110 | assertThat(x.assumedIdentities, not(sameInstance(y.assumedIdentities)));
111 | assertThat(x.assumedIdentities.get(0), not(sameInstance(y.assumedIdentities.get(0))));
112 | assertThat(x.assumedIdentities.get(1), not(sameInstance(y.assumedIdentities.get(1))));
113 | }
114 |
115 | @Test
116 | public void canDeclareThatElementsOfDifferentCollectionAreTheSame() {
117 | Maker anAgent = a(SecretAgent,
118 | with(assumedIdentities, listOf(
119 | theSame(Identity, with(name, "austin powers")),
120 | theSame(Identity, with(name, "harry palmer")))));
121 |
122 | SecretAgent x = make(anAgent);
123 | SecretAgent y = make(anAgent);
124 |
125 | assertThat(x.assumedIdentities, not(sameInstance(y.assumedIdentities)));
126 | assertThat(x.assumedIdentities.get(0), sameInstance(y.assumedIdentities.get(0)));
127 | assertThat(x.assumedIdentities.get(1), sameInstance(y.assumedIdentities.get(1)));
128 | }
129 |
130 | @Test
131 | public void canDeclareThatSameCollectionIsUsedForEveryMadeObject() {
132 | Maker anAgent = a(SecretAgent,
133 | with(assumedIdentities, theSame(listOf(
134 | an(Identity, with(name, "jason bourne")),
135 | an(Identity, with(name, "james bond"))))));
136 |
137 | SecretAgent x = make(anAgent);
138 | SecretAgent y = make(anAgent);
139 |
140 | assertThat(x.assumedIdentities, sameInstance(y.assumedIdentities));
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | A tiny framework that makes it easy to write Test Data Builders in Java
2 |
3 | [](https://travis-ci.org/npryce/make-it-easy)
4 |
5 | Test Data Builders are described in the book [Growing Object-Oriented Software, Guided by Tests](http://www.growing-object-oriented-software.com) by [Steve Freeman](http://www.m3p.co.uk) and [Nat Pryce](http://www.natpryce.com). This library lets you write Test Data Builders with much less duplication and boilerplate code than the approach described in the book.
6 |
7 | ## Download ##
8 |
9 | You can download from Maven Central with the artifact coordinates:
10 |
11 | com.natpryce:make-it-easy:4.0.0
12 |
13 | ## Example ##
14 |
15 | Consider the following class hierarchy. This hierarchy illustrates a couple of complicating factors: there is an abstract base class and there is a property (Fruit.ripeness) that is not set via the constructor but by an operation of the Fruit class.
16 |
17 |
18 | ```java
19 | public abstract class Fruit {
20 | private double ripeness = 0.0;
21 |
22 | public void ripen(double amount) {
23 | ripeness = Math.min(1.0, ripeness+amount);
24 | }
25 |
26 | public boolean isRipe() {
27 | return ripeness == 1.0;
28 | }
29 | }
30 |
31 | public class Apple extends Fruit {
32 | private int leaves;
33 |
34 | public Apple(int leaves) {
35 | this.leaves = leaves;
36 | }
37 |
38 | public int numberOfLeaves() {
39 | return leaves;
40 | }
41 | }
42 |
43 | public class Banana extends Fruit {
44 | public final double curve;
45 |
46 | public Banana(double curve) {
47 | this.curve = curve;
48 | }
49 |
50 | public double curve() {
51 | return curve;
52 | }
53 | }
54 | ```
55 |
56 | You can define Test Data Builders for Apples and Bananas with Make It Easy as follows:
57 |
58 | ```java
59 | public class FruitMakers {
60 | public static final Property ripeness = newProperty();
61 |
62 | public static final Property leaves = newProperty();
63 |
64 | public static final Property curve = newProperty();
65 |
66 | public static final Instantiator Apple = new Instantiator() {
67 | @Override public Apple instantiate(PropertyLookup lookup) {
68 | Apple apple = new Apple(lookup.valueOf(leaves, 2));
69 | apple.ripen(lookup.valueOf(ripeness, 0.0));
70 | return apple;
71 | }
72 | };
73 |
74 | public static final Instantiator Banana = new Instantiator() {
75 | @Override public Banana instantiate(PropertyLookup lookup) {
76 | Banana banana = new Banana(lookup.valueOf(curve, 0.5));
77 | banana.ripen(lookup.valueOf(ripeness, 0.0));
78 | return banana;
79 | }
80 | };
81 | }
82 | ```
83 |
84 | And use them like this:
85 |
86 | ```java
87 | Maker appleWith2Leaves = an(Apple, with(2, leaves));
88 | Maker ripeApple = appleWith2Leaves.but(with(ripeness, 0.9));
89 | Maker unripeApple = appleWith2Leaves.but(with(ripeness, 0.125));
90 |
91 | Apple apple1 = make(ripeApple);
92 | Apple apple2 = make(unripeApple);
93 |
94 | Banana defaultBanana = make(a(Banana));
95 | Banana straightBanana = make(a(Banana, with(curve, 0.0)));
96 | Banana squishyBanana = make(a(Banana, with(ripeness, 1.0)));
97 | ```
98 |
99 | In contrast, doing so in the style documented in _Growing Object-Oriented Software, Guided by Tests_ would look like this:
100 |
101 | ```java
102 | public interface Builder {
103 | T build();
104 | }
105 |
106 | public class AppleBuilder implements Builder {
107 | private double ripeness = 0.0;
108 | private int leaves = 1;
109 |
110 | private AppleBuilder() {}
111 |
112 | public static AppleBuilder anApple() {
113 | return new AppleBuilder();
114 | }
115 |
116 | public Apple build() {
117 | Apple apple = new Apple(leaves);
118 | apple.ripen(ripeness);
119 | return apple;
120 | }
121 |
122 | public AppleBuilder withRipeness(double ripeness){
123 | this.ripeness = ripeness;
124 | return this;
125 | }
126 |
127 | public AppleBuilder withLeaves(int leaves) {
128 | this.leaves = leaves;
129 | return this;
130 | }
131 |
132 | public AppleBuilder but() {
133 | return new AppleBuilder()
134 | .withRipeness(ripeness)
135 | .withLeaves(leaves);
136 | }
137 | }
138 |
139 | public class BananaBuilder implements Builder {
140 | private double ripeness = 0.0;
141 | private double curve = 0.5;
142 |
143 | private BananaBuilder() {}
144 |
145 | public static BananaBuilder aBanana() {
146 | return new BananaBuilder();
147 | }
148 |
149 | public Banana build() {
150 | Banana apple = new Banana(curve);
151 | apple.ripen(ripeness);
152 | return apple;
153 | }
154 |
155 | public BananaBuilder withRipeness(double ripeness){
156 | this.ripeness = ripeness;
157 | return this;
158 | }
159 |
160 | public BananaBuilder withCurve(double curve) {
161 | this.curve = curve;
162 | return this;
163 | }
164 |
165 | public BananaBuilder but() {
166 | return new BananaBuilder()
167 | .withRipeness(ripeness)
168 | .withCurve(curve);
169 | }
170 | }
171 | ```
172 |
173 | And be used like this:
174 |
175 | ```java
176 | AppleBuilder appleWith2Leaves = anApple().withLeaves(2);
177 | AppleBuilder ripeApple = appleWith2Leaves.but().withRipeness(0.9);
178 | AppleBuilder unripeApple = appleWith2Leaves.but().withRipeness(0.125);
179 |
180 | Apple apple1 = ripeApple.build();
181 | Apple apple2 = unripeApple.build();
182 |
183 | Banana defaultBanana = aBanana().build();
184 | Banana straightBanana = aBanana().withCurve(0.0).build();
185 | Banana squishyBanana = aBanana().withRipeness(1.0).build();
186 | ```
187 |
188 | As you can see, with Make It Easy you have to write a lot less duplicated and boilerplate code. What duplication there is - in the declaration of anonymous Instantiator classes, for example - can be automatically inserted and refactored by modern IDEs. (You could also factor out calls to Fruit.ripen to a private helper method, but I left them duplicated for clarity.)
189 |
190 | The full code for this example is [in the Make It Easy repository](src/test/java/example/fruit).
191 |
--------------------------------------------------------------------------------
/.idea/uiDesigner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------