├── .gitignore ├── .travis.yml ├── README.adoc ├── datatyper-example ├── pom.xml └── src │ ├── main │ ├── datatyper │ │ └── Request.typer │ └── java │ │ └── com │ │ └── theoryinpractise │ │ └── gadt │ │ └── Shouter.java │ └── test │ └── java │ └── com │ └── theoryinpractise │ └── gadt │ └── TestGadt.java ├── datatyper-lib ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── theoryinpractise │ │ └── datatyper │ │ ├── AccepterGenerator.java │ │ ├── DataTypeCompiler.java │ │ ├── DataTypeGenerator.java │ │ ├── DatatypeParser.java │ │ ├── MatcherGenerator.java │ │ ├── Support.java │ │ └── model │ │ ├── DataType.java │ │ ├── DataTypeContainer.java │ │ └── Field.java │ └── test │ ├── java │ └── com │ │ └── theoryinpractise │ │ └── datatyper │ │ ├── DatatyperTest.java │ │ └── PackageSettings.java │ └── resources │ ├── Test.typer │ └── com │ └── theoryinpractise │ └── datatyper │ └── DatatyperTest.testGadtFiles.approved.txt ├── datatyper-maven-plugin ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── theoryinpractise │ └── datatyper │ └── mojo │ └── DatatyperMojo.java ├── datatyper-maven-tile ├── pom.xml └── tile.xml └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | ### Maven template 2 | target/ 3 | pom.xml.tag 4 | pom.xml.releaseBackup 5 | pom.xml.versionsBackup 6 | pom.xml.next 7 | release.properties 8 | dependency-reduced-pom.xml 9 | buildNumber.properties 10 | .mvn/timing.properties 11 | 12 | .idea 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | sudo: false 4 | 5 | addons: 6 | apt: 7 | packages: 8 | - oracle-java8-installer 9 | 10 | script: 11 | - java -version 12 | - mvn install -pl datatyper-maven-tile 13 | - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V 14 | - mvn test 15 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | # Java ADT Generator 2 | :toc: 3 | :toc-placement: preamble 4 | 5 | Algebraic Data-Types for Java. 6 | 7 | `datatyper-maven-plugin` is an http://maven.apache.org[Apache Maven] plugin to generate _immutable_, _algebraic data types_ for use in JDK 8 based projects. 8 | 9 | ## Example DataTyper File 10 | 11 | .Request.typer 12 | [source,haskell] 13 | ---- 14 | -- A simple model for an HTTP based API 15 | package com.theoryinpractise.typer.examples; 16 | 17 | -- By not specifying the return type with "->" for a given case, 18 | -- the inferred return type should just be the individual case. 19 | data Request implements (com.theoryinpractise.typer.Shouter) 20 | = GET (path : String) 21 | | DELETE (path : String) 22 | | POST (path : String, body : String); 23 | 24 | -- By not specifying any arguments for a data type, it is generated as a 25 | -- singleton instance which could be used in place of standard enums. 26 | data Day 27 | = Monday | Tuesday | Wednesday 28 | | Thursday | Friday | Saturday | Sunday; 29 | 30 | -- java.lang is "imported" by default, but if you want to use other classes, 31 | -- simply import them 32 | 33 | import java.util.Date; 34 | 35 | data Log 36 | = info(date: Date, message: String) 37 | | debug(date: Date, message: String) 38 | | warn(date: Date, message: String) 39 | | error(date: Date, message: String); 40 | ---- 41 | 42 | ### Generated Classes 43 | 44 | * A top level `abstract` class acting as a _module_ for the datatype. 45 | * Separate _inner classes_ and _constructor functions_ for each alterative type. 46 | * A _matcher_ interface providing total coverage for each alternative type. 47 | * A _fluent matching_ class for simple, inline matching. 48 | 49 | A top-level `abstract` class is generated for each datatype, with additional `abstract` subclasses for each individual case, these classes are annotatd with `@AutoValue` and will generate concrete, immutable, private instance classes via the Google Autovalue library. 50 | 51 | Static methods on the top level class are provided to construct instances of your dataypes. 52 | 53 | The constructor methods return their concrete type, allowing local code to easily access the types members. 54 | 55 | ---- 56 | Request.GET("/api/story/32").path() 57 | ---- 58 | 59 | 60 | ## Matching values 61 | 62 | ### Total Match Coverage via Visitor 63 | 64 | An inner `Matcher` interface is also generated, containing a separate `match` method for each unique datatype case: 65 | 66 | [source,java] 67 | ---- 68 | public interface Matcher { 69 | Return GET(Request.GET GET); 70 | Return DELETE(Request.DELETE DELETE); 71 | Return POST(Request.POST POST); 72 | ... 73 | } 74 | ---- 75 | 76 | which is used in conjunction with the instance level `match` method defined in the top level abstract class, this is used as such: 77 | 78 | [source,java] 79 | ---- 80 | int pathLength = req.match(new Request.Matcher() { 81 | @Override 82 | public Integer GET(Request.GET GET) { 83 | return GET.path().length(); 84 | } 85 | 86 | @Override 87 | public Integer DELETE(Request.DELETE DELETE) { 88 | return DELETE.path().length(); 89 | } 90 | 91 | @Override 92 | public Integer POST(Request.POST POST) { 93 | return POST.path().length(); 94 | } 95 | }); 96 | 97 | ---- 98 | 99 | ### Fluent Matchers 100 | 101 | DataTyper supports an alternate matching style based on a fluent lambda syntax: 102 | 103 | [source,java] 104 | ---- 105 | Request.Matching matched = req.matching() 106 | .GET( get -> get.path().length() ); 107 | 108 | if (matched.isMatched()) { 109 | int length = matched.get(); 110 | } 111 | 112 | int length = req.matching() 113 | .GET( get -> get.path().length() ) 114 | .orElse(0); 115 | 116 | Optional length = req.matching() 117 | .GET( get -> get.path().length() ) 118 | .find(); 119 | 120 | ---- 121 | 122 | The generated `Request.Matching` class is somewhat akin to an `Optional` value, the only difference being individual type case methods providing a functional style match expression. 123 | 124 | 125 | ## Accepting Values 126 | 127 | There are times you simply want to consume a specific value and don't require a result, for this we have a `Request.Accepting` fluent interface: 128 | 129 | [source,java] 130 | ---- 131 | public interface Accepting { 132 | void GET(Request.GET GET); 133 | void DELETE(Request.DELETE DELETE); 134 | void POST(Request.POST POST); 135 | ... 136 | } 137 | ---- 138 | 139 | and is used: 140 | 141 | [source,java] 142 | ---- 143 | Request.Matching matched = req.accepting() 144 | .DELETE( delete -> handleDeleting(delete.path()) ) 145 | .orElse(req -> throw UnsupportedOperation("lol wut?")); 146 | 147 | ---- 148 | 149 | 150 | ### Supporting Super-Type marker interfaces 151 | 152 | [source,java] 153 | ---- 154 | data SomeType implements (some.marker.Interface, some.other.Interface) 155 | = Value(); 156 | ---- 157 | 158 | Data Type declarations define a list of Java class names that the base class should `implement`. These classes *MUST* be interfaces, and only contain `static` or `default` methods ( otherwise the generated code will be fail to compile ). 159 | 160 | [NOTE] 161 | ==== 162 | I really don't like the `())` style syntax, but as yet I'm not sure yet how to get the https://github.com/jparsec/jparsec[jparsec] parser library to terminate the CSV list without failing the parse. This is being tracked as https://github.com/talios/datatyper/issues/8[issue #8]. 163 | ==== 164 | 165 | ## Compile time dependencies 166 | 167 | The code generated by `datatyper-maven-plugin` uses the https://github.com/google/auto/tree/master/value[Google Auto-Value] annotations to generate it's immutable classes, so this is required to be listed as a `compile` dependency in your maven project. 168 | 169 | NOTE: There are _no_ run-time dependencies introduced by the DataTyper project. 170 | 171 | 172 | ## Configuring Your Maven Build 173 | 174 | ### Standard Usage 175 | 176 | .pom.xml 177 | [source,xml] 178 | ---- 179 | 180 | 181 | com.theoryinpractise.datatyper 182 | datatyper-maven-plugin 183 | 1.0.1 184 | 185 | 186 | datatyper 187 | 188 | datatyper 189 | 190 | 191 | 192 | 193 | 194 | ... 195 | 196 | 197 | com.google.auto.value 198 | auto-value 199 | 1.3 200 | provided 201 | 202 | 203 | ---- 204 | 205 | ### Maven Tiles Usage 206 | 207 | [source,xml] 208 | ---- 209 | 210 | 211 | io.repaint.maven 212 | tiles-maven-plugin 213 | 2.10 214 | true 215 | 216 | 217 | com.theoryinpractise.datatyper:datatyper-maven-tile:[1.0.0,2.0.0) 218 | 219 | 220 | 221 | 222 | ---- 223 | -------------------------------------------------------------------------------- /datatyper-example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.theoryinpractise.datatyper 7 | datatyper 8 | 1.1.4-SNAPSHOT 9 | 10 | 11 | com.theoryinpractise.datatyper 12 | datatyper-example 13 | 1.1.4-SNAPSHOT 14 | 15 | datatyper-example 16 | http://maven.apache.org 17 | 18 | 19 | UTF-8 20 | 21 | 22 | 23 | 24 | 25 | com.google.auto.value 26 | auto-value 27 | 1.9 28 | 29 | 30 | * 31 | * 32 | 33 | 34 | 35 | 36 | com.google.auto.value 37 | auto-value-annotations 38 | 1.9 39 | 40 | 41 | * 42 | * 43 | 44 | 45 | 46 | 47 | 48 | org.junit.jupiter 49 | junit-jupiter-engine 50 | 5.8.2 51 | test 52 | 53 | 54 | com.google.truth 55 | truth 56 | 1.1.3 57 | test 58 | 59 | 60 | 61 | 62 | 63 | 64 | com.theoryinpractise.datatyper 65 | datatyper-maven-plugin 66 | 1.1.4-SNAPSHOT 67 | 68 | 69 | datatyper 70 | 71 | datatyper 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /datatyper-example/src/main/datatyper/Request.typer: -------------------------------------------------------------------------------- 1 | -- A simple model for an HTTP based API 2 | package com.theoryinpractise.gadt.examples; 3 | 4 | -- java.lang is "imported" by default, but if you want to use other classes, 5 | -- simply import them 6 | 7 | import java.util.Date; 8 | import java.util.Set; 9 | import com.theoryinpractise.gadt.Shouter; 10 | 11 | data Customer = SimpleCustomer(firstName: String, lastName: String); 12 | 13 | data Request implements (Shouter) 14 | = GET (path : String) 15 | | DELETE (path : String) 16 | | POST (path : String, body : String); 17 | 18 | -- By not specifying any arguments for a data type, it is generated as a 19 | -- singleton instance which could be used in place of standard enums. 20 | data Day 21 | = Monday | Tuesday | Wednesday 22 | | Thursday | Friday | Saturday | Sunday; 23 | 24 | data Log 25 | = info(date: Date, message: String) 26 | | debug(date: Date, message: String) 27 | | warn(date: Date, message: String) 28 | | error(date: Date, message: String); 29 | 30 | data GenericType implements (Shouter) 31 | = Empty 32 | | Single(value: T) 33 | | Container(value: Set); 34 | -------------------------------------------------------------------------------- /datatyper-example/src/main/java/com/theoryinpractise/gadt/Shouter.java: -------------------------------------------------------------------------------- 1 | package com.theoryinpractise.gadt; 2 | 3 | public interface Shouter { 4 | 5 | default void shout() { 6 | System.out.println("WOAH NELLY!"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /datatyper-example/src/test/java/com/theoryinpractise/gadt/TestGadt.java: -------------------------------------------------------------------------------- 1 | package com.theoryinpractise.gadt; 2 | 3 | import com.theoryinpractise.gadt.examples.Customer; 4 | import com.theoryinpractise.gadt.examples.GenericType; 5 | import com.theoryinpractise.gadt.examples.Request; 6 | import org.junit.jupiter.api.Test; 7 | import java.util.Optional; 8 | import java.util.concurrent.atomic.AtomicBoolean; 9 | 10 | import static com.google.common.truth.Truth.assertThat; 11 | 12 | /** Test class is in a different package to generated files to check visibility constraints. */ 13 | public class TestGadt { 14 | @Test 15 | public void testUsingGadt() { 16 | Request req = Request.GET("/api/story/32"); 17 | 18 | assertThat(req.toString()).startsWith("GET{path=/api/story/32"); 19 | assertThat(req).isInstanceOf(Request.class); 20 | assertThat(req).isInstanceOf(Request.GET.class); 21 | 22 | // Test for total matching 23 | 24 | int pathLength = 25 | req.match( 26 | new Request.Matcher() { 27 | @Override 28 | public Integer GET(Request.GET GET) { 29 | return GET.path().length(); 30 | } 31 | 32 | @Override 33 | public Integer DELETE(Request.DELETE DELETE) { 34 | return DELETE.path().length(); 35 | } 36 | 37 | @Override 38 | public Integer POST(Request.POST POST) { 39 | return POST.path().length(); 40 | } 41 | }); 42 | 43 | assertThat(pathLength).isEqualTo(13); 44 | 45 | // Test for total matching via lambdas 46 | 47 | pathLength = 48 | req.matching( 49 | GET -> GET.path().length(), 50 | DELETE -> DELETE.path().length(), 51 | POST -> POST.path().length()); 52 | 53 | assertThat(pathLength).isEqualTo(13); 54 | 55 | // Test for fluent matching 56 | Request.Matching matching = req.matching().GET(get -> "Hello"); 57 | if (matching.isMatched()) { 58 | assertThat(matching.get()).isEqualTo("Hello"); 59 | } else { 60 | throw new AssertionError("This should have been matched."); 61 | } 62 | 63 | // Test for failed matching 64 | try { 65 | matching = req.matching().DELETE(get -> "Hello"); 66 | assertThat(matching.get()).isEqualTo("Hello"); 67 | throw new AssertionError("This should throw."); 68 | } catch (IllegalStateException e) { 69 | // expected 70 | } 71 | 72 | // Test for total matching 73 | String returnValue = req.matching().POST(post -> "Hello").orElse("Goodbye"); 74 | 75 | assertThat(returnValue).isEqualTo("Goodbye"); 76 | 77 | // Test for optional matching 78 | Optional optionalReturnValue = req.matching().POST(post -> "Hello").find(); 79 | 80 | assertThat(optionalReturnValue.isPresent()).isFalse(); 81 | 82 | req.shout(); 83 | } 84 | 85 | @Test 86 | public void testUsingBasicSingleton() { 87 | Customer.SimpleCustomer cust = Customer.simpleCustomer("Test", "Customer"); 88 | assertThat(cust.firstName()).isEqualTo("Test"); 89 | } 90 | 91 | @Test 92 | public void testGenericType() { 93 | GenericType genType = GenericType.single("Hello"); 94 | 95 | genType.shout(); 96 | 97 | int length = 98 | genType.matching( 99 | empty -> 0, single -> single.value().length(), container -> container.value().size()); 100 | 101 | assertThat(length).isEqualTo(5); 102 | } 103 | 104 | @Test 105 | public void testAcceptingBasicSingleton() { 106 | Request req = Request.GET("/api/story/32"); 107 | 108 | AtomicBoolean done = new AtomicBoolean(false); 109 | 110 | req.accepting().POST(r -> done.set(false)).orElse(r -> done.set(true)); 111 | 112 | assertThat(done.get()).isTrue(); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /datatyper-lib/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.theoryinpractise.datatyper 7 | datatyper 8 | 1.1.4-SNAPSHOT 9 | 10 | 11 | com.theoryinpractise.datatyper 12 | datatyper-lib 13 | 1.1.4-SNAPSHOT 14 | jar 15 | 16 | datatyper-lib 17 | 18 | 19 | UTF-8 20 | 21 | 22 | 23 | 24 | com.approvaltests 25 | approvaltests 26 | 15.1.2 27 | test 28 | 29 | 30 | org.jparsec 31 | jparsec 32 | 3.1 33 | 34 | 35 | 36 | com.squareup 37 | javapoet 38 | 1.13.0 39 | 40 | 41 | 42 | org.junit.jupiter 43 | junit-jupiter-engine 44 | 5.8.2 45 | test 46 | 47 | 48 | com.google.truth 49 | truth 50 | 1.1.3 51 | test 52 | 53 | 54 | junit 55 | junit 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /datatyper-lib/src/main/java/com/theoryinpractise/datatyper/AccepterGenerator.java: -------------------------------------------------------------------------------- 1 | package com.theoryinpractise.datatyper; 2 | 3 | import com.squareup.javapoet.ClassName; 4 | import com.squareup.javapoet.MethodSpec; 5 | import com.squareup.javapoet.ParameterizedTypeName; 6 | import com.squareup.javapoet.TypeName; 7 | import com.squareup.javapoet.TypeSpec; 8 | import com.squareup.javapoet.TypeVariableName; 9 | import com.theoryinpractise.datatyper.model.DataType; 10 | import com.theoryinpractise.datatyper.model.DataTypeContainer; 11 | 12 | import javax.lang.model.element.Modifier; 13 | import java.util.List; 14 | import java.util.function.Consumer; 15 | 16 | import static java.util.stream.Collectors.toList; 17 | import static com.theoryinpractise.datatyper.Support.camelCase; 18 | import static com.theoryinpractise.datatyper.Support.classNameFor; 19 | import static com.theoryinpractise.datatyper.Support.typeNameFor; 20 | 21 | public class AccepterGenerator { 22 | 23 | public static void generateAccepterInterfaceAndCall( 24 | TypeSpec.Builder gadtTypeBuilder, DataTypeContainer dataTypeContainer) { 25 | // Generate matcher interface 26 | TypeSpec.Builder accepterTypeBuilder = 27 | TypeSpec.interfaceBuilder("Accepter").addModifiers(Modifier.PUBLIC); 28 | 29 | List genericTypeVariableNames = 30 | dataTypeContainer.genericTypes().stream().map(TypeVariableName::get).collect(toList()); 31 | 32 | for (TypeVariableName genericType : genericTypeVariableNames) { 33 | accepterTypeBuilder.addTypeVariable(genericType); 34 | } 35 | 36 | ClassName accepterClassName = 37 | ClassName.get(dataTypeContainer.packageName(), dataTypeContainer.name() + ".Accepter"); 38 | 39 | for (DataType dataType : dataTypeContainer.dataTypes()) { 40 | accepterTypeBuilder.addMethod( 41 | MethodSpec.methodBuilder(camelCase(dataType.name())) 42 | .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) 43 | .addParameter(typeNameFor(dataTypeContainer, dataType), camelCase(dataType.name())) 44 | .build()); 45 | } 46 | 47 | TypeSpec accepterType = accepterTypeBuilder.build(); 48 | gadtTypeBuilder.addType(accepterType); 49 | 50 | MethodSpec.Builder accepterBuilder = 51 | MethodSpec.methodBuilder("accept") 52 | .addModifiers(Modifier.FINAL, Modifier.PUBLIC) 53 | .addParameter(accepterClassName, "accepter"); 54 | for (DataType dataType : dataTypeContainer.dataTypes()) { 55 | TypeName typeName = typeNameFor(dataTypeContainer, dataType); 56 | ClassName className = classNameFor(dataTypeContainer, dataType); 57 | accepterBuilder.beginControlFlow("if (this instanceof $T)", className); 58 | accepterBuilder.addStatement("accepter.$L(($T) this)", camelCase(dataType.name()), typeName); 59 | accepterBuilder.addStatement("return"); 60 | accepterBuilder.endControlFlow(); 61 | } 62 | accepterBuilder.addStatement( 63 | "throw new $T(\"Unexpected $L subclass encountered.\")", 64 | IllegalStateException.class, 65 | dataTypeContainer.name()); 66 | 67 | gadtTypeBuilder.addMethod(accepterBuilder.build()); 68 | } 69 | 70 | public static void generateFluentAcceptingInterfaceAndCall( 71 | TypeSpec.Builder gadtTypeBuilder, DataTypeContainer dataTypeContainer) { 72 | TypeSpec.Builder acceptingTypeBuilder = 73 | TypeSpec.classBuilder("Accepting") 74 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC); 75 | 76 | List genericTypeVariableNames = 77 | dataTypeContainer.genericTypes().stream().map(TypeVariableName::get).collect(toList()); 78 | 79 | for (TypeVariableName genericType : genericTypeVariableNames) { 80 | acceptingTypeBuilder.addTypeVariable(genericType); 81 | } 82 | 83 | ClassName gadtClassName = 84 | ClassName.get(dataTypeContainer.packageName(), dataTypeContainer.name()); 85 | acceptingTypeBuilder.addField(boolean.class, "done", Modifier.PRIVATE); 86 | acceptingTypeBuilder.addField(gadtClassName, "value", Modifier.PRIVATE); 87 | acceptingTypeBuilder.addMethod( 88 | MethodSpec.constructorBuilder() 89 | .addModifiers(Modifier.PRIVATE) 90 | .addParameter(gadtClassName, "value", Modifier.FINAL) 91 | .addStatement("this.value = value") 92 | .build()); 93 | 94 | ClassName acceptingClassName = 95 | ClassName.get(dataTypeContainer.packageName(), dataTypeContainer.name() + ".Accepting"); 96 | 97 | gadtTypeBuilder.addMethod( 98 | MethodSpec.methodBuilder("accepting") 99 | .addModifiers(Modifier.PUBLIC) 100 | .returns(acceptingClassName) 101 | .addStatement("return new $T(this)", acceptingClassName) 102 | .build()); 103 | 104 | for (DataType dataType : dataTypeContainer.dataTypes()) { 105 | TypeName dataTypeClassName = classNameFor(dataTypeContainer, dataType); 106 | TypeName dataTypeTypeName = typeNameFor(dataTypeContainer, dataType); 107 | ParameterizedTypeName function = 108 | ParameterizedTypeName.get(ClassName.get(Consumer.class), dataTypeTypeName); 109 | 110 | acceptingTypeBuilder.addMethod( 111 | MethodSpec.methodBuilder(dataType.name()) 112 | .addModifiers(Modifier.PUBLIC) 113 | .addParameter(function, "fn", Modifier.FINAL) 114 | .returns(acceptingClassName) 115 | .beginControlFlow("if (this.value instanceof $T)", dataTypeClassName) 116 | .addStatement("fn.accept(($T) this.value)", dataTypeTypeName) 117 | .addStatement("this.done = true", dataTypeTypeName) 118 | .endControlFlow() 119 | .addStatement("return this") 120 | .build()); 121 | } 122 | 123 | acceptingTypeBuilder.addMethod( 124 | MethodSpec.methodBuilder("wasAccepted") 125 | .addModifiers(Modifier.PUBLIC) 126 | .returns(boolean.class) 127 | .addStatement("return this.done") 128 | .build()); 129 | 130 | // Add exception throwing partial matching result 131 | acceptingTypeBuilder.addMethod( 132 | MethodSpec.methodBuilder("accept") 133 | .addModifiers(Modifier.PUBLIC) 134 | .beginControlFlow("if (!this.done)") 135 | .addStatement( 136 | "throw new $T(\"Unmatched value: \" + this.value)", IllegalStateException.class) 137 | .endControlFlow() 138 | .build()); 139 | 140 | ParameterizedTypeName function = 141 | ParameterizedTypeName.get(ClassName.get(Consumer.class), gadtClassName); 142 | 143 | acceptingTypeBuilder.addMethod( 144 | MethodSpec.methodBuilder("orElse") 145 | .addModifiers(Modifier.PUBLIC) 146 | .addParameter(function, "fn", Modifier.FINAL) 147 | .beginControlFlow("if (!this.done)") 148 | .addStatement("fn.accept(this.value)") 149 | .endControlFlow() 150 | .beginControlFlow("else") 151 | .addStatement( 152 | "throw new $T(\"Value already matched: \" + this.value)", 153 | IllegalStateException.class) 154 | .endControlFlow() 155 | .build()); 156 | 157 | TypeSpec matchingType = acceptingTypeBuilder.build(); 158 | gadtTypeBuilder.addType(matchingType); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /datatyper-lib/src/main/java/com/theoryinpractise/datatyper/DataTypeCompiler.java: -------------------------------------------------------------------------------- 1 | package com.theoryinpractise.datatyper; 2 | 3 | import com.theoryinpractise.datatyper.model.DataTypeContainer; 4 | 5 | import java.io.File; 6 | import java.io.FileReader; 7 | import java.util.List; 8 | 9 | import static com.theoryinpractise.datatyper.DataTypeGenerator.generateJavaForTypeContainer; 10 | 11 | public final class DataTypeCompiler { 12 | 13 | public static void compileFile(File file, File targetDirectory) { 14 | 15 | try { 16 | List dataTypeContainers = 17 | DatatypeParser.typerFile().parse(new FileReader(file)); 18 | 19 | for (DataTypeContainer dataTypeContainer : dataTypeContainers) { 20 | generateJavaForTypeContainer(dataTypeContainer).writeTo(targetDirectory); 21 | } 22 | 23 | } catch (Exception e) { 24 | throw new RuntimeException(file.getPath() + ": " + e.getMessage(), e); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /datatyper-lib/src/main/java/com/theoryinpractise/datatyper/DataTypeGenerator.java: -------------------------------------------------------------------------------- 1 | package com.theoryinpractise.datatyper; 2 | 3 | import com.squareup.javapoet.ClassName; 4 | import com.squareup.javapoet.FieldSpec; 5 | import com.squareup.javapoet.JavaFile; 6 | import com.squareup.javapoet.MethodSpec; 7 | import com.squareup.javapoet.ParameterizedTypeName; 8 | import com.squareup.javapoet.TypeName; 9 | import com.squareup.javapoet.TypeSpec; 10 | import com.squareup.javapoet.TypeVariableName; 11 | import com.theoryinpractise.datatyper.model.DataType; 12 | import com.theoryinpractise.datatyper.model.Field; 13 | import com.theoryinpractise.datatyper.model.DataTypeContainer; 14 | 15 | import javax.lang.model.element.Modifier; 16 | import java.io.IOException; 17 | import java.util.ArrayList; 18 | import java.util.HashSet; 19 | import java.util.List; 20 | import java.util.Set; 21 | import java.util.function.Function; 22 | import static java.util.stream.Collectors.toList; 23 | import static com.theoryinpractise.datatyper.Support.camelCase; 24 | import static com.theoryinpractise.datatyper.Support.classNameFor; 25 | import static com.theoryinpractise.datatyper.Support.typeNameFor; 26 | 27 | /** Created by amrk on 2/08/15. */ 28 | public class DataTypeGenerator { 29 | 30 | public static JavaFile generateJavaForTypeContainer(DataTypeContainer dataTypeContainer) 31 | throws IOException { 32 | 33 | ClassName gadtClassName = 34 | ClassName.get(dataTypeContainer.packageName(), dataTypeContainer.name()); 35 | ClassName autoValue = ClassName.get("com.google.auto.value", "AutoValue"); 36 | 37 | // top level gadt class 38 | TypeSpec.Builder gadtTypeBuilder = 39 | TypeSpec.classBuilder(dataTypeContainer.name()) 40 | .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT); 41 | 42 | for (String genericType : dataTypeContainer.genericTypes()) { 43 | gadtTypeBuilder.addTypeVariable(TypeVariableName.get(genericType)); 44 | } 45 | 46 | List genericTypeVariableNames = 47 | dataTypeContainer.genericTypes().stream().map(TypeVariableName::get).collect(toList()); 48 | 49 | TypeName gadtTypeName; 50 | if (genericTypeVariableNames.isEmpty()) { 51 | gadtTypeName = gadtClassName; 52 | } else { 53 | gadtTypeName = 54 | ParameterizedTypeName.get( 55 | ClassName.bestGuess(dataTypeContainer.name()), 56 | genericTypeVariableNames.toArray(new TypeVariableName[] {})); 57 | } 58 | 59 | for (String implementsClass : dataTypeContainer.implememts()) { 60 | gadtTypeBuilder.addSuperinterface(resolveClassNameFor(dataTypeContainer, implementsClass)); 61 | } 62 | 63 | gadtTypeBuilder.addMethod( 64 | MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build()); 65 | 66 | for (DataType dataType : dataTypeContainer.dataTypes()) { 67 | 68 | ClassName dataTypeInstanceClassName = 69 | ClassName.get( 70 | dataTypeContainer.packageName(), 71 | "AutoValue_" + dataTypeContainer.name() + "_" + dataType.name()); 72 | TypeName rawDataTypeInterfaceName = classNameFor(dataTypeContainer, dataType); 73 | TypeName dataTypeInterfaceName = typeNameFor(dataTypeContainer, dataType); 74 | 75 | // Create data-type constructor/factory method for each data type 76 | MethodSpec.Builder dataTypeConstuctorBuilder = 77 | MethodSpec.methodBuilder(camelCase(dataType.name())) 78 | .addModifiers(Modifier.PUBLIC, Modifier.STATIC) 79 | .returns(dataTypeInterfaceName); 80 | 81 | // Create data type itself 82 | TypeSpec.Builder dataTypeBuilder = 83 | TypeSpec.classBuilder(dataType.name()) 84 | .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT, Modifier.STATIC) 85 | .addAnnotation(autoValue) 86 | .superclass(gadtTypeName); 87 | 88 | // For each field, if it's a generic type, add that to the declaration(s) 89 | Set usedGenericTypes = new HashSet<>(); 90 | for (String genericType : dataTypeContainer.genericTypes()) { 91 | if (!usedGenericTypes.contains(genericType)) { 92 | TypeVariableName fieldVariable = TypeVariableName.get(genericType); 93 | dataTypeConstuctorBuilder.addTypeVariable(fieldVariable); 94 | dataTypeBuilder.addTypeVariable(fieldVariable); 95 | usedGenericTypes.add(genericType); 96 | } 97 | } 98 | 99 | // For each field, add an argument to the constructor method, and a field to the class. 100 | for (Field field : dataType.fields()) { 101 | TypeName argType = resolveClassNameFor(dataTypeContainer, field.type()); 102 | 103 | dataTypeConstuctorBuilder.addParameter(argType, camelCase(field.name())); 104 | 105 | dataTypeBuilder.addMethod( 106 | MethodSpec.methodBuilder(camelCase(field.name())) 107 | .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) 108 | .returns(argType) 109 | .build()); 110 | } 111 | 112 | TypeSpec dataTypeSpec = dataTypeBuilder.build(); 113 | gadtTypeBuilder.addType(dataTypeSpec); 114 | 115 | // Handle singleton? 116 | if (dataType.fields().isEmpty()) { 117 | // declare singleton 118 | gadtTypeBuilder.addField( 119 | FieldSpec.builder( 120 | rawDataTypeInterfaceName, 121 | dataType.name(), 122 | Modifier.PRIVATE, 123 | Modifier.STATIC, 124 | Modifier.FINAL) 125 | .initializer("new $T()", dataTypeInstanceClassName) 126 | .build()); 127 | 128 | // generate accessor method 129 | MethodSpec dataTypeConstuctor = 130 | dataTypeConstuctorBuilder 131 | .addStatement("return $L", dataType.name()) 132 | .returns(dataTypeInterfaceName) 133 | .build(); 134 | 135 | gadtTypeBuilder.addMethod(dataTypeConstuctor); 136 | 137 | } else { 138 | 139 | MethodSpec dataTypeConstuctor = 140 | dataTypeConstuctorBuilder 141 | .addStatement( 142 | "return new $T($L)", 143 | dataTypeInstanceClassName, 144 | String.join(", ", liftFieldNames(dataType))) 145 | .returns(dataTypeInterfaceName) 146 | .build(); 147 | 148 | gadtTypeBuilder.addMethod(dataTypeConstuctor); 149 | } 150 | } 151 | 152 | if (dataTypeContainer.dataTypes().size() > 1) { 153 | MatcherGenerator.generateMatcherInterfaceAndCall(gadtTypeBuilder, dataTypeContainer); 154 | MatcherGenerator.generateFluentMatcherInterfaceAndCall(gadtTypeBuilder, dataTypeContainer); 155 | AccepterGenerator.generateAccepterInterfaceAndCall(gadtTypeBuilder, dataTypeContainer); 156 | AccepterGenerator.generateFluentAcceptingInterfaceAndCall(gadtTypeBuilder, dataTypeContainer); 157 | generateLambdaMatchingInterfaceAndCall(gadtTypeBuilder, dataTypeContainer); 158 | } 159 | 160 | TypeSpec gadtType = gadtTypeBuilder.build(); 161 | 162 | return JavaFile.builder(dataTypeContainer.packageName(), gadtType) 163 | .skipJavaLangImports(true) 164 | .build(); 165 | } 166 | 167 | private static List liftFieldNames(DataType dataType) { 168 | List fieldNames = new ArrayList<>(); 169 | for (Field field : dataType.fields()) { 170 | fieldNames.add(field.name()); 171 | } 172 | return fieldNames; 173 | } 174 | 175 | private static void generateLambdaMatchingInterfaceAndCall( 176 | TypeSpec.Builder gadtTypeBuilder, DataTypeContainer dataTypeContainer) { 177 | TypeVariableName returnTypeVariable = TypeVariableName.get("Return"); 178 | 179 | MethodSpec.Builder lambdaMethod = 180 | MethodSpec.methodBuilder("matching") 181 | .addModifiers(Modifier.PUBLIC) 182 | .addTypeVariable(returnTypeVariable) 183 | .returns(returnTypeVariable); 184 | 185 | dataTypeContainer 186 | .dataTypes() 187 | .forEach( 188 | dataType -> { 189 | TypeName dataTypeClassName = classNameFor(dataTypeContainer, dataType); 190 | TypeName dataTypeTypeName = typeNameFor(dataTypeContainer, dataType); 191 | ParameterizedTypeName function = 192 | ParameterizedTypeName.get( 193 | ClassName.get(Function.class), dataTypeTypeName, returnTypeVariable); 194 | String funtionArgName = camelCase(dataType.name()); 195 | 196 | lambdaMethod.addParameter(function, funtionArgName); 197 | 198 | lambdaMethod.beginControlFlow("if (this instanceof $T)", dataTypeClassName); 199 | lambdaMethod.addStatement( 200 | "return $L.apply(($T) this)", funtionArgName, dataTypeTypeName); 201 | lambdaMethod.endControlFlow(); 202 | }); 203 | 204 | lambdaMethod.addStatement( 205 | "throw new $T(\"Unexpected $L subclass encountered.\")", 206 | IllegalStateException.class, 207 | dataTypeContainer.name()); 208 | 209 | gadtTypeBuilder.addMethod(lambdaMethod.build()); 210 | } 211 | 212 | public static String expandImport(List imports, String value) { 213 | String expanded = value; 214 | for (String importVal : imports) { 215 | String localType = importVal.substring(importVal.lastIndexOf(".") + 1); 216 | expanded = expanded.replaceAll(localType, importVal); 217 | } 218 | return expanded; 219 | } 220 | 221 | private static TypeName resolveClassNameFor( 222 | DataTypeContainer dataTypeContainer, String classReference) { 223 | 224 | // generic type 225 | if (dataTypeContainer.genericTypes().contains(classReference)) { 226 | return TypeVariableName.get(classReference); 227 | } 228 | 229 | // test for generics - do a half arsed attempt as wacky resolution with javapoet 230 | if (classReference.contains("<")) { 231 | String expandedClassReference = expandImport(dataTypeContainer.imports(), classReference); 232 | String[] types = expandedClassReference.replaceAll("[<|>|,]", " ").split(" "); 233 | ClassName baseClassName = ClassName.bestGuess(types[0]); 234 | ClassName[] typeArgs = new ClassName[types.length - 1]; 235 | for (int i = 1; i < types.length; i++) { 236 | typeArgs[i - 1] = ClassName.bestGuess(types[i]); 237 | } 238 | return ParameterizedTypeName.get(baseClassName, typeArgs); 239 | } 240 | 241 | // if full class, just guess 242 | if (classReference.contains(".")) { 243 | return ClassName.bestGuess(classReference); 244 | } 245 | 246 | // else find imported class 247 | for (String importedClass : dataTypeContainer.imports()) { 248 | if (importedClass.endsWith(classReference)) { 249 | return ClassName.bestGuess(importedClass); 250 | } 251 | } 252 | 253 | // standard class? 254 | try { 255 | Class.forName("java.lang." + classReference); 256 | return ClassName.get("java.lang", classReference); 257 | } catch (ClassNotFoundException e) { 258 | throw new RuntimeException("Undeclared class: " + classReference); 259 | } 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /datatyper-lib/src/main/java/com/theoryinpractise/datatyper/DatatypeParser.java: -------------------------------------------------------------------------------- 1 | package com.theoryinpractise.datatyper; 2 | 3 | import com.theoryinpractise.datatyper.model.DataType; 4 | import com.theoryinpractise.datatyper.model.Field; 5 | import com.theoryinpractise.datatyper.model.DataTypeContainer; 6 | import org.jparsec.Parser; 7 | import org.jparsec.Parsers; 8 | import org.jparsec.Scanners; 9 | import org.jparsec.functors.Pair; 10 | import org.jparsec.pattern.Patterns; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Collections; 14 | import static java.util.Collections.emptyList; 15 | import java.util.HashSet; 16 | import java.util.List; 17 | import java.util.concurrent.atomic.AtomicReference; 18 | 19 | import static org.jparsec.Parsers.between; 20 | import static org.jparsec.Parsers.or; 21 | import static org.jparsec.Scanners.WHITESPACES; 22 | import static org.jparsec.Scanners.isChar; 23 | import static org.jparsec.Scanners.string; 24 | 25 | /** Parser for GADT data declarations. */ 26 | public final class DatatypeParser { 27 | 28 | static Parser comments = or(WHITESPACES, Scanners.HASKELL_LINE_COMMENT).skipMany(); 29 | 30 | public static Parser label = Patterns.regex("[a-z|A-Z]+").toScanner("regex").source(); 31 | 32 | public static Parser genericType = 33 | Patterns.regex("[a-z|A-Z][a-z|A-Z]*").toScanner("generic-type-regex").source(); 34 | 35 | public static Parser className = 36 | Patterns.regex("[a-z|A-Z][a-z|A-Z|0-9|\\.]+").toScanner("class-regex").source(); 37 | 38 | public static Parser typeName = 39 | Patterns.regex("[a-z|A-Z][<|>|a-z|A-Z|0-9|\\.]*").toScanner("type-regex").source(); 40 | 41 | public static Parser separator(char separator) { 42 | return WHITESPACES.optional(null).next(isChar(separator)).next(WHITESPACES.optional(null)); 43 | } 44 | 45 | public static Parser typeSeparator = separator(':'); 46 | 47 | public static Parser fieldSeparator = separator(','); 48 | 49 | public static Parser packageDecl() { 50 | return string("package").next(WHITESPACES).next(className.followedBy(string(";"))); 51 | } 52 | 53 | public static Parser importDecl() { 54 | return WHITESPACES 55 | .optional(null) 56 | .next(string("import")) 57 | .next(WHITESPACES) 58 | .next(className) 59 | .followedBy(isChar(';')); 60 | } 61 | 62 | public static Parser field() { 63 | return Parsers.tuple(label.followedBy(typeSeparator), typeName) 64 | .map(field -> new Field(field.a, field.b)); 65 | } 66 | 67 | public static Parser> fields() { 68 | return between(string("("), field().sepBy(fieldSeparator), string(")")); 69 | } 70 | 71 | public static Parser dataType() { 72 | Parser> fields = fields().optional(new ArrayList<>()); 73 | return Parsers.tuple(label.followedBy(WHITESPACES.optional(null)), fields) 74 | .map(dataType -> new DataType(dataType.a, dataType.b)); 75 | } 76 | 77 | public static Parser> dataTypes() { 78 | return dataType() 79 | .sepBy1(comments.next(Parsers.sequence(comments, string("|"), comments))) 80 | .followedBy(or(wsString("implements"), isChar(';')).peek()); 81 | } 82 | 83 | private static Parser parenthesize(Parser parser) { 84 | return parser.between(isChar('('), isChar(')')); 85 | } 86 | 87 | private static Parser angleParenthesize(Parser parser) { 88 | return parser.between(isChar('<'), isChar('>')); 89 | } 90 | 91 | private static Parser wsString(String string) { 92 | return WHITESPACES.optional(null).next(string(string)).next(WHITESPACES.optional(null)); 93 | } 94 | 95 | public static Parser gadt( 96 | AtomicReference packageName, 97 | Pair>, AtomicReference>> importList) { 98 | 99 | Parser> classNameList = className.followedBy(fieldSeparator.optional(null)).many(); 100 | 101 | Parser> genericTypeList = 102 | genericType.followedBy(fieldSeparator.optional(null)).many(); 103 | 104 | Parser> genericTypesClause = angleParenthesize(genericTypeList); 105 | 106 | Parser> optionalGenericTypes = genericTypesClause.optional(emptyList()); 107 | 108 | Parser> implementsClause = 109 | wsString("implements").next(parenthesize(classNameList)).followedBy(wsString("=")); 110 | 111 | Parser> optionalImplementsClause = 112 | or(wsString("=").map(aVoid -> emptyList()), implementsClause); 113 | 114 | return comments 115 | .next(importList.a) 116 | .next(comments) 117 | .next( 118 | string("data") 119 | .next(WHITESPACES) 120 | .next( 121 | Parsers.tuple( 122 | label, optionalGenericTypes, optionalImplementsClause, dataTypes()) 123 | .followedBy(comments) 124 | .map( 125 | gadt -> 126 | new DataTypeContainer( 127 | gadt.a, 128 | packageName.get(), 129 | gadt.b, 130 | gadt.d, 131 | copyOfList(importList.b.get()), 132 | new HashSet<>(gadt.c))))) 133 | .followedBy(isChar(';').next(comments)); 134 | } 135 | 136 | private static List copyOfList(List... originalLists) { 137 | if (originalLists != null && originalLists.length != 0) { 138 | List copiedList = new ArrayList<>(); 139 | for (List originalList : originalLists) { 140 | if (originalList != null && !originalList.isEmpty()) { 141 | copiedList.addAll(originalList); 142 | } 143 | } 144 | return copiedList; 145 | } else { 146 | return Collections.emptyList(); 147 | } 148 | } 149 | 150 | public static Parser> typerFile() { 151 | AtomicReference pacakgeRef = new AtomicReference<>(); 152 | 153 | Parser packageName = 154 | packageDecl() 155 | .map( 156 | pack -> { 157 | pacakgeRef.set(pack); 158 | return pack; 159 | }); 160 | 161 | Pair>, AtomicReference>> importList = buildImportListParser(); 162 | 163 | return comments 164 | .next(packageName) 165 | .next(comments.next(importList.a)) 166 | .next(gadt(pacakgeRef, importList).many()); 167 | } 168 | 169 | public static Pair>, AtomicReference>> buildImportListParser() { 170 | AtomicReference> importListRef = new AtomicReference<>(); 171 | 172 | Parser> parser = 173 | importDecl() 174 | .many() 175 | .map( 176 | imports -> { 177 | importListRef.getAndUpdate( 178 | existingImports -> copyOfList(imports, existingImports)); 179 | return imports; 180 | }); 181 | 182 | return new Pair<>(parser, importListRef); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /datatyper-lib/src/main/java/com/theoryinpractise/datatyper/MatcherGenerator.java: -------------------------------------------------------------------------------- 1 | package com.theoryinpractise.datatyper; 2 | 3 | import com.squareup.javapoet.ClassName; 4 | import com.squareup.javapoet.MethodSpec; 5 | import com.squareup.javapoet.ParameterizedTypeName; 6 | import com.squareup.javapoet.TypeName; 7 | import com.squareup.javapoet.TypeSpec; 8 | import com.squareup.javapoet.TypeVariableName; 9 | import com.theoryinpractise.datatyper.model.DataType; 10 | import com.theoryinpractise.datatyper.model.DataTypeContainer; 11 | 12 | import javax.lang.model.element.Modifier; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Optional; 16 | import java.util.function.Consumer; 17 | import java.util.function.Function; 18 | 19 | import static java.util.stream.Collectors.toList; 20 | import static com.theoryinpractise.datatyper.Support.camelCase; 21 | import static com.theoryinpractise.datatyper.Support.classNameFor; 22 | import static com.theoryinpractise.datatyper.Support.typeNameFor; 23 | 24 | public class MatcherGenerator { 25 | 26 | public static void generateMatcherInterfaceAndCall( 27 | TypeSpec.Builder gadtTypeBuilder, DataTypeContainer dataTypeContainer) { 28 | // Generate matcher interface 29 | TypeVariableName returnTypeVariable = TypeVariableName.get("Return"); 30 | TypeSpec.Builder matcherTypeBuilder = 31 | TypeSpec.interfaceBuilder("Matcher").addModifiers(Modifier.PUBLIC); 32 | 33 | List genericTypeVariableNames = 34 | dataTypeContainer.genericTypes().stream().map(TypeVariableName::get).collect(toList()); 35 | 36 | for (TypeVariableName genericType : genericTypeVariableNames) { 37 | matcherTypeBuilder.addTypeVariable(genericType); 38 | } 39 | matcherTypeBuilder.addTypeVariable(returnTypeVariable); 40 | 41 | ClassName matcherClassName = 42 | ClassName.get(dataTypeContainer.packageName(), dataTypeContainer.name() + ".Matcher"); 43 | 44 | for (DataType dataType : dataTypeContainer.dataTypes()) { 45 | matcherTypeBuilder.addMethod( 46 | MethodSpec.methodBuilder(camelCase(dataType.name())) 47 | .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) 48 | .addParameter(typeNameFor(dataTypeContainer, dataType), camelCase(dataType.name())) 49 | .returns(returnTypeVariable) 50 | .build()); 51 | } 52 | 53 | TypeSpec matcherType = matcherTypeBuilder.build(); 54 | gadtTypeBuilder.addType(matcherType); 55 | 56 | // add Matcher match method 57 | List matcherTypes = new ArrayList<>(genericTypeVariableNames); 58 | matcherTypes.add(returnTypeVariable); 59 | 60 | ParameterizedTypeName matcherWithReturnType = 61 | ParameterizedTypeName.get( 62 | matcherClassName, matcherTypes.toArray(new TypeVariableName[] {})); 63 | 64 | MethodSpec.Builder matcherBuilder = 65 | MethodSpec.methodBuilder("match") 66 | .addModifiers(Modifier.FINAL, Modifier.PUBLIC) 67 | .addTypeVariable(returnTypeVariable) 68 | .addParameter(matcherWithReturnType, "matcher"); 69 | for (DataType dataType : dataTypeContainer.dataTypes()) { 70 | TypeName typeName = typeNameFor(dataTypeContainer, dataType); 71 | ClassName className = classNameFor(dataTypeContainer, dataType); 72 | matcherBuilder.beginControlFlow("if (this instanceof $T)", className); 73 | matcherBuilder.addStatement( 74 | "return matcher.$L(($T) this)", camelCase(dataType.name()), typeName); 75 | matcherBuilder.endControlFlow(); 76 | } 77 | matcherBuilder.addStatement( 78 | "throw new $T(\"Unexpected $L subclass encountered.\")", 79 | IllegalStateException.class, 80 | dataTypeContainer.name()); 81 | matcherBuilder.returns(returnTypeVariable).build(); 82 | 83 | gadtTypeBuilder.addMethod(matcherBuilder.build()); 84 | } 85 | 86 | public static void generateFluentMatcherInterfaceAndCall( 87 | TypeSpec.Builder gadtTypeBuilder, DataTypeContainer dataTypeContainer) { 88 | TypeVariableName returnTypeVariable = TypeVariableName.get("Return"); 89 | TypeSpec.Builder matchingTypeBuilder = 90 | TypeSpec.classBuilder("Matching") 91 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC); 92 | 93 | List genericTypeVariableNames = 94 | dataTypeContainer.genericTypes().stream().map(TypeVariableName::get).collect(toList()); 95 | 96 | for (TypeVariableName genericType : genericTypeVariableNames) { 97 | matchingTypeBuilder.addTypeVariable(genericType); 98 | } 99 | matchingTypeBuilder.addTypeVariable(returnTypeVariable); 100 | 101 | ClassName gadtClassName = 102 | ClassName.get(dataTypeContainer.packageName(), dataTypeContainer.name()); 103 | matchingTypeBuilder.addField(gadtClassName, "value", Modifier.PRIVATE); 104 | matchingTypeBuilder.addField(returnTypeVariable, "returnValue", Modifier.PRIVATE); 105 | matchingTypeBuilder.addMethod( 106 | MethodSpec.constructorBuilder() 107 | .addModifiers(Modifier.PRIVATE) 108 | .addParameter(gadtClassName, "value", Modifier.FINAL) 109 | .addStatement("this.value = value") 110 | .build()); 111 | 112 | ClassName matchingClassName = 113 | ClassName.get(dataTypeContainer.packageName(), dataTypeContainer.name() + ".Matching"); 114 | 115 | // TODO pulling out generic type variable names is becoming common, extract. 116 | List matcherTypes = new ArrayList<>(genericTypeVariableNames); 117 | matcherTypes.add(returnTypeVariable); 118 | 119 | ParameterizedTypeName matchingWithReturnType = 120 | ParameterizedTypeName.get( 121 | matchingClassName, matcherTypes.toArray(new TypeVariableName[] {})); 122 | 123 | gadtTypeBuilder.addMethod( 124 | MethodSpec.methodBuilder("matching") 125 | .addModifiers(Modifier.PUBLIC) 126 | .addTypeVariable(returnTypeVariable) 127 | .returns(matchingWithReturnType) 128 | .addStatement("return new $T(this)", matchingWithReturnType) 129 | .build()); 130 | 131 | for (DataType dataType : dataTypeContainer.dataTypes()) { 132 | TypeName dataTypeClassName = classNameFor(dataTypeContainer, dataType); 133 | TypeName dataTypeTypeName = typeNameFor(dataTypeContainer, dataType); 134 | ParameterizedTypeName function = 135 | ParameterizedTypeName.get( 136 | ClassName.get(Function.class), dataTypeTypeName, returnTypeVariable); 137 | 138 | matchingTypeBuilder.addMethod( 139 | MethodSpec.methodBuilder(dataType.name()) 140 | .addModifiers(Modifier.PUBLIC) 141 | .addParameter(function, "fn", Modifier.FINAL) 142 | .returns(matchingWithReturnType) 143 | .beginControlFlow("if (this.value instanceof $T)", dataTypeClassName) 144 | .addStatement("this.returnValue = fn.apply(($T) this.value)", dataTypeTypeName) 145 | .endControlFlow() 146 | .addStatement("return this") 147 | .build()); 148 | } 149 | 150 | matchingTypeBuilder.addMethod( 151 | MethodSpec.methodBuilder("isMatched") 152 | .addModifiers(Modifier.PUBLIC) 153 | .returns(boolean.class) 154 | .addStatement("return this.returnValue != null") 155 | .build()); 156 | 157 | ParameterizedTypeName optionalReturnType = 158 | ParameterizedTypeName.get(ClassName.get(Optional.class), returnTypeVariable); 159 | 160 | // Add exception throwing partial matching result 161 | matchingTypeBuilder.addMethod( 162 | MethodSpec.methodBuilder("get") 163 | .addModifiers(Modifier.PUBLIC) 164 | .returns(returnTypeVariable) 165 | .beginControlFlow("if (this.returnValue != null)") 166 | .addStatement("return this.returnValue") 167 | .endControlFlow() 168 | .beginControlFlow("else") 169 | .addStatement( 170 | "throw new $T(\"Unmatched value: \" + this.value)", IllegalStateException.class) 171 | .endControlFlow() 172 | .build()); 173 | 174 | // Add partial matching result 175 | matchingTypeBuilder.addMethod( 176 | MethodSpec.methodBuilder("find") 177 | .addModifiers(Modifier.PUBLIC) 178 | .returns(optionalReturnType) 179 | .addStatement("return $T.ofNullable(this.returnValue)", Optional.class) 180 | .build()); 181 | // Add total matching result 182 | matchingTypeBuilder.addMethod( 183 | MethodSpec.methodBuilder("orElse") 184 | .addModifiers(Modifier.PUBLIC) 185 | .addParameter(returnTypeVariable, "returnValue", Modifier.FINAL) 186 | .returns(returnTypeVariable) 187 | .beginControlFlow("if (this.returnValue != null)") 188 | .addStatement("return this.returnValue") 189 | .endControlFlow() 190 | .beginControlFlow("else") 191 | .addStatement("return returnValue") 192 | .endControlFlow() 193 | .build()); 194 | 195 | TypeSpec matchingType = matchingTypeBuilder.build(); 196 | gadtTypeBuilder.addType(matchingType); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /datatyper-lib/src/main/java/com/theoryinpractise/datatyper/Support.java: -------------------------------------------------------------------------------- 1 | package com.theoryinpractise.datatyper; 2 | 3 | import com.squareup.javapoet.ClassName; 4 | import com.squareup.javapoet.ParameterizedTypeName; 5 | import com.squareup.javapoet.TypeName; 6 | import com.squareup.javapoet.TypeVariableName; 7 | import com.theoryinpractise.datatyper.model.DataType; 8 | import com.theoryinpractise.datatyper.model.DataTypeContainer; 9 | 10 | public class Support { 11 | 12 | // Convert to camelCase unless ALLCAPS 13 | public static String camelCase(String name) { 14 | if (name.toUpperCase().equals(name)) { 15 | return name; 16 | } else { 17 | return name.length() == 1 18 | ? name.toLowerCase() 19 | : name.substring(0, 1).toLowerCase() + name.substring(1); 20 | } 21 | } 22 | 23 | public static ClassName classNameFor(DataTypeContainer dataTypeContainer, DataType dataType) { 24 | return ClassName.get( 25 | dataTypeContainer.packageName(), dataTypeContainer.name() + "." + dataType.name()); 26 | } 27 | 28 | public static TypeName typeNameFor(DataTypeContainer dataTypeContainer, DataType dataType) { 29 | 30 | ClassName className = 31 | ClassName.get( 32 | dataTypeContainer.packageName(), dataTypeContainer.name() + "." + dataType.name()); 33 | 34 | if (dataTypeContainer.genericTypes().isEmpty()) { 35 | return className; 36 | 37 | } else { 38 | 39 | TypeVariableName[] typeNames = new TypeVariableName[dataTypeContainer.genericTypes().size()]; 40 | for (int i = 0; i < dataTypeContainer.genericTypes().size(); i++) { 41 | typeNames[i] = TypeVariableName.get(dataTypeContainer.genericTypes().get(i)); 42 | } 43 | return ParameterizedTypeName.get(className, typeNames); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /datatyper-lib/src/main/java/com/theoryinpractise/datatyper/model/DataType.java: -------------------------------------------------------------------------------- 1 | package com.theoryinpractise.datatyper.model; 2 | 3 | import java.util.List; 4 | import java.util.Objects; 5 | 6 | public final class DataType { 7 | 8 | private String name; 9 | 10 | private List fields; 11 | 12 | public DataType(String name, List fields) { 13 | this.name = name; 14 | this.fields = fields; 15 | } 16 | 17 | public String name() { 18 | return name; 19 | } 20 | 21 | public List fields() { 22 | return fields; 23 | } 24 | 25 | @Override 26 | public boolean equals(Object o) { 27 | if (this == o) return true; 28 | if (o == null || getClass() != o.getClass()) return false; 29 | DataType dataType = (DataType) o; 30 | return Objects.equals(name, dataType.name) && Objects.equals(fields, dataType.fields); 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | return Objects.hash(name, fields); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /datatyper-lib/src/main/java/com/theoryinpractise/datatyper/model/DataTypeContainer.java: -------------------------------------------------------------------------------- 1 | package com.theoryinpractise.datatyper.model; 2 | 3 | import java.util.List; 4 | import java.util.Objects; 5 | import java.util.Set; 6 | 7 | public final class DataTypeContainer { 8 | 9 | private String name; 10 | 11 | private String packageName; 12 | 13 | private List genericTypes; 14 | 15 | private List dataTypes; 16 | 17 | private List imports; 18 | 19 | private Set implememts; 20 | 21 | public DataTypeContainer( 22 | String name, 23 | String packageName, 24 | List genericTypes, 25 | List dataTypes, 26 | List imports, 27 | Set implememts) { 28 | this.name = name; 29 | this.packageName = packageName; 30 | this.genericTypes = genericTypes; 31 | this.dataTypes = dataTypes; 32 | this.imports = imports; 33 | this.implememts = implememts; 34 | } 35 | 36 | public String name() { 37 | return name; 38 | } 39 | 40 | public String packageName() { 41 | return packageName; 42 | } 43 | 44 | public List genericTypes() { 45 | return genericTypes; 46 | } 47 | 48 | public List dataTypes() { 49 | return dataTypes; 50 | } 51 | 52 | public List imports() { 53 | return imports; 54 | } 55 | 56 | public Set implememts() { 57 | return implememts; 58 | } 59 | 60 | @Override 61 | public boolean equals(Object o) { 62 | if (this == o) return true; 63 | if (o == null || getClass() != o.getClass()) return false; 64 | DataTypeContainer dataTypeContainer = (DataTypeContainer) o; 65 | return Objects.equals(name, dataTypeContainer.name) 66 | && Objects.equals(packageName, dataTypeContainer.packageName) 67 | && Objects.equals(genericTypes, dataTypeContainer.genericTypes) 68 | && Objects.equals(dataTypes, dataTypeContainer.dataTypes) 69 | && Objects.equals(imports, dataTypeContainer.imports) 70 | && Objects.equals(implememts, dataTypeContainer.implememts); 71 | } 72 | 73 | @Override 74 | public int hashCode() { 75 | return Objects.hash(name, packageName, genericTypes, dataTypes, imports, implememts); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /datatyper-lib/src/main/java/com/theoryinpractise/datatyper/model/Field.java: -------------------------------------------------------------------------------- 1 | package com.theoryinpractise.datatyper.model; 2 | 3 | import java.util.Objects; 4 | 5 | public final class Field { 6 | 7 | private String name; 8 | 9 | private String type; 10 | 11 | public Field(String name, String type) { 12 | this.name = name; 13 | this.type = type; 14 | } 15 | 16 | public String name() { 17 | return name; 18 | } 19 | 20 | public String type() { 21 | return type; 22 | } 23 | 24 | @Override 25 | public boolean equals(Object o) { 26 | if (this == o) return true; 27 | if (o == null || getClass() != o.getClass()) return false; 28 | Field field = (Field) o; 29 | return Objects.equals(name, field.name) && Objects.equals(type, field.type); 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(name, type); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /datatyper-lib/src/test/java/com/theoryinpractise/datatyper/DatatyperTest.java: -------------------------------------------------------------------------------- 1 | package com.theoryinpractise.datatyper; 2 | 3 | import com.theoryinpractise.datatyper.model.DataType; 4 | import com.theoryinpractise.datatyper.model.Field; 5 | import com.theoryinpractise.datatyper.model.DataTypeContainer; 6 | import org.jparsec.Parser; 7 | import org.jparsec.functors.Pair; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStreamReader; 11 | import java.util.Arrays; 12 | import java.util.Collections; 13 | import java.util.List; 14 | import java.util.concurrent.atomic.AtomicReference; 15 | 16 | import static com.google.common.truth.Truth.assertThat; 17 | import static com.theoryinpractise.datatyper.DataTypeGenerator.expandImport; 18 | import static com.theoryinpractise.datatyper.DataTypeGenerator.generateJavaForTypeContainer; 19 | import static com.theoryinpractise.datatyper.DatatypeParser.buildImportListParser; 20 | import java.io.StringWriter; 21 | import java.io.Writer; 22 | import org.approvaltests.Approvals; 23 | import org.junit.jupiter.api.Test; 24 | 25 | public class DatatyperTest { 26 | 27 | final String string = "java.lang.String"; 28 | 29 | @Test 30 | public void testLabel() { 31 | assertThat(DatatypeParser.label.parse("name")).isEqualTo("name"); 32 | } 33 | 34 | @Test 35 | public void testFields() { 36 | assertThat(DatatypeParser.field().parse("name : java.lang.String")) 37 | .isEqualTo(new Field("name", string)); 38 | assertThat(DatatypeParser.field().parse("name:java.lang.String")) 39 | .isEqualTo(new Field("name", string)); 40 | } 41 | 42 | @Test 43 | public void testSingletonDataTypes() { 44 | assertThat(DatatypeParser.dataType().parse("DataType")) 45 | .isEqualTo(new DataType("DataType", Collections.emptyList())); 46 | } 47 | 48 | @Test 49 | public void testDataTypes() { 50 | assertThat(DatatypeParser.dataType().parse("DataType(name: java.lang.String)")) 51 | .isEqualTo(new DataType("DataType", Arrays.asList(new Field("name", string)))); 52 | 53 | assertThat(DatatypeParser.dataType().parse("DataType (name: java.lang.String)")) 54 | .isEqualTo(new DataType("DataType", Arrays.asList(new Field("name", string)))); 55 | 56 | assertThat( 57 | DatatypeParser.dataType() 58 | .parse("DataType(name: java.lang.String, age: java.lang.Integer)")) 59 | .isEqualTo( 60 | new DataType( 61 | "DataType", 62 | Arrays.asList(new Field("name", string), new Field("age", "java.lang.Integer")))); 63 | } 64 | 65 | @Test 66 | public void testGadtDeclarations() { 67 | AtomicReference testPackage = new AtomicReference<>("com.test"); 68 | Pair>, AtomicReference>> testImports = buildImportListParser(); 69 | 70 | assertThat( 71 | DatatypeParser.gadt(testPackage, testImports) 72 | .parse("data Type = DataType(name: java.lang.String);") 73 | .dataTypes()) 74 | .hasSize(1); 75 | 76 | assertThat( 77 | DatatypeParser.gadt(testPackage, testImports) 78 | .parse("data Type = DataType(name: java.lang.String)\n | SecondDataType(age:int);") 79 | .dataTypes()) 80 | .hasSize(2); 81 | 82 | String source = 83 | "import some.Object;\n" 84 | + "import some.OtherObject;\n\n" 85 | + "data Type = DataType(name: java.lang.String)\n" 86 | + " | SecondDataType(age: Integer);\n\n\n"; 87 | 88 | DataTypeContainer gadt = DatatypeParser.gadt(testPackage, testImports).parse(source); 89 | 90 | assertThat(gadt.imports()).containsExactly("some.Object", "some.OtherObject"); 91 | 92 | System.out.println(gadt); 93 | } 94 | 95 | @Test 96 | public void testImportExpansion() { 97 | List imports = Arrays.asList("some.Object", "some.OtherObject", "java.util.Set"); 98 | assertThat(expandImport(imports, "Set")).isEqualTo("java.util.Set"); 99 | } 100 | 101 | @Test 102 | public void testGadtFiles() throws IOException { 103 | List dataTypeContainers = 104 | DatatypeParser.typerFile() 105 | .parse(new InputStreamReader(DatatyperTest.class.getResourceAsStream("/Test.typer"))); 106 | 107 | assertThat(dataTypeContainers).hasSize(5); 108 | 109 | Writer writer = new StringWriter(); 110 | 111 | for (DataTypeContainer dataTypeContainer : dataTypeContainers) { 112 | generateJavaForTypeContainer(dataTypeContainer).writeTo(writer); 113 | writer.append("\n\n"); 114 | } 115 | 116 | Approvals.verify(writer.toString()); 117 | } 118 | 119 | @Test 120 | public void testImplementsClause() { 121 | String source = 122 | "" 123 | + "data Type\n" 124 | + " implements (foo)" 125 | + " = DataType(name: java.lang.String)\n" 126 | + " | SecondDataType(age: Integer);\n\n\n"; 127 | 128 | AtomicReference testPackage = new AtomicReference<>("com.test"); 129 | Pair>, AtomicReference>> testImports = buildImportListParser(); 130 | 131 | DatatypeParser.gadt(testPackage, testImports).parse(source); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /datatyper-lib/src/test/java/com/theoryinpractise/datatyper/PackageSettings.java: -------------------------------------------------------------------------------- 1 | package com.theoryinpractise.datatyper; 2 | 3 | /** 4 | * Approval test configuration - it's kinda weird way of doing it, but we're working on changing 5 | * that. 6 | * 7 | *

See https://github.com/approvals/ApprovalTests.Java/issues/2 8 | */ 9 | public class PackageSettings { 10 | public static String ApprovalBaseDirectory = "../resources/"; 11 | } 12 | -------------------------------------------------------------------------------- /datatyper-lib/src/test/resources/Test.typer: -------------------------------------------------------------------------------- 1 | -- Standard Data Types to use in the program 2 | package com.test; 3 | 4 | import java.util.Set; 5 | 6 | data Type = DataType(name: String) 7 | | SecondDataType(age: Integer); 8 | 9 | data Request 10 | = GET(path : String, query: String) 11 | | PUT(path : String, body: String) 12 | | DELETE(path : String); 13 | 14 | data Day 15 | = Monday | Tuesday | Wednesday 16 | | Thursday | Friday | Saturday | Sunday; 17 | 18 | data GenericAttribute 19 | = Empty 20 | | Container(value: Set); 21 | 22 | data GenericType 23 | = Empty 24 | | Single(value: T) 25 | | Container(value: Set); 26 | -------------------------------------------------------------------------------- /datatyper-lib/src/test/resources/com/theoryinpractise/datatyper/DatatyperTest.testGadtFiles.approved.txt: -------------------------------------------------------------------------------- 1 | package com.test; 2 | 3 | import com.google.auto.value.AutoValue; 4 | import java.util.Optional; 5 | import java.util.function.Consumer; 6 | import java.util.function.Function; 7 | 8 | public abstract class Type { 9 | private Type() { 10 | } 11 | 12 | public static Type.DataType dataType(String name) { 13 | return new AutoValue_Type_DataType(name); 14 | } 15 | 16 | public static Type.SecondDataType secondDataType(Integer age) { 17 | return new AutoValue_Type_SecondDataType(age); 18 | } 19 | 20 | public final Return match(Type.Matcher matcher) { 21 | if (this instanceof Type.DataType) { 22 | return matcher.dataType((Type.DataType) this); 23 | } 24 | if (this instanceof Type.SecondDataType) { 25 | return matcher.secondDataType((Type.SecondDataType) this); 26 | } 27 | throw new IllegalStateException("Unexpected Type subclass encountered."); 28 | } 29 | 30 | public Type.Matching matching() { 31 | return new Type.Matching(this); 32 | } 33 | 34 | public final void accept(Type.Accepter accepter) { 35 | if (this instanceof Type.DataType) { 36 | accepter.dataType((Type.DataType) this); 37 | return; 38 | } 39 | if (this instanceof Type.SecondDataType) { 40 | accepter.secondDataType((Type.SecondDataType) this); 41 | return; 42 | } 43 | throw new IllegalStateException("Unexpected Type subclass encountered."); 44 | } 45 | 46 | public Type.Accepting accepting() { 47 | return new Type.Accepting(this); 48 | } 49 | 50 | public Return matching(Function dataType, 51 | Function secondDataType) { 52 | if (this instanceof Type.DataType) { 53 | return dataType.apply((Type.DataType) this); 54 | } 55 | if (this instanceof Type.SecondDataType) { 56 | return secondDataType.apply((Type.SecondDataType) this); 57 | } 58 | throw new IllegalStateException("Unexpected Type subclass encountered."); 59 | } 60 | 61 | @AutoValue 62 | public abstract static class DataType extends Type { 63 | public abstract String name(); 64 | } 65 | 66 | @AutoValue 67 | public abstract static class SecondDataType extends Type { 68 | public abstract Integer age(); 69 | } 70 | 71 | public interface Matcher { 72 | Return dataType(Type.DataType dataType); 73 | 74 | Return secondDataType(Type.SecondDataType secondDataType); 75 | } 76 | 77 | public static final class Matching { 78 | private Type value; 79 | 80 | private Return returnValue; 81 | 82 | private Matching(final Type value) { 83 | this.value = value; 84 | } 85 | 86 | public Type.Matching DataType(final Function fn) { 87 | if (this.value instanceof Type.DataType) { 88 | this.returnValue = fn.apply((Type.DataType) this.value); 89 | } 90 | return this; 91 | } 92 | 93 | public Type.Matching SecondDataType(final Function fn) { 94 | if (this.value instanceof Type.SecondDataType) { 95 | this.returnValue = fn.apply((Type.SecondDataType) this.value); 96 | } 97 | return this; 98 | } 99 | 100 | public boolean isMatched() { 101 | return this.returnValue != null; 102 | } 103 | 104 | public Return get() { 105 | if (this.returnValue != null) { 106 | return this.returnValue; 107 | } 108 | else { 109 | throw new IllegalStateException("Unmatched value: " + this.value); 110 | } 111 | } 112 | 113 | public Optional find() { 114 | return Optional.ofNullable(this.returnValue); 115 | } 116 | 117 | public Return orElse(final Return returnValue) { 118 | if (this.returnValue != null) { 119 | return this.returnValue; 120 | } 121 | else { 122 | return returnValue; 123 | } 124 | } 125 | } 126 | 127 | public interface Accepter { 128 | void dataType(Type.DataType dataType); 129 | 130 | void secondDataType(Type.SecondDataType secondDataType); 131 | } 132 | 133 | public static final class Accepting { 134 | private boolean done; 135 | 136 | private Type value; 137 | 138 | private Accepting(final Type value) { 139 | this.value = value; 140 | } 141 | 142 | public Type.Accepting DataType(final Consumer fn) { 143 | if (this.value instanceof Type.DataType) { 144 | fn.accept((Type.DataType) this.value); 145 | this.done = true; 146 | } 147 | return this; 148 | } 149 | 150 | public Type.Accepting SecondDataType(final Consumer fn) { 151 | if (this.value instanceof Type.SecondDataType) { 152 | fn.accept((Type.SecondDataType) this.value); 153 | this.done = true; 154 | } 155 | return this; 156 | } 157 | 158 | public boolean wasAccepted() { 159 | return this.done; 160 | } 161 | 162 | public void accept() { 163 | if (!this.done) { 164 | throw new IllegalStateException("Unmatched value: " + this.value); 165 | } 166 | } 167 | 168 | public void orElse(final Consumer fn) { 169 | if (!this.done) { 170 | fn.accept(this.value); 171 | } 172 | else { 173 | throw new IllegalStateException("Value already matched: " + this.value); 174 | } 175 | } 176 | } 177 | } 178 | 179 | 180 | package com.test; 181 | 182 | import com.google.auto.value.AutoValue; 183 | import java.util.Optional; 184 | import java.util.function.Consumer; 185 | import java.util.function.Function; 186 | 187 | public abstract class Request { 188 | private Request() { 189 | } 190 | 191 | public static Request.GET GET(String path, String query) { 192 | return new AutoValue_Request_GET(path, query); 193 | } 194 | 195 | public static Request.PUT PUT(String path, String body) { 196 | return new AutoValue_Request_PUT(path, body); 197 | } 198 | 199 | public static Request.DELETE DELETE(String path) { 200 | return new AutoValue_Request_DELETE(path); 201 | } 202 | 203 | public final Return match(Request.Matcher matcher) { 204 | if (this instanceof Request.GET) { 205 | return matcher.GET((Request.GET) this); 206 | } 207 | if (this instanceof Request.PUT) { 208 | return matcher.PUT((Request.PUT) this); 209 | } 210 | if (this instanceof Request.DELETE) { 211 | return matcher.DELETE((Request.DELETE) this); 212 | } 213 | throw new IllegalStateException("Unexpected Request subclass encountered."); 214 | } 215 | 216 | public Request.Matching matching() { 217 | return new Request.Matching(this); 218 | } 219 | 220 | public final void accept(Request.Accepter accepter) { 221 | if (this instanceof Request.GET) { 222 | accepter.GET((Request.GET) this); 223 | return; 224 | } 225 | if (this instanceof Request.PUT) { 226 | accepter.PUT((Request.PUT) this); 227 | return; 228 | } 229 | if (this instanceof Request.DELETE) { 230 | accepter.DELETE((Request.DELETE) this); 231 | return; 232 | } 233 | throw new IllegalStateException("Unexpected Request subclass encountered."); 234 | } 235 | 236 | public Request.Accepting accepting() { 237 | return new Request.Accepting(this); 238 | } 239 | 240 | public Return matching(Function GET, 241 | Function PUT, Function DELETE) { 242 | if (this instanceof Request.GET) { 243 | return GET.apply((Request.GET) this); 244 | } 245 | if (this instanceof Request.PUT) { 246 | return PUT.apply((Request.PUT) this); 247 | } 248 | if (this instanceof Request.DELETE) { 249 | return DELETE.apply((Request.DELETE) this); 250 | } 251 | throw new IllegalStateException("Unexpected Request subclass encountered."); 252 | } 253 | 254 | @AutoValue 255 | public abstract static class GET extends Request { 256 | public abstract String path(); 257 | 258 | public abstract String query(); 259 | } 260 | 261 | @AutoValue 262 | public abstract static class PUT extends Request { 263 | public abstract String path(); 264 | 265 | public abstract String body(); 266 | } 267 | 268 | @AutoValue 269 | public abstract static class DELETE extends Request { 270 | public abstract String path(); 271 | } 272 | 273 | public interface Matcher { 274 | Return GET(Request.GET GET); 275 | 276 | Return PUT(Request.PUT PUT); 277 | 278 | Return DELETE(Request.DELETE DELETE); 279 | } 280 | 281 | public static final class Matching { 282 | private Request value; 283 | 284 | private Return returnValue; 285 | 286 | private Matching(final Request value) { 287 | this.value = value; 288 | } 289 | 290 | public Request.Matching GET(final Function fn) { 291 | if (this.value instanceof Request.GET) { 292 | this.returnValue = fn.apply((Request.GET) this.value); 293 | } 294 | return this; 295 | } 296 | 297 | public Request.Matching PUT(final Function fn) { 298 | if (this.value instanceof Request.PUT) { 299 | this.returnValue = fn.apply((Request.PUT) this.value); 300 | } 301 | return this; 302 | } 303 | 304 | public Request.Matching DELETE(final Function fn) { 305 | if (this.value instanceof Request.DELETE) { 306 | this.returnValue = fn.apply((Request.DELETE) this.value); 307 | } 308 | return this; 309 | } 310 | 311 | public boolean isMatched() { 312 | return this.returnValue != null; 313 | } 314 | 315 | public Return get() { 316 | if (this.returnValue != null) { 317 | return this.returnValue; 318 | } 319 | else { 320 | throw new IllegalStateException("Unmatched value: " + this.value); 321 | } 322 | } 323 | 324 | public Optional find() { 325 | return Optional.ofNullable(this.returnValue); 326 | } 327 | 328 | public Return orElse(final Return returnValue) { 329 | if (this.returnValue != null) { 330 | return this.returnValue; 331 | } 332 | else { 333 | return returnValue; 334 | } 335 | } 336 | } 337 | 338 | public interface Accepter { 339 | void GET(Request.GET GET); 340 | 341 | void PUT(Request.PUT PUT); 342 | 343 | void DELETE(Request.DELETE DELETE); 344 | } 345 | 346 | public static final class Accepting { 347 | private boolean done; 348 | 349 | private Request value; 350 | 351 | private Accepting(final Request value) { 352 | this.value = value; 353 | } 354 | 355 | public Request.Accepting GET(final Consumer fn) { 356 | if (this.value instanceof Request.GET) { 357 | fn.accept((Request.GET) this.value); 358 | this.done = true; 359 | } 360 | return this; 361 | } 362 | 363 | public Request.Accepting PUT(final Consumer fn) { 364 | if (this.value instanceof Request.PUT) { 365 | fn.accept((Request.PUT) this.value); 366 | this.done = true; 367 | } 368 | return this; 369 | } 370 | 371 | public Request.Accepting DELETE(final Consumer fn) { 372 | if (this.value instanceof Request.DELETE) { 373 | fn.accept((Request.DELETE) this.value); 374 | this.done = true; 375 | } 376 | return this; 377 | } 378 | 379 | public boolean wasAccepted() { 380 | return this.done; 381 | } 382 | 383 | public void accept() { 384 | if (!this.done) { 385 | throw new IllegalStateException("Unmatched value: " + this.value); 386 | } 387 | } 388 | 389 | public void orElse(final Consumer fn) { 390 | if (!this.done) { 391 | fn.accept(this.value); 392 | } 393 | else { 394 | throw new IllegalStateException("Value already matched: " + this.value); 395 | } 396 | } 397 | } 398 | } 399 | 400 | 401 | package com.test; 402 | 403 | import com.google.auto.value.AutoValue; 404 | import java.util.Optional; 405 | import java.util.function.Consumer; 406 | import java.util.function.Function; 407 | 408 | public abstract class Day { 409 | private static final Day.Monday Monday = new AutoValue_Day_Monday(); 410 | 411 | private static final Day.Tuesday Tuesday = new AutoValue_Day_Tuesday(); 412 | 413 | private static final Day.Wednesday Wednesday = new AutoValue_Day_Wednesday(); 414 | 415 | private static final Day.Thursday Thursday = new AutoValue_Day_Thursday(); 416 | 417 | private static final Day.Friday Friday = new AutoValue_Day_Friday(); 418 | 419 | private static final Day.Saturday Saturday = new AutoValue_Day_Saturday(); 420 | 421 | private static final Day.Sunday Sunday = new AutoValue_Day_Sunday(); 422 | 423 | private Day() { 424 | } 425 | 426 | public static Day.Monday monday() { 427 | return Monday; 428 | } 429 | 430 | public static Day.Tuesday tuesday() { 431 | return Tuesday; 432 | } 433 | 434 | public static Day.Wednesday wednesday() { 435 | return Wednesday; 436 | } 437 | 438 | public static Day.Thursday thursday() { 439 | return Thursday; 440 | } 441 | 442 | public static Day.Friday friday() { 443 | return Friday; 444 | } 445 | 446 | public static Day.Saturday saturday() { 447 | return Saturday; 448 | } 449 | 450 | public static Day.Sunday sunday() { 451 | return Sunday; 452 | } 453 | 454 | public final Return match(Day.Matcher matcher) { 455 | if (this instanceof Day.Monday) { 456 | return matcher.monday((Day.Monday) this); 457 | } 458 | if (this instanceof Day.Tuesday) { 459 | return matcher.tuesday((Day.Tuesday) this); 460 | } 461 | if (this instanceof Day.Wednesday) { 462 | return matcher.wednesday((Day.Wednesday) this); 463 | } 464 | if (this instanceof Day.Thursday) { 465 | return matcher.thursday((Day.Thursday) this); 466 | } 467 | if (this instanceof Day.Friday) { 468 | return matcher.friday((Day.Friday) this); 469 | } 470 | if (this instanceof Day.Saturday) { 471 | return matcher.saturday((Day.Saturday) this); 472 | } 473 | if (this instanceof Day.Sunday) { 474 | return matcher.sunday((Day.Sunday) this); 475 | } 476 | throw new IllegalStateException("Unexpected Day subclass encountered."); 477 | } 478 | 479 | public Day.Matching matching() { 480 | return new Day.Matching(this); 481 | } 482 | 483 | public final void accept(Day.Accepter accepter) { 484 | if (this instanceof Day.Monday) { 485 | accepter.monday((Day.Monday) this); 486 | return; 487 | } 488 | if (this instanceof Day.Tuesday) { 489 | accepter.tuesday((Day.Tuesday) this); 490 | return; 491 | } 492 | if (this instanceof Day.Wednesday) { 493 | accepter.wednesday((Day.Wednesday) this); 494 | return; 495 | } 496 | if (this instanceof Day.Thursday) { 497 | accepter.thursday((Day.Thursday) this); 498 | return; 499 | } 500 | if (this instanceof Day.Friday) { 501 | accepter.friday((Day.Friday) this); 502 | return; 503 | } 504 | if (this instanceof Day.Saturday) { 505 | accepter.saturday((Day.Saturday) this); 506 | return; 507 | } 508 | if (this instanceof Day.Sunday) { 509 | accepter.sunday((Day.Sunday) this); 510 | return; 511 | } 512 | throw new IllegalStateException("Unexpected Day subclass encountered."); 513 | } 514 | 515 | public Day.Accepting accepting() { 516 | return new Day.Accepting(this); 517 | } 518 | 519 | public Return matching(Function monday, 520 | Function tuesday, Function wednesday, 521 | Function thursday, Function friday, 522 | Function saturday, Function sunday) { 523 | if (this instanceof Day.Monday) { 524 | return monday.apply((Day.Monday) this); 525 | } 526 | if (this instanceof Day.Tuesday) { 527 | return tuesday.apply((Day.Tuesday) this); 528 | } 529 | if (this instanceof Day.Wednesday) { 530 | return wednesday.apply((Day.Wednesday) this); 531 | } 532 | if (this instanceof Day.Thursday) { 533 | return thursday.apply((Day.Thursday) this); 534 | } 535 | if (this instanceof Day.Friday) { 536 | return friday.apply((Day.Friday) this); 537 | } 538 | if (this instanceof Day.Saturday) { 539 | return saturday.apply((Day.Saturday) this); 540 | } 541 | if (this instanceof Day.Sunday) { 542 | return sunday.apply((Day.Sunday) this); 543 | } 544 | throw new IllegalStateException("Unexpected Day subclass encountered."); 545 | } 546 | 547 | @AutoValue 548 | public abstract static class Monday extends Day { 549 | } 550 | 551 | @AutoValue 552 | public abstract static class Tuesday extends Day { 553 | } 554 | 555 | @AutoValue 556 | public abstract static class Wednesday extends Day { 557 | } 558 | 559 | @AutoValue 560 | public abstract static class Thursday extends Day { 561 | } 562 | 563 | @AutoValue 564 | public abstract static class Friday extends Day { 565 | } 566 | 567 | @AutoValue 568 | public abstract static class Saturday extends Day { 569 | } 570 | 571 | @AutoValue 572 | public abstract static class Sunday extends Day { 573 | } 574 | 575 | public interface Matcher { 576 | Return monday(Day.Monday monday); 577 | 578 | Return tuesday(Day.Tuesday tuesday); 579 | 580 | Return wednesday(Day.Wednesday wednesday); 581 | 582 | Return thursday(Day.Thursday thursday); 583 | 584 | Return friday(Day.Friday friday); 585 | 586 | Return saturday(Day.Saturday saturday); 587 | 588 | Return sunday(Day.Sunday sunday); 589 | } 590 | 591 | public static final class Matching { 592 | private Day value; 593 | 594 | private Return returnValue; 595 | 596 | private Matching(final Day value) { 597 | this.value = value; 598 | } 599 | 600 | public Day.Matching Monday(final Function fn) { 601 | if (this.value instanceof Day.Monday) { 602 | this.returnValue = fn.apply((Day.Monday) this.value); 603 | } 604 | return this; 605 | } 606 | 607 | public Day.Matching Tuesday(final Function fn) { 608 | if (this.value instanceof Day.Tuesday) { 609 | this.returnValue = fn.apply((Day.Tuesday) this.value); 610 | } 611 | return this; 612 | } 613 | 614 | public Day.Matching Wednesday(final Function fn) { 615 | if (this.value instanceof Day.Wednesday) { 616 | this.returnValue = fn.apply((Day.Wednesday) this.value); 617 | } 618 | return this; 619 | } 620 | 621 | public Day.Matching Thursday(final Function fn) { 622 | if (this.value instanceof Day.Thursday) { 623 | this.returnValue = fn.apply((Day.Thursday) this.value); 624 | } 625 | return this; 626 | } 627 | 628 | public Day.Matching Friday(final Function fn) { 629 | if (this.value instanceof Day.Friday) { 630 | this.returnValue = fn.apply((Day.Friday) this.value); 631 | } 632 | return this; 633 | } 634 | 635 | public Day.Matching Saturday(final Function fn) { 636 | if (this.value instanceof Day.Saturday) { 637 | this.returnValue = fn.apply((Day.Saturday) this.value); 638 | } 639 | return this; 640 | } 641 | 642 | public Day.Matching Sunday(final Function fn) { 643 | if (this.value instanceof Day.Sunday) { 644 | this.returnValue = fn.apply((Day.Sunday) this.value); 645 | } 646 | return this; 647 | } 648 | 649 | public boolean isMatched() { 650 | return this.returnValue != null; 651 | } 652 | 653 | public Return get() { 654 | if (this.returnValue != null) { 655 | return this.returnValue; 656 | } 657 | else { 658 | throw new IllegalStateException("Unmatched value: " + this.value); 659 | } 660 | } 661 | 662 | public Optional find() { 663 | return Optional.ofNullable(this.returnValue); 664 | } 665 | 666 | public Return orElse(final Return returnValue) { 667 | if (this.returnValue != null) { 668 | return this.returnValue; 669 | } 670 | else { 671 | return returnValue; 672 | } 673 | } 674 | } 675 | 676 | public interface Accepter { 677 | void monday(Day.Monday monday); 678 | 679 | void tuesday(Day.Tuesday tuesday); 680 | 681 | void wednesday(Day.Wednesday wednesday); 682 | 683 | void thursday(Day.Thursday thursday); 684 | 685 | void friday(Day.Friday friday); 686 | 687 | void saturday(Day.Saturday saturday); 688 | 689 | void sunday(Day.Sunday sunday); 690 | } 691 | 692 | public static final class Accepting { 693 | private boolean done; 694 | 695 | private Day value; 696 | 697 | private Accepting(final Day value) { 698 | this.value = value; 699 | } 700 | 701 | public Day.Accepting Monday(final Consumer fn) { 702 | if (this.value instanceof Day.Monday) { 703 | fn.accept((Day.Monday) this.value); 704 | this.done = true; 705 | } 706 | return this; 707 | } 708 | 709 | public Day.Accepting Tuesday(final Consumer fn) { 710 | if (this.value instanceof Day.Tuesday) { 711 | fn.accept((Day.Tuesday) this.value); 712 | this.done = true; 713 | } 714 | return this; 715 | } 716 | 717 | public Day.Accepting Wednesday(final Consumer fn) { 718 | if (this.value instanceof Day.Wednesday) { 719 | fn.accept((Day.Wednesday) this.value); 720 | this.done = true; 721 | } 722 | return this; 723 | } 724 | 725 | public Day.Accepting Thursday(final Consumer fn) { 726 | if (this.value instanceof Day.Thursday) { 727 | fn.accept((Day.Thursday) this.value); 728 | this.done = true; 729 | } 730 | return this; 731 | } 732 | 733 | public Day.Accepting Friday(final Consumer fn) { 734 | if (this.value instanceof Day.Friday) { 735 | fn.accept((Day.Friday) this.value); 736 | this.done = true; 737 | } 738 | return this; 739 | } 740 | 741 | public Day.Accepting Saturday(final Consumer fn) { 742 | if (this.value instanceof Day.Saturday) { 743 | fn.accept((Day.Saturday) this.value); 744 | this.done = true; 745 | } 746 | return this; 747 | } 748 | 749 | public Day.Accepting Sunday(final Consumer fn) { 750 | if (this.value instanceof Day.Sunday) { 751 | fn.accept((Day.Sunday) this.value); 752 | this.done = true; 753 | } 754 | return this; 755 | } 756 | 757 | public boolean wasAccepted() { 758 | return this.done; 759 | } 760 | 761 | public void accept() { 762 | if (!this.done) { 763 | throw new IllegalStateException("Unmatched value: " + this.value); 764 | } 765 | } 766 | 767 | public void orElse(final Consumer fn) { 768 | if (!this.done) { 769 | fn.accept(this.value); 770 | } 771 | else { 772 | throw new IllegalStateException("Value already matched: " + this.value); 773 | } 774 | } 775 | } 776 | } 777 | 778 | 779 | package com.test; 780 | 781 | import com.google.auto.value.AutoValue; 782 | import java.util.Optional; 783 | import java.util.Set; 784 | import java.util.function.Consumer; 785 | import java.util.function.Function; 786 | 787 | public abstract class GenericAttribute { 788 | private static final GenericAttribute.Empty Empty = new AutoValue_GenericAttribute_Empty(); 789 | 790 | private GenericAttribute() { 791 | } 792 | 793 | public static GenericAttribute.Empty empty() { 794 | return Empty; 795 | } 796 | 797 | public static GenericAttribute.Container container(Set value) { 798 | return new AutoValue_GenericAttribute_Container(value); 799 | } 800 | 801 | public final Return match(GenericAttribute.Matcher matcher) { 802 | if (this instanceof GenericAttribute.Empty) { 803 | return matcher.empty((GenericAttribute.Empty) this); 804 | } 805 | if (this instanceof GenericAttribute.Container) { 806 | return matcher.container((GenericAttribute.Container) this); 807 | } 808 | throw new IllegalStateException("Unexpected GenericAttribute subclass encountered."); 809 | } 810 | 811 | public GenericAttribute.Matching matching() { 812 | return new GenericAttribute.Matching(this); 813 | } 814 | 815 | public final void accept(GenericAttribute.Accepter accepter) { 816 | if (this instanceof GenericAttribute.Empty) { 817 | accepter.empty((GenericAttribute.Empty) this); 818 | return; 819 | } 820 | if (this instanceof GenericAttribute.Container) { 821 | accepter.container((GenericAttribute.Container) this); 822 | return; 823 | } 824 | throw new IllegalStateException("Unexpected GenericAttribute subclass encountered."); 825 | } 826 | 827 | public GenericAttribute.Accepting accepting() { 828 | return new GenericAttribute.Accepting(this); 829 | } 830 | 831 | public Return matching(Function empty, 832 | Function container) { 833 | if (this instanceof GenericAttribute.Empty) { 834 | return empty.apply((GenericAttribute.Empty) this); 835 | } 836 | if (this instanceof GenericAttribute.Container) { 837 | return container.apply((GenericAttribute.Container) this); 838 | } 839 | throw new IllegalStateException("Unexpected GenericAttribute subclass encountered."); 840 | } 841 | 842 | @AutoValue 843 | public abstract static class Empty extends GenericAttribute { 844 | } 845 | 846 | @AutoValue 847 | public abstract static class Container extends GenericAttribute { 848 | public abstract Set value(); 849 | } 850 | 851 | public interface Matcher { 852 | Return empty(GenericAttribute.Empty empty); 853 | 854 | Return container(GenericAttribute.Container container); 855 | } 856 | 857 | public static final class Matching { 858 | private GenericAttribute value; 859 | 860 | private Return returnValue; 861 | 862 | private Matching(final GenericAttribute value) { 863 | this.value = value; 864 | } 865 | 866 | public GenericAttribute.Matching Empty( 867 | final Function fn) { 868 | if (this.value instanceof GenericAttribute.Empty) { 869 | this.returnValue = fn.apply((GenericAttribute.Empty) this.value); 870 | } 871 | return this; 872 | } 873 | 874 | public GenericAttribute.Matching Container( 875 | final Function fn) { 876 | if (this.value instanceof GenericAttribute.Container) { 877 | this.returnValue = fn.apply((GenericAttribute.Container) this.value); 878 | } 879 | return this; 880 | } 881 | 882 | public boolean isMatched() { 883 | return this.returnValue != null; 884 | } 885 | 886 | public Return get() { 887 | if (this.returnValue != null) { 888 | return this.returnValue; 889 | } 890 | else { 891 | throw new IllegalStateException("Unmatched value: " + this.value); 892 | } 893 | } 894 | 895 | public Optional find() { 896 | return Optional.ofNullable(this.returnValue); 897 | } 898 | 899 | public Return orElse(final Return returnValue) { 900 | if (this.returnValue != null) { 901 | return this.returnValue; 902 | } 903 | else { 904 | return returnValue; 905 | } 906 | } 907 | } 908 | 909 | public interface Accepter { 910 | void empty(GenericAttribute.Empty empty); 911 | 912 | void container(GenericAttribute.Container container); 913 | } 914 | 915 | public static final class Accepting { 916 | private boolean done; 917 | 918 | private GenericAttribute value; 919 | 920 | private Accepting(final GenericAttribute value) { 921 | this.value = value; 922 | } 923 | 924 | public GenericAttribute.Accepting Empty(final Consumer fn) { 925 | if (this.value instanceof GenericAttribute.Empty) { 926 | fn.accept((GenericAttribute.Empty) this.value); 927 | this.done = true; 928 | } 929 | return this; 930 | } 931 | 932 | public GenericAttribute.Accepting Container(final Consumer fn) { 933 | if (this.value instanceof GenericAttribute.Container) { 934 | fn.accept((GenericAttribute.Container) this.value); 935 | this.done = true; 936 | } 937 | return this; 938 | } 939 | 940 | public boolean wasAccepted() { 941 | return this.done; 942 | } 943 | 944 | public void accept() { 945 | if (!this.done) { 946 | throw new IllegalStateException("Unmatched value: " + this.value); 947 | } 948 | } 949 | 950 | public void orElse(final Consumer fn) { 951 | if (!this.done) { 952 | fn.accept(this.value); 953 | } 954 | else { 955 | throw new IllegalStateException("Value already matched: " + this.value); 956 | } 957 | } 958 | } 959 | } 960 | 961 | 962 | package com.test; 963 | 964 | import com.google.auto.value.AutoValue; 965 | import java.util.Optional; 966 | import java.util.Set; 967 | import java.util.function.Consumer; 968 | import java.util.function.Function; 969 | 970 | public abstract class GenericType { 971 | private static final GenericType.Empty Empty = new AutoValue_GenericType_Empty(); 972 | 973 | private GenericType() { 974 | } 975 | 976 | public static GenericType.Empty empty() { 977 | return Empty; 978 | } 979 | 980 | public static GenericType.Single single(T value) { 981 | return new AutoValue_GenericType_Single(value); 982 | } 983 | 984 | public static GenericType.Container container(Set value) { 985 | return new AutoValue_GenericType_Container(value); 986 | } 987 | 988 | public final Return match(GenericType.Matcher matcher) { 989 | if (this instanceof GenericType.Empty) { 990 | return matcher.empty((GenericType.Empty) this); 991 | } 992 | if (this instanceof GenericType.Single) { 993 | return matcher.single((GenericType.Single) this); 994 | } 995 | if (this instanceof GenericType.Container) { 996 | return matcher.container((GenericType.Container) this); 997 | } 998 | throw new IllegalStateException("Unexpected GenericType subclass encountered."); 999 | } 1000 | 1001 | public GenericType.Matching matching() { 1002 | return new GenericType.Matching(this); 1003 | } 1004 | 1005 | public final void accept(GenericType.Accepter accepter) { 1006 | if (this instanceof GenericType.Empty) { 1007 | accepter.empty((GenericType.Empty) this); 1008 | return; 1009 | } 1010 | if (this instanceof GenericType.Single) { 1011 | accepter.single((GenericType.Single) this); 1012 | return; 1013 | } 1014 | if (this instanceof GenericType.Container) { 1015 | accepter.container((GenericType.Container) this); 1016 | return; 1017 | } 1018 | throw new IllegalStateException("Unexpected GenericType subclass encountered."); 1019 | } 1020 | 1021 | public GenericType.Accepting accepting() { 1022 | return new GenericType.Accepting(this); 1023 | } 1024 | 1025 | public Return matching(Function, Return> empty, 1026 | Function, Return> single, 1027 | Function, Return> container) { 1028 | if (this instanceof GenericType.Empty) { 1029 | return empty.apply((GenericType.Empty) this); 1030 | } 1031 | if (this instanceof GenericType.Single) { 1032 | return single.apply((GenericType.Single) this); 1033 | } 1034 | if (this instanceof GenericType.Container) { 1035 | return container.apply((GenericType.Container) this); 1036 | } 1037 | throw new IllegalStateException("Unexpected GenericType subclass encountered."); 1038 | } 1039 | 1040 | @AutoValue 1041 | public abstract static class Empty extends GenericType { 1042 | } 1043 | 1044 | @AutoValue 1045 | public abstract static class Single extends GenericType { 1046 | public abstract T value(); 1047 | } 1048 | 1049 | @AutoValue 1050 | public abstract static class Container extends GenericType { 1051 | public abstract Set value(); 1052 | } 1053 | 1054 | public interface Matcher { 1055 | Return empty(GenericType.Empty empty); 1056 | 1057 | Return single(GenericType.Single single); 1058 | 1059 | Return container(GenericType.Container container); 1060 | } 1061 | 1062 | public static final class Matching { 1063 | private GenericType value; 1064 | 1065 | private Return returnValue; 1066 | 1067 | private Matching(final GenericType value) { 1068 | this.value = value; 1069 | } 1070 | 1071 | public GenericType.Matching Empty(final Function, Return> fn) { 1072 | if (this.value instanceof GenericType.Empty) { 1073 | this.returnValue = fn.apply((GenericType.Empty) this.value); 1074 | } 1075 | return this; 1076 | } 1077 | 1078 | public GenericType.Matching Single( 1079 | final Function, Return> fn) { 1080 | if (this.value instanceof GenericType.Single) { 1081 | this.returnValue = fn.apply((GenericType.Single) this.value); 1082 | } 1083 | return this; 1084 | } 1085 | 1086 | public GenericType.Matching Container( 1087 | final Function, Return> fn) { 1088 | if (this.value instanceof GenericType.Container) { 1089 | this.returnValue = fn.apply((GenericType.Container) this.value); 1090 | } 1091 | return this; 1092 | } 1093 | 1094 | public boolean isMatched() { 1095 | return this.returnValue != null; 1096 | } 1097 | 1098 | public Return get() { 1099 | if (this.returnValue != null) { 1100 | return this.returnValue; 1101 | } 1102 | else { 1103 | throw new IllegalStateException("Unmatched value: " + this.value); 1104 | } 1105 | } 1106 | 1107 | public Optional find() { 1108 | return Optional.ofNullable(this.returnValue); 1109 | } 1110 | 1111 | public Return orElse(final Return returnValue) { 1112 | if (this.returnValue != null) { 1113 | return this.returnValue; 1114 | } 1115 | else { 1116 | return returnValue; 1117 | } 1118 | } 1119 | } 1120 | 1121 | public interface Accepter { 1122 | void empty(GenericType.Empty empty); 1123 | 1124 | void single(GenericType.Single single); 1125 | 1126 | void container(GenericType.Container container); 1127 | } 1128 | 1129 | public static final class Accepting { 1130 | private boolean done; 1131 | 1132 | private GenericType value; 1133 | 1134 | private Accepting(final GenericType value) { 1135 | this.value = value; 1136 | } 1137 | 1138 | public GenericType.Accepting Empty(final Consumer> fn) { 1139 | if (this.value instanceof GenericType.Empty) { 1140 | fn.accept((GenericType.Empty) this.value); 1141 | this.done = true; 1142 | } 1143 | return this; 1144 | } 1145 | 1146 | public GenericType.Accepting Single(final Consumer> fn) { 1147 | if (this.value instanceof GenericType.Single) { 1148 | fn.accept((GenericType.Single) this.value); 1149 | this.done = true; 1150 | } 1151 | return this; 1152 | } 1153 | 1154 | public GenericType.Accepting Container(final Consumer> fn) { 1155 | if (this.value instanceof GenericType.Container) { 1156 | fn.accept((GenericType.Container) this.value); 1157 | this.done = true; 1158 | } 1159 | return this; 1160 | } 1161 | 1162 | public boolean wasAccepted() { 1163 | return this.done; 1164 | } 1165 | 1166 | public void accept() { 1167 | if (!this.done) { 1168 | throw new IllegalStateException("Unmatched value: " + this.value); 1169 | } 1170 | } 1171 | 1172 | public void orElse(final Consumer fn) { 1173 | if (!this.done) { 1174 | fn.accept(this.value); 1175 | } 1176 | else { 1177 | throw new IllegalStateException("Value already matched: " + this.value); 1178 | } 1179 | } 1180 | } 1181 | } 1182 | 1183 | 1184 | -------------------------------------------------------------------------------- /datatyper-maven-plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.theoryinpractise.datatyper 7 | datatyper 8 | 1.1.4-SNAPSHOT 9 | 10 | 11 | com.theoryinpractise.datatyper 12 | datatyper-maven-plugin 13 | 1.1.4-SNAPSHOT 14 | maven-plugin 15 | 16 | datatyper-maven-plugin Maven Mojo 17 | http://maven.apache.org 18 | 19 | 20 | 21 | org.apache.maven 22 | maven-core 23 | 3.8.5 24 | 25 | 26 | org.apache.maven 27 | maven-plugin-api 28 | 3.8.5 29 | 30 | 31 | org.apache.maven.plugin-tools 32 | maven-plugin-annotations 33 | 3.6.4 34 | 35 | 36 | com.theoryinpractise.datatyper 37 | datatyper-lib 38 | 1.1.4-SNAPSHOT 39 | 40 | 41 | 42 | 43 | 44 | 45 | org.apache.maven.plugins 46 | maven-plugin-plugin 47 | 3.6.4 48 | 49 | true 50 | 51 | 52 | 53 | mojo-descriptor 54 | 55 | descriptor 56 | 57 | process-classes 58 | 59 | 60 | help-goal 61 | 62 | helpmojo 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /datatyper-maven-plugin/src/main/java/com/theoryinpractise/datatyper/mojo/DatatyperMojo.java: -------------------------------------------------------------------------------- 1 | package com.theoryinpractise.datatyper.mojo; 2 | 3 | import com.theoryinpractise.datatyper.DataTypeCompiler; 4 | import org.apache.maven.plugin.AbstractMojo; 5 | import org.apache.maven.plugin.MojoExecutionException; 6 | import org.apache.maven.plugins.annotations.LifecyclePhase; 7 | import org.apache.maven.plugins.annotations.Mojo; 8 | import org.apache.maven.plugins.annotations.Parameter; 9 | import org.apache.maven.plugins.annotations.ResolutionScope; 10 | import org.apache.maven.project.MavenProject; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | import java.util.Set; 17 | import java.util.stream.Collectors; 18 | 19 | /** Goal which transpiles .typer files into java source */ 20 | @Mojo( 21 | name = "datatyper", 22 | defaultPhase = LifecyclePhase.GENERATE_SOURCES, 23 | requiresDependencyResolution = ResolutionScope.COMPILE 24 | ) 25 | public class DatatyperMojo extends AbstractMojo { 26 | 27 | @Parameter(required = true, readonly = true, property = "project") 28 | protected MavenProject project; 29 | 30 | @Parameter( 31 | required = true, 32 | property = "typerDirectory", 33 | defaultValue = "${basedir}/src/main/datatyper" 34 | ) 35 | private File typerDirectory; 36 | 37 | @Parameter( 38 | required = true, 39 | property = "outputDirectory", 40 | defaultValue = "${project.build.directory}/generated-sources/datatyper" 41 | ) 42 | private File outputDirectory; 43 | 44 | public void execute() throws MojoExecutionException { 45 | File f = outputDirectory; 46 | 47 | if (!f.exists()) { 48 | f.mkdirs(); 49 | } 50 | 51 | project.addCompileSourceRoot(outputDirectory.getAbsolutePath()); 52 | 53 | try { 54 | discoverTypeFiles(typerDirectory) 55 | .forEach(typeFile -> DataTypeCompiler.compileFile(typeFile, outputDirectory)); 56 | } catch (IOException e) { 57 | throw new MojoExecutionException(e.getMessage()); 58 | } 59 | } 60 | 61 | protected Set discoverTypeFiles(File topPath) throws IOException { 62 | return Files.find( 63 | topPath.toPath(), 64 | 10, 65 | (path, attr) -> path.toFile().getName().endsWith(".typer") && attr.isRegularFile()) 66 | .map(Path::toFile) 67 | .collect(Collectors.toSet()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /datatyper-maven-tile/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.theoryinpractise.datatyper 7 | datatyper 8 | 1.1.4-SNAPSHOT 9 | 10 | 11 | com.theoryinpractise.datatyper 12 | datatyper-maven-tile 13 | 1.1.4-SNAPSHOT 14 | tile 15 | 16 | datatyper-maven-tile 17 | Java ADT Generator Tile 18 | https://github.com/talios/javagadt 19 | 20 | 21 | 22 | 23 | io.repaint.maven 24 | tiles-maven-plugin 25 | 2.10 26 | true 27 | 28 | dependencies 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /datatyper-maven-tile/tile.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | com.theoryinpractise.datatyper 13 | datatyper-maven-plugin 14 | 1.1.3 15 | 16 | 17 | datatyper 18 | 19 | datatyper 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.theoryinpractise.datatyper 6 | datatyper 7 | 1.1.4-SNAPSHOT 8 | pom 9 | 10 | datatyper 11 | DataType Generator for java 12 | http://github.com 13 | 14 | 15 | Apache License 16 | 17 | 18 | 19 | 20 | 21 | talios 22 | Mark Derricutt 23 | mark@talios.com 24 | http://twitter.com/talios 25 | 26 | 27 | 28 | 29 | datatyper-lib 30 | datatyper-maven-plugin 31 | datatyper-maven-tile 32 | datatyper-example 33 | 34 | 35 | 36 | scm:git:git@github.com:talios/datatyper.git 37 | HEAD 38 | http://github.com/talios/datatyper 39 | 40 | 41 | 42 | sonatype-staging 43 | oss.sonatype.org Staging Repository 44 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 45 | 46 | 47 | github-snapshots 48 | oss.sonatype.org github Release Repository Snapshot Repository 49 | https://oss.sonatype.org/content/repositories/github-snapshots/ 50 | 51 | 52 | 53 | 54 | UTF-8 55 | 56 | 57 | 58 | 59 | 60 | 61 | maven-surefire-plugin 62 | 3.0.0-M6 63 | 64 | 65 | maven-clean-plugin 66 | 3.2.0 67 | 68 | 69 | maven-install-plugin 70 | 3.0.0-M1 71 | 72 | 73 | maven-resources-plugin 74 | 3.2.0 75 | 76 | 77 | maven-deploy-plugin 78 | 3.0.0-M2 79 | 80 | 81 | maven-dependency-plugin 82 | 3.3.0 83 | 84 | 85 | maven-source-plugin 86 | 3.2.1 87 | 88 | 89 | 90 | 91 | 92 | maven-compiler-plugin 93 | 3.10.1 94 | 95 | 1.8 96 | 1.8 97 | false 98 | 99 | -Xlint:deprecation 100 | -Xlint:unchecked 101 | 102 | 103 | 104 | 105 | com.theoryinpractise 106 | googleformatter-maven-plugin 107 | 1.3.1 108 | 109 | 110 | reformat-sources 111 | 112 | format 113 | 114 | process-sources 115 | 116 | false 117 | 118 | 119 | 120 | 121 | 122 | 123 | org.apache.maven.plugins 124 | maven-release-plugin 125 | 2.5.3 126 | 127 | clean install 128 | true 129 | deploy 130 | false 131 | true 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | release 140 | 141 | 142 | performRelease 143 | true 144 | 145 | 146 | 147 | 148 | 149 | org.apache.maven.plugins 150 | maven-gpg-plugin 151 | 1.6 152 | 153 | gpg2 154 | 155 | 156 | 157 | sign-artifacts 158 | 159 | sign 160 | 161 | verify 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | --------------------------------------------------------------------------------