├── .editorconfig ├── .gitattributes ├── .github ├── purge-workflows.sh └── workflows │ ├── ci.yaml │ └── release.yaml ├── .gitignore ├── .mvn ├── mvnd.properties └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .run ├── Template JUnit.run.xml └── mvnDebug.run.xml ├── .vscode └── settings.json ├── LICENSE.txt ├── README.md ├── annotation ├── pom.xml └── src │ └── main │ └── java │ ├── module-info.java │ └── online │ └── sharedtype │ ├── SharedType.java │ └── TagLiterals.java ├── client-test ├── go │ ├── go.mod │ ├── go.sum │ ├── server.go │ ├── setenv │ └── sharedtype │ │ ├── types.go │ │ └── types_test.go ├── rust │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── main.rs │ │ └── types.rs └── typescript │ ├── .gitignore │ ├── .npmrc │ ├── eslint.config.js │ ├── package-lock.json │ ├── package.json │ ├── setenv │ ├── src │ ├── index.java17.ts │ ├── index.java8.d.ts │ ├── server.ts │ ├── types.java17.ts │ └── types.java8.ts │ ├── tests │ └── runtime.test.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── doc ├── Development.md └── Usage.md ├── e2e ├── pom.xml └── src │ └── test │ └── java │ └── online │ └── sharedtype │ └── e2e │ ├── JsonE2eTest.java │ └── ObjectRemoteClientCaller.java ├── internal └── src │ ├── main │ └── java │ │ └── online │ │ └── sharedtype │ │ └── processor │ │ ├── domain │ │ ├── Constants.java │ │ ├── MappableType.java │ │ ├── component │ │ │ ├── AbstractComponentInfo.java │ │ │ ├── ComponentInfo.java │ │ │ ├── ConstantField.java │ │ │ ├── EnumValueInfo.java │ │ │ └── FieldComponentInfo.java │ │ ├── def │ │ │ ├── ClassDef.java │ │ │ ├── ConcreteTypeDef.java │ │ │ ├── ConstantNamespaceDef.java │ │ │ ├── EnumDef.java │ │ │ └── TypeDef.java │ │ ├── type │ │ │ ├── ArrayTypeInfo.java │ │ │ ├── ConcreteTypeInfo.java │ │ │ ├── DateTimeInfo.java │ │ │ ├── NoTypeInfo.java │ │ │ ├── ReferableTypeInfo.java │ │ │ ├── TypeInfo.java │ │ │ └── TypeVariableInfo.java │ │ └── value │ │ │ ├── EnumConstantValue.java │ │ │ ├── LiteralValue.java │ │ │ └── ValueHolder.java │ │ └── support │ │ └── annotation │ │ ├── Issue.java │ │ ├── Nullable.java │ │ ├── SideEffect.java │ │ └── VisibleForTesting.java │ └── test │ └── java │ └── online │ └── sharedtype │ └── it │ └── support │ └── TypeDefDeserializer.java ├── it ├── custom-code.go ├── custom-code.rs ├── custom-code.ts ├── java17 │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── online │ │ │ └── sharedtype │ │ │ └── it │ │ │ ├── java17 │ │ │ └── JavaRecord.java │ │ │ ├── java8 │ │ │ └── java8_is_symlink │ │ └── test │ │ └── java │ │ └── online │ │ └── sharedtype │ │ └── it │ │ ├── ConstantsIntegrationTest.java │ │ ├── CustomAnnoClassIntegrationTest.java │ │ ├── EnumIntegrationTest.java │ │ ├── JavaRecordIntegrationTest.java │ │ ├── JsonSerializationTest.java │ │ ├── MapClassIntegrationTest.java │ │ ├── OptionalTypeIntegrationTest.java │ │ └── TypeDefIntegrationTest.java ├── java8 │ ├── pom.xml │ ├── setenv │ └── src │ │ ├── main │ │ └── java │ │ │ ├── module-info.java │ │ │ └── online │ │ │ └── sharedtype │ │ │ └── it │ │ │ └── java8 │ │ │ ├── ArrayClass.java │ │ │ ├── Container.java │ │ │ ├── CustomList.java │ │ │ ├── CustomMap.java │ │ │ ├── DependencyClassA.java │ │ │ ├── DependencyClassB.java │ │ │ ├── DependencyClassC.java │ │ │ ├── EnumGalaxy.java │ │ │ ├── EnumSize.java │ │ │ ├── EnumTShirt.java │ │ │ ├── GenericTypeReifyIssue44.java │ │ │ ├── IgnoredInterfaceB.java │ │ │ ├── IgnoredSuperClassB.java │ │ │ ├── InterfaceA.java │ │ │ ├── InterfaceSubA.java │ │ │ ├── JavaClass.java │ │ │ ├── JavaTimeClass.java │ │ │ ├── JodaTimeClass.java │ │ │ ├── MapClass.java │ │ │ ├── MathClass.java │ │ │ ├── MyConstants.java │ │ │ ├── OptionalMethod.java │ │ │ ├── RecursiveClass.java │ │ │ ├── SuperClassA.java │ │ │ ├── TempClass.java │ │ │ ├── anno │ │ │ ├── AsAccessor.java │ │ │ ├── AsEnumValue.java │ │ │ ├── CustomAnnoClass.java │ │ │ └── ToIgnore.java │ │ │ ├── other │ │ │ ├── JavaClass.java │ │ │ └── OtherConstants.java │ │ │ └── types │ │ │ └── ArbitraryTypeMapping.java │ │ └── test │ │ └── java │ │ └── online │ │ └── sharedtype │ │ └── it │ │ └── JavaClassIntegrationTest.java ├── pom.xml └── sharedtype.properties ├── misc ├── by.svg ├── logo-color.svg ├── release.sh ├── setversion.sh └── start-client-servers.sh ├── mount-tmpfs.sh ├── mvne ├── mvnw ├── pom.xml ├── processor ├── pom.xml └── src │ ├── main │ ├── java │ │ ├── module-info.java │ │ └── online │ │ │ └── sharedtype │ │ │ └── processor │ │ │ ├── AnnotationProcessorImpl.java │ │ │ ├── context │ │ │ ├── Config.java │ │ │ ├── Context.java │ │ │ ├── EnumCtorIndex.java │ │ │ ├── EnumParsingUtils.java │ │ │ ├── OutputTarget.java │ │ │ ├── Props.java │ │ │ ├── PropsFactory.java │ │ │ ├── RenderFlags.java │ │ │ └── TypeStore.java │ │ │ ├── parser │ │ │ ├── ClassTypeDefParser.java │ │ │ ├── CompositeTypeDefParser.java │ │ │ ├── ConstantTypeDefParser.java │ │ │ ├── EnumTypeDefParser.java │ │ │ ├── TypeDefParser.java │ │ │ ├── type │ │ │ │ ├── MappableTypeInfoParser.java │ │ │ │ ├── TypeInfoParser.java │ │ │ │ └── TypeInfoParserImpl.java │ │ │ └── value │ │ │ │ ├── CompositeValueParser.java │ │ │ │ ├── ConstantValueParser.java │ │ │ │ ├── EnumValueParser.java │ │ │ │ ├── ValueParser.java │ │ │ │ ├── ValueResolveContext.java │ │ │ │ ├── ValueResolveUtils.java │ │ │ │ ├── ValueResolverBackend.java │ │ │ │ └── ValueResolverBackendImpl.java │ │ │ ├── resolver │ │ │ ├── CompositeTypeResolver.java │ │ │ ├── LoopTypeResolver.java │ │ │ ├── OptionalTypeResolver.java │ │ │ ├── ReferenceResolver.java │ │ │ ├── SubtypeResolver.java │ │ │ └── TypeResolver.java │ │ │ ├── support │ │ │ ├── Preconditions.java │ │ │ ├── exception │ │ │ │ ├── SharedTypeException.java │ │ │ │ └── SharedTypeInternalError.java │ │ │ ├── github │ │ │ │ └── RepositoryInfo.java │ │ │ └── utils │ │ │ │ ├── Tuple.java │ │ │ │ └── Utils.java │ │ │ └── writer │ │ │ ├── CompositeWriter.java │ │ │ ├── ConsoleWriter.java │ │ │ ├── JavaSerializationFileWriter.java │ │ │ ├── TemplateTypeFileWriter.java │ │ │ ├── TypeWriter.java │ │ │ ├── adaptor │ │ │ ├── AbstractDataAdaptor.java │ │ │ ├── GoHeaderDataAdaptor.java │ │ │ ├── RenderDataAdaptor.java │ │ │ ├── RenderDataAdaptorFactory.java │ │ │ ├── RustHeaderDataAdaptor.java │ │ │ └── TypescriptHeaderDataAdaptor.java │ │ │ ├── converter │ │ │ ├── AbstractEnumConverter.java │ │ │ ├── AbstractFieldExpr.java │ │ │ ├── AbstractStructConverter.java │ │ │ ├── AbstractTypeExpr.java │ │ │ ├── ConstantConverter.java │ │ │ ├── ConversionUtils.java │ │ │ ├── GoEnumConverter.java │ │ │ ├── GoStructConverter.java │ │ │ ├── RustEnumConverter.java │ │ │ ├── RustMacroTraitsGenerator.java │ │ │ ├── RustMacroTraitsGeneratorImpl.java │ │ │ ├── RustStructConverter.java │ │ │ ├── TemplateDataConverter.java │ │ │ ├── TypescriptEnumConverter.java │ │ │ ├── TypescriptInterfaceConverter.java │ │ │ └── type │ │ │ │ ├── AbstractTypeExpressionConverter.java │ │ │ │ ├── GoTypeExpressionConverter.java │ │ │ │ ├── RustLiteralTypeExpressionConverter.java │ │ │ │ ├── RustTypeExpressionConverter.java │ │ │ │ ├── RustTypeNameMappings.java │ │ │ │ ├── TypeExpressionConverter.java │ │ │ │ └── TypescriptTypeExpressionConverter.java │ │ │ └── render │ │ │ ├── MustacheTemplateRenderer.java │ │ │ ├── Template.java │ │ │ └── TemplateRenderer.java │ └── resources │ │ ├── sharedtype-default.properties │ │ └── templates │ │ ├── go │ │ ├── const-enum.mustache │ │ ├── constant-inline.mustache │ │ ├── constant.mustache │ │ ├── header.mustache │ │ ├── struct-enum.mustache │ │ └── struct.mustache │ │ ├── rust │ │ ├── constant-inline.mustache │ │ ├── constant.mustache │ │ ├── enum.mustache │ │ ├── header.mustache │ │ └── struct.mustache │ │ └── typescript │ │ ├── constant-inline.mustache │ │ ├── constant.mustache │ │ ├── enum.mustache │ │ ├── header.mustache │ │ ├── interface.mustache │ │ └── union-type-enum.mustache │ └── test │ ├── java │ └── online │ │ └── sharedtype │ │ └── processor │ │ ├── AnnotationProcessorImplTest.java │ │ ├── context │ │ ├── AbstractElementMock.java │ │ ├── AbstractTreeMock.java │ │ ├── AnnotationMirrorMock.java │ │ ├── ArrayTypeMock.java │ │ ├── AssignmentTreeMock.java │ │ ├── ConfigTest.java │ │ ├── ContextMocks.java │ │ ├── ContextTest.java │ │ ├── DeclaredTypeVariableElementMock.java │ │ ├── ExecutableElementMock.java │ │ ├── ExpressionTreeMock.java │ │ ├── IdentifierTreeMock.java │ │ ├── LiteralTreeMock.java │ │ ├── MemberSelectTreeMock.java │ │ ├── MockName.java │ │ ├── NewClassTreeMock.java │ │ ├── PackageElementMock.java │ │ ├── PrimitiveVariableElementMock.java │ │ ├── PropsFactoryTest.java │ │ ├── RecordComponentMock.java │ │ ├── TestUtils.java │ │ ├── TypeElementMock.java │ │ ├── TypeParameterElementMock.java │ │ └── VariableTreeMock.java │ │ ├── domain │ │ ├── ClassDefTest.java │ │ ├── TypeEqualityTest.java │ │ ├── def │ │ │ └── EnumDefTest.java │ │ ├── type │ │ │ └── ReferableTypeInfoTest.java │ │ └── value │ │ │ └── ValueHolderTest.java │ │ ├── parser │ │ ├── ClassTypeDefParserForRecordTest.java │ │ ├── ClassTypeDefParserTest.java │ │ ├── CompositeTypeDefParserTest.java │ │ ├── ConstantTypeDefParserTest.java │ │ ├── EnumTypeDefParserTest.java │ │ ├── type │ │ │ ├── MappableTypeInfoParserTest.java │ │ │ └── TypeInfoParserImplTest.java │ │ └── value │ │ │ ├── ConstantValueParserTest.java │ │ │ ├── EnumValueParserTest.java │ │ │ ├── ValueResolveUtilsTest.java │ │ │ └── ValueResolverBackendImplTest.java │ │ ├── resolver │ │ ├── CompositeTypeResolverTest.java │ │ ├── LoopTypeResolverTest.java │ │ ├── OptionalTypeResolverTest.java │ │ ├── ReferenceResolverTest.java │ │ └── SubtypeResolverTest.java │ │ └── writer │ │ ├── TemplateTypeFileWriterTest.java │ │ ├── adaptor │ │ ├── RustHeaderDataAdaptorTest.java │ │ └── TypescriptHeaderDataAdaptorTest.java │ │ ├── converter │ │ ├── ConstantConverterRustTest.java │ │ ├── ConstantConverterTypescriptTest.java │ │ ├── ConversionUtilsTest.java │ │ ├── GoEnumConverterTest.java │ │ ├── GoStructConverterTest.java │ │ ├── RustEnumConverterTest.java │ │ ├── RustMacroTraitsGeneratorImplTest.java │ │ ├── RustStructConverterTest.java │ │ ├── TypescriptEnumConverterTest.java │ │ ├── TypescriptInterfaceConverterIntegrationTest.java │ │ └── type │ │ │ ├── GoTypeExpressionConverterTest.java │ │ │ ├── RustTypeExpressionConverterTest.java │ │ │ └── TypescriptTypeExpressionConverterTest.java │ │ └── render │ │ └── MustacheTemplateRendererTest.java │ └── resources │ ├── custom-code.ts │ ├── test-sharedtype-type-mappings.properties │ ├── test-sharedtype-user.properties │ ├── test-sharedtype-wrong-target.properties │ └── test-sharedtype-wrong-ts-optional-field-format.properties └── setenv /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | indent_style = space 4 | indent_size = 4 5 | end_of_line = lf 6 | max_line_length = 160 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.java] 11 | ij_continuation_indent_size = 4 12 | ij_java_class_count_to_use_import_on_demand = 999 13 | ij_java_names_count_to_use_import_on_demand = 20 14 | ij_java_imports_layout = *,|,javax.**,java.**,|,$* 15 | 16 | [*.yaml] 17 | indent_size = 2 18 | 19 | [*.ts] 20 | ij_javascript_enforce_trailing_comma = whenmultiline 21 | 22 | [*.svg] 23 | charset = unset 24 | end_of_line = unset 25 | insert_final_newline = unset 26 | trim_trailing_whitespace = unset 27 | indent_style = unset 28 | indent_size = unset 29 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/purge-workflows.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Requires GitHub CLI installed and authenticated. 4 | 5 | export USER="cuzfrog" 6 | export REPO="sharedtype" 7 | 8 | gh api repos/$USER/$REPO/actions/runs | jq -r '.workflow_runs[] | "\(.id)"' | xargs -n1 -I % gh api repos/$USER/$REPO/actions/runs/% -X DELETE 9 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Lib Release 2 | on: 3 | push: 4 | tags: 5 | - 'release*' 6 | 7 | permissions: 8 | contents: write 9 | pull-requests: write 10 | 11 | jobs: 12 | release: 13 | runs-on: ubuntu-24.04 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-java@v4 17 | with: 18 | distribution: 'temurin' 19 | java-version: 21 20 | - uses: actions/cache@v4 21 | with: 22 | path: ~/.m2/repository 23 | key: release_maven 24 | - uses: s4u/maven-settings-action@v3.1.0 25 | with: 26 | servers: '[{"id": "central", "username": "${env.SONATYPE_CENTRAL_USER}", "password": "${env.SONATYPE_CENTRAL_PASS}"}]' 27 | - uses: crazy-max/ghaction-import-gpg@v6 28 | with: 29 | gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} 30 | passphrase: ${{ secrets.MAVEN_GPG_PASSPHRASE }} 31 | - name: Release 32 | id: release 33 | env: 34 | MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} 35 | SONATYPE_CENTRAL_USER: ${{ secrets.SONATYPE_CENTRAL_USER }} 36 | SONATYPE_CENTRAL_PASS: ${{ secrets.SONATYPE_CENTRAL_PASS }} 37 | run: | 38 | . ./misc/release.sh 39 | echo "new_version=$(cat NEW_VERSION.cache)" >> "$GITHUB_OUTPUT" 40 | - name: Create Pull Request 41 | uses: peter-evans/create-pull-request@v7 42 | with: 43 | branch: create-pull-request/bump_version 44 | base: master 45 | delete-branch: true 46 | title: Bump version to ${{ steps.release.outputs.new_version }} 47 | commit-message: | 48 | Bump version to ${{ steps.release.outputs.new_version }} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | .run/*.cache 6 | NEW_VERSION.cache 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | 17 | ### IntelliJ IDEA ### 18 | .idea 19 | *.iws 20 | *.iml 21 | *.ipr 22 | 23 | ### NetBeans ### 24 | /nbproject/private/ 25 | /nbbuild/ 26 | /dist/ 27 | /nbdist/ 28 | /.nb-gradle/ 29 | build/ 30 | !**/src/main/**/build/ 31 | !**/src/test/**/build/ 32 | 33 | ### VS Code ### 34 | .vscode/* 35 | !.vscode/settings.json 36 | 37 | ### MacOS ### 38 | .DS_Store 39 | -------------------------------------------------------------------------------- /.mvn/mvnd.properties: -------------------------------------------------------------------------------- 1 | mvnd.noBuffering=true 2 | mvnd.threads=1 3 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharedType/sharedtype/66eea847fb581dcde5ada2e886a1eca92871db81/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /.run/Template JUnit.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /.run/mvnDebug.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.home": "/home/cuz/rt/jdk-17.0.12" 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sharedtype-logo 2 | 3 | [![Gitter](https://badges.gitter.im/sharedtype/sharedtype.svg)](https://app.gitter.im/#/room/#sharedtype:gitter.im) 4 | [![CI](https://github.com/cuzfrog/sharedtype/actions/workflows/ci.yaml/badge.svg)](https://github.com/cuzfrog/sharedtype/actions/workflows/ci.yaml) 5 | [![Maven Central](https://img.shields.io/maven-central/v/online.sharedtype/sharedtype?style=social)](https://central.sonatype.com/search?q=g:online.sharedtype++a:sharedtype&smo=true) 6 | 7 | # SharedType - Sharing Java Types made easy 8 | From Java: 9 | ```java 10 | @SharedType 11 | record User(String name, int age, String email) {} 12 | ``` 13 | To Typescript: 14 | ```typescript 15 | export interface User { 16 | name: string; 17 | age: number; 18 | email: string; 19 | } 20 | ``` 21 | Go: 22 | ```golang 23 | type User struct { 24 | Name string 25 | Age int 26 | Email string 27 | } 28 | ``` 29 | Rust: 30 | ```rust 31 | pub struct User { 32 | name: String, 33 | age: i32, 34 | email: String, 35 | } 36 | ``` 37 | 38 | ## Features 39 | * Java8 compatible; Java 9 module (Jigsaw) compatible. 40 | * Generics support. 41 | * Compile-time constant support. 42 | * Client source dependency is only `@SharedType` retained at source code level. 43 | * SharedType annotation processor has only 1 dependency: [mustache](https://github.com/spullara/mustache.java). 44 | * Parsing takes milliseconds with `-proc:only`. 45 | * Intuitive defaults, put `@SharedType` and there you go. Global + class level options. 46 | 47 | ## Documentation 48 | * [User Guide](doc/Usage.md) 49 | * [Developer Guide](doc/Development.md) 50 | 51 | ## Similar Projects 52 | * [bsorrentino/java2typescript](https://github.com/bsorrentino/java2typescript) 53 | * [vojtechhabarta/typescript-generator](https://github.com/vojtechhabarta/typescript-generator) 54 | * [aws/smithy](https://github.com/smithy-lang/smithy) 55 | 56 | ## Authors 57 | Cause Chung (cuzfrog@gmail.com), Jeremy Zhou (hb.zhou.jeremy@gmail.com) 58 | 59 | ## License 60 | ![CC BY 4.0](./misc/by.svg) 61 | CC BY 4.0 62 | -------------------------------------------------------------------------------- /annotation/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | online.sharedtype 6 | sharedtype-parent 7 | 0.12.1-SNAPSHOT 8 | ../pom.xml 9 | 10 | 11 | sharedtype 12 | 13 | SharedType Annotations 14 | 15 | SharedType is an annotation processor to generate type information from Java source code into different target languages or schemas. 16 | 17 | 18 | 19 | 20 | 21 | org.apache.maven.plugins 22 | maven-compiler-plugin 23 | 24 | 25 | default-compile 26 | 27 | 9 28 | 29 | 30 | 31 | base-compile 32 | 33 | compile 34 | 35 | 36 | 37 | module-info.java 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /annotation/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module online.sharedtype.annotation { 2 | exports online.sharedtype; 3 | requires java.base; 4 | } 5 | -------------------------------------------------------------------------------- /annotation/src/main/java/online/sharedtype/TagLiterals.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.FIELD}) 9 | @Retention(RetentionPolicy.SOURCE) 10 | public @interface TagLiterals { 11 | SharedType.TagLiteral[] value(); 12 | } 13 | -------------------------------------------------------------------------------- /client-test/go/go.mod: -------------------------------------------------------------------------------- 1 | module sharedtype.online/go-client-test 2 | 3 | go 1.23.2 4 | 5 | require ( 6 | github.com/go-chi/chi/v5 v5.2.1 7 | github.com/stretchr/testify v1.10.0 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /client-test/go/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= 4 | github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 8 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /client-test/go/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log/slog" 7 | "net/http" 8 | 9 | "github.com/go-chi/chi/v5" 10 | "github.com/go-chi/chi/v5/middleware" 11 | "sharedtype.online/go-client-test/sharedtype" 12 | ) 13 | 14 | func main() { 15 | r := chi.NewRouter() 16 | r.Use(middleware.Logger) 17 | r.Get("/health", func(w http.ResponseWriter, r *http.Request) { 18 | w.Write([]byte(`ok`)) 19 | }) 20 | r.Post("/{typeName}", route) 21 | fmt.Println("Server started on :3001, waiting for requests...") 22 | http.ListenAndServe(":3001", r) 23 | defer fmt.Println("Server stopped.") 24 | } 25 | 26 | func route(w http.ResponseWriter, r *http.Request) { 27 | typeName := chi.URLParam(r, "typeName") 28 | res := createStruct(typeName) 29 | 30 | if res == nil { 31 | http.Error(w, fmt.Sprintf("Unknown typeName: %s", typeName), 400) 32 | return 33 | } 34 | 35 | if err := json.NewDecoder(r.Body).Decode(res); err != nil { 36 | http.Error(w, err.Error(), 400) 37 | slog.Error("Failed to decode request body", "error", err) 38 | return 39 | } 40 | 41 | w.Header().Set("Content-Type", "application/json") 42 | 43 | if err := json.NewEncoder(w).Encode(res); err != nil { 44 | http.Error(w, err.Error(), 500) 45 | return 46 | } 47 | } 48 | 49 | func createStruct(typename string) any { 50 | switch typename { 51 | case "JavaClass": 52 | return &sharedtype.JavaClass{} 53 | case "JavaTimeClass": 54 | return &sharedtype.JavaTimeClass{} 55 | case "SubtypeWithNestedCustomTypeString": 56 | return &sharedtype.SubtypeWithNestedCustomTypeString{} 57 | case "DependencyClassA": 58 | return &sharedtype.DependencyClassA{} 59 | case "MapClass": 60 | return &sharedtype.MapClass{} 61 | case "ArrayClass": 62 | return &sharedtype.ArrayClass{} 63 | case "JavaRecord": 64 | return &sharedtype.JavaRecord[string]{} 65 | case "MathClass": 66 | return &sharedtype.MathClass{} 67 | default: 68 | return nil 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /client-test/go/setenv: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export PATH=$GO_HOME/bin:$PATH 4 | go version 5 | 6 | -------------------------------------------------------------------------------- /client-test/go/sharedtype/types.go: -------------------------------------------------------------------------------- 1 | ../../../it/java17/target/generated-sources/types.go -------------------------------------------------------------------------------- /client-test/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sharedtype-client-test-rust" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | serde = { version = "1.0", features = ["derive"] } 8 | serde_json = "1.0" 9 | tiny_http = "0.11" 10 | -------------------------------------------------------------------------------- /client-test/rust/src/types.rs: -------------------------------------------------------------------------------- 1 | ../../../it/java17/target/generated-sources/types.rs -------------------------------------------------------------------------------- /client-test/typescript/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | .vite/ 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | !.vscode/launch.json 20 | !.vscode/settings.json 21 | .idea 22 | .DS_Store 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | tmp/ 29 | -------------------------------------------------------------------------------- /client-test/typescript/.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | -------------------------------------------------------------------------------- /client-test/typescript/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import tseslint from "typescript-eslint"; 3 | import { defineConfig } from "eslint/config"; 4 | 5 | 6 | export default defineConfig([ 7 | { files: ["**/*.{js,mjs,cjs,ts}"], plugins: { js }, extends: ["js/recommended"] }, 8 | tseslint.configs.recommended, 9 | { 10 | rules: { 11 | semi: ["warn", "always"], 12 | "@typescript-eslint/no-explicit-any": "off", 13 | // tsc will handle below: 14 | "@typescript-eslint/no-unused-vars": "off", 15 | "no-undef": "off", 16 | "no-case-declarations": "off", 17 | } 18 | } 19 | ]); -------------------------------------------------------------------------------- /client-test/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-client-test", 3 | "version": "0.1.0", 4 | "main": "index.js", 5 | "type": "module", 6 | "scripts": { 7 | "lint": "tsc --noEmit && eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 8 | "test": "npx tsc --noEmit && vitest", 9 | "start": "tsc && node dist/client-test/typescript/src/server.js" 10 | }, 11 | "keywords": [], 12 | "author": "Cause Chung", 13 | "license": "CC BY 4.0", 14 | "description": "typescript-client-test", 15 | "devDependencies": { 16 | "@eslint/js": "9.26.0", 17 | "@types/express": "5.0.1", 18 | "@types/morgan": "1.9.9", 19 | "@types/node": "22.15.17", 20 | "eslint": "9.24.0", 21 | "typescript": "5.6.3", 22 | "typescript-eslint": "8.32.0", 23 | "vitest": "3.1.1" 24 | }, 25 | "dependencies": { 26 | "body-parser": "2.2.0", 27 | "express": "5.1.0", 28 | "morgan": "1.10.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /client-test/typescript/setenv: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export PATH=$NODE22_HOME/bin:./node_modules/.bin:$PATH 4 | echo Node:"$(node -v)" 5 | 6 | -------------------------------------------------------------------------------- /client-test/typescript/src/index.java17.ts: -------------------------------------------------------------------------------- 1 | export * from "../../../it/java17/target/generated-sources/types"; 2 | -------------------------------------------------------------------------------- /client-test/typescript/src/index.java8.d.ts: -------------------------------------------------------------------------------- 1 | export * from "../../../it/java8/target/generated-sources/types"; 2 | -------------------------------------------------------------------------------- /client-test/typescript/src/types.java8.ts: -------------------------------------------------------------------------------- 1 | import type { CustomCode, JavaClass } from "./index.java8.js"; 2 | 3 | export const javaClass: JavaClass = { 4 | string: "", 5 | size: 1, 6 | notIgnoredImplementedMethod: 0, 7 | a: 0, 8 | value: 0 9 | }; 10 | 11 | export const customCodeType: CustomCode = { 12 | }; -------------------------------------------------------------------------------- /client-test/typescript/tests/runtime.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest'; 2 | import { 3 | FLOAT_VALUE, LONG_VALUE, MyEnumConstants, REFERENCED_LOCAL_VALUE, SELF_REFERENCED_LOCAL_VALUE, REFERENCED_IMPORTED_VALUE, 4 | REFERENCED_NESTED_VALUE, REFERENCED_STATIC_IMPORTED_VALUE, STATIC_FIELD_FROM_JAVA_RECORD, DOUBLE_REFERENCED_VALUE, 5 | REFERENCED_PACKAGE_PRIVATE_VALUE, REFERENCED_SUPER_VALUE, SELECTED_SUPER_VALUE, REFERENCED_VALUE_IN_STATIC_BLOCK, 6 | REFERENCED_LOCAL_VALUE_IN_STATIC_BLOCK, SELF_REFERENCED_LOCAL_VALUE_IN_STATIC_BLOCK, REFERENCED_IMPORTED_VALUE_IN_STATIC_BLOCK, 7 | REFERENCED_NESTED_VALUE_IN_STATIC_BLOCK, REFERENCED_STATIC_IMPORTED_VALUE_IN_STATIC_BLOCK, 8 | REFERENCED_PACKAGE_PRIVATE_VALUE_IN_STATIC_BLOCK, REFERENCED_SUPER_VALUE_IN_STATIC_BLOCK, SELECTED_SUPER_VALUE_IN_STATIC_BLOCK, 9 | REFERENCED_STATIC_VALUE_IN_STATIC_BLOCK, INNER_REFERENCED_SUPER_VALUE_IN_STATIC, 10 | REFERENCED_ENUM_VALUE, REFERENCED_ENUM_VALUE_IN_STATIC_BLOCK, 11 | MATH_VALUE, MATH_VALUE_QUALIFIED_NAME, MATH_VALUE_REFERENCED_LOCAL_VALUE 12 | } from '../src/index.java17'; 13 | 14 | test('Constant values', () => { 15 | expect(FLOAT_VALUE).toBe(1.888); 16 | expect(LONG_VALUE).toBe(999); 17 | expect(MyEnumConstants.INT_VALUE).toBe(1); 18 | expect(MyEnumConstants.STR_VALUE).toBe("abc"); 19 | expect(STATIC_FIELD_FROM_JAVA_RECORD).toBe(888); 20 | expect(REFERENCED_LOCAL_VALUE).toBe(555); 21 | expect(SELF_REFERENCED_LOCAL_VALUE).toBe(555); 22 | expect(REFERENCED_IMPORTED_VALUE).toBe(666); 23 | expect(REFERENCED_NESTED_VALUE).toBe(777); 24 | expect(REFERENCED_STATIC_IMPORTED_VALUE).toBe(999); 25 | expect(DOUBLE_REFERENCED_VALUE).toBe(555); 26 | expect(REFERENCED_PACKAGE_PRIVATE_VALUE).toBe(123); 27 | expect(REFERENCED_SUPER_VALUE).toBe(345); 28 | expect(SELECTED_SUPER_VALUE).toBe(345); 29 | expect(REFERENCED_VALUE_IN_STATIC_BLOCK).toBe(112); 30 | expect(REFERENCED_LOCAL_VALUE_IN_STATIC_BLOCK).toBe(555); 31 | expect(SELF_REFERENCED_LOCAL_VALUE_IN_STATIC_BLOCK).toBe(555); 32 | expect(REFERENCED_IMPORTED_VALUE_IN_STATIC_BLOCK).toBe(666); 33 | expect(REFERENCED_NESTED_VALUE_IN_STATIC_BLOCK).toBe(777); 34 | expect(REFERENCED_STATIC_IMPORTED_VALUE_IN_STATIC_BLOCK).toBe(999); 35 | expect(REFERENCED_PACKAGE_PRIVATE_VALUE_IN_STATIC_BLOCK).toBe(123); 36 | expect(REFERENCED_SUPER_VALUE_IN_STATIC_BLOCK).toBe(345); 37 | expect(SELECTED_SUPER_VALUE_IN_STATIC_BLOCK).toBe(345); 38 | expect(REFERENCED_STATIC_VALUE_IN_STATIC_BLOCK).toBe(787); 39 | expect(INNER_REFERENCED_SUPER_VALUE_IN_STATIC).toBe(345); 40 | expect(REFERENCED_ENUM_VALUE).toBe("MilkyWay"); 41 | expect(REFERENCED_ENUM_VALUE_IN_STATIC_BLOCK).toBe("MilkyWay"); 42 | expect(MATH_VALUE).toBe("1.1"); 43 | expect(MATH_VALUE_QUALIFIED_NAME).toBe("88885555"); 44 | expect(MATH_VALUE_REFERENCED_LOCAL_VALUE).toBe("555"); 45 | }); 46 | -------------------------------------------------------------------------------- /client-test/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "target": "es2022", 5 | "module": "es2022", 6 | "lib": [ 7 | "esnext", 8 | ], 9 | "moduleResolution": "node", 10 | "esModuleInterop": true, 11 | "resolveJsonModule": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "skipLibCheck": false, 14 | /* Linting */ 15 | "strict": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": false, 18 | "noFallthroughCasesInSwitch": true, 19 | "noUncheckedIndexedAccess": true 20 | }, 21 | "include": [ 22 | "src" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /client-test/typescript/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | include: ['./tests/**/*.test.tsx', './tests/**/*.test.ts'], 7 | watch: false, 8 | poolOptions: { 9 | threads: { 10 | singleThread: true, 11 | } 12 | }, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | online.sharedtype 6 | sharedtype-parent 7 | 0.12.1-SNAPSHOT 8 | ../pom.xml 9 | 10 | 11 | sharedtype-e2e 12 | SharedType E2E Test 13 | 14 | 15 | 21 16 | 21 17 | 18 | 19 | 20 | 21 | ${project.groupId} 22 | sharedtype 23 | provided 24 | true 25 | 26 | 27 | ${project.groupId} 28 | sharedtype-it-java17 29 | ${project.version} 30 | 31 | 32 | 33 | org.junit.jupiter 34 | junit-jupiter 35 | test 36 | 37 | 38 | org.assertj 39 | assertj-core 40 | test 41 | 42 | 43 | com.fasterxml.jackson.core 44 | jackson-databind 45 | test 46 | 47 | 48 | com.fasterxml.jackson.datatype 49 | jackson-datatype-jsr310 50 | test 51 | 52 | 53 | com.fasterxml.jackson.datatype 54 | jackson-datatype-jdk8 55 | test 56 | 57 | 58 | org.awaitility 59 | awaitility 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/MappableType.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain; 2 | 3 | import online.sharedtype.SharedType; 4 | 5 | import online.sharedtype.processor.support.annotation.Nullable; 6 | 7 | public interface MappableType { 8 | String qualifiedName(); 9 | 10 | @Nullable 11 | String mappedName(@Nullable SharedType.TargetType targetType); 12 | 13 | default String mappedNameOrDefault(SharedType.TargetType targetType, String defaultExpr) { 14 | String name = mappedName(targetType); 15 | return name == null ? defaultExpr : name; 16 | } 17 | 18 | void addMappedName(SharedType.TargetType targetType, String mappedName); 19 | } 20 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/component/AbstractComponentInfo.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.component; 2 | 3 | import lombok.Builder; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import lombok.experimental.SuperBuilder; 8 | import online.sharedtype.SharedType; 9 | 10 | import javax.lang.model.element.Element; 11 | import java.util.Collections; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | @EqualsAndHashCode(of = {"element", "name"}) 16 | @SuperBuilder(toBuilder = true) 17 | public abstract class AbstractComponentInfo implements ComponentInfo { 18 | private static final long serialVersionUID = -3498751865425579350L; 19 | 20 | @Getter 21 | private transient final Element element; 22 | private final String name; 23 | @Setter @Builder.Default 24 | private Map> tagLiterals = Collections.emptyMap(); 25 | 26 | @Override 27 | public final String name() { 28 | return name; 29 | } 30 | 31 | @Override 32 | public final List getTagLiterals(SharedType.TargetType targetType) { 33 | return tagLiterals.getOrDefault(targetType, Collections.emptyList()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/component/ComponentInfo.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.component; 2 | 3 | import online.sharedtype.SharedType; 4 | import online.sharedtype.processor.domain.def.TypeDef; 5 | 6 | import java.io.Serializable; 7 | import java.util.List; 8 | 9 | /** 10 | * Represents internal components in a {@link TypeDef}. 11 | * 12 | * @author Cause Chung 13 | */ 14 | public interface ComponentInfo extends Serializable { 15 | boolean resolved(); 16 | String name(); 17 | List getTagLiterals(SharedType.TargetType targetType); 18 | } 19 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/component/ConstantField.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.component; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.experimental.SuperBuilder; 6 | import online.sharedtype.processor.domain.type.TypeInfo; 7 | import online.sharedtype.processor.domain.value.ValueHolder; 8 | 9 | /** 10 | * Represents a constant literal. 11 | * Only literals with values resolvable at compile time are supported. 12 | * 13 | * @author Cause Chung 14 | */ 15 | @EqualsAndHashCode(of = {"value"}, callSuper = true) 16 | @SuperBuilder 17 | public final class ConstantField extends AbstractComponentInfo { 18 | private static final long serialVersionUID = -155863067131290289L; 19 | private final ValueHolder value; 20 | 21 | public ValueHolder value() { 22 | return value; 23 | } 24 | 25 | @Override 26 | public boolean resolved() { 27 | return value.getValueType().resolved(); 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return String.format("%s=%s", name(), value); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/component/EnumValueInfo.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.component; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.experimental.SuperBuilder; 6 | import online.sharedtype.SharedType; 7 | import online.sharedtype.processor.domain.def.EnumDef; 8 | import online.sharedtype.processor.domain.value.EnumConstantValue; 9 | 10 | /** 11 | * Represents an enum value, which is the value in the target code that corresponds to an enum constant. 12 | *
13 | * By default, enum value is String value of the enum constant. It can be configured with {@link SharedType.EnumValue}. 14 | * 15 | * @see EnumDef 16 | * @see SharedType.EnumValue 17 | * @author Cause Chung 18 | */ 19 | @EqualsAndHashCode(of = {"value"}, callSuper = true) 20 | @SuperBuilder 21 | public final class EnumValueInfo extends AbstractComponentInfo { 22 | private static final long serialVersionUID = 1117324458104635595L; 23 | private final EnumConstantValue value; 24 | 25 | public EnumConstantValue value() { 26 | return value; 27 | } 28 | 29 | @Override 30 | public boolean resolved() { 31 | return value.getValueType().resolved(); 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return value.toString(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/component/FieldComponentInfo.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.component; 2 | 3 | import lombok.Setter; 4 | import lombok.experimental.SuperBuilder; 5 | import online.sharedtype.processor.domain.type.TypeInfo; 6 | 7 | import javax.lang.model.element.Modifier; 8 | import java.util.Set; 9 | 10 | /** 11 | * Represents a field or accessor. 12 | * 13 | * @author Cause Chung 14 | */ 15 | @SuperBuilder(toBuilder = true) 16 | public final class FieldComponentInfo extends AbstractComponentInfo { 17 | private static final long serialVersionUID = -155863067131290289L; 18 | @Setter 19 | private TypeInfo type; 20 | @Setter 21 | private boolean optional; 22 | 23 | public boolean optional() { 24 | return optional; 25 | } 26 | 27 | public TypeInfo type() { 28 | return type; 29 | } 30 | 31 | @Override 32 | public boolean resolved() { 33 | return type.resolved(); 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return String.format("%s %s%s", type, name(), optional ? "?" : ""); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/def/ConcreteTypeDef.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.def; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.experimental.SuperBuilder; 6 | import online.sharedtype.processor.domain.type.ConcreteTypeInfo; 7 | 8 | import javax.lang.model.element.Element; 9 | import java.util.Set; 10 | 11 | @SuperBuilder(toBuilder = true) 12 | @Setter 13 | @Getter 14 | public abstract class ConcreteTypeDef implements TypeDef { 15 | private static final long serialVersionUID = 2346502341189835693L; 16 | @Getter 17 | private final transient Element element; 18 | /** Whether this type is explicitly annotated with {@link online.sharedtype.SharedType} */ 19 | private final boolean annotated; 20 | /** Directly or indirectly referenced by a type that is annotated. */ 21 | private boolean referencedByAnnotated; 22 | 23 | /** Referenced or as a supertype of another type. */ 24 | private boolean depended; 25 | 26 | private boolean cyclicReferenced; 27 | 28 | public abstract Set typeInfoSet(); 29 | 30 | /** 31 | * Register a counterpart typeInfo. 32 | * Note: This method should not be called from outside, please use {@link ConcreteTypeInfo#shallowResolved()} 33 | */ 34 | public abstract void linkTypeInfo(ConcreteTypeInfo typeInfo); 35 | } 36 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/def/ConstantNamespaceDef.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.def; 2 | 3 | import lombok.Builder; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import online.sharedtype.processor.domain.component.ConstantField; 8 | 9 | import javax.lang.model.element.Element; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * Represents a constant namespace, i.e. a java class that contains static fields. 15 | */ 16 | @Builder 17 | @EqualsAndHashCode(of = "qualifiedName") 18 | public final class ConstantNamespaceDef implements TypeDef { 19 | private static final long serialVersionUID = 4249235760298548628L; 20 | @Getter 21 | private final transient Element element; 22 | private final String qualifiedName; 23 | private final String simpleName; 24 | @Builder.Default 25 | private final List constants = new ArrayList<>(); 26 | @Getter @Setter 27 | private boolean annotated; 28 | 29 | @Override 30 | public String qualifiedName() { 31 | return qualifiedName; 32 | } 33 | 34 | @Override 35 | public String simpleName() { 36 | return simpleName; 37 | } 38 | 39 | @Override 40 | public List components() { 41 | return constants; 42 | } 43 | 44 | @Override 45 | public boolean resolved() { 46 | for (ConstantField constant : constants) { 47 | if (!constant.resolved()) { 48 | return false; 49 | } 50 | } 51 | return true; 52 | } 53 | 54 | @Override 55 | public boolean isCyclicReferenced() { 56 | return false; 57 | } 58 | 59 | @Override 60 | public void setCyclicReferenced(boolean cyclicReferenced) { 61 | } 62 | 63 | @Override 64 | public boolean isReferencedByAnnotated() { 65 | return false; 66 | } 67 | 68 | @Override 69 | public void setReferencedByAnnotated(boolean referencedByAnnotated) { 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | StringBuilder sb = new StringBuilder(); 75 | sb.append("const ").append(qualifiedName).append(" {").append(System.lineSeparator()); 76 | for (ConstantField constant : constants) { 77 | sb.append(" ").append(constant).append(";").append(System.lineSeparator()); 78 | } 79 | sb.append("}"); 80 | return sb.toString(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/def/TypeDef.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.def; 2 | 3 | import online.sharedtype.processor.domain.component.ComponentInfo; 4 | import online.sharedtype.processor.domain.type.TypeInfo; 5 | 6 | import javax.lang.model.element.Element; 7 | import java.io.Serializable; 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | /** 12 | * Type definition. Represents type structure used for target output. 13 | * This is the unit for target code generation. 14 | * On the other hand, {@link TypeInfo} represents a type without structural information, which is referenced in {@link TypeDef}. 15 | * 16 | * @see TypeInfo 17 | * @author Cause Chung 18 | */ 19 | public interface TypeDef extends Serializable { 20 | Element getElement(); 21 | 22 | String qualifiedName(); 23 | 24 | String simpleName(); 25 | 26 | List components(); 27 | 28 | default List directSupertypes() { 29 | return Collections.emptyList(); 30 | } 31 | 32 | /** 33 | * @return true if all required types are resolved. 34 | */ 35 | boolean resolved(); 36 | 37 | boolean isCyclicReferenced(); 38 | void setCyclicReferenced(boolean cyclicReferenced); 39 | 40 | boolean isAnnotated(); 41 | 42 | boolean isReferencedByAnnotated(); 43 | void setReferencedByAnnotated(boolean referencedByAnnotated); 44 | } 45 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/type/ArrayTypeInfo.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.type; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.RequiredArgsConstructor; 5 | import online.sharedtype.processor.domain.def.TypeDef; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * Represents an array-like type. 11 | * During parsing, a predefined array-like type and its subtypes is captured as this class. 12 | * A type will be recognized as this type with higher priority than {@link ConcreteTypeInfo}. 13 | * It has no counterpart typeDef, explicitly annotating {@code @SharedType} on an array type is ignored with a warning. 14 | *
15 | * Predefined array-like types can be configured in global properties. Default is {@link java.lang.Iterable}. 16 | * 17 | * @see ConcreteTypeInfo 18 | * @author Cause Chung 19 | */ 20 | @RequiredArgsConstructor 21 | @EqualsAndHashCode 22 | public final class ArrayTypeInfo implements TypeInfo { 23 | private static final long serialVersionUID = -6969192495547169811L; 24 | private final TypeInfo component; 25 | 26 | public TypeInfo component() { 27 | return component; 28 | } 29 | 30 | @Override 31 | public boolean resolved() { 32 | return component.resolved(); 33 | } 34 | 35 | @Override 36 | public TypeInfo reify(Map mappings) { 37 | TypeInfo reifiedComponent = component.reify(mappings); 38 | return reifiedComponent == component ? this : new ArrayTypeInfo(reifiedComponent); 39 | } 40 | @Override 41 | public void addReferencingType(TypeDef typeDef) { 42 | component.addReferencingType(typeDef); 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return component + "[]"; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/type/DateTimeInfo.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.type; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.RequiredArgsConstructor; 5 | import online.sharedtype.SharedType; 6 | import online.sharedtype.processor.domain.MappableType; 7 | 8 | import online.sharedtype.processor.support.annotation.Nullable; 9 | import java.util.EnumMap; 10 | import java.util.Map; 11 | 12 | /** 13 | * Represents a date/time type, which is simply be emitted as a string or any custom type literal. 14 | * It has no counterpart typeDef, explicitly annotating {@code @SharedType} on a date/time type is ignored with a warning. 15 | * 16 | * @author Cause Chung 17 | */ 18 | @EqualsAndHashCode(of = "qualifiedName", callSuper = false) 19 | @RequiredArgsConstructor 20 | public final class DateTimeInfo extends ReferableTypeInfo implements MappableType { 21 | private static final long serialVersionUID = 5428192893749821359L; 22 | 23 | private final String qualifiedName; 24 | /** Defined type mapping, see {@link SharedType} for details */ 25 | private final Map mappedNames = new EnumMap<>(SharedType.TargetType.class); 26 | 27 | public String qualifiedName() { 28 | return qualifiedName; 29 | } 30 | 31 | @Nullable 32 | @Override 33 | public String mappedName(@Nullable SharedType.TargetType targetType) { 34 | return targetType == null ? null : mappedNames.get(targetType); 35 | } 36 | 37 | @Override 38 | public void addMappedName(SharedType.TargetType targetType, String mappedName) { 39 | mappedNames.put(targetType, mappedName); 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return String.format("DateTime(%s)", qualifiedName); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/type/NoTypeInfo.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.type; 2 | 3 | import online.sharedtype.processor.domain.def.TypeDef; 4 | 5 | /** 6 | * Present no type info. 7 | * @see online.sharedtype.processor.parser.type.TypeInfoParser 8 | * @author Cause Chung 9 | */ 10 | final class NoTypeInfo implements TypeInfo { 11 | private static final long serialVersionUID = 1773678739237108430L; 12 | 13 | static final NoTypeInfo INSTANCE = new NoTypeInfo(); 14 | private NoTypeInfo() {} 15 | 16 | @Override 17 | public void addReferencingType(TypeDef typeDef) { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/type/ReferableTypeInfo.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.type; 2 | 3 | import online.sharedtype.processor.domain.def.TypeDef; 4 | 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | public abstract class ReferableTypeInfo implements TypeInfo { 9 | private static final long serialVersionUID = -8637192825773596439L; 10 | /** 11 | * Qualified names of types from where this typeInfo is strongly referenced, i.e. as a component type. 12 | */ 13 | private final Set referencingTypes = new HashSet<>(); 14 | 15 | @Override 16 | public final void addReferencingType(TypeDef typeDef) { 17 | referencingTypes.add(typeDef); 18 | } 19 | 20 | public final Set referencingTypes() { 21 | return referencingTypes; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/type/TypeInfo.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.type; 2 | 3 | import online.sharedtype.processor.domain.def.TypeDef; 4 | 5 | import java.io.Serializable; 6 | import java.util.Map; 7 | 8 | /** 9 | * Type information. Similar concept as {@link javax.lang.model.type.TypeMirror}. 10 | * 11 | * @author Cause Chung 12 | * @see TypeDef 13 | */ 14 | public interface TypeInfo extends Serializable { 15 | /** 16 | *

Check if this type and its dependency types are resolved.

17 | *

18 | * When parsing a type's structure, a dependency type is firstly captured as a {@link TypeInfo}. 19 | * At this stage, because we don't know its output structure or if it needs to be output at all, we mark it as unresolved. 20 | * Also, due to possible cyclic dependencies, the resolution stage needs to be performed after initial parsing state. 21 | * During resolution, once a type is parsed, it's marked as resolved. 22 | * Note that a type is marked as resolved when created, if it can be determined at that time. 23 | *

24 | *

Object contains resolved flag as a mutable state

25 | * 26 | * @return true is this type and its dependency types are resolved. 27 | */ 28 | default boolean resolved() { 29 | return true; 30 | } 31 | 32 | /** 33 | * Replace type variables with type arguments. 34 | * 35 | * @param mappings key is a type variable e.g. T 36 | * value is a type argument, a concrete type e.g. Integer, or a generic type with concrete type parameter, e.g. {@code Tuple} 37 | * @return a newly created type info if updated. 38 | */ 39 | default TypeInfo reify(Map mappings) { 40 | return this; 41 | } 42 | 43 | /** 44 | * Mark this type as referenced by another type. Used for e.g. cyclic reference detection. 45 | * 46 | * @param typeDef type that references this type 47 | */ 48 | void addReferencingType(TypeDef typeDef); 49 | 50 | /** 51 | * Represents no type. This can happen when a type is not visible, e.g. not on module path. 52 | * @see online.sharedtype.processor.parser.type.TypeInfoParser 53 | */ 54 | TypeInfo NO_TYPE_INFO = NoTypeInfo.INSTANCE; 55 | } 56 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/type/TypeVariableInfo.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.type; 2 | 3 | import lombok.Builder; 4 | import lombok.EqualsAndHashCode; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * Represents a generic type variable. 10 | *
11 | * A type variable refers to a generic type parameter, it has a notation like "T" or bound information like "T extends Number". 12 | * A type argument is the actual type of the type variable. E.g. {@code "Integer" in "List"}. 13 | * 14 | * @see ConcreteTypeInfo#typeArgs() 15 | * @author Cause Chung 16 | */ 17 | @EqualsAndHashCode(of = {"contextTypeQualifiedName", "name"}, callSuper = false) 18 | @Builder 19 | public final class TypeVariableInfo extends ReferableTypeInfo { 20 | private static final long serialVersionUID = 7632941203572660271L; 21 | private final String contextTypeQualifiedName; // TODO: reference to TypeDef to avoid string 22 | private final String name; 23 | private String qualifiedName; 24 | // TODO: support generic bounds 25 | 26 | public static String concatQualifiedName(String contextTypeQualifiedName, String name) { 27 | return contextTypeQualifiedName + "@" + name; 28 | } 29 | 30 | public String contextTypeQualifiedName() { 31 | return contextTypeQualifiedName; 32 | } 33 | 34 | public String name() { 35 | return name; 36 | } 37 | 38 | public String qualifiedName() { 39 | if (qualifiedName == null) { 40 | qualifiedName = concatQualifiedName(contextTypeQualifiedName, name); 41 | } 42 | return qualifiedName; 43 | } 44 | 45 | @Override 46 | public TypeInfo reify(Map mappings) { 47 | TypeInfo reifiedType = mappings.get(this); 48 | return reifiedType == null ? this : reifiedType; 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return name; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/value/EnumConstantValue.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.value; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import online.sharedtype.processor.domain.type.ConcreteTypeInfo; 6 | import online.sharedtype.processor.domain.type.TypeInfo; 7 | 8 | @Getter 9 | @EqualsAndHashCode(callSuper = true) 10 | public final class EnumConstantValue extends LiteralValue { 11 | private static final long serialVersionUID = -6711930218877737970L; 12 | private final ConcreteTypeInfo enumType; 13 | private final String enumConstantName; 14 | EnumConstantValue(ConcreteTypeInfo enumType, String enumConstantName, ConcreteTypeInfo valueType, Object value) { 15 | super(valueType, value); 16 | this.enumType = enumType; 17 | this.enumConstantName = enumConstantName; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return String.format("%s(%s)", enumConstantName, getValue()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/value/LiteralValue.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.value; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | import online.sharedtype.processor.domain.type.ConcreteTypeInfo; 8 | import online.sharedtype.processor.domain.type.TypeInfo; 9 | 10 | import java.util.Objects; 11 | 12 | @Getter 13 | @EqualsAndHashCode 14 | @RequiredArgsConstructor(access = AccessLevel.PACKAGE) 15 | public class LiteralValue implements ValueHolder { 16 | private static final long serialVersionUID = -7324230239169028973L; 17 | private final ConcreteTypeInfo valueType; 18 | private final Object value; 19 | 20 | @Override 21 | public String toString() { 22 | return Objects.toString(value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/domain/value/ValueHolder.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.value; 2 | 3 | import online.sharedtype.processor.domain.Constants; 4 | import online.sharedtype.processor.domain.type.ConcreteTypeInfo; 5 | 6 | import java.io.Serializable; 7 | 8 | public interface ValueHolder extends Serializable { 9 | ConcreteTypeInfo getValueType(); 10 | Object getValue(); 11 | 12 | default String literalValue() { 13 | Object value = getValue(); 14 | if (value instanceof CharSequence || value instanceof Character) { 15 | return String.format("\"%s\"", value); // TODO: options single or double quotes? 16 | } else { 17 | return String.valueOf(value); 18 | } 19 | } 20 | 21 | static ValueHolder of(ConcreteTypeInfo valueType, Object value) { 22 | if (value instanceof ValueHolder) { 23 | return (ValueHolder) value; 24 | } else { 25 | return new LiteralValue(valueType, value); 26 | } 27 | } 28 | 29 | static EnumConstantValue ofEnum(ConcreteTypeInfo enumType, String enumConstantName, ConcreteTypeInfo valueType, Object value) { 30 | ConcreteTypeInfo actualValueType = valueType; 31 | Object actualValue = value; 32 | while (actualValue instanceof ValueHolder) { 33 | ValueHolder valueHolder = (ValueHolder) actualValue; 34 | actualValueType = valueHolder.getValueType(); 35 | actualValue = valueHolder.getValue(); 36 | } 37 | return new EnumConstantValue(enumType, enumConstantName, actualValueType, actualValue); 38 | } 39 | 40 | LiteralValue NULL = new LiteralValue(Constants.NULL_TYPE_INFO,null); 41 | } 42 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/support/annotation/Issue.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.support.annotation; 2 | 3 | 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | 7 | /** 8 | * Mark an issue number. 9 | * 10 | * @author Cause Chung 11 | */ 12 | @Retention(RetentionPolicy.SOURCE) 13 | public @interface Issue { 14 | int value(); 15 | String comment() default ""; 16 | } 17 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/support/annotation/Nullable.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.support.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Nullable. To avoid jsr305 dependency. 11 | * @author Cause Chung 12 | */ 13 | @Documented 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER, ElementType.PARAMETER, ElementType.FIELD}) 16 | public @interface Nullable { 17 | } 18 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/support/annotation/SideEffect.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.support.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Indicate side effect performed by a method or on a parameter. 10 | * 11 | * @author Cause Chung 12 | */ 13 | @Target({ElementType.METHOD, ElementType.PARAMETER}) 14 | @Retention(RetentionPolicy.SOURCE) 15 | public @interface SideEffect { 16 | String value() default ""; 17 | } 18 | -------------------------------------------------------------------------------- /internal/src/main/java/online/sharedtype/processor/support/annotation/VisibleForTesting.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.support.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Indicate that the visibility of a method or class is greater than needed in compile scope code, but exposed for testing purpose. 10 | * 11 | * @author Cause Chung 12 | */ 13 | @Target({ ElementType.METHOD, ElementType.TYPE }) 14 | @Retention(RetentionPolicy.SOURCE) 15 | public @interface VisibleForTesting { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /internal/src/test/java/online/sharedtype/it/support/TypeDefDeserializer.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.support; 2 | 3 | import online.sharedtype.processor.domain.def.TypeDef; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.ObjectInputStream; 8 | 9 | import static java.util.Objects.requireNonNull; 10 | 11 | public final class TypeDefDeserializer { 12 | private static final ClassLoader classLoader = TypeDefDeserializer.class.getClassLoader(); 13 | private TypeDefDeserializer() {} 14 | 15 | public static TypeDef deserializeTypeDef(String serFilename) { 16 | try (InputStream is = classLoader.getResourceAsStream(serFilename); 17 | ObjectInputStream ois = new ObjectInputStream(requireNonNull(is, "Cannot find " + serFilename))) { 18 | return (TypeDef) ois.readObject(); 19 | } catch (IOException | ClassNotFoundException e) { 20 | throw new RuntimeException(e); 21 | } 22 | } 23 | 24 | public static boolean doesResourceExist(String serFilename) { 25 | return classLoader.getResource(serFilename) != null; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /it/custom-code.go: -------------------------------------------------------------------------------- 1 | // some custom code for go 2 | 3 | -------------------------------------------------------------------------------- /it/custom-code.rs: -------------------------------------------------------------------------------- 1 | // test code snippet 2 | pub struct CustomInjectedStruct { 3 | pub field: i32, 4 | } 5 | 6 | fn serialize_any(value: &Box, serializer: S) -> Result 7 | where 8 | S: serde::Serializer, 9 | { 10 | if let Some(value) = value.downcast_ref::() { 11 | serializer.serialize_str(value) 12 | } else if let Some(value) = value.downcast_ref::() { 13 | serializer.serialize_i32(*value) 14 | } else { 15 | Err(serde::ser::Error::custom("Unsupported type")) 16 | } 17 | } 18 | fn deserialize_any<'de, D>(deserializer: D) -> Result, D::Error> 19 | where 20 | D: serde::Deserializer<'de>, 21 | { 22 | let s: &str = serde::Deserialize::deserialize(deserializer)?; 23 | Ok(Box::new(s.to_string())) 24 | } 25 | -------------------------------------------------------------------------------- /it/custom-code.ts: -------------------------------------------------------------------------------- 1 | // test code snippet 2 | export interface CustomCode { 3 | } 4 | -------------------------------------------------------------------------------- /it/java17/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | online.sharedtype 6 | sharedtype-it-parent 7 | 0.12.1-SNAPSHOT 8 | ../pom.xml 9 | 10 | 11 | sharedtype-it-java17 12 | SharedType Integration Test Java17 13 | 14 | 15 | 21 16 | 21 17 | 18 | 19 | -------------------------------------------------------------------------------- /it/java17/src/main/java/online/sharedtype/it/java17/JavaRecord.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java17; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.Builder; 6 | import online.sharedtype.SharedType; 7 | import online.sharedtype.it.java8.Container; 8 | import online.sharedtype.it.java8.DependencyClassA; 9 | import online.sharedtype.it.java8.EnumGalaxy; 10 | import online.sharedtype.it.java8.EnumSize; 11 | import online.sharedtype.it.java8.InterfaceA; 12 | 13 | import java.util.Collection; 14 | import java.util.List; 15 | import java.util.Set; 16 | 17 | @SharedType( 18 | constantNamespaced = SharedType.OptionalBool.FALSE, 19 | includes = { 20 | SharedType.ComponentType.CONSTANTS, 21 | SharedType.ComponentType.FIELDS, 22 | SharedType.ComponentType.ACCESSORS, 23 | }, 24 | rustMacroTraits = {"serde::Serialize", "serde::Deserialize"} 25 | ) 26 | @Builder 27 | public record JavaRecord( 28 | String string, 29 | byte primitiveByte, 30 | Byte boxedByte, 31 | short primitiveShort, 32 | Short boxedShort, 33 | int primitiveInt, 34 | Integer boxedInt, 35 | long primitiveLong, 36 | Long boxedLong, 37 | float primitiveFloat, 38 | Float boxedFloat, 39 | double primitiveDouble, 40 | Double boxedDouble, 41 | boolean primitiveBoolean, 42 | Boolean boxedBoolean, 43 | char primitiveChar, 44 | Character boxedChar, 45 | @SharedType.TagLiteral(tags = "// test comments for class") 46 | @SharedType.TagLiteral(tags = "#[serde(serialize_with = \"serialize_any\", deserialize_with = \"deserialize_any\")]", targets = SharedType.TargetType.RUST) 47 | Object object, 48 | // Void aVoid, 49 | 50 | DependencyClassA cyclicDependency,// cyclic a ->b ->c ->a 51 | 52 | List> containerStringList, 53 | List>> containerStringListCollection, 54 | 55 | List genericList, 56 | Set genericSet, 57 | List> genericListSet, 58 | // Map genericMap, 59 | int[] intArray, 60 | Integer[] boxedIntArray, 61 | 62 | EnumGalaxy enumGalaxy, 63 | EnumSize enumSize, 64 | 65 | String duplicateAccessor, 66 | @SharedType.Ignore @JsonIgnore String explicitlyIgnored, 67 | T value 68 | ) implements InterfaceA { 69 | static final int STATIC_FIELD_FROM_JAVA_RECORD = 888; 70 | 71 | @SharedType.Accessor 72 | String getDuplicateAccessor() { 73 | return duplicateAccessor; 74 | } 75 | 76 | String shouldNotBeIncluded() { 77 | return null; 78 | } 79 | 80 | @JsonProperty(access = JsonProperty.Access.READ_WRITE) 81 | @Override 82 | public T getValue() { 83 | return value; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /it/java17/src/main/java/online/sharedtype/it/java8: -------------------------------------------------------------------------------- 1 | ../../../../../../../java8/src/main/java/online/sharedtype/it/java8 -------------------------------------------------------------------------------- /it/java17/src/main/java/online/sharedtype/it/java8_is_symlink: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharedType/sharedtype/66eea847fb581dcde5ada2e886a1eca92871db81/it/java17/src/main/java/online/sharedtype/it/java8_is_symlink -------------------------------------------------------------------------------- /it/java17/src/test/java/online/sharedtype/it/CustomAnnoClassIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it; 2 | 3 | import online.sharedtype.processor.domain.def.ClassDef; 4 | import online.sharedtype.processor.domain.def.EnumDef; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static online.sharedtype.it.support.TypeDefDeserializer.deserializeTypeDef; 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | final class CustomAnnoClassIntegrationTest { 11 | @Test 12 | void customAnnoClass() { 13 | ClassDef classDef = (ClassDef)deserializeTypeDef("online.sharedtype.it.java8.anno.CustomAnnoClass.ser"); 14 | assertThat(classDef.simpleName()).isEqualTo("CustomAnnoClass"); 15 | assertThat(classDef.components()).hasSize(1); 16 | var field1 = classDef.components().get(0); 17 | assertThat(field1.name()).isEqualTo("someValue"); 18 | } 19 | 20 | @Test 21 | void customAnnoEnum() { 22 | EnumDef enumDef = (EnumDef)deserializeTypeDef("online.sharedtype.it.java8.anno.CustomAnnoEnum.ser"); 23 | assertThat(enumDef.simpleName()).isEqualTo("CustomAnnoEnum"); 24 | assertThat(enumDef.components()).hasSize(2); 25 | var field1 = enumDef.components().get(0); 26 | assertThat(field1.name()).isEqualTo("A"); 27 | assertThat(field1.value().getValue()).isEqualTo(1); 28 | var field2 = enumDef.components().get(1); 29 | assertThat(field2.name()).isEqualTo("B"); 30 | assertThat(field2.value().getValue()).isEqualTo(2); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /it/java17/src/test/java/online/sharedtype/it/JsonSerializationTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; 5 | import online.sharedtype.it.java17.JavaRecord; 6 | import online.sharedtype.it.java8.Container; 7 | import online.sharedtype.it.java8.DependencyClassA; 8 | import online.sharedtype.it.java8.DependencyClassB; 9 | import online.sharedtype.it.java8.DependencyClassC; 10 | import online.sharedtype.it.java8.EnumGalaxy; 11 | import online.sharedtype.it.java8.EnumSize; 12 | import online.sharedtype.it.java8.OptionalMethod; 13 | import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration; 14 | import org.junit.jupiter.api.Test; 15 | import org.junit.jupiter.api.TestInstance; 16 | 17 | import java.util.List; 18 | import java.util.Set; 19 | 20 | import static org.assertj.core.api.Assertions.assertThat; 21 | 22 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 23 | final class JsonSerializationTest { 24 | private final ObjectMapper objectMapper = new ObjectMapper(); 25 | { 26 | objectMapper.registerModule(new Jdk8Module()); 27 | } 28 | 29 | @Test 30 | void jsonOptionalMethod() throws Exception { 31 | var obj = new OptionalMethod(); 32 | obj.setValue("value"); 33 | var json = objectMapper.writeValueAsString(obj); 34 | System.out.println(json); 35 | } 36 | 37 | @Test 38 | void test() throws Exception { 39 | var obj = JavaRecord 40 | .builder() 41 | .build(); 42 | var json = objectMapper.writeValueAsString(obj); 43 | System.out.println(json); 44 | var deser = objectMapper.readValue(json, JavaRecord.class); 45 | assertThat(deser).usingRecursiveComparison(RecursiveComparisonConfiguration.builder().withIgnoredFields("explicitlyIgnored").build()) 46 | .isEqualTo(obj); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /it/java17/src/test/java/online/sharedtype/it/OptionalTypeIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it; 2 | 3 | import online.sharedtype.processor.domain.type.ArrayTypeInfo; 4 | import online.sharedtype.processor.domain.def.ClassDef; 5 | import online.sharedtype.processor.domain.type.ConcreteTypeInfo; 6 | import online.sharedtype.processor.domain.Constants; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import static online.sharedtype.it.support.TypeDefDeserializer.deserializeTypeDef; 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | final class OptionalTypeIntegrationTest { 13 | @Test 14 | void optionalMethodClass() { 15 | ClassDef classDef = (ClassDef)deserializeTypeDef("online.sharedtype.it.java8.OptionalMethod.ser"); 16 | assertThat(classDef.simpleName()).isEqualTo("OptionalMethod"); 17 | assertThat(classDef.components()).satisfiesExactly( 18 | field1 -> { 19 | assertThat(field1.name()).isEqualTo("valueOptional"); 20 | assertThat(field1.optional()).isTrue(); 21 | assertThat(field1.type()).isEqualTo(Constants.STRING_TYPE_INFO); 22 | }, 23 | field2 -> { 24 | assertThat(field2.name()).isEqualTo("nestedValueOptional"); 25 | assertThat(field2.optional()).isTrue(); 26 | assertThat(field2.type()).isEqualTo(Constants.STRING_TYPE_INFO); 27 | }, 28 | field3 -> { 29 | assertThat(field3.name()).isEqualTo("setNestedValueOptional"); 30 | assertThat(field3.optional()).isTrue(); 31 | ArrayTypeInfo arrayTypeInfo = (ArrayTypeInfo) field3.type(); 32 | assertThat(arrayTypeInfo.component()).isEqualTo(Constants.STRING_TYPE_INFO); 33 | }, 34 | field4 -> { 35 | assertThat(field4.name()).isEqualTo("mapNestedValueOptional"); 36 | assertThat(field4.optional()).isTrue(); 37 | ConcreteTypeInfo fieldTypeInfo = (ConcreteTypeInfo) field4.type(); 38 | assertThat(fieldTypeInfo.getKind()).isEqualTo(ConcreteTypeInfo.Kind.MAP); 39 | assertThat(fieldTypeInfo.qualifiedName()).isEqualTo("java.util.Map"); 40 | assertThat(fieldTypeInfo.typeArgs()).hasSize(2).satisfiesExactly( 41 | keyType -> assertThat(keyType).isEqualTo(Constants.BOXED_INT_TYPE_INFO), 42 | valueType -> assertThat(valueType).isEqualTo(Constants.STRING_TYPE_INFO) 43 | ); 44 | } 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /it/java8/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | online.sharedtype 6 | sharedtype-it-parent 7 | 0.12.1-SNAPSHOT 8 | ../pom.xml 9 | 10 | 11 | sharedtype-it-java8 12 | SharedType Integration Test Java8 13 | 14 | 15 | ${java.version} 16 | ${java.version} 17 | ${java.version} 18 | ${java.version} 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | it-no-jpms 30 | 31 | true 32 | 33 | 34 | 8 35 | module-info.java 36 | 37 | 38 | 39 | it-jpms 40 | 41 | 9 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /it/java8/setenv: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export JAVA_HOME=$JAVA8_HOME 4 | export PATH=$JAVA_HOME/bin:$PATH 5 | 6 | java -version 7 | alias mvnw="../../mvnw" 8 | echo "alias mvnw is point to ../../mvnw" 9 | -------------------------------------------------------------------------------- /it/java8/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module sharedtype.it { 2 | requires java.compiler; 3 | requires java.base; 4 | requires java.sql; 5 | requires online.sharedtype.annotation; 6 | requires com.fasterxml.jackson.annotation; 7 | requires org.joda.time; 8 | 9 | requires static lombok; 10 | } 11 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/ArrayClass.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import lombok.Data; 4 | import online.sharedtype.SharedType; 5 | 6 | @SharedType( 7 | rustMacroTraits = {"PartialEq", "Eq", "Hash", "serde::Serialize", "serde::Deserialize"} 8 | ) 9 | @Data 10 | public final class ArrayClass { 11 | private CustomList arr; 12 | } 13 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/Container.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import lombok.Data; 4 | import online.sharedtype.SharedType; 5 | 6 | @SharedType( 7 | rustMacroTraits = {"serde::Serialize", "serde::Deserialize"} 8 | ) 9 | @Data 10 | public final class Container { 11 | private T t; 12 | } 13 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/CustomList.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import java.util.ArrayList; 4 | 5 | public final class CustomList extends ArrayList { 6 | private static final long serialVersionUID = 5138328998123758779L; 7 | } 8 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/CustomMap.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import java.util.HashMap; 4 | 5 | public final class CustomMap extends HashMap { 6 | private static final long serialVersionUID = 2346546437868922578L; 7 | } 8 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/DependencyClassA.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import online.sharedtype.SharedType; 6 | 7 | @SharedType(rustMacroTraits = {"PartialEq", "serde::Serialize", "serde::Deserialize"}) 8 | @Data 9 | @EqualsAndHashCode(callSuper = true) 10 | public final class DependencyClassA extends SuperClassA { 11 | private DependencyClassB b; 12 | } 13 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/DependencyClassB.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import lombok.Data; 4 | import online.sharedtype.SharedType; 5 | 6 | @SharedType(rustMacroTraits = {"PartialEq", "serde::Serialize", "serde::Deserialize"}) 7 | @Data 8 | public final class DependencyClassB { 9 | private DependencyClassC c; 10 | } 11 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/DependencyClassC.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import lombok.Data; 4 | import online.sharedtype.SharedType; 5 | 6 | @SharedType(rustMacroTraits = {"PartialEq", "serde::Serialize", "serde::Deserialize"}) 7 | @Data 8 | public final class DependencyClassC { 9 | private DependencyClassA a; 10 | } 11 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/EnumGalaxy.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import online.sharedtype.SharedType; 5 | 6 | @SharedType( 7 | rustMacroTraits = {"PartialEq", "Eq", "Hash", "serde::Serialize", "serde::Deserialize"} 8 | ) 9 | public enum EnumGalaxy { 10 | MilkyWay, Andromeda, Triangulum; 11 | } 12 | 13 | @RequiredArgsConstructor 14 | @SharedType 15 | enum EnumConstReference { 16 | ReferenceConstantInOther(MyConstants.LONG_VALUE), 17 | ReferenceConstantLocally(EnumConstReference.LONG_VALUE), 18 | ; 19 | private static final long LONG_VALUE = 156L; 20 | @SharedType.EnumValue 21 | private final long longValue; 22 | } 23 | 24 | @RequiredArgsConstructor 25 | @SharedType 26 | enum EnumEnumReference { 27 | ReferenceAnother(EnumSize.LARGE), 28 | ; 29 | @SharedType.EnumValue 30 | private final EnumSize enumValue; 31 | } 32 | 33 | @RequiredArgsConstructor 34 | @SharedType 35 | enum EnumSimpleEnumReference { 36 | ReferenceAnother1(EnumGalaxy.Andromeda), 37 | ; 38 | @SharedType.EnumValue 39 | private final EnumGalaxy enumValue; 40 | } 41 | 42 | @RequiredArgsConstructor 43 | @SharedType 44 | enum EnumEnumEnumReference { 45 | ReferenceAnother2(EnumSimpleEnumReference.ReferenceAnother1), 46 | ; 47 | @SharedType.EnumValue 48 | private final EnumSimpleEnumReference enumValue; 49 | } 50 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/EnumSize.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import com.fasterxml.jackson.annotation.JsonValue; 4 | import lombok.RequiredArgsConstructor; 5 | import online.sharedtype.SharedType; 6 | 7 | @SharedType( 8 | rustMacroTraits = {"PartialEq", "Eq", "Hash", "serde::Serialize", "serde::Deserialize"}, 9 | typescriptEnumFormat = "const_enum", 10 | goEnumFormat = "struct" 11 | ) 12 | @RequiredArgsConstructor 13 | public enum EnumSize { 14 | SMALL(1), 15 | MEDIUM(2), 16 | @SharedType.TagLiteral(tags = "// test comments for enum") 17 | LARGE(3); 18 | 19 | @JsonValue 20 | @SharedType.EnumValue 21 | private final int size; 22 | } 23 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/EnumTShirt.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import lombok.Getter; 4 | import online.sharedtype.SharedType; 5 | 6 | @SharedType(typescriptEnumFormat = "enum") 7 | @Getter 8 | public enum EnumTShirt { 9 | S(EnumSize.SMALL, "S"), 10 | M(EnumSize.MEDIUM, "M"), 11 | L(EnumSize.LARGE, "L"), 12 | ; 13 | 14 | private final EnumSize size; 15 | @SharedType.EnumValue 16 | private final String name; 17 | 18 | EnumTShirt(EnumSize size, String name) { 19 | this.size = size; 20 | this.name = name; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/IgnoredInterfaceB.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import online.sharedtype.SharedType; 5 | 6 | @SharedType.Ignore 7 | interface IgnoredInterfaceB { 8 | @JsonIgnore 9 | default boolean getBooleanValue() { 10 | return false; 11 | } 12 | 13 | int getNotIgnoredImplementedMethod(); 14 | } 15 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/IgnoredSuperClassB.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import online.sharedtype.SharedType; 4 | 5 | @SharedType.Ignore 6 | abstract class IgnoredSuperClassB { 7 | static final long LONG_VALUE_IN_SUPER = 345L; 8 | int field; 9 | } 10 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/InterfaceA.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | public interface InterfaceA { 4 | T getValue(); 5 | } 6 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/InterfaceSubA.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | public interface InterfaceSubA extends InterfaceA { 4 | } 5 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/JavaClass.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import online.sharedtype.SharedType; 7 | 8 | @EqualsAndHashCode(callSuper = true) 9 | @Setter 10 | @SharedType( 11 | rustMacroTraits = {"PartialEq", "Eq", "Hash", "serde::Serialize", "serde::Deserialize"} 12 | ) 13 | public final class JavaClass extends SuperClassA { 14 | static final long SOME_LONG_VALUE = 123L; 15 | 16 | @Getter 17 | private String string; 18 | @Getter 19 | private EnumSize size; 20 | // private IgnoredInterfaceB b; // compilation failure 21 | private @SharedType.Ignore IgnoredInterfaceB ignoredB; 22 | 23 | @Override 24 | public int getNotIgnoredImplementedMethod() { 25 | return 1; 26 | } 27 | 28 | @SharedType 29 | static class InnerClass { 30 | private int value; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/JavaTimeClass.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.Data; 5 | import online.sharedtype.SharedType; 6 | 7 | import java.time.LocalDate; 8 | import java.time.LocalDateTime; 9 | import java.time.LocalTime; 10 | import java.time.OffsetDateTime; 11 | import java.time.OffsetTime; 12 | import java.time.ZonedDateTime; 13 | 14 | @SharedType( 15 | rustMacroTraits = {"PartialEq", "Eq", "Hash", "serde::Serialize", "serde::Deserialize"}, 16 | typescriptTargetDatetimeTypeLiteral = "Date" 17 | ) 18 | @Data 19 | public final class JavaTimeClass { 20 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSXXX") 21 | java.util.Date utilDate; 22 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSXXX") 23 | java.sql.Date sqlDate; 24 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") 25 | LocalDate localDate; 26 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss.SSSSSSSSS") 27 | LocalTime localTime; 28 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS") 29 | LocalDateTime localDateTime; 30 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSXXX") 31 | ZonedDateTime zonedDateTime; 32 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSXXX") 33 | OffsetDateTime offsetDateTime; 34 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss.SSSSSSSSSXXX") 35 | OffsetTime offsetTime; 36 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSXXX", timezone = "UTC") 37 | java.time.Instant instant; 38 | } 39 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/JodaTimeClass.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import online.sharedtype.SharedType; 4 | 5 | @SharedType 6 | final class JodaTimeClass { 7 | org.joda.time.DateTime jodaDateTime; 8 | org.joda.time.LocalDate jodaLocalDate; 9 | org.joda.time.MonthDay jodaMonthDay; 10 | org.joda.time.LocalTime jodaLocalTime; 11 | org.joda.time.LocalDateTime jodaLocalDateTime; 12 | org.joda.time.Instant jodaOffsetDateTime; 13 | } 14 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/MapClass.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import lombok.Data; 4 | import online.sharedtype.SharedType; 5 | 6 | import java.util.Map; 7 | import java.util.concurrent.ConcurrentMap; 8 | 9 | @SharedType(rustMacroTraits = {"PartialEq", "serde::Serialize", "serde::Deserialize"}) 10 | @Data 11 | public final class MapClass { 12 | private ConcurrentMap mapField; 13 | private Map enumKeyMapField; 14 | private CustomMap customMapField; 15 | private Map> nestedMapField; 16 | } 17 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/MathClass.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import lombok.Data; 4 | import online.sharedtype.SharedType; 5 | 6 | import java.math.BigDecimal; 7 | import java.math.BigInteger; 8 | 9 | @SharedType( 10 | rustMacroTraits = {"PartialEq", "Eq", "Hash", "serde::Serialize", "serde::Deserialize"} 11 | ) 12 | @Data 13 | public final class MathClass { 14 | private BigInteger bigInteger; 15 | private BigDecimal bigDecimal; 16 | } 17 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/OptionalMethod.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import com.fasterxml.jackson.annotation.JsonGetter; 4 | import lombok.Setter; 5 | import online.sharedtype.SharedType; 6 | 7 | import java.util.Collections; 8 | import java.util.Map; 9 | import java.util.Optional; 10 | import java.util.Set; 11 | 12 | @SharedType( 13 | rustMacroTraits = {"PartialEq", "serde::Serialize", "serde::Deserialize"}, 14 | typescriptOptionalFieldFormat = {"undefined", "null"}, 15 | typescriptFieldReadonlyType = "none" 16 | ) 17 | @Setter 18 | public class OptionalMethod { 19 | @SharedType.Ignore 20 | private String value; 21 | 22 | @JsonGetter 23 | Optional getValueOptional() { 24 | return Optional.ofNullable(value); 25 | } 26 | 27 | @JsonGetter 28 | Optional> getNestedValueOptional() { 29 | return Optional.of(Optional.ofNullable(value)); 30 | } 31 | 32 | @JsonGetter 33 | Optional>> getSetNestedValueOptional() { 34 | return Optional.of(Collections.singleton(Optional.ofNullable(value))); 35 | } 36 | 37 | @JsonGetter 38 | Optional>> getMapNestedValueOptional() { 39 | return Optional.of(Collections.singletonMap(1, Optional.ofNullable(value))); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/RecursiveClass.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import online.sharedtype.SharedType; 4 | 5 | @SharedType(rustMacroTraits = {"PartialEq", "serde::Serialize", "serde::Deserialize"}) 6 | final class RecursiveClass { 7 | RecursiveClass directRef; 8 | RecursiveClass[] arrayRef; 9 | } 10 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/SuperClassA.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | @EqualsAndHashCode(callSuper = false) 9 | @Setter 10 | @Getter 11 | public abstract class SuperClassA extends IgnoredSuperClassB implements InterfaceSubA, IgnoredInterfaceB { 12 | private int a; 13 | 14 | @JsonProperty(access = JsonProperty.Access.READ_ONLY) 15 | @Override 16 | public Integer getValue() { 17 | return a; 18 | } 19 | 20 | @JsonProperty(access = JsonProperty.Access.READ_ONLY) 21 | @Override 22 | public int getNotIgnoredImplementedMethod() { 23 | return 0; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/TempClass.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8; 2 | 3 | import online.sharedtype.SharedType; 4 | 5 | import java.math.BigDecimal; 6 | 7 | @SharedType(includes = SharedType.ComponentType.CONSTANTS) 8 | public class TempClass extends IgnoredSuperClassB { 9 | // static final BigDecimal VALUE2 = BigDecimal.valueOf(555); 10 | } 11 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/anno/AsAccessor.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8.anno; 2 | 3 | @interface AsAccessor { 4 | } 5 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/anno/AsEnumValue.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8.anno; 2 | 3 | @interface AsEnumValue { 4 | } 5 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/anno/CustomAnnoClass.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8.anno; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import online.sharedtype.SharedType; 5 | 6 | @SharedType 7 | final class CustomAnnoClass { 8 | @ToIgnore 9 | int ignoredField; 10 | 11 | @AsAccessor 12 | int someValue() { 13 | return 1; 14 | } 15 | } 16 | 17 | @RequiredArgsConstructor 18 | @SharedType 19 | enum CustomAnnoEnum { 20 | A(1), B(2) 21 | ; 22 | @AsEnumValue 23 | private final int value; 24 | } 25 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/anno/ToIgnore.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8.anno; 2 | 3 | @interface ToIgnore { 4 | } 5 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/other/JavaClass.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8.other; 2 | 3 | import online.sharedtype.SharedType; 4 | 5 | @SharedType(name = "AnotherJavaClass") 6 | public class JavaClass { 7 | int value; 8 | } 9 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/other/OtherConstants.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8.other; 2 | 3 | public final class OtherConstants { 4 | public static final long LONG_VALUE = 666L; 5 | 6 | public static final class Inner1 { 7 | public static final long STATIC_IMPORTED_VALUE = 999L; 8 | public static final long INNER_LONG_VALUE_IN_STATIC_BLOCK; 9 | static { 10 | INNER_LONG_VALUE_IN_STATIC_BLOCK = 787L; 11 | } 12 | 13 | public static final class Inner2 { 14 | public static final long INNER_LONG_VALUE = 777L; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /it/java8/src/main/java/online/sharedtype/it/java8/types/ArbitraryTypeMapping.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.it.java8.types; 2 | 3 | import online.sharedtype.SharedType; 4 | 5 | @SharedType.Ignore 6 | class MyType1 { 7 | T value; 8 | } 9 | 10 | class MyType2 { 11 | int value; 12 | } 13 | 14 | // mapping defined in it/sharedtype.properties 15 | @SharedType 16 | class ArbitraryTypeMapping { 17 | private MyType1 myType1T; 18 | private MyType1 myType1String; 19 | private MyType2 myType2; 20 | } 21 | 22 | // todo: 1. SharedType.Ignore won't raise error. 23 | // 2. update mapped name at earlier stage into domain object to simplify mapping logics in type converters. 24 | // 3. verify global properties to ignore types 25 | -------------------------------------------------------------------------------- /it/sharedtype.properties: -------------------------------------------------------------------------------- 1 | sharedtype.targets=CONSOLE, JAVA_SERIALIZED, TYPESCRIPT, RUST, GO 2 | sharedtype.ignore-annotations=online.sharedtype.it.java8.anno.ToIgnore 3 | sharedtype.accessor-annotations=online.sharedtype.it.java8.anno.AsAccessor 4 | sharedtype.enum-value-annotations=online.sharedtype.it.java8.anno.AsEnumValue 5 | 6 | sharedtype.typescript.constant-inlined=false 7 | sharedtype.typescript.type-mappings=online.sharedtype.it.java8.types.MyType1:Array,\ 8 | online.sharedtype.it.java8.types.MyType2:number 9 | 10 | sharedtype.rust.type-mappings=online.sharedtype.it.java8.types.MyType1:Vec 11 | 12 | sharedtype.go.type-mappings=online.sharedtype.it.java8.types.MyType1:[] 13 | -------------------------------------------------------------------------------- /misc/logo-color.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #set -e 3 | 4 | # To test locally, need to prepare GPG keys 5 | # gpg --gen-key 6 | # See also: https://maven.apache.org/plugins/maven-gpg-plugin/ 7 | # See also: https://stackoverflow.com/questions/3174537/how-to-transfer-pgp-private-key-to-another-computer 8 | 9 | if [ -z "$MAVEN_GPG_PASSPHRASE" ];then 10 | echo "No MAVEN_GPG_PASSPHRASE provided, exit 1" 11 | exit 1 12 | fi 13 | 14 | # https://stackoverflow.com/a/64390598/5172925 15 | ### Increments the part of the string 16 | ## $1: version itself 17 | ## $2: number of part: 0 – major, 1 – minor, 2 – patch 18 | increment_version() { 19 | local delimiter='.' 20 | IFS=$delimiter read -r -a array <<< "$1" 21 | array[$2]=$((array[$2]+1)) 22 | if [ $2 -lt 2 ]; then array[2]=0; fi 23 | if [ $2 -lt 1 ]; then array[1]=0; fi 24 | printf '%s' "$(local IFS=$delimiter ; echo "${array[*]}")" 25 | } 26 | 27 | snapshotVersion=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout) 28 | version="$(printf '%s' "$snapshotVersion" | sed -e "s/-SNAPSHOT//g")" 29 | 30 | ./mvnw versions:set -DgenerateBackupPoms=false -DnewVersion="$version" --ntp -B 31 | sed -i -E "s/[0-9]+\.[0-9\.\[0-9]+\.[0-9]+<\/sharedtype\.version>/$version<\/sharedtype.version>/g" doc/Usage.md 32 | ./mvnw deploy -DskipTests -Prelease --ntp -B # to debug release can add -DskipPublishing=true to prevent actual upload 33 | NEW_VERSION="$(increment_version "$version" 1)-SNAPSHOT" 34 | ./mvnw versions:set -DgenerateBackupPoms=false -DnewVersion="$NEW_VERSION" --ntp -B 35 | printf '%s' "$NEW_VERSION" > NEW_VERSION.cache 36 | -------------------------------------------------------------------------------- /misc/setversion.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./mvnw versions:set -DgenerateBackupPoms=false -DnewVersion="$1" --ntp 4 | -------------------------------------------------------------------------------- /misc/start-client-servers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # this script is used by JsonE2eTest to spin up client servers 3 | 4 | 5 | 6 | BASE_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) 7 | echo "Base dir: $BASE_DIR" 8 | 9 | cd "$BASE_DIR/client-test/typescript" || exit 1 10 | . setenv 11 | npm i && npm run start & 12 | 13 | cd "$BASE_DIR/client-test/go" || exit 1 14 | . setenv 15 | go run server.go & 16 | 17 | cd "$BASE_DIR/client-test/rust" || exit 1 18 | cargo run & 19 | 20 | # kill all child processes upon exit 21 | trap 'pkill -P $$' EXIT 22 | 23 | # wait for signal to exit 24 | wait 25 | -------------------------------------------------------------------------------- /mount-tmpfs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 5 | MAVEN_REPO_CACHE_PATH="$DIR/.run/maven-repository-path.cache" 6 | if [ -f "$MAVEN_REPO_CACHE_PATH" ]; then 7 | MAVEN_REPO_DIR=$(cat "$MAVEN_REPO_CACHE_PATH") 8 | else 9 | MAVEN_REPO_DIR="$(./mvnw help:evaluate -Dexpression=settings.localRepository -q -DforceStdout)/online/sharedtype" 10 | printf '%s' "$MAVEN_REPO_DIR" > "$MAVEN_REPO_CACHE_PATH" 11 | fi 12 | 13 | function mountTmpfs() { 14 | mkdir -p "$1" 15 | sudo mount -t tmpfs -o size="$2" -o noatime tmpfs "$1" 16 | echo "tmpfs mounted at $1 of size $2" 17 | } 18 | 19 | mountTmpfs "$DIR/target" 8M 20 | mountTmpfs "$DIR/annotation/target" 32M 21 | mountTmpfs "$DIR/processor/target" 64M 22 | mountTmpfs "$DIR/it/java17/target" 64M 23 | mountTmpfs "$DIR/it/java8/target" 64M 24 | mountTmpfs "$DIR/e2e/target" 64M 25 | mountTmpfs "$MAVEN_REPO_DIR" 64M 26 | mountTmpfs "$DIR/client-test/rust/target" 512M 27 | mountTmpfs "$DIR/client-test/typescript/dist" 32M 28 | -------------------------------------------------------------------------------- /mvne: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export MAVEN_OPTS="-Xdebug -Xnoagent -Xint -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005 $MAVEN_OPTS" 4 | ./mvnw "$@" 5 | -------------------------------------------------------------------------------- /processor/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module online.sharedtype.processor { 2 | requires online.sharedtype.annotation; 3 | requires java.base; 4 | requires java.compiler; 5 | requires jdk.compiler; 6 | requires static lombok; 7 | requires static com.google.auto.service; 8 | 9 | requires com.github.mustachejava; 10 | 11 | provides javax.annotation.processing.Processor with online.sharedtype.processor.AnnotationProcessorImpl; 12 | } 13 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/context/EnumCtorIndex.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | import online.sharedtype.processor.support.annotation.Nullable; 7 | import javax.lang.model.element.VariableElement; 8 | 9 | 10 | @RequiredArgsConstructor 11 | @Getter 12 | public final class EnumCtorIndex { 13 | public static final EnumCtorIndex NONE = new EnumCtorIndex(-1, null); 14 | /** 15 | * The index of enum constant constructor argument, or -1 if no constructor argument. 16 | */ 17 | private final int idx; 18 | private final VariableElement field; 19 | } 20 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/context/EnumParsingUtils.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import lombok.experimental.UtilityClass; 4 | 5 | import java.util.Collection; 6 | import java.util.EnumSet; 7 | import java.util.Set; 8 | import java.util.function.Function; 9 | 10 | @UtilityClass 11 | final class EnumParsingUtils { 12 | /** Match by provided function */ 13 | static > Set parseEnumSet(Collection values, Class type, Function enumValueOf) { 14 | Set set = EnumSet.noneOf(type); 15 | for (String trimmed : values) { 16 | set.add(enumValueOf.apply(trimmed)); 17 | } 18 | return set; 19 | } 20 | 21 | /** Match uppercase of values */ 22 | static > Set parseEnumSet(Collection values, Class type) { 23 | Set set = EnumSet.noneOf(type); 24 | for (String trimmed : values) { 25 | set.add(Enum.valueOf(type, trimmed.toUpperCase())); 26 | } 27 | return set; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/context/OutputTarget.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import online.sharedtype.SharedType; 6 | 7 | import online.sharedtype.processor.support.annotation.Nullable; 8 | 9 | /** 10 | * @author Cause Chung 11 | */ 12 | @Getter 13 | @RequiredArgsConstructor 14 | public enum OutputTarget { 15 | /** Print metadata to console. */ 16 | CONSOLE, 17 | /** Write metadata to Java serialized file. Used for integration test. */ 18 | JAVA_SERIALIZED, 19 | TYPESCRIPT(SharedType.TargetType.TYPESCRIPT), 20 | GO(SharedType.TargetType.GO), 21 | RUST(SharedType.TargetType.RUST), 22 | ; 23 | @Nullable 24 | private final SharedType.TargetType targetType; 25 | 26 | OutputTarget() { 27 | this(null); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/context/RenderFlags.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | /** 7 | * Flags that can only be marked during processing runtime. 8 | * Anything that can be configured beforehand should be in {@link Props}. 9 | */ 10 | @Getter 11 | @Setter 12 | public final class RenderFlags { 13 | private boolean useRustAny = false; 14 | private boolean useRustMap = false; 15 | } 16 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/parser/CompositeTypeDefParser.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.parser; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import online.sharedtype.processor.context.Context; 5 | import online.sharedtype.processor.domain.component.AbstractComponentInfo; 6 | import online.sharedtype.processor.domain.component.ComponentInfo; 7 | import online.sharedtype.processor.domain.def.TypeDef; 8 | 9 | import javax.lang.model.element.TypeElement; 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | /** 15 | * @author Cause Chung 16 | */ 17 | @RequiredArgsConstructor 18 | final class CompositeTypeDefParser implements TypeDefParser { 19 | private final Context ctx; 20 | private final List parsers; 21 | 22 | @Override 23 | public List parse(TypeElement typeElement) { 24 | if (ctx.isIgnored(typeElement)) { 25 | return Collections.emptyList(); 26 | } 27 | String qualifiedName = typeElement.getQualifiedName().toString(); 28 | List cachedDef = ctx.getTypeStore().getTypeDefs(qualifiedName); 29 | if (cachedDef != null) { 30 | return new ArrayList<>(cachedDef); 31 | } 32 | 33 | if (ctx.isArraylike(typeElement.asType())) { 34 | ctx.warn(typeElement, "Type '%s' is an array type, which cannot be parsed and emitted as a standalone type.", typeElement.getQualifiedName()); 35 | return Collections.emptyList(); 36 | } 37 | if (ctx.isDatetimelike(typeElement.asType())) { 38 | ctx.warn(typeElement, "Type '%s' is a datetime type, which cannot be parsed and emitted as a standalone type.", typeElement.getQualifiedName()); 39 | return Collections.emptyList(); 40 | } 41 | // TODO: warn for maplikeType 42 | 43 | ctx.info("Processing: %s", typeElement.getQualifiedName()); 44 | List typeDefs = new ArrayList<>(); 45 | for (TypeDefParser typeDefParser : parsers) { 46 | List parsedTypeDefs = typeDefParser.parse(typeElement); 47 | for (TypeDef parsedTypeDef : parsedTypeDefs) { 48 | ctx.getTypeStore().saveTypeDef(qualifiedName, parsedTypeDef); 49 | populateTagLiterals(parsedTypeDef); 50 | } 51 | typeDefs.addAll(parsedTypeDefs); 52 | } 53 | return typeDefs; 54 | } 55 | 56 | private void populateTagLiterals(TypeDef typeDef) { 57 | for (ComponentInfo component : typeDef.components()) { 58 | if (component instanceof AbstractComponentInfo) { 59 | AbstractComponentInfo abstractComponentInfo = (AbstractComponentInfo) component; 60 | abstractComponentInfo.setTagLiterals(ctx.extractTagLiterals(abstractComponentInfo.getElement())); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/parser/TypeDefParser.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.parser; 2 | 3 | import online.sharedtype.processor.domain.def.TypeDef; 4 | import online.sharedtype.processor.context.Context; 5 | import online.sharedtype.processor.parser.type.TypeInfoParser; 6 | import online.sharedtype.processor.parser.value.ValueParser; 7 | 8 | import javax.lang.model.element.TypeElement; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * Parse type structural information. 14 | * 15 | * @see TypeDef 16 | * @see TypeInfoParser 17 | * @author Cause Chung 18 | */ 19 | public interface TypeDefParser { 20 | /** 21 | * Parse structural information. 22 | * 23 | * @return empty if the typeElement is ignored or invalid. 24 | * A typeElement can be ignored via configuration. 25 | * An invalid type can be an unsupported type, e.g. a non-static inner class. 26 | * The main classDef or enumDef must be the first element in the list, constantDef should be the 2nd element if exists. 27 | */ 28 | List parse(TypeElement typeElement); 29 | 30 | static TypeDefParser create(Context ctx) { 31 | TypeInfoParser typeInfoParser = TypeInfoParser.create(ctx); 32 | ValueParser valueParser = ValueParser.create(ctx, typeInfoParser); 33 | List parsers = new ArrayList<>(3); // order matters! see #parse 34 | parsers.add(new ClassTypeDefParser(ctx, typeInfoParser)); 35 | parsers.add(new EnumTypeDefParser(ctx, typeInfoParser, valueParser)); 36 | parsers.add(new ConstantTypeDefParser(ctx, typeInfoParser, valueParser)); 37 | return new CompositeTypeDefParser(ctx, parsers); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/parser/type/MappableTypeInfoParser.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.parser.type; 2 | 3 | import online.sharedtype.SharedType; 4 | import online.sharedtype.processor.context.Context; 5 | import online.sharedtype.processor.domain.MappableType; 6 | import online.sharedtype.processor.domain.type.TypeInfo; 7 | 8 | import javax.lang.model.element.TypeElement; 9 | import javax.lang.model.type.TypeMirror; 10 | import java.util.Map; 11 | 12 | final class MappableTypeInfoParser implements TypeInfoParser { 13 | private final TypeInfoParser delegate; 14 | private final Map typescriptTypeMappings; 15 | private final Map goTypeMappings; 16 | private final Map rustTypeMappings; 17 | 18 | MappableTypeInfoParser(Context ctx, TypeInfoParser delegate) { 19 | this.delegate = delegate; 20 | this.typescriptTypeMappings = ctx.getProps().getTypescript().getTypeMappings(); 21 | this.goTypeMappings = ctx.getProps().getGo().getTypeMappings(); 22 | this.rustTypeMappings = ctx.getProps().getRust().getTypeMappings(); 23 | } 24 | 25 | @Override 26 | public TypeInfo parse(TypeMirror typeMirror, TypeElement ctxTypeElement) { 27 | TypeInfo typeInfo = delegate.parse(typeMirror, ctxTypeElement); 28 | 29 | if (typeInfo instanceof MappableType) { 30 | MappableType mappableType = (MappableType) typeInfo; 31 | updateTypeMappings(mappableType, SharedType.TargetType.TYPESCRIPT, typescriptTypeMappings); 32 | updateTypeMappings(mappableType, SharedType.TargetType.GO, goTypeMappings); 33 | updateTypeMappings(mappableType, SharedType.TargetType.RUST, rustTypeMappings); 34 | } 35 | return typeInfo; 36 | } 37 | 38 | private static void updateTypeMappings(MappableType mappableType, SharedType.TargetType targetType, Map typeMappings) { 39 | String mappedName = typeMappings.get(mappableType.qualifiedName()); 40 | if (mappedName != null) { 41 | mappableType.addMappedName(targetType, mappedName); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/parser/type/TypeInfoParser.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.parser.type; 2 | 3 | import online.sharedtype.SharedType; 4 | import online.sharedtype.processor.parser.TypeDefParser; 5 | import online.sharedtype.processor.domain.type.TypeInfo; 6 | import online.sharedtype.processor.context.Context; 7 | 8 | import javax.lang.model.element.TypeElement; 9 | import javax.lang.model.type.TypeMirror; 10 | 11 | /** 12 | * Parse type specific information. 13 | * 14 | * @see TypeInfo 15 | * @see TypeDefParser 16 | * @author Cause Chung 17 | */ 18 | public interface TypeInfoParser { 19 | /** 20 | * Parse type specific information. 21 | *

22 | * If a dependent type is not explicitly registered by {@link SharedType}, 23 | * it may not have been resolved in the context by the time of the call. 24 | * In such case, the caller would resolve the type in another iteration and resolve the type again. 25 | *

26 | *

27 | * An unresolved type is a type whose TypeElement hasn't been processed. 28 | * So there's no structural information, e.g. name, saved in global context yet. 29 | *

30 | * 31 | * @return a no type info instance if the kind of the typeMirror is ERROR. 32 | */ 33 | TypeInfo parse(TypeMirror typeMirror, TypeElement ctxTypeElement); 34 | 35 | static TypeInfoParser create(Context ctx) { 36 | return new MappableTypeInfoParser(ctx, new TypeInfoParserImpl(ctx)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/parser/value/CompositeValueParser.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.parser.value; 2 | 3 | import online.sharedtype.processor.domain.value.ValueHolder; 4 | import online.sharedtype.processor.support.exception.SharedTypeInternalError; 5 | 6 | import javax.lang.model.element.Element; 7 | import javax.lang.model.element.ElementKind; 8 | import javax.lang.model.element.TypeElement; 9 | import java.util.EnumMap; 10 | import java.util.Map; 11 | 12 | final class CompositeValueParser implements ValueParser { 13 | private final Map resolvers = new EnumMap<>(ElementKind.class); 14 | 15 | void registerResolver(ElementKind kind, ValueParser resolver) { 16 | resolvers.put(kind, resolver); 17 | } 18 | 19 | @Override 20 | public ValueHolder resolve(Element element, TypeElement ctxTypeElement) { 21 | ValueParser resolver = resolvers.get(element.getKind()); 22 | if (resolver != null) { 23 | return resolver.resolve(element, ctxTypeElement); 24 | } 25 | throw new SharedTypeInternalError(String.format("Unable to resolve value for element '%s', unsupported element kind: %s", element, element.getKind())); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/parser/value/ConstantValueParser.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.parser.value; 2 | 3 | import com.sun.source.tree.Tree; 4 | import lombok.RequiredArgsConstructor; 5 | import online.sharedtype.processor.context.Context; 6 | import online.sharedtype.processor.domain.type.ConcreteTypeInfo; 7 | import online.sharedtype.processor.domain.type.TypeInfo; 8 | import online.sharedtype.processor.domain.value.ValueHolder; 9 | import online.sharedtype.processor.parser.type.TypeInfoParser; 10 | import online.sharedtype.processor.support.exception.SharedTypeException; 11 | 12 | import javax.lang.model.element.Element; 13 | import javax.lang.model.element.TypeElement; 14 | 15 | @RequiredArgsConstructor 16 | final class ConstantValueParser implements ValueParser { 17 | private final Context ctx; 18 | private final TypeInfoParser typeInfoParser; 19 | private final ValueResolverBackend valueResolverBackend; 20 | 21 | @Override 22 | public ValueHolder resolve(Element fieldElement, TypeElement ctxTypeElement) { 23 | Tree tree = ctx.getTrees().getTree(fieldElement); 24 | if (tree == null) { 25 | ctx.error(fieldElement, "Cannot parse constant value for field: %s in %s, tree is null from the field element. " + 26 | "If the type is from a dependency jar/compiled class file, tree is not available at the time of annotation processing. " + 27 | "Check if the type or its custom mapping is correct.", 28 | fieldElement, ctxTypeElement); 29 | return ValueHolder.NULL; 30 | } 31 | try { 32 | ValueResolveContext parsingContext = ValueResolveContext.builder(ctx) 33 | .fieldElement(fieldElement) 34 | .tree(tree).enclosingTypeElement(ctxTypeElement) 35 | .build(); 36 | TypeInfo fieldTypeInfo = typeInfoParser.parse(fieldElement.asType(), ctxTypeElement); 37 | if (fieldTypeInfo instanceof ConcreteTypeInfo) { 38 | ConcreteTypeInfo valueType = (ConcreteTypeInfo) fieldTypeInfo; 39 | return ValueHolder.of(valueType, valueResolverBackend.recursivelyResolve(parsingContext)); 40 | } 41 | ctx.error(fieldElement, "Complex field types are not supported for value resolving. Only literal types or references are supported," + 42 | " the type is '%s'.", fieldElement.asType()); 43 | return ValueHolder.NULL; 44 | } catch (SharedTypeException e) { 45 | ctx.error(fieldElement, "Failed to parse constant value. " + 46 | "Field tree: '%s' in '%s'. Consider to ignore this field or exclude constants generation for this type. " + 47 | "Error message: %s", 48 | tree, ctxTypeElement, e.getMessage()); 49 | } 50 | return ValueHolder.NULL; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/parser/value/ValueParser.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.parser.value; 2 | 3 | import online.sharedtype.processor.context.Context; 4 | import online.sharedtype.processor.domain.value.ValueHolder; 5 | import online.sharedtype.processor.parser.type.TypeInfoParser; 6 | 7 | import javax.lang.model.element.Element; 8 | import javax.lang.model.element.ElementKind; 9 | import javax.lang.model.element.TypeElement; 10 | 11 | public interface ValueParser { 12 | ValueHolder resolve(Element element, TypeElement ctxTypeElement); 13 | 14 | static ValueParser create(Context ctx, TypeInfoParser typeInfoParser) { 15 | CompositeValueParser compositeValueResolver = new CompositeValueParser(); 16 | ValueResolverBackend backend = new ValueResolverBackendImpl(compositeValueResolver); 17 | compositeValueResolver.registerResolver(ElementKind.ENUM_CONSTANT, new EnumValueParser(ctx, typeInfoParser, backend)); 18 | compositeValueResolver.registerResolver(ElementKind.FIELD, new ConstantValueParser(ctx, typeInfoParser, backend)); 19 | return compositeValueResolver; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/parser/value/ValueResolverBackend.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.parser.value; 2 | 3 | interface ValueResolverBackend { 4 | Object recursivelyResolve(ValueResolveContext parsingContext); 5 | } 6 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/resolver/CompositeTypeResolver.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.resolver; 2 | 3 | import online.sharedtype.processor.domain.def.TypeDef; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | final class CompositeTypeResolver implements TypeResolver { 9 | private final List resolvers; 10 | 11 | public CompositeTypeResolver(TypeResolver... resolvers) { 12 | this.resolvers = Arrays.asList(resolvers); 13 | } 14 | 15 | @Override 16 | public List resolve(List typeDefs) { 17 | List result = typeDefs; 18 | for (TypeResolver resolver : resolvers) { 19 | result = resolver.resolve(result); 20 | } 21 | return result; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/resolver/ReferenceResolver.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.resolver; 2 | 3 | import online.sharedtype.processor.domain.def.ConcreteTypeDef; 4 | import online.sharedtype.processor.domain.def.TypeDef; 5 | import online.sharedtype.processor.support.annotation.SideEffect; 6 | 7 | import java.util.ArrayDeque; 8 | import java.util.Deque; 9 | import java.util.HashSet; 10 | import java.util.List; 11 | import java.util.Set; 12 | import java.util.stream.Collectors; 13 | 14 | /** 15 | * Traverse type graph to mark: 16 | *
    17 | *
  • cyclic references
  • 18 | *
  • direct or indirect reference from explicitly annotated classes
  • 19 | *
20 | */ 21 | final class ReferenceResolver implements TypeResolver { 22 | 23 | @Override 24 | public List resolve(@SideEffect List typeDefs) { 25 | for (TypeDef typeDef : typeDefs) { 26 | traverse(typeDef); 27 | } 28 | return typeDefs; 29 | } 30 | 31 | private static void traverse(TypeDef typeDef) { 32 | Set visited = new HashSet<>(); 33 | Deque typeDefStack = new ArrayDeque<>(); 34 | typeDefStack.push(typeDef); 35 | boolean referencedByAnnotated = typeDef.isReferencedByAnnotated(); 36 | 37 | while (!typeDefStack.isEmpty()) { 38 | TypeDef cur = typeDefStack.pop(); 39 | if (visited.contains(cur)) { 40 | cur.setCyclicReferenced(true); 41 | continue; 42 | } else { 43 | visited.add(cur); 44 | } 45 | 46 | if (cur instanceof ConcreteTypeDef) { 47 | ConcreteTypeDef curConcreteTypeDef = (ConcreteTypeDef) cur; 48 | List referencingTypeDefs = curConcreteTypeDef.typeInfoSet().stream() 49 | .flatMap(ts -> ts.referencingTypes().stream()).collect(Collectors.toList()); 50 | if (!referencingTypeDefs.isEmpty()){ 51 | curConcreteTypeDef.setDepended(true); 52 | } 53 | for (TypeDef referencingTypeDef : referencingTypeDefs) { 54 | typeDefStack.push(referencingTypeDef); 55 | if (referencingTypeDef instanceof ConcreteTypeDef) { 56 | ConcreteTypeDef dependingClassDef = (ConcreteTypeDef) referencingTypeDef; 57 | if (dependingClassDef.isAnnotated() || dependingClassDef.isReferencedByAnnotated()) { 58 | cur.setReferencedByAnnotated(true); 59 | referencedByAnnotated = true; 60 | } 61 | } 62 | } 63 | } 64 | } 65 | typeDef.setReferencedByAnnotated(referencedByAnnotated); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/resolver/SubtypeResolver.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.resolver; 2 | 3 | import online.sharedtype.processor.domain.def.ClassDef; 4 | import online.sharedtype.processor.domain.type.ConcreteTypeInfo; 5 | import online.sharedtype.processor.domain.def.TypeDef; 6 | import online.sharedtype.processor.domain.type.TypeInfo; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Resolve subtype relationships. 12 | */ 13 | final class SubtypeResolver implements TypeResolver { 14 | @Override 15 | public List resolve(List typeDefs) { 16 | for (TypeDef typeDef : typeDefs) { 17 | traverseDirectSupertypes(typeDef); 18 | } 19 | return typeDefs; 20 | } 21 | 22 | private static void traverseDirectSupertypes(TypeDef typeDef) { 23 | for (TypeInfo typeInfo : typeDef.directSupertypes()) { 24 | if (typeInfo instanceof ConcreteTypeInfo) { 25 | ConcreteTypeInfo concreteTypeInfo = (ConcreteTypeInfo) typeInfo; 26 | TypeDef supertypeDef = concreteTypeInfo.typeDef(); 27 | if (supertypeDef instanceof ClassDef) { 28 | ClassDef supertypeClassDef = (ClassDef) supertypeDef; 29 | supertypeClassDef.addSubtype(typeDef); 30 | supertypeClassDef.setDepended(true); 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/resolver/TypeResolver.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.resolver; 2 | 3 | import online.sharedtype.SharedType; 4 | import online.sharedtype.processor.domain.def.TypeDef; 5 | import online.sharedtype.processor.domain.type.TypeInfo; 6 | import online.sharedtype.processor.context.Context; 7 | import online.sharedtype.processor.parser.TypeDefParser; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * Resolve required but unknown type information after initial parsing stage. 13 | * 14 | * @see TypeDefParser 15 | * @author Cause Chung 16 | */ 17 | public interface TypeResolver { 18 | /** 19 | * Resolve {@link TypeInfo} by traversing all types. 20 | * 21 | * @param typeDefs the types discovered in initial parsing stage, they are types directly annotated with {@link SharedType}. 22 | * @return all type definitions needed to generate output. Including dependency types, e.g. referenced types, super types. 23 | */ 24 | List resolve(List typeDefs); 25 | 26 | static TypeResolver create(Context ctx, TypeDefParser typeDefParser) { 27 | // order matters 28 | // TypeInfo and TypeDef resolution is completed in LoopTypeResolver, 29 | // Other resolvers may depend on the result. 30 | return new CompositeTypeResolver( 31 | new LoopTypeResolver(ctx, typeDefParser), 32 | new OptionalTypeResolver(ctx), 33 | new ReferenceResolver(), 34 | new SubtypeResolver() 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/support/Preconditions.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.support; 2 | 3 | import online.sharedtype.processor.support.exception.SharedTypeInternalError; 4 | 5 | import java.util.Collection; 6 | 7 | /** 8 | * 9 | * @author Cause Chung 10 | */ 11 | // TODO: remove varargs to improve performance 12 | public final class Preconditions { 13 | private Preconditions() { 14 | } 15 | 16 | 17 | public static void checkArgument(boolean condition, String message, Object... objects) { 18 | if (!condition) { 19 | throw new SharedTypeInternalError(String.format(message, objects)); 20 | } 21 | } 22 | 23 | public static T requireNonNull(T o, String message, Object... objects) { 24 | if (o == null) { 25 | throw new SharedTypeInternalError(String.format(message, objects)); 26 | } 27 | return o; 28 | } 29 | 30 | public static > T requireNonEmpty(T c, String message, Object... objects) { 31 | if (c.isEmpty()) { 32 | throw new SharedTypeInternalError(String.format(message, objects)); 33 | } 34 | return c; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/support/exception/SharedTypeException.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.support.exception; 2 | 3 | /** 4 | * Indicate an exception, equivalent to {@link RuntimeException}. 5 | * Note: compilation error should be printed via {@link online.sharedtype.processor.context.Context#error}. 6 | * 7 | * @author Cause Chung 8 | */ 9 | public final class SharedTypeException extends RuntimeException { 10 | public SharedTypeException(String message) { 11 | super(message); 12 | } 13 | 14 | public SharedTypeException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/support/exception/SharedTypeInternalError.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.support.exception; 2 | 3 | import online.sharedtype.processor.support.github.RepositoryInfo; 4 | 5 | /** 6 | * Indicate an error. 7 | * 8 | * @author Cause Chung 9 | */ 10 | public final class SharedTypeInternalError extends Error { 11 | public SharedTypeInternalError(String message) { 12 | super(format(message)); 13 | } 14 | 15 | public SharedTypeInternalError(String message, Throwable cause) { 16 | super(format(message), cause); 17 | } 18 | 19 | private static String format(String message) { 20 | return String.format("%s (this could be an implementation error, please post an issue at %s/issues)", message, RepositoryInfo.PROJECT_REPO_URL); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/support/github/RepositoryInfo.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.support.github; 2 | 3 | /** 4 | * @author Cause Chung 5 | */ 6 | public final class RepositoryInfo { 7 | public static final String PROJECT_REPO_URL = "https://github.com/cuzfrog/sharedtype"; 8 | } 9 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/support/utils/Tuple.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.support.utils; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | /** 7 | * @author Cause Chung 8 | */ 9 | @EqualsAndHashCode 10 | @RequiredArgsConstructor 11 | public final class Tuple { 12 | private final A a; 13 | private final B b; 14 | 15 | public static Tuple of(A a, B b) { 16 | return new Tuple<>(a, b); 17 | } 18 | 19 | public A a() { 20 | return a; 21 | } 22 | 23 | public B b() { 24 | return b; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return String.format("(%s, %s)", a, b); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/support/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.support.utils; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import online.sharedtype.SharedType; 5 | import online.sharedtype.processor.support.exception.SharedTypeException; 6 | import online.sharedtype.processor.support.exception.SharedTypeInternalError; 7 | 8 | import javax.lang.model.element.Element; 9 | import javax.lang.model.element.ElementKind; 10 | import javax.lang.model.element.Modifier; 11 | import javax.lang.model.element.TypeElement; 12 | import javax.lang.model.element.VariableElement; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.function.Supplier; 16 | 17 | /** 18 | * @author Cause Chung 19 | */ 20 | @UtilityClass 21 | public final class Utils { 22 | private static final String[] EMPTY_STRING_ARRAY = new String[0]; 23 | 24 | public static String substringAndUncapitalize(String str, int beginIndex) { 25 | try{ 26 | return Character.toLowerCase(str.charAt(beginIndex)) + str.substring(beginIndex + 1); // TODO: see if can optimize 27 | } catch (IndexOutOfBoundsException e) { 28 | throw new SharedTypeInternalError(String.format("Failed to substringAndUncapitalize string: '%s'", str) ,e); 29 | } 30 | } 31 | 32 | public static String[] emptyStringArray() { 33 | return EMPTY_STRING_ARRAY; 34 | } 35 | 36 | public static String notEmptyOrDefault(String value, String defaultValue, Supplier message) { 37 | String res = value != null && !value.isEmpty() ? value : defaultValue; 38 | if (res == null || res.isEmpty()) { 39 | throw new SharedTypeException("Either value or defaultValue must not be empty. " + message.get()); 40 | } 41 | return res; 42 | } 43 | 44 | public static List allInstanceFields(TypeElement typeElement) { 45 | List enclosedElements = typeElement.getEnclosedElements(); 46 | List fields = new ArrayList<>(enclosedElements.size()); 47 | for (Element enclosedElement : enclosedElements) { 48 | if (enclosedElement.getKind() == ElementKind.FIELD && !enclosedElement.getModifiers().contains(Modifier.STATIC)) { 49 | fields.add((VariableElement) enclosedElement); 50 | } 51 | } 52 | return fields; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/CompositeWriter.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import online.sharedtype.processor.domain.def.TypeDef; 5 | 6 | import java.io.IOException; 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | /** 11 | * 12 | * @author Cause Chung 13 | */ 14 | @RequiredArgsConstructor 15 | final class CompositeWriter implements TypeWriter{ 16 | private final Set writers; 17 | 18 | @Override 19 | public void write(List typeDefs) throws IOException { 20 | for (TypeWriter writer : writers) { 21 | writer.write(typeDefs); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/ConsoleWriter.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import online.sharedtype.processor.domain.def.TypeDef; 5 | import online.sharedtype.processor.context.Context; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * 11 | * @author Cause Chung 12 | */ 13 | @RequiredArgsConstructor 14 | final class ConsoleWriter implements TypeWriter{ 15 | private final Context ctx; 16 | 17 | @Override 18 | public void write(List typeDefs) { 19 | typeDefs.forEach(d-> ctx.info("Write type: %s%s", System.lineSeparator(), d)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/JavaSerializationFileWriter.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer; 2 | 3 | import online.sharedtype.processor.domain.def.ConstantNamespaceDef; 4 | import online.sharedtype.processor.domain.def.TypeDef; 5 | import online.sharedtype.processor.context.Context; 6 | import online.sharedtype.processor.support.exception.SharedTypeException; 7 | 8 | import javax.annotation.processing.Filer; 9 | import javax.tools.FileObject; 10 | import javax.tools.StandardLocation; 11 | import java.io.IOException; 12 | import java.io.ObjectOutputStream; 13 | import java.io.OutputStream; 14 | import java.util.List; 15 | 16 | /** 17 | * For internal usage, where integration tests deserialize the generated files back to objects. 18 | * 19 | * @author Cause Chung 20 | */ 21 | final class JavaSerializationFileWriter implements TypeWriter { 22 | private final Filer filer; 23 | 24 | JavaSerializationFileWriter(Context ctx) { 25 | this.filer = ctx.getProcessingEnv().getFiler(); 26 | } 27 | 28 | @Override 29 | public void write(List typeDefs) { 30 | try { 31 | for (TypeDef typeDef : typeDefs) { 32 | FileObject file = filer.createResource(StandardLocation.CLASS_OUTPUT, "", getTypeName(typeDef) + ".ser"); 33 | try(OutputStream outputStream = file.openOutputStream(); 34 | ObjectOutputStream oos = new ObjectOutputStream(outputStream)) { 35 | oos.writeObject(typeDef); 36 | } 37 | } 38 | } catch (IOException e) { 39 | throw new SharedTypeException("Failed to write to file,", e); 40 | } 41 | } 42 | 43 | private String getTypeName(TypeDef typeDef) { 44 | if (typeDef instanceof ConstantNamespaceDef) { 45 | return String.format("$%s", typeDef.qualifiedName()); 46 | } 47 | return typeDef.qualifiedName(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/TemplateTypeFileWriter.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import online.sharedtype.processor.context.Context; 5 | import online.sharedtype.processor.domain.def.ConstantNamespaceDef; 6 | import online.sharedtype.processor.domain.def.TypeDef; 7 | import online.sharedtype.processor.writer.adaptor.RenderDataAdaptorFactory; 8 | import online.sharedtype.processor.writer.converter.TemplateDataConverter; 9 | import online.sharedtype.processor.writer.render.Template; 10 | import online.sharedtype.processor.writer.render.TemplateRenderer; 11 | import online.sharedtype.processor.support.utils.Tuple; 12 | 13 | import javax.tools.FileObject; 14 | import java.io.IOException; 15 | import java.io.OutputStream; 16 | import java.io.OutputStreamWriter; 17 | import java.io.Writer; 18 | import java.util.ArrayList; 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.Set; 23 | 24 | /** 25 | * 26 | * @author Cause Chung 27 | */ 28 | @RequiredArgsConstructor 29 | final class TemplateTypeFileWriter implements TypeWriter { 30 | private final Context ctx; 31 | private final TemplateRenderer renderer; 32 | private final RenderDataAdaptorFactory renderDataAdaptorFactory; 33 | private final Set converters; 34 | private final String outputFileName; 35 | 36 | @Override 37 | public void write(List typeDefs) throws IOException { 38 | List> data = new ArrayList<>(typeDefs.size() * converters.size()); 39 | data.add(renderDataAdaptorFactory.header(ctx)); 40 | 41 | Map simpleNames = new HashMap<>(typeDefs.size()); 42 | for (TypeDef typeDef : typeDefs) { 43 | TypeDef duplicate = typeDef instanceof ConstantNamespaceDef ? null : simpleNames.get(typeDef.simpleName()); // todo: split class/enum and constant duplication checks 44 | if (duplicate != null) { 45 | ctx.warn("Duplicate names found: %s and %s, which is not allowed in output code." + 46 | " You may use @SharedType(name=\"...\") to rename a type.", typeDef.qualifiedName(), duplicate.qualifiedName()); 47 | } 48 | simpleNames.put(typeDef.simpleName(), typeDef); 49 | for (TemplateDataConverter converter : converters) { 50 | if (converter.shouldAccept(typeDef)) { 51 | data.add(converter.convert(typeDef)); 52 | } 53 | } 54 | } 55 | 56 | FileObject file = ctx.createSourceOutput(outputFileName); 57 | try (OutputStream outputStream = file.openOutputStream(); 58 | Writer writer = new OutputStreamWriter(outputStream)) { 59 | renderer.render(writer, data); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/TypeWriter.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer; 2 | 3 | import online.sharedtype.processor.domain.def.TypeDef; 4 | import online.sharedtype.processor.context.Context; 5 | import online.sharedtype.processor.context.OutputTarget; 6 | import online.sharedtype.processor.writer.adaptor.RenderDataAdaptorFactory; 7 | import online.sharedtype.processor.writer.converter.TemplateDataConverter; 8 | import online.sharedtype.processor.writer.render.TemplateRenderer; 9 | 10 | import java.io.IOException; 11 | import java.util.HashSet; 12 | import java.util.List; 13 | import java.util.Set; 14 | 15 | /** 16 | * Writes type meta to target output. 17 | * 18 | * @author Cause Chung 19 | */ 20 | public interface TypeWriter { 21 | /** 22 | * Writes type meta to target output. 23 | * 24 | * @param typeDefs type definitions required to generate output, assumed to be completed. 25 | * @throws IOException if underlying IO error occurs 26 | */ 27 | void write(List typeDefs) throws IOException; 28 | 29 | static TypeWriter create(Context ctx) { 30 | Set writers = new HashSet<>(OutputTarget.values().length); 31 | if (ctx.getProps().getTargets().contains(OutputTarget.CONSOLE)) { 32 | writers.add(new ConsoleWriter(ctx)); 33 | } 34 | if (ctx.getProps().getTargets().contains(OutputTarget.JAVA_SERIALIZED)) { 35 | writers.add(new JavaSerializationFileWriter(ctx)); 36 | } 37 | 38 | TemplateRenderer renderer = TemplateRenderer.create(); 39 | if (ctx.getProps().getTargets().contains(OutputTarget.TYPESCRIPT)) { 40 | writers.add(new TemplateTypeFileWriter( 41 | ctx, renderer, RenderDataAdaptorFactory::typescript, TemplateDataConverter.typescript(ctx), ctx.getProps().getTypescript().getOutputFileName() 42 | )); 43 | } 44 | if (ctx.getProps().getTargets().contains(OutputTarget.GO)) { 45 | writers.add(new TemplateTypeFileWriter( 46 | ctx, renderer, RenderDataAdaptorFactory::go, TemplateDataConverter.go(ctx), ctx.getProps().getGo().getOutputFileName() 47 | )); 48 | } 49 | if (ctx.getProps().getTargets().contains(OutputTarget.RUST)) { 50 | writers.add(new TemplateTypeFileWriter( 51 | ctx, renderer, RenderDataAdaptorFactory::rust, TemplateDataConverter.rust(ctx), ctx.getProps().getRust().getOutputFileName() 52 | )); 53 | } 54 | return new CompositeWriter(writers); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/adaptor/AbstractDataAdaptor.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.adaptor; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import online.sharedtype.processor.context.Context; 5 | import online.sharedtype.processor.context.RenderFlags; 6 | import online.sharedtype.processor.support.exception.SharedTypeException; 7 | 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.nio.file.Paths; 12 | 13 | @RequiredArgsConstructor 14 | abstract class AbstractDataAdaptor implements RenderDataAdaptor { 15 | final Context ctx; 16 | 17 | @SuppressWarnings("unused") 18 | final RenderFlags renderFlags() { 19 | return ctx.getRenderFlags(); 20 | } 21 | 22 | abstract String customCodeSnippet(); 23 | 24 | static String readCustomCodeSnippet(String path) { 25 | Path customCodePath = Paths.get(path); 26 | if (Files.notExists(customCodePath)) { 27 | return ""; 28 | } 29 | 30 | try { 31 | return new String(Files.readAllBytes(customCodePath)); 32 | } catch (IOException e) { 33 | throw new SharedTypeException(String.format("Failed to read custom code snippet from path '%s'", customCodePath), e); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/adaptor/GoHeaderDataAdaptor.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.adaptor; 2 | 3 | import online.sharedtype.processor.context.Context; 4 | 5 | final class GoHeaderDataAdaptor extends AbstractDataAdaptor { 6 | public GoHeaderDataAdaptor(Context ctx) { 7 | super(ctx); 8 | } 9 | 10 | @SuppressWarnings("unused") 11 | String packageName() { 12 | return ctx.getProps().getGo().getOutputFilePackageName(); 13 | } 14 | 15 | @Override 16 | String customCodeSnippet() { 17 | return readCustomCodeSnippet(ctx.getProps().getGo().getCustomCodePath()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/adaptor/RenderDataAdaptor.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.adaptor; 2 | 3 | /** 4 | * Implementations of the type is used as render objects feeding to template renderer. 5 | * @author Cause Chung 6 | */ 7 | public interface RenderDataAdaptor { 8 | } 9 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/adaptor/RenderDataAdaptorFactory.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.adaptor; 2 | 3 | import online.sharedtype.processor.context.Context; 4 | import online.sharedtype.processor.support.utils.Tuple; 5 | import online.sharedtype.processor.writer.render.Template; 6 | 7 | public interface RenderDataAdaptorFactory { 8 | Tuple header(Context ctx); 9 | 10 | static Tuple typescript(Context ctx) { 11 | return Tuple.of(Template.TEMPLATE_TYPESCRIPT_HEADER, new TypescriptHeaderDataAdaptor(ctx)); 12 | } 13 | 14 | static Tuple rust(Context ctx) { 15 | return Tuple.of(Template.TEMPLATE_RUST_HEADER, new RustHeaderDataAdaptor(ctx)); 16 | } 17 | 18 | static Tuple go(Context ctx) { 19 | return Tuple.of(Template.TEMPLATE_GO_HEADER, new GoHeaderDataAdaptor(ctx)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/adaptor/RustHeaderDataAdaptor.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.adaptor; 2 | 3 | import online.sharedtype.processor.context.Context; 4 | import online.sharedtype.processor.context.Props; 5 | 6 | import java.util.LinkedHashSet; 7 | import java.util.Set; 8 | 9 | final class RustHeaderDataAdaptor extends AbstractDataAdaptor { 10 | RustHeaderDataAdaptor(Context ctx) { 11 | super(ctx); 12 | } 13 | 14 | String allowExpr() { 15 | Set allows = new LinkedHashSet<>(); 16 | Props.Rust rust = ctx.getProps().getRust(); 17 | if (rust.isAllowDeadcode()) { 18 | allows.add("dead_code"); 19 | } 20 | 21 | if (!rust.isConvertToSnakeCase()) { 22 | allows.add("non_snake_case"); 23 | } 24 | 25 | return allows.isEmpty() ? null : String.format("#![allow(%s)]", String.join(", ", allows)); 26 | } 27 | 28 | @Override 29 | String customCodeSnippet() { 30 | return readCustomCodeSnippet(ctx.getProps().getRust().getCustomCodePath()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/adaptor/TypescriptHeaderDataAdaptor.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.adaptor; 2 | 3 | import online.sharedtype.processor.context.Context; 4 | 5 | final class TypescriptHeaderDataAdaptor extends AbstractDataAdaptor { 6 | TypescriptHeaderDataAdaptor(Context ctx) { 7 | super(ctx); 8 | } 9 | 10 | @Override 11 | String customCodeSnippet() { 12 | return readCustomCodeSnippet(ctx.getProps().getTypescript().getCustomCodePath()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/converter/AbstractEnumConverter.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.converter; 2 | 3 | import online.sharedtype.processor.domain.def.EnumDef; 4 | import online.sharedtype.processor.domain.def.TypeDef; 5 | 6 | abstract class AbstractEnumConverter implements TemplateDataConverter { 7 | @Override 8 | public final boolean shouldAccept(TypeDef typeDef) { 9 | return typeDef instanceof EnumDef && !((EnumDef) typeDef).components().isEmpty(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/converter/AbstractFieldExpr.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.converter; 2 | 3 | import online.sharedtype.SharedType; 4 | import online.sharedtype.processor.domain.component.ComponentInfo; 5 | 6 | import java.util.List; 7 | 8 | abstract class AbstractFieldExpr { 9 | final String name; 10 | final List tagLiterals; 11 | 12 | AbstractFieldExpr(ComponentInfo componentInfo, SharedType.TargetType targetType) { 13 | name = componentInfo.name(); 14 | tagLiterals = componentInfo.getTagLiterals(targetType); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/converter/AbstractStructConverter.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.converter; 2 | 3 | import online.sharedtype.processor.domain.def.ClassDef; 4 | import online.sharedtype.processor.domain.def.TypeDef; 5 | 6 | abstract class AbstractStructConverter implements TemplateDataConverter { 7 | @Override 8 | public boolean shouldAccept(TypeDef typeDef) { 9 | if (!(typeDef instanceof ClassDef)) { 10 | return false; 11 | } 12 | ClassDef classDef = (ClassDef) typeDef; 13 | if (classDef.isMapType()) { 14 | return false; 15 | } 16 | return !classDef.components().isEmpty() || classDef.isDepended(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/converter/AbstractTypeExpr.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.converter; 2 | 3 | /** Represents struct, interface, enum type expression. */ 4 | public abstract class AbstractTypeExpr { 5 | } 6 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/converter/ConversionUtils.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.converter; 2 | 3 | import online.sharedtype.processor.domain.component.FieldComponentInfo; 4 | import online.sharedtype.processor.domain.type.ConcreteTypeInfo; 5 | 6 | import java.util.Set; 7 | import java.util.regex.Pattern; 8 | 9 | final class ConversionUtils { 10 | private final static Pattern CAMEL_CASE_PATTERN = Pattern.compile("([a-z])([A-Z]+)"); 11 | private ConversionUtils() {} 12 | 13 | static String toSnakeCase(String camelCase) { 14 | return CAMEL_CASE_PATTERN.matcher(camelCase).replaceAll("$1_$2").toLowerCase(); 15 | } 16 | 17 | static String capitalize(String str) { 18 | if (str == null || str.isEmpty()) { 19 | return str; 20 | } 21 | return str.substring(0, 1).toUpperCase() + str.substring(1); 22 | } 23 | 24 | static boolean isOptionalField(FieldComponentInfo field) { 25 | if (field.optional()) { 26 | return true; 27 | } 28 | return isOfCyclicReferencedType(field); 29 | } 30 | 31 | static boolean isOfCyclicReferencedType(FieldComponentInfo field) { 32 | if (field.type() instanceof ConcreteTypeInfo) { 33 | ConcreteTypeInfo type = (ConcreteTypeInfo) field.type(); 34 | return type.typeDef() != null && type.typeDef().isCyclicReferenced(); 35 | } 36 | return false; 37 | } 38 | 39 | static String buildRustMacroTraitsExpr(Set macroTraits) { 40 | if (macroTraits.isEmpty()) { 41 | return null; 42 | } 43 | return String.format("#[derive(%s)]", String.join(", ", macroTraits)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/converter/RustMacroTraitsGenerator.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.converter; 2 | 3 | import online.sharedtype.processor.domain.def.TypeDef; 4 | 5 | import java.util.Set; 6 | 7 | interface RustMacroTraitsGenerator { 8 | Set generate(TypeDef typeDef); 9 | } 10 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/converter/RustMacroTraitsGeneratorImpl.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.converter; 2 | 3 | import online.sharedtype.processor.context.Config; 4 | import online.sharedtype.processor.context.Context; 5 | import online.sharedtype.processor.domain.def.TypeDef; 6 | 7 | import java.util.Collections; 8 | import java.util.LinkedHashSet; 9 | import java.util.Set; 10 | 11 | final class RustMacroTraitsGeneratorImpl implements RustMacroTraitsGenerator { 12 | final Context ctx; 13 | private final Set defaultTraits; 14 | 15 | RustMacroTraitsGeneratorImpl(Context ctx) { 16 | this.ctx = ctx; 17 | this.defaultTraits = ctx.getProps().getRust().getDefaultTypeMacros(); 18 | } 19 | 20 | @Override 21 | public Set generate(TypeDef typeDef) { 22 | Config config = ctx.getTypeStore().getConfig(typeDef); 23 | String[] typeMacroTraits = config.getAnno().rustMacroTraits(); 24 | Set traits = new LinkedHashSet<>(typeMacroTraits.length + defaultTraits.size()); 25 | traits.addAll(defaultTraits); 26 | Collections.addAll(traits, typeMacroTraits); 27 | return traits; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/converter/TemplateDataConverter.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.converter; 2 | 3 | import online.sharedtype.SharedType; 4 | import online.sharedtype.processor.context.Context; 5 | import online.sharedtype.processor.domain.def.TypeDef; 6 | import online.sharedtype.processor.support.utils.Tuple; 7 | import online.sharedtype.processor.writer.converter.type.TypeExpressionConverter; 8 | import online.sharedtype.processor.writer.render.Template; 9 | 10 | import java.util.HashSet; 11 | import java.util.Set; 12 | 13 | public interface TemplateDataConverter { 14 | 15 | boolean shouldAccept(TypeDef typeDef); 16 | 17 | Tuple convert(TypeDef typeDef); 18 | 19 | static Set typescript(Context ctx) { 20 | Set converters = new HashSet<>(3); 21 | converters.add(new TypescriptInterfaceConverter(ctx, TypeExpressionConverter.typescript(ctx))); 22 | converters.add(new TypescriptEnumConverter(ctx)); 23 | converters.add(new ConstantConverter(ctx, TypeExpressionConverter.nullOp(), SharedType.TargetType.TYPESCRIPT)); 24 | return converters; 25 | } 26 | 27 | static Set go(Context ctx) { 28 | Set converters = new HashSet<>(3); 29 | TypeExpressionConverter typeExpressionConverter = TypeExpressionConverter.go(ctx); 30 | converters.add(new GoStructConverter(typeExpressionConverter)); 31 | converters.add(new GoEnumConverter(ctx, typeExpressionConverter)); 32 | converters.add(new ConstantConverter(ctx, typeExpressionConverter, SharedType.TargetType.GO)); 33 | return converters; 34 | } 35 | 36 | static Set rust(Context ctx) { 37 | RustMacroTraitsGenerator rustMacroTraitsGenerator = new RustMacroTraitsGeneratorImpl(ctx); 38 | TypeExpressionConverter rustTypeExpressionConverter = TypeExpressionConverter.rust(ctx); 39 | TypeExpressionConverter rustLiteralTypeExpressionConverter = TypeExpressionConverter.rustLiteral(ctx); 40 | Set converters = new HashSet<>(3); 41 | converters.add(new RustStructConverter(ctx, rustTypeExpressionConverter, rustMacroTraitsGenerator)); 42 | converters.add(new RustEnumConverter(ctx, rustLiteralTypeExpressionConverter, rustMacroTraitsGenerator)); 43 | converters.add(new ConstantConverter(ctx, rustLiteralTypeExpressionConverter, SharedType.TargetType.RUST)); 44 | return converters; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/converter/type/RustLiteralTypeExpressionConverter.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.converter.type; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import online.sharedtype.processor.context.Context; 5 | import online.sharedtype.processor.domain.Constants; 6 | import online.sharedtype.processor.domain.def.EnumDef; 7 | import online.sharedtype.processor.domain.def.TypeDef; 8 | import online.sharedtype.processor.domain.type.ConcreteTypeInfo; 9 | import online.sharedtype.processor.domain.type.TypeInfo; 10 | import online.sharedtype.processor.support.exception.SharedTypeInternalError; 11 | 12 | import java.util.HashSet; 13 | import java.util.Set; 14 | 15 | @RequiredArgsConstructor 16 | final class RustLiteralTypeExpressionConverter implements TypeExpressionConverter { 17 | private static final Set STRING_TYPES = new HashSet<>(3); 18 | static { 19 | STRING_TYPES.add(Constants.STRING_TYPE_INFO); 20 | STRING_TYPES.addAll(Constants.MATH_TYPES); 21 | } 22 | 23 | private final Context ctx; 24 | 25 | @Override 26 | public String toTypeExpr(TypeInfo typeInfo, TypeDef contextTypeDef) { 27 | if (!(typeInfo instanceof ConcreteTypeInfo)) { 28 | throw new SharedTypeInternalError(String.format("Literal types must be concrete types, but got: %s in %s", typeInfo, contextTypeDef)); 29 | } 30 | ConcreteTypeInfo concreteTypeInfo = (ConcreteTypeInfo) typeInfo; 31 | if (concreteTypeInfo.getKind() == ConcreteTypeInfo.Kind.ENUM) { 32 | EnumDef enumDef = (EnumDef) concreteTypeInfo.typeDef(); 33 | if (enumDef.hasComponentValueType() && ctx.getProps().getRust().hasEnumValueTypeAlias()) { 34 | return enumDef.valueTypeAlias(); 35 | } 36 | } 37 | 38 | if (STRING_TYPES.contains(concreteTypeInfo)) { 39 | return "&'static str"; 40 | } 41 | return RustTypeNameMappings.getOrDefault(concreteTypeInfo, concreteTypeInfo.simpleName()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/converter/type/RustTypeExpressionConverter.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.converter.type; 2 | 3 | import online.sharedtype.SharedType; 4 | import online.sharedtype.processor.context.Config; 5 | import online.sharedtype.processor.context.Context; 6 | import online.sharedtype.processor.context.RenderFlags; 7 | import online.sharedtype.processor.domain.def.EnumDef; 8 | import online.sharedtype.processor.domain.type.ConcreteTypeInfo; 9 | import online.sharedtype.processor.domain.Constants; 10 | import online.sharedtype.processor.domain.type.DateTimeInfo; 11 | import online.sharedtype.processor.domain.type.TypeInfo; 12 | 13 | import online.sharedtype.processor.support.annotation.Nullable; 14 | 15 | final class RustTypeExpressionConverter extends AbstractTypeExpressionConverter { 16 | private static final ArraySpec ARRAY_SPEC = new ArraySpec("Vec<", ">"); 17 | private static final MapSpec DEFAULT_MAP_SPEC = new MapSpec("HashMap<", ", ", ">"); 18 | private final RenderFlags renderFlags; 19 | 20 | RustTypeExpressionConverter(Context ctx) { 21 | super(ctx); 22 | this.renderFlags = ctx.getRenderFlags(); 23 | } 24 | 25 | @Override 26 | void beforeVisitTypeInfo(TypeInfo typeInfo) { 27 | if (typeInfo.equals(Constants.OBJECT_TYPE_INFO)) { 28 | renderFlags.setUseRustAny(true); 29 | } else if (typeInfo instanceof ConcreteTypeInfo && ((ConcreteTypeInfo) typeInfo).getKind() == ConcreteTypeInfo.Kind.MAP) { 30 | renderFlags.setUseRustMap(true); 31 | } 32 | } 33 | 34 | @Override 35 | ArraySpec arraySpec() { 36 | return ARRAY_SPEC; 37 | } 38 | 39 | @Override 40 | MapSpec mapSpec(ConcreteTypeInfo typeInfo) { 41 | return DEFAULT_MAP_SPEC; 42 | } 43 | 44 | @Override 45 | String dateTimeTypeExpr(DateTimeInfo dateTimeInfo, Config config) { 46 | return dateTimeInfo.mappedNameOrDefault(SharedType.TargetType.RUST, config.getRustTargetDatetimeTypeLiteral()); 47 | } 48 | 49 | @Override 50 | @Nullable 51 | String toTypeExpression(ConcreteTypeInfo typeInfo, @Nullable String defaultExpr) { 52 | String expr = typeInfo.mappedName(SharedType.TargetType.RUST); 53 | if (expr == null) { 54 | expr = RustTypeNameMappings.getOrDefault(typeInfo, defaultExpr); 55 | } 56 | if (expr != null) { 57 | if (typeInfo.typeDef() != null && typeInfo.typeDef().isCyclicReferenced()) { 58 | expr = String.format("Box<%s>", expr); 59 | } 60 | } 61 | return expr; 62 | } 63 | 64 | @Override 65 | TypeInfo mapEnumValueType(ConcreteTypeInfo enumType, EnumDef enumDef) { 66 | return enumDef.getComponentValueType(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/converter/type/RustTypeNameMappings.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.converter.type; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import online.sharedtype.processor.domain.Constants; 5 | import online.sharedtype.processor.domain.type.TypeInfo; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | @UtilityClass 11 | final class RustTypeNameMappings { 12 | private static final Map typeNameMappings = new HashMap<>(32); 13 | static { 14 | typeNameMappings.put(Constants.BOOLEAN_TYPE_INFO, "bool"); 15 | typeNameMappings.put(Constants.BYTE_TYPE_INFO, "i8"); 16 | typeNameMappings.put(Constants.CHAR_TYPE_INFO, "char"); 17 | typeNameMappings.put(Constants.DOUBLE_TYPE_INFO, "f64"); 18 | typeNameMappings.put(Constants.FLOAT_TYPE_INFO, "f32"); 19 | typeNameMappings.put(Constants.INT_TYPE_INFO, "i32"); 20 | typeNameMappings.put(Constants.LONG_TYPE_INFO, "i64"); 21 | typeNameMappings.put(Constants.SHORT_TYPE_INFO, "i16"); 22 | 23 | typeNameMappings.put(Constants.BOXED_BOOLEAN_TYPE_INFO, "bool"); 24 | typeNameMappings.put(Constants.BOXED_BYTE_TYPE_INFO, "i8"); 25 | typeNameMappings.put(Constants.BOXED_CHAR_TYPE_INFO, "char"); 26 | typeNameMappings.put(Constants.BOXED_DOUBLE_TYPE_INFO, "f64"); 27 | typeNameMappings.put(Constants.BOXED_FLOAT_TYPE_INFO, "f32"); 28 | typeNameMappings.put(Constants.BOXED_INT_TYPE_INFO, "i32"); 29 | typeNameMappings.put(Constants.BOXED_LONG_TYPE_INFO, "i64"); 30 | typeNameMappings.put(Constants.BOXED_SHORT_TYPE_INFO, "i16"); 31 | typeNameMappings.put(Constants.BIG_INTEGER_TYPE_INFO, "String"); 32 | typeNameMappings.put(Constants.BIG_DECIMAL_TYPE_INFO, "String"); 33 | 34 | typeNameMappings.put(Constants.STRING_TYPE_INFO, "String"); 35 | typeNameMappings.put(Constants.VOID_TYPE_INFO, "!"); 36 | typeNameMappings.put(Constants.OBJECT_TYPE_INFO, "Box"); 37 | } 38 | 39 | public static String getOrDefault(TypeInfo typeInfo, String defaultExpr) { 40 | return typeNameMappings.getOrDefault(typeInfo, defaultExpr); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/converter/type/TypeExpressionConverter.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.converter.type; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import online.sharedtype.processor.context.Context; 5 | import online.sharedtype.processor.domain.def.TypeDef; 6 | import online.sharedtype.processor.domain.type.TypeInfo; 7 | import online.sharedtype.processor.support.annotation.Nullable; 8 | 9 | public interface TypeExpressionConverter { 10 | /** @return null when it's nullOp impl, e.g. typescript constant generation does not need type info. */ 11 | @Nullable 12 | String toTypeExpr(TypeInfo typeInfo, TypeDef contextTypeDef); 13 | 14 | static TypeExpressionConverter typescript(Context ctx) { 15 | return new TypescriptTypeExpressionConverter(ctx); 16 | } 17 | 18 | static TypeExpressionConverter go(Context ctx) { 19 | return new GoTypeExpressionConverter(ctx); 20 | } 21 | 22 | static TypeExpressionConverter rust(Context ctx) { 23 | return new RustTypeExpressionConverter(ctx); 24 | } 25 | 26 | static TypeExpressionConverter rustLiteral(Context ctx) { 27 | return new RustLiteralTypeExpressionConverter(ctx); 28 | } 29 | 30 | static TypeExpressionConverter nullOp() { 31 | return (typeInfo, contextTypeDef) -> null; 32 | } 33 | 34 | @RequiredArgsConstructor 35 | final class ArraySpec { 36 | final String prefix; 37 | final String suffix; 38 | } 39 | 40 | @RequiredArgsConstructor 41 | final class MapSpec { 42 | final String prefix; 43 | final String delimiter; 44 | final String suffix; 45 | } 46 | 47 | @RequiredArgsConstructor 48 | final class TypeArgsSpec { 49 | final String prefix; 50 | final String delimiter; 51 | final String suffix; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/render/MustacheTemplateRenderer.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.render; 2 | 3 | import com.github.mustachejava.Mustache; 4 | import com.github.mustachejava.MustacheFactory; 5 | import lombok.RequiredArgsConstructor; 6 | import online.sharedtype.processor.support.exception.SharedTypeInternalError; 7 | import online.sharedtype.processor.support.utils.Tuple; 8 | 9 | import java.io.Writer; 10 | import java.util.List; 11 | 12 | /** 13 | * Implementation via on Mustache. 14 | * The underlying implementation already caches compiled templates. 15 | * 16 | * @author Cause Chung 17 | */ 18 | @RequiredArgsConstructor 19 | final class MustacheTemplateRenderer implements TemplateRenderer { 20 | private final MustacheFactory mf; 21 | 22 | @Override 23 | public void render(Writer writer, List> data) { 24 | for (Tuple tuple : data) { 25 | Template template = tuple.a(); 26 | Object values = tuple.b(); 27 | Mustache mustache = mf.compile(template.getResourcePath()); 28 | if (mustache == null) { 29 | throw new SharedTypeInternalError(String.format("Template not found: '%s'", template)); 30 | } 31 | mustache.execute(writer, values); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/render/Template.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.render; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import online.sharedtype.SharedType; 6 | 7 | /** 8 | * Represents a template used by {@link TemplateRenderer}. 9 | * 10 | * @author Cause Chung 11 | */ 12 | @EqualsAndHashCode 13 | @Getter 14 | public final class Template { 15 | public static final Template TEMPLATE_TYPESCRIPT_HEADER = new Template(SharedType.TargetType.TYPESCRIPT, "header"); 16 | public static final Template TEMPLATE_TYPESCRIPT_INTERFACE = new Template(SharedType.TargetType.TYPESCRIPT, "interface"); 17 | public static final Template TEMPLATE_TYPESCRIPT_UNION_TYPE_ENUM = new Template(SharedType.TargetType.TYPESCRIPT, "union-type-enum"); 18 | public static final Template TEMPLATE_TYPESCRIPT_ENUM = new Template(SharedType.TargetType.TYPESCRIPT, "enum"); 19 | public static final Template TEMPLATE_RUST_HEADER = new Template(SharedType.TargetType.RUST, "header"); 20 | public static final Template TEMPLATE_RUST_STRUCT = new Template(SharedType.TargetType.RUST, "struct"); 21 | public static final Template TEMPLATE_RUST_ENUM = new Template(SharedType.TargetType.RUST, "enum"); 22 | public static final Template TEMPLATE_GO_HEADER = new Template(SharedType.TargetType.GO, "header"); 23 | public static final Template TEMPLATE_GO_STRUCT = new Template(SharedType.TargetType.GO, "struct"); 24 | public static final Template TEMPLATE_GO_CONST_ENUM = new Template(SharedType.TargetType.GO, "const-enum"); 25 | public static final Template TEMPLATE_GO_STRUCT_ENUM = new Template(SharedType.TargetType.GO, "struct-enum"); 26 | 27 | private final SharedType.TargetType targetType; 28 | private final String resourcePath; 29 | 30 | Template(SharedType.TargetType targetType, String resourceName) { 31 | this.targetType = targetType; 32 | this.resourcePath = String.format("templates/%s/%s.mustache", targetType.name().toLowerCase(), resourceName); 33 | } 34 | 35 | public static Template forConstant(SharedType.TargetType targetType, boolean constantNamespaced) { 36 | return new Template(targetType, constantNamespaced ? "constant" : "constant-inline"); 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return resourcePath; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /processor/src/main/java/online/sharedtype/processor/writer/render/TemplateRenderer.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.render; 2 | 3 | import com.github.mustachejava.DefaultMustacheFactory; 4 | import online.sharedtype.processor.support.utils.Tuple; 5 | 6 | import java.io.Writer; 7 | import java.util.List; 8 | 9 | /** 10 | * 11 | * @author Cause Chung 12 | */ 13 | public interface TemplateRenderer { 14 | 15 | /** 16 | * Renders the target output to the writer specified. 17 | * 18 | * @param writer java.io.Writer 19 | * @param data a list of tuple containing the template and corresponding data for rendering. 20 | */ 21 | void render(Writer writer, List> data); 22 | 23 | static TemplateRenderer create() { 24 | return new MustacheTemplateRenderer(new DefaultMustacheFactory()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /processor/src/main/resources/templates/go/const-enum.mustache: -------------------------------------------------------------------------------- 1 | type {{name}} = {{valueType}} 2 | const ( 3 | {{#enumerations}} 4 | {{#tagLiterals}} 5 | {{{.}}} 6 | {{/tagLiterals}} 7 | {{name}} {{enumName}} = {{{value}}} 8 | {{/enumerations}} 9 | ) 10 | -------------------------------------------------------------------------------- /processor/src/main/resources/templates/go/constant-inline.mustache: -------------------------------------------------------------------------------- 1 | const ( 2 | {{#constants}} 3 | {{#tagLiterals}} 4 | {{{.}}} 5 | {{/tagLiterals}} 6 | {{name}} {{{type}}} = {{{value}}}; 7 | {{/constants}} 8 | ) 9 | -------------------------------------------------------------------------------- /processor/src/main/resources/templates/go/constant.mustache: -------------------------------------------------------------------------------- 1 | var {{name}} = struct { 2 | {{#constants}} 3 | {{#tagLiterals}} 4 | {{{.}}} 5 | {{/tagLiterals}} 6 | {{name}} {{type}} 7 | {{/constants}} 8 | }{ 9 | {{#constants}} 10 | {{{value}}}, 11 | {{/constants}} 12 | } 13 | -------------------------------------------------------------------------------- /processor/src/main/resources/templates/go/header.mustache: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/SharedType/sharedtype 2 | package {{packageName}} 3 | 4 | {{{customCodeSnippet}}} 5 | 6 | -------------------------------------------------------------------------------- /processor/src/main/resources/templates/go/struct-enum.mustache: -------------------------------------------------------------------------------- 1 | type {{name}} = {{valueType}} 2 | var {{name}}Enums = struct { 3 | {{#enumerations}} 4 | {{#tagLiterals}} 5 | {{{.}}} 6 | {{/tagLiterals}} 7 | {{name}} {{enumName}} 8 | {{/enumerations}} 9 | }{ 10 | {{#enumerations}} 11 | {{{value}}}, 12 | {{/enumerations}} 13 | } 14 | -------------------------------------------------------------------------------- /processor/src/main/resources/templates/go/struct.mustache: -------------------------------------------------------------------------------- 1 | type {{name}}{{typeParametersExpr}} struct { 2 | {{#supertypes}} 3 | {{.}} 4 | {{/supertypes}} 5 | {{#properties}} 6 | {{#tagLiterals}} 7 | {{{.}}} 8 | {{/tagLiterals}} 9 | {{capitalizedName}} {{{typeExpr}}} `{{{tagsExpr}}}` 10 | {{/properties}} 11 | } 12 | -------------------------------------------------------------------------------- /processor/src/main/resources/templates/rust/constant-inline.mustache: -------------------------------------------------------------------------------- 1 | {{#constants}} 2 | {{#tagLiterals}} 3 | {{{.}}} 4 | {{/tagLiterals}} 5 | pub const {{name}}: {{{type}}} = {{{value}}}; 6 | {{/constants}} 7 | 8 | -------------------------------------------------------------------------------- /processor/src/main/resources/templates/rust/constant.mustache: -------------------------------------------------------------------------------- 1 | pub mod {{name}} { 2 | {{#constants}} 3 | {{#tagLiterals}} 4 | {{{.}}} 5 | {{/tagLiterals}} 6 | pub const {{name}}: {{{type}}} = {{{value}}}; 7 | {{/constants}} 8 | } 9 | 10 | -------------------------------------------------------------------------------- /processor/src/main/resources/templates/rust/enum.mustache: -------------------------------------------------------------------------------- 1 | {{macroTraitsExpr}} 2 | pub enum {{name}} { 3 | {{#enumerations}} 4 | {{#tagLiterals}} 5 | {{{.}}} 6 | {{/tagLiterals}} 7 | {{name}}, 8 | {{/enumerations}} 9 | } 10 | {{#hasValue}} 11 | {{#valueTypeAlias}} 12 | pub type {{valueTypeAlias}} = {{{valueType}}}; 13 | {{/valueTypeAlias}} 14 | impl {{name}} { 15 | pub const fn value(self) -> {{valueTypeAlias}} { 16 | use {{name}}::*; 17 | match self { 18 | {{#enumerations}} 19 | {{name}} => {{{value}}}, 20 | {{/enumerations}} 21 | } 22 | } 23 | } 24 | {{/hasValue}} 25 | -------------------------------------------------------------------------------- /processor/src/main/resources/templates/rust/header.mustache: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/SharedType/sharedtype 2 | {{allowExpr}} 3 | {{#renderFlags.useRustAny}}use std::any::Any;{{/renderFlags.useRustAny}} 4 | {{#renderFlags.useRustMap}}use std::collections::HashMap;{{/renderFlags.useRustMap}} 5 | 6 | {{{customCodeSnippet}}} 7 | 8 | -------------------------------------------------------------------------------- /processor/src/main/resources/templates/rust/struct.mustache: -------------------------------------------------------------------------------- 1 | {{macroTraitsExpr}} 2 | pub struct {{name}}{{{typeParametersExpr}}} { 3 | {{#properties}} 4 | {{#tagLiterals}} 5 | {{{.}}} 6 | {{/tagLiterals}} 7 | pub {{name}}: {{{typeExpr}}}, 8 | {{/properties}} 9 | } 10 | 11 | -------------------------------------------------------------------------------- /processor/src/main/resources/templates/typescript/constant-inline.mustache: -------------------------------------------------------------------------------- 1 | {{#constants}} 2 | {{#tagLiterals}} 3 | {{{.}}} 4 | {{/tagLiterals}} 5 | export const {{name}} = {{{value}}}; 6 | {{/constants}} 7 | 8 | -------------------------------------------------------------------------------- /processor/src/main/resources/templates/typescript/constant.mustache: -------------------------------------------------------------------------------- 1 | export const {{name}} = { 2 | {{#constants}} 3 | {{#tagLiterals}} 4 | {{{.}}} 5 | {{/tagLiterals}} 6 | {{name}}: {{{value}}}, 7 | {{/constants}} 8 | } as const; 9 | 10 | -------------------------------------------------------------------------------- /processor/src/main/resources/templates/typescript/enum.mustache: -------------------------------------------------------------------------------- 1 | export {{#isConst}}const {{/isConst}}enum {{name}} { 2 | {{#values}} 3 | {{#tagLiterals}} 4 | {{{.}}} 5 | {{/tagLiterals}} 6 | {{name}} = {{{value}}}, 7 | {{/values}} 8 | } 9 | 10 | -------------------------------------------------------------------------------- /processor/src/main/resources/templates/typescript/header.mustache: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/SharedType/sharedtype 2 | 3 | {{{customCodeSnippet}}} 4 | 5 | -------------------------------------------------------------------------------- /processor/src/main/resources/templates/typescript/interface.mustache: -------------------------------------------------------------------------------- 1 | export interface {{name}}{{{typeParametersExpr}}} {{{supertypesExpr}}}{ 2 | {{#properties}} 3 | {{#tagLiterals}} 4 | {{{.}}} 5 | {{/tagLiterals}} 6 | {{#readonly}}readonly {{/readonly}}{{name}}{{#optional}}?{{/optional}}: {{{type}}}{{#unionNull}} | null{{/unionNull}}{{#unionUndefined}} | undefined{{/unionUndefined}}{{propDelimiter}} 7 | {{/properties}} 8 | } 9 | 10 | -------------------------------------------------------------------------------- /processor/src/main/resources/templates/typescript/union-type-enum.mustache: -------------------------------------------------------------------------------- 1 | export type {{name}} = {{{valuesExpr}}}; 2 | 3 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/AnnotationProcessorImplTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor; 2 | 3 | import online.sharedtype.processor.domain.def.ClassDef; 4 | import online.sharedtype.processor.domain.Constants; 5 | import online.sharedtype.processor.context.ContextMocks; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import org.mockito.ArgumentCaptor; 9 | import online.sharedtype.processor.parser.TypeDefParser; 10 | import online.sharedtype.processor.resolver.TypeResolver; 11 | import online.sharedtype.processor.writer.TypeWriter; 12 | 13 | import java.util.Collections; 14 | import java.util.List; 15 | import java.util.Set; 16 | 17 | import static org.assertj.core.api.Assertions.assertThat; 18 | import static org.mockito.ArgumentMatchers.eq; 19 | import static org.mockito.Mockito.mock; 20 | import static org.mockito.Mockito.verify; 21 | import static org.mockito.Mockito.when; 22 | 23 | class AnnotationProcessorImplTest { 24 | private final ContextMocks ctxMocks = new ContextMocks(); 25 | private final TypeDefParser typeDefParser = mock(TypeDefParser.class); 26 | private final TypeResolver typeResolver = mock(TypeResolver.class); 27 | private final TypeWriter typeWriter = mock(TypeWriter.class); 28 | private final AnnotationProcessorImpl processor = new AnnotationProcessorImpl(); 29 | 30 | private final ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); 31 | 32 | @BeforeEach 33 | void setUp() { 34 | processor.ctx = ctxMocks.getContext(); 35 | processor.parser = typeDefParser; 36 | processor.resolver = typeResolver; 37 | processor.writer = typeWriter; 38 | } 39 | 40 | @Test 41 | void doProcess() throws Exception { 42 | var typeElement1 = ctxMocks.typeElement("com.github.cuzfrog.Abc").element(); 43 | var typeElement2 = ctxMocks.typeElement("com.github.cuzfrog.IgnoredClass").element(); 44 | var classDef1 = ClassDef.builder().qualifiedName("com.github.cuzfrog.Abc").simpleName("Abc").build(); 45 | when(typeDefParser.parse(typeElement1)).thenReturn(Collections.singletonList(classDef1)); 46 | when(typeDefParser.parse(typeElement2)).thenReturn(Collections.emptyList()); 47 | 48 | var dependencyDef = ClassDef.builder().qualifiedName("com.github.cuzfrog.Dependency").simpleName("Dependency").build(); 49 | when(typeResolver.resolve(List.of(classDef1))).thenReturn(List.of(classDef1, dependencyDef)); 50 | 51 | processor.doProcess(Set.of(typeElement1, typeElement2)); 52 | 53 | verify(typeWriter).write(List.of(classDef1, dependencyDef)); 54 | verify(ctxMocks.getContext()).warn(eq(typeElement2), messageCaptor.capture(), eq("com.github.cuzfrog.IgnoredClass"), eq(Constants.ANNOTATION_QUALIFIED_NAME)); 55 | assertThat(messageCaptor.getValue()).contains("is ignored or invalid"); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/AbstractTreeMock.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import com.sun.source.tree.Tree; 4 | import lombok.Getter; 5 | 6 | import javax.lang.model.element.Element; 7 | 8 | import static org.mockito.Mockito.when; 9 | 10 | public abstract class AbstractTreeMock> { 11 | final Context ctx; 12 | @Getter 13 | final T tree; 14 | 15 | AbstractTreeMock(T tree, Context ctx) { 16 | this.ctx = ctx; 17 | this.tree = tree; 18 | } 19 | 20 | void fromElement(Element element) { 21 | when(ctx.getTrees().getTree(element)).thenReturn(tree); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/AnnotationMirrorMock.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import javax.lang.model.element.AnnotationMirror; 4 | import javax.lang.model.type.DeclaredType; 5 | 6 | import static org.mockito.Mockito.mock; 7 | import static org.mockito.Mockito.when; 8 | 9 | public final class AnnotationMirrorMock { 10 | private final AnnotationMirror mocked = mock(AnnotationMirror.class); 11 | 12 | AnnotationMirrorMock(DeclaredType declaredType) { 13 | when(mocked.getAnnotationType()).thenReturn(declaredType); 14 | } 15 | 16 | public AnnotationMirror mocked() { 17 | return mocked; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/ArrayTypeMock.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import javax.lang.model.type.ArrayType; 4 | import javax.lang.model.type.TypeKind; 5 | import javax.lang.model.type.TypeMirror; 6 | 7 | import static org.mockito.Mockito.mock; 8 | import static org.mockito.Mockito.when; 9 | 10 | public final class ArrayTypeMock { 11 | private final ArrayType type; 12 | 13 | ArrayTypeMock(TypeMirror componentType) { 14 | type = mock(ArrayType.class); 15 | when(type.getComponentType()).thenReturn(componentType); 16 | when(type.getKind()).thenReturn(TypeKind.ARRAY); 17 | } 18 | 19 | public ArrayType type() { 20 | return type; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/AssignmentTreeMock.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import com.sun.source.tree.AssignmentTree; 4 | import com.sun.source.tree.ExpressionTree; 5 | import com.sun.source.tree.Tree; 6 | import com.sun.source.tree.VariableTree; 7 | 8 | import static org.mockito.Mockito.mock; 9 | import static org.mockito.Mockito.when; 10 | 11 | public final class AssignmentTreeMock extends AbstractTreeMock { 12 | AssignmentTreeMock(Context ctx) { 13 | super(mock(AssignmentTree.class), ctx); 14 | when(tree.getKind()).thenReturn(Tree.Kind.ASSIGNMENT); 15 | } 16 | 17 | public AssignmentTreeMock withVariable(ExpressionTree variableTree) { 18 | when(tree.getVariable()).thenReturn(variableTree); 19 | return this; 20 | } 21 | 22 | public AssignmentTreeMock withExpression(ExpressionTree expressionTree) { 23 | when(tree.getExpression()).thenReturn(expressionTree); 24 | return this; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/DeclaredTypeVariableElementMock.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import javax.lang.model.element.VariableElement; 4 | import javax.lang.model.type.DeclaredType; 5 | import javax.lang.model.type.TypeKind; 6 | import javax.lang.model.util.Types; 7 | 8 | import static org.mockito.Mockito.mock; 9 | import static org.mockito.Mockito.when; 10 | 11 | public final class DeclaredTypeVariableElementMock extends AbstractElementMock { 12 | DeclaredTypeVariableElementMock(String name, DeclaredType declaredType, Context ctx) { 13 | super(mock(VariableElement.class, name), declaredType, ctx); 14 | setSimpleName(element, name); 15 | } 16 | 17 | public DeclaredTypeVariableElementMock withTypeKind(TypeKind typeKind) { 18 | when(type.getKind()).thenReturn(typeKind); 19 | return this; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/ExecutableElementMock.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import javax.lang.model.element.ElementKind; 4 | import javax.lang.model.element.ExecutableElement; 5 | import javax.lang.model.element.VariableElement; 6 | import javax.lang.model.type.ExecutableType; 7 | import javax.lang.model.type.TypeKind; 8 | import javax.lang.model.type.TypeMirror; 9 | import javax.lang.model.util.Types; 10 | 11 | import java.util.Arrays; 12 | 13 | import static org.mockito.Mockito.mock; 14 | import static org.mockito.Mockito.when; 15 | 16 | public final class ExecutableElementMock extends AbstractElementMock { 17 | private static final ElementKind DEFAULT_ELEMENT_KIND = ElementKind.METHOD; 18 | 19 | ExecutableElementMock(String name, Context ctx) { 20 | super(mock(ExecutableElement.class, name), mock(ExecutableType.class, name), ctx); 21 | setSimpleName(element, name); 22 | when(type.getKind()).thenReturn(TypeKind.EXECUTABLE); 23 | when(element.getKind()).thenReturn(DEFAULT_ELEMENT_KIND); 24 | } 25 | 26 | public ExecutableElementMock withReturnType(TypeMirror returnType) { 27 | when(element.getReturnType()).thenReturn(returnType); 28 | when(type.getReturnType()).thenReturn(returnType); 29 | return this; 30 | } 31 | 32 | public ExecutableElementMock withParameters(VariableElement... parameters) { 33 | when(element.getParameters()).then(invoc -> Arrays.asList(parameters)); 34 | return this; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/ExpressionTreeMock.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import com.sun.source.tree.ExpressionTree; 4 | 5 | public abstract class ExpressionTreeMock> extends AbstractTreeMock { 6 | ExpressionTreeMock(T tree, Context ctx) { 7 | super(tree, ctx); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/IdentifierTreeMock.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import com.sun.source.tree.IdentifierTree; 4 | import com.sun.source.tree.Tree; 5 | 6 | import javax.lang.model.element.Name; 7 | 8 | import static org.mockito.Mockito.mock; 9 | import static org.mockito.Mockito.when; 10 | 11 | public final class IdentifierTreeMock extends ExpressionTreeMock { 12 | IdentifierTreeMock(String name, Context ctx) { 13 | super(mock(IdentifierTree.class, String.format("Tree(%s)", name)), ctx); 14 | Name elementName = new MockName(name); 15 | when(tree.getName()).thenReturn(elementName); 16 | when(tree.getKind()).thenReturn(Tree.Kind.IDENTIFIER); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/LiteralTreeMock.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import com.sun.source.tree.LiteralTree; 4 | import com.sun.source.tree.Tree; 5 | 6 | import static org.mockito.Mockito.mock; 7 | import static org.mockito.Mockito.when; 8 | 9 | public final class LiteralTreeMock extends ExpressionTreeMock { 10 | LiteralTreeMock(Object value, Context ctx) { 11 | super(mock(LiteralTree.class), ctx); 12 | when(tree.getValue()).thenReturn(value); 13 | when(tree.getKind()).thenReturn(getKind(value)); 14 | } 15 | 16 | private static Tree.Kind getKind(Object value) { 17 | return switch (value) { 18 | case String ignored -> Tree.Kind.STRING_LITERAL; 19 | case Integer ignored -> Tree.Kind.INT_LITERAL; 20 | case Long ignored -> Tree.Kind.LONG_LITERAL; 21 | case Float ignored -> Tree.Kind.FLOAT_LITERAL; 22 | case Double ignored -> Tree.Kind.DOUBLE_LITERAL; 23 | case Character ignored -> Tree.Kind.CHAR_LITERAL; 24 | case Boolean ignored -> Tree.Kind.BOOLEAN_LITERAL; 25 | case null -> Tree.Kind.NULL_LITERAL; 26 | default -> throw new IllegalArgumentException("Unsupported literal type: " + value.getClass()); 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/MemberSelectTreeMock.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import com.sun.source.tree.ExpressionTree; 4 | import com.sun.source.tree.MemberSelectTree; 5 | import com.sun.source.tree.Tree; 6 | 7 | import static org.mockito.Mockito.mock; 8 | import static org.mockito.Mockito.when; 9 | 10 | public final class MemberSelectTreeMock extends ExpressionTreeMock { 11 | MemberSelectTreeMock(String identifier, Context ctx) { 12 | super(mock(MemberSelectTree.class), ctx); 13 | when(tree.getIdentifier()).thenReturn(new MockName(identifier)); 14 | when(tree.toString()).thenReturn(identifier); 15 | when(tree.getKind()).thenReturn(Tree.Kind.MEMBER_SELECT); 16 | } 17 | 18 | public MemberSelectTreeMock withExpression(ExpressionTree expressionTree) { 19 | when(tree.getExpression()).thenReturn(expressionTree); 20 | return this; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/MockName.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | import javax.lang.model.element.Name; 7 | 8 | @EqualsAndHashCode 9 | @RequiredArgsConstructor 10 | final class MockName implements Name { 11 | private final String name; 12 | 13 | @Override 14 | public boolean contentEquals(CharSequence cs) { 15 | return name.contentEquals(cs); 16 | } 17 | @Override 18 | public int length() { 19 | return name.length(); 20 | } 21 | @Override 22 | public char charAt(int index) { 23 | return name.charAt(index); 24 | } 25 | @Override 26 | public CharSequence subSequence(int start, int end) { 27 | return name.subSequence(start, end); 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return name; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/NewClassTreeMock.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import com.sun.source.tree.ExpressionTree; 4 | import com.sun.source.tree.NewClassTree; 5 | import com.sun.source.tree.Tree; 6 | 7 | import java.util.Arrays; 8 | import java.util.stream.Collectors; 9 | 10 | import static org.mockito.Mockito.mock; 11 | import static org.mockito.Mockito.when; 12 | 13 | public final class NewClassTreeMock extends ExpressionTreeMock { 14 | NewClassTreeMock(Context ctx) { 15 | super(mock(NewClassTree.class), ctx); 16 | when(tree.getKind()).thenReturn(NewClassTree.Kind.NEW_CLASS); 17 | } 18 | 19 | public NewClassTreeMock withArguments(ExpressionTree... arguments) { 20 | when(tree.getArguments()).then(invoc -> Arrays.stream(arguments).collect(Collectors.toList())); 21 | return this; 22 | } 23 | 24 | public NewClassTreeMock withIdentifier(ExpressionTree identifier) { 25 | when(tree.getIdentifier()).thenReturn(identifier); 26 | return this; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/PackageElementMock.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import javax.lang.model.element.PackageElement; 4 | import javax.lang.model.type.NoType; 5 | 6 | import static org.mockito.Mockito.mock; 7 | import static org.mockito.Mockito.when; 8 | 9 | public final class PackageElementMock extends AbstractElementMock { 10 | PackageElementMock(String qualifiedName, Context ctx) { 11 | super(mock(PackageElement.class, qualifiedName), mock(NoType.class), ctx); 12 | setQualifiedName(element, qualifiedName); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/PrimitiveVariableElementMock.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import javax.lang.model.element.ElementKind; 4 | import javax.lang.model.element.VariableElement; 5 | import javax.lang.model.type.PrimitiveType; 6 | import javax.lang.model.type.TypeKind; 7 | import javax.lang.model.util.Types; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | import static org.mockito.Mockito.mock; 11 | import static org.mockito.Mockito.when; 12 | 13 | public final class PrimitiveVariableElementMock extends AbstractElementMock { 14 | PrimitiveVariableElementMock(String name, TypeKind typeKind, Context ctx) { 15 | super(mock(VariableElement.class, name), mock(PrimitiveType.class, typeKind.name()), ctx); 16 | assertThat(typeKind.isPrimitive()).isTrue(); 17 | setSimpleName(element, name); 18 | when(element.getKind()).thenReturn(ElementKind.FIELD); 19 | when(type.getKind()).thenReturn(typeKind); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/RecordComponentMock.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import javax.lang.model.element.ElementKind; 4 | import javax.lang.model.element.ExecutableElement; 5 | import javax.lang.model.element.RecordComponentElement; 6 | import javax.lang.model.type.TypeMirror; 7 | import javax.lang.model.util.Types; 8 | 9 | import static org.mockito.Mockito.mock; 10 | import static org.mockito.Mockito.when; 11 | 12 | public final class RecordComponentMock extends AbstractElementMock> { 13 | RecordComponentMock(String name, T type, Context ctx) { 14 | super(mock(RecordComponentElement.class, name), type, ctx); 15 | setSimpleName(element, name); 16 | when(element.asType()).thenReturn(type); 17 | when(element.getKind()).thenReturn(ElementKind.RECORD_COMPONENT); 18 | } 19 | 20 | public RecordComponentMock withAccessor(ExecutableElement accessor) { 21 | when(element.getAccessor()).thenReturn(accessor); 22 | return this; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/TestUtils.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import online.sharedtype.SharedType; 5 | 6 | import static org.mockito.Mockito.spy; 7 | 8 | @UtilityClass 9 | public final class TestUtils { 10 | public static SharedType defaultSharedTypeAnnotation() { 11 | return spy(Config.DummyDefault.class.getAnnotation(Config.AnnoContainer.class).anno()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/TypeParameterElementMock.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import javax.lang.model.element.TypeParameterElement; 4 | import javax.lang.model.type.TypeKind; 5 | import javax.lang.model.type.TypeVariable; 6 | import javax.lang.model.util.Types; 7 | 8 | import static org.mockito.Mockito.mock; 9 | import static org.mockito.Mockito.when; 10 | 11 | public final class TypeParameterElementMock extends AbstractElementMock { 12 | TypeParameterElementMock(String name, Context ctx) { 13 | super(mock(TypeParameterElement.class), mock(TypeVariable.class), ctx); 14 | when(type.getKind()).thenReturn(TypeKind.TYPEVAR); 15 | when(type.asElement()).thenReturn(element); 16 | when(types.asElement(type)).thenReturn(element); 17 | setSimpleName(element, name); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/context/VariableTreeMock.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.context; 2 | 3 | import com.sun.source.tree.ExpressionTree; 4 | import com.sun.source.tree.Tree; 5 | import com.sun.source.tree.VariableTree; 6 | 7 | import static org.mockito.Mockito.mock; 8 | import static org.mockito.Mockito.when; 9 | 10 | public final class VariableTreeMock extends AbstractTreeMock { 11 | VariableTreeMock(Context ctx) { 12 | super(mock(VariableTree.class), ctx); 13 | when(tree.getKind()).thenReturn(VariableTree.Kind.VARIABLE); 14 | } 15 | 16 | public VariableTreeMock withInitializer(ExpressionTree initializer) { 17 | when(tree.getInitializer()).thenReturn(initializer); 18 | return this; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/domain/TypeEqualityTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.domain; 2 | 3 | import online.sharedtype.processor.domain.type.ArrayTypeInfo; 4 | import online.sharedtype.processor.domain.type.ConcreteTypeInfo; 5 | import online.sharedtype.processor.domain.type.TypeInfo; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.List; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | final class TypeEqualityTest { 13 | @Test 14 | void differentTypeOfTypeInfoShouldNotEqual() { 15 | TypeInfo type1 = ConcreteTypeInfo.builder().qualifiedName("java.lang.String").build(); 16 | TypeInfo arr = new ArrayTypeInfo(type1); 17 | assertThat(arr).isNotEqualTo(type1); 18 | } 19 | 20 | @Test 21 | void arrayWithSameComponentTypeShouldEqual() { 22 | TypeInfo type1 = ConcreteTypeInfo.builder().qualifiedName("java.lang.String").build(); 23 | TypeInfo arr1 = new ArrayTypeInfo(type1); 24 | TypeInfo arr2 = new ArrayTypeInfo(type1); 25 | assertThat(arr1).isEqualTo(arr2); 26 | } 27 | 28 | @Test 29 | void genericTypeWithDifferentTypeArgumentsShouldNotEqual() { 30 | TypeInfo type1 = ConcreteTypeInfo.builder().qualifiedName("java.util.List").typeArgs(List.of( 31 | ConcreteTypeInfo.builder().qualifiedName("java.lang.String").build() 32 | )).build(); 33 | TypeInfo type2 = ConcreteTypeInfo.builder().qualifiedName("java.util.List").typeArgs(List.of( 34 | ConcreteTypeInfo.builder().qualifiedName("java.lang.Integer").build() 35 | )).build(); 36 | assertThat(type1).isNotEqualTo(type2); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/domain/def/EnumDefTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.def; 2 | 3 | import online.sharedtype.processor.domain.Constants; 4 | import online.sharedtype.processor.domain.component.EnumValueInfo; 5 | import online.sharedtype.processor.domain.type.ConcreteTypeInfo; 6 | import online.sharedtype.processor.domain.value.ValueHolder; 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.api.TestInstance; 9 | 10 | import java.util.Collections; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 15 | final class EnumDefTest { 16 | private final ConcreteTypeInfo enumTypeInfo = ConcreteTypeInfo.builder().simpleName("EnumA").kind(ConcreteTypeInfo.Kind.ENUM).build(); 17 | 18 | @Test 19 | void componentValueTypeIsNullForEmptyEnum() { 20 | EnumDef enumDef = EnumDef.builder() 21 | .qualifiedName("com.github.cuzfrog.EnumA").simpleName("EnumA") 22 | .enumValueInfos(Collections.emptyList()) 23 | .typeInfo(enumTypeInfo) 24 | .build(); 25 | assertThat(enumDef.getComponentValueType()).isEqualTo(enumTypeInfo); 26 | } 27 | 28 | @Test 29 | void componentValueType() { 30 | EnumDef enumDef = EnumDef.builder() 31 | .qualifiedName("com.github.cuzfrog.EnumA").simpleName("EnumA") 32 | .enumValueInfos(Collections.singletonList( 33 | EnumValueInfo.builder().name("Value1") 34 | .value(ValueHolder.ofEnum(enumTypeInfo, "Value1", Constants.BOOLEAN_TYPE_INFO, true)) 35 | .build() 36 | )) 37 | .typeInfo(enumTypeInfo) 38 | .build(); 39 | assertThat(enumDef.getComponentValueType()).isEqualTo(Constants.BOOLEAN_TYPE_INFO); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/domain/type/ReferableTypeInfoTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.type; 2 | 3 | import online.sharedtype.processor.domain.def.ClassDef; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | final class ReferableTypeInfoTest { 9 | @Test 10 | void arrayNestedReferencedType() { 11 | var concreteTypeInfo = ConcreteTypeInfo.builder().build(); 12 | var wrapped = new ArrayTypeInfo(concreteTypeInfo); 13 | 14 | var typeDef = ClassDef.builder().build(); 15 | wrapped.addReferencingType(typeDef); 16 | 17 | assertThat(concreteTypeInfo.referencingTypes()).containsExactly(typeDef); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/domain/value/ValueHolderTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.domain.value; 2 | 3 | import online.sharedtype.processor.domain.type.ConcreteTypeInfo; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static online.sharedtype.processor.domain.Constants.INT_TYPE_INFO; 7 | import static online.sharedtype.processor.domain.Constants.STRING_TYPE_INFO; 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | final class ValueHolderTest { 11 | 12 | @Test 13 | void literalValue() { 14 | assertThat(ValueHolder.of(STRING_TYPE_INFO, "abc").literalValue()).isEqualTo("\"abc\""); 15 | assertThat(ValueHolder.of(INT_TYPE_INFO, 105).literalValue()).isEqualTo("105"); 16 | } 17 | 18 | @Test 19 | void getNestedValue() { 20 | var enumType = ConcreteTypeInfo.builder().qualifiedName("com.github.cuzfrog.EnumA").build(); 21 | var value = ValueHolder.of( 22 | null, 23 | ValueHolder.of( 24 | enumType, 25 | ValueHolder.ofEnum( 26 | enumType, 27 | "ENUM_CONST", 28 | STRING_TYPE_INFO, 29 | ValueHolder.of(STRING_TYPE_INFO, ValueHolder.of(STRING_TYPE_INFO, "Value1")) 30 | ) 31 | ) 32 | ); 33 | assertThat(value.getValue()).isEqualTo("Value1"); 34 | } 35 | 36 | @Test 37 | void equalities() { 38 | assertThat(ValueHolder.of(STRING_TYPE_INFO, "abc")) 39 | .isEqualTo(ValueHolder.of(STRING_TYPE_INFO, ValueHolder.of(STRING_TYPE_INFO,"abc"))); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/parser/type/MappableTypeInfoParserTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.parser.type; 2 | 3 | import online.sharedtype.SharedType; 4 | import online.sharedtype.processor.context.ContextMocks; 5 | import online.sharedtype.processor.domain.type.DateTimeInfo; 6 | import org.junit.jupiter.api.Test; 7 | import org.junitpioneer.jupiter.SetSystemProperty; 8 | 9 | import javax.lang.model.element.TypeElement; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | import static org.mockito.Mockito.mock; 13 | import static org.mockito.Mockito.when; 14 | 15 | @SetSystemProperty(key = "sharedtype.typescript.type-mappings", value = "a.b.MyDateTime:MyString") 16 | @SetSystemProperty(key = "sharedtype.go.type-mappings", value = "a.b.MyDateTime:MyStringG") 17 | @SetSystemProperty(key = "sharedtype.rust.type-mappings", value = "a.b.MyDateTime:MyStringR") 18 | final class MappableTypeInfoParserTest { 19 | private final ContextMocks ctxMocks = new ContextMocks(); 20 | private final TypeInfoParser delegate = mock(TypeInfoParser.class); 21 | private final MappableTypeInfoParser parser = new MappableTypeInfoParser(ctxMocks.getContext(), delegate); 22 | 23 | @Test 24 | void addTypeMappings() { 25 | TypeElement typeElement1 = ctxMocks.typeElement("a.b.MyDateTime").element(); 26 | 27 | DateTimeInfo dateTimeInfo = new DateTimeInfo("a.b.MyDateTime"); 28 | when(delegate.parse(typeElement1.asType(), typeElement1)).thenReturn(dateTimeInfo); 29 | 30 | var resTypeInfo = parser.parse(typeElement1.asType(), typeElement1); 31 | assertThat(resTypeInfo).isSameAs(dateTimeInfo); 32 | assertThat(dateTimeInfo.mappedNameOrDefault(SharedType.TargetType.TYPESCRIPT, "DefaultName")).isEqualTo("MyString"); 33 | assertThat(dateTimeInfo.mappedNameOrDefault(SharedType.TargetType.GO, "DefaultName")).isEqualTo("MyStringG"); 34 | assertThat(dateTimeInfo.mappedNameOrDefault(SharedType.TargetType.RUST, "DefaultName")).isEqualTo("MyStringR"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/parser/value/ConstantValueParserTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.parser.value; 2 | 3 | import online.sharedtype.processor.context.ContextMocks; 4 | import online.sharedtype.processor.domain.Constants; 5 | import online.sharedtype.processor.parser.type.TypeInfoParser; 6 | import org.junit.jupiter.api.Test; 7 | import org.mockito.ArgumentCaptor; 8 | 9 | import javax.lang.model.type.TypeKind; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | import static org.mockito.ArgumentMatchers.any; 13 | import static org.mockito.Mockito.mock; 14 | import static org.mockito.Mockito.verify; 15 | import static org.mockito.Mockito.when; 16 | 17 | final class ConstantValueParserTest { 18 | private final ContextMocks ctxMocks = new ContextMocks(); 19 | private final TypeInfoParser typeInfoParser = mock(TypeInfoParser.class); 20 | private final ValueResolverBackend valueResolverBackend = mock(ValueResolverBackend.class); 21 | private final ConstantValueParser resolver = new ConstantValueParser(ctxMocks.getContext(), typeInfoParser, valueResolverBackend); 22 | 23 | private final ArgumentCaptor valueResolveContextCaptor = ArgumentCaptor.forClass(ValueResolveContext.class); 24 | 25 | @Test 26 | void resolve() { 27 | var typeElement = ctxMocks.typeElement("com.github.cuzfrog.Abc").element(); 28 | var fieldTree = ctxMocks.variableTree().withInitializer(ctxMocks.literalTree(123).getTree()); 29 | var fieldElement = ctxMocks.primitiveVariable("field1", TypeKind.INT) 30 | .withEnclosingElement(typeElement) 31 | .ofTree(fieldTree) 32 | .element(); 33 | 34 | when(typeInfoParser.parse(fieldElement.asType(), typeElement)).thenReturn(Constants.INT_TYPE_INFO); 35 | when(valueResolverBackend.recursivelyResolve(any())).thenReturn(123); 36 | 37 | var value = resolver.resolve(fieldElement, typeElement); 38 | assertThat(value.getValue()).isEqualTo(123); 39 | assertThat(value.getValueType()).isEqualTo(Constants.INT_TYPE_INFO); 40 | 41 | verify(valueResolverBackend).recursivelyResolve(valueResolveContextCaptor.capture()); 42 | var valueResolveContext = valueResolveContextCaptor.getValue(); 43 | assertThat(valueResolveContext.getEnclosingTypeElement()).isEqualTo(typeElement); 44 | assertThat(valueResolveContext.getFieldElement()).isEqualTo(fieldElement); 45 | assertThat(valueResolveContext.getTree()).isEqualTo(fieldTree.getTree()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/resolver/CompositeTypeResolverTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.resolver; 2 | 3 | import online.sharedtype.processor.domain.def.ClassDef; 4 | import online.sharedtype.processor.domain.def.TypeDef; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.extension.ExtendWith; 8 | import org.mockito.Mock; 9 | import org.mockito.junit.jupiter.MockitoExtension; 10 | 11 | import java.util.List; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | import static org.mockito.Mockito.when; 15 | 16 | @ExtendWith(MockitoExtension.class) 17 | final class CompositeTypeResolverTest { 18 | private @Mock TypeResolver delegate1; 19 | private @Mock TypeResolver delegate2; 20 | private CompositeTypeResolver resolver; 21 | 22 | @BeforeEach 23 | void setup() { 24 | resolver = new CompositeTypeResolver(delegate1, delegate2); 25 | } 26 | 27 | @Test 28 | void callInOrder() { 29 | List input = List.of(ClassDef.builder().qualifiedName("a.A").build()); 30 | List out1 = List.of(ClassDef.builder().qualifiedName("a.B").build()); 31 | List out2 = List.of(ClassDef.builder().qualifiedName("a.C").build()); 32 | when(delegate1.resolve(input)).thenReturn(out1); 33 | when(delegate2.resolve(out1)).thenReturn(out2); 34 | 35 | assertThat(resolver.resolve(input)).isSameAs(out2); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/resolver/SubtypeResolverTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.resolver; 2 | 3 | import online.sharedtype.processor.domain.def.ClassDef; 4 | import online.sharedtype.processor.domain.type.ConcreteTypeInfo; 5 | import online.sharedtype.processor.domain.def.TypeDef; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.List; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | final class SubtypeResolverTest { 13 | private final SubtypeResolver resolver = new SubtypeResolver(); 14 | 15 | @Test 16 | void resolve() { 17 | ClassDef superTypeDef1 = ClassDef.builder().simpleName("SuperClassA").build(); 18 | ClassDef superTypeDef2 = ClassDef.builder().simpleName("SuperClassB").build(); 19 | 20 | ConcreteTypeInfo superType1 = ConcreteTypeInfo.builder() 21 | .qualifiedName("com.github.cuzfrog.SuperClassA") 22 | .simpleName("SuperClassA") 23 | .typeDef(superTypeDef1) 24 | .build(); 25 | ConcreteTypeInfo superType2 = ConcreteTypeInfo.builder() 26 | .qualifiedName("com.github.cuzfrog.SuperClassB") 27 | .simpleName("SuperClassB") 28 | .typeDef(superTypeDef2) 29 | .build(); 30 | ClassDef classDef1 = ClassDef.builder() 31 | .simpleName("ClassA") 32 | .qualifiedName("com.github.cuzfrog.ClassA") 33 | .supertypes(List.of(superType1, superType2)) 34 | .build(); 35 | ClassDef classDef2 = ClassDef.builder() 36 | .simpleName("ClassB") 37 | .qualifiedName("com.github.cuzfrog.ClassB") 38 | .supertypes(List.of(superType1)) 39 | .build(); 40 | 41 | List input = List.of(classDef1, classDef2); 42 | var res = resolver.resolve(input); 43 | assertThat(res).isEqualTo(input); 44 | assertThat(superTypeDef1.directSubtypes()).containsExactlyInAnyOrder(classDef1, classDef2); 45 | assertThat(superTypeDef2.directSubtypes()).containsExactly(classDef1); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/writer/adaptor/RustHeaderDataAdaptorTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.adaptor; 2 | 3 | import online.sharedtype.processor.context.ContextMocks; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | 9 | final class RustHeaderDataAdaptorTest { 10 | private final ContextMocks ctxMocks = new ContextMocks(); 11 | private final RustHeaderDataAdaptor adaptor = new RustHeaderDataAdaptor(ctxMocks.getContext()); 12 | 13 | @Test 14 | void allowExpr() { 15 | assertThat(adaptor.allowExpr()).containsPattern("#!\\[allow\\([\\w\\s,]+\\)]"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/writer/adaptor/TypescriptHeaderDataAdaptorTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.adaptor; 2 | 3 | import online.sharedtype.processor.context.ContextMocks; 4 | import org.junit.jupiter.api.Test; 5 | import org.junitpioneer.jupiter.SetSystemProperty; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | 9 | 10 | final class TypescriptHeaderDataAdaptorTest { 11 | @SetSystemProperty(key = "sharedtype.typescript.custom-code-path", value = "src/test/resources/custom-code.ts") 12 | @Test 13 | void readCustomCodeSnippet() { 14 | ContextMocks ctxMocks = new ContextMocks(); 15 | TypescriptHeaderDataAdaptor adaptor = new TypescriptHeaderDataAdaptor(ctxMocks.getContext()); 16 | assertThat(adaptor.customCodeSnippet()).isEqualTo("interface A {}" + System.lineSeparator()); 17 | } 18 | 19 | @SetSystemProperty(key = "sharedtype.typescript.custom-code-path", value = "not-exists.ts") 20 | @Test 21 | void customCodeSnippetNoFile() { 22 | ContextMocks ctxMocks = new ContextMocks(); 23 | TypescriptHeaderDataAdaptor adaptor = new TypescriptHeaderDataAdaptor(ctxMocks.getContext()); 24 | assertThat(adaptor.customCodeSnippet()).isEqualTo(""); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/writer/converter/ConversionUtilsTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.converter; 2 | 3 | import online.sharedtype.processor.domain.def.ClassDef; 4 | import online.sharedtype.processor.domain.type.ConcreteTypeInfo; 5 | import online.sharedtype.processor.domain.component.FieldComponentInfo; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.LinkedHashSet; 9 | import java.util.Set; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | final class ConversionUtilsTest { 14 | @Test 15 | void toSnakeCase() { 16 | assertThat(ConversionUtils.toSnakeCase("camelCase")).isEqualTo("camel_case"); 17 | assertThat(ConversionUtils.toSnakeCase("CamelCase")).isEqualTo("camel_case"); 18 | assertThat(ConversionUtils.toSnakeCase("camelcase")).isEqualTo("camelcase"); 19 | assertThat(ConversionUtils.toSnakeCase("CAMELCASE")).isEqualTo("camelcase"); 20 | assertThat(ConversionUtils.toSnakeCase("camelCase123")).isEqualTo("camel_case123"); 21 | assertThat(ConversionUtils.toSnakeCase("CamelCase123")).isEqualTo("camel_case123"); 22 | assertThat(ConversionUtils.toSnakeCase("camelcase123")).isEqualTo("camelcase123"); 23 | assertThat(ConversionUtils.toSnakeCase("CAMELCASE123")).isEqualTo("camelcase123"); 24 | } 25 | 26 | @Test 27 | void optionalField() { 28 | var field = FieldComponentInfo.builder().build(); 29 | assertThat(ConversionUtils.isOptionalField(field)).isFalse(); 30 | field.setOptional(true); 31 | assertThat(ConversionUtils.isOptionalField(field)).isTrue(); 32 | } 33 | 34 | @Test 35 | void cyclicReferencedTypeFieldIsOptional() { 36 | var typeDef = ClassDef.builder().build(); 37 | var typeInfo = ConcreteTypeInfo.builder().typeDef(typeDef).build(); 38 | var field = FieldComponentInfo.builder().type(typeInfo).build(); 39 | assertThat(ConversionUtils.isOptionalField(field)).isFalse(); 40 | 41 | typeDef.setCyclicReferenced(true); 42 | assertThat(ConversionUtils.isOptionalField(field)).isTrue(); 43 | } 44 | 45 | @Test 46 | void buildRustMacroTraitsExpr() { 47 | assertThat(ConversionUtils.buildRustMacroTraitsExpr(Set.of())).isNull(); 48 | var macros = new LinkedHashSet(2); 49 | macros.add("A"); 50 | macros.add("B"); 51 | assertThat(ConversionUtils.buildRustMacroTraitsExpr(macros)).isEqualTo("#[derive(A, B)]"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/writer/converter/RustMacroTraitsGeneratorImplTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.converter; 2 | 3 | import online.sharedtype.processor.context.Config; 4 | import online.sharedtype.processor.context.ContextMocks; 5 | import online.sharedtype.processor.context.TestUtils; 6 | import online.sharedtype.processor.domain.def.ClassDef; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | import static org.mockito.Mockito.mock; 12 | import static org.mockito.Mockito.when; 13 | 14 | final class RustMacroTraitsGeneratorImplTest { 15 | private final ContextMocks ctxMocks = new ContextMocks(); 16 | private final RustMacroTraitsGenerator generator = new RustMacroTraitsGeneratorImpl(ctxMocks.getContext()); 17 | 18 | 19 | private final ClassDef typeDef = ClassDef.builder().build(); 20 | private final Config config = mock(Config.class); 21 | 22 | @BeforeEach 23 | void setup() { 24 | when(ctxMocks.getContext().getTypeStore().getConfig(typeDef)).thenReturn(config); 25 | } 26 | 27 | @Test 28 | void genMacroTraits() { 29 | var anno = TestUtils.defaultSharedTypeAnnotation(); 30 | when(config.getAnno()).thenReturn(anno); 31 | when(anno.rustMacroTraits()).thenReturn(new String[]{"PartialEq", "Clone"}); 32 | assertThat(generator.generate(typeDef)).containsExactly("Debug", "PartialEq", "Clone"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/writer/converter/type/GoTypeExpressionConverterTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.converter.type; 2 | 3 | import online.sharedtype.SharedType; 4 | import online.sharedtype.processor.context.Config; 5 | import online.sharedtype.processor.context.ContextMocks; 6 | import online.sharedtype.processor.domain.Constants; 7 | import online.sharedtype.processor.domain.def.ClassDef; 8 | import online.sharedtype.processor.domain.type.ArrayTypeInfo; 9 | import online.sharedtype.processor.domain.type.DateTimeInfo; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | import static org.mockito.Mockito.mock; 14 | import static org.mockito.Mockito.when; 15 | 16 | final class GoTypeExpressionConverterTest { 17 | private final ContextMocks ctxMocks = new ContextMocks(); 18 | private final GoTypeExpressionConverter converter = new GoTypeExpressionConverter(ctxMocks.getContext()); 19 | 20 | private final Config config = mock(Config.class); 21 | private final ClassDef contextTypeDef = ClassDef.builder().simpleName("Abc").build(); 22 | 23 | @Test 24 | void typeContract() { 25 | assertThat(converter.typeNameMappings.keySet()).containsAll(Constants.LITERAL_TYPES); 26 | } 27 | 28 | @Test 29 | void convertArrayType() { 30 | String expr = converter.toTypeExpr(new ArrayTypeInfo(Constants.INT_TYPE_INFO), contextTypeDef); 31 | assertThat(expr).isEqualTo("[]int32"); 32 | } 33 | 34 | @Test 35 | void convertObjectType() { 36 | assertThat(converter.toTypeExpr(Constants.OBJECT_TYPE_INFO, contextTypeDef)).isEqualTo("any"); 37 | } 38 | 39 | @Test 40 | void convertDateTimeAndTypeMappings() { 41 | when(config.getGoTargetDatetimeTypeLiteral()).thenReturn("DefaultDateLiteral"); 42 | DateTimeInfo dateTimeInfo = new DateTimeInfo("a.b.A2"); 43 | assertThat(converter.dateTimeTypeExpr(dateTimeInfo, config)).isEqualTo("DefaultDateLiteral"); 44 | dateTimeInfo.addMappedName(SharedType.TargetType.GO, "BBB"); 45 | assertThat(converter.dateTimeTypeExpr(dateTimeInfo, config)).isEqualTo("BBB"); 46 | } 47 | 48 | @Test 49 | void convertPrimitiveType() { 50 | assertThat(converter.toTypeExpr(Constants.INT_TYPE_INFO, contextTypeDef)).isEqualTo("int32"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /processor/src/test/java/online/sharedtype/processor/writer/render/MustacheTemplateRendererTest.java: -------------------------------------------------------------------------------- 1 | package online.sharedtype.processor.writer.render; 2 | 3 | import com.github.mustachejava.Mustache; 4 | import com.github.mustachejava.MustacheFactory; 5 | import online.sharedtype.SharedType; 6 | import online.sharedtype.processor.support.utils.Tuple; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.extension.ExtendWith; 10 | import org.mockito.Mock; 11 | import org.mockito.junit.jupiter.MockitoExtension; 12 | 13 | import java.io.Writer; 14 | import java.util.Collections; 15 | import java.util.HashMap; 16 | 17 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 18 | import static org.mockito.Mockito.verify; 19 | import static org.mockito.Mockito.when; 20 | 21 | @ExtendWith(MockitoExtension.class) 22 | final class MustacheTemplateRendererTest { 23 | private @Mock MustacheFactory mf; 24 | private MustacheTemplateRenderer renderer; 25 | 26 | private @Mock Mustache compiledMustache; 27 | private @Mock Writer writer; 28 | private final Template template = new Template(SharedType.TargetType.GO, "test"); 29 | 30 | @BeforeEach 31 | void setUp() { 32 | renderer = new MustacheTemplateRenderer(mf); 33 | } 34 | 35 | @Test 36 | void loadTemplatesAndRender() { 37 | when(mf.compile("templates/go/test.mustache")).thenReturn(compiledMustache); 38 | 39 | renderer.render(writer, Collections.singletonList(Tuple.of(template, new HashMap<>()))); 40 | verify(compiledMustache).execute(writer, new HashMap<>()); 41 | } 42 | 43 | @Test 44 | void errorIfTemplateNotLoaded() { 45 | assertThatThrownBy(() -> renderer.render(writer, Collections.singletonList(Tuple.of(template, new HashMap<>())))) 46 | .hasMessageContaining("Template not found"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /processor/src/test/resources/custom-code.ts: -------------------------------------------------------------------------------- 1 | interface A {} 2 | -------------------------------------------------------------------------------- /processor/src/test/resources/test-sharedtype-type-mappings.properties: -------------------------------------------------------------------------------- 1 | sharedtype.typescript.type-mappings=MyType1:RenamedType1,\ 2 | MyType2:RenamedType2 3 | -------------------------------------------------------------------------------- /processor/src/test/resources/test-sharedtype-user.properties: -------------------------------------------------------------------------------- 1 | sharedtype.targets=TYPESCRIPT, CONSOLE, 2 | 3 | sharedtype.optional-annotations=a.b.TsOptional 4 | 5 | sharedtype.typescript.java-object-map-type=unknown 6 | -------------------------------------------------------------------------------- /processor/src/test/resources/test-sharedtype-wrong-target.properties: -------------------------------------------------------------------------------- 1 | sharedtype.targets=ENGLISH, 2 | -------------------------------------------------------------------------------- /processor/src/test/resources/test-sharedtype-wrong-ts-optional-field-format.properties: -------------------------------------------------------------------------------- 1 | sharedtype.typescript.optional-field-format=abc,? 2 | -------------------------------------------------------------------------------- /setenv: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | version=$1 3 | 4 | if [ -z "$version" ];then 5 | version="21" 6 | echo "No version provided, use default jdk$version" 7 | fi 8 | 9 | if [[ "$version" == "17" ]];then 10 | export JAVA_HOME=$JAVA17_HOME 11 | elif [[ "$version" == "8" ]];then 12 | export JAVA_HOME=$JAVA8_HOME 13 | elif [[ "$version" == "11" ]];then 14 | export JAVA_HOME=$JAVA11_HOME 15 | elif [[ "$version" == "21" ]];then 16 | export JAVA_HOME=$JAVA21_HOME 17 | else 18 | echo "Unsupported version $version" 19 | return 20 | fi 21 | 22 | export MAVEN_OPTS="-Xmx512m -Xms512m" 23 | export PATH=$JAVA_HOME/bin:$MVND_HOME/bin:$PATH 24 | 25 | if [ -z "$MVND_HOME" ];then 26 | echo "MVND_HOME is not set, mvnd is recommended for local development, see https://github.com/apache/maven-mvnd" 27 | java -version 28 | else 29 | mvnd -version 30 | fi 31 | --------------------------------------------------------------------------------