Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ jobs:

- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
profile: minimal
toolchain: 1.83.0
- name: Test Rust
working-directory: client-test/rust
Expand Down
11 changes: 11 additions & 0 deletions .run/Template JUnit.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<component name="ProjectRunConfigurationManager">
<configuration default="true" type="JUnit" factoryName="JUnit">
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="VM_PARAMETERS" value="-ea -javaagent:$MAVEN_REPOSITORY$/org/mockito/mockito-core/5.15.2/mockito-core-5.15.2.jar" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[![CI](https://github.com/cuzfrog/sharedtype/actions/workflows/ci.yaml/badge.svg)](https://github.com/cuzfrog/sharedtype/actions/workflows/ci.yaml)
![Maven Central Version](https://img.shields.io/maven-central/v/online.sharedtype/sharedtype?style=plastic)
[![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)

# SharedType - Sharing Java Types made easy
From Java:
Expand Down
4 changes: 2 additions & 2 deletions annotation/src/main/java/online/sharedtype/SharedType.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@
*
* <p>
* <b>Maps:</b>
* (Not supported yet.)
* Key must be String or numeric types. Enum is support given that its value is a literal.
* <ul>
* <li>Typescript: {@code [key: string]: T} where {@code T} can be a reified type.</li>
* <li>Typescript: e.g. {@code Record<string, T>} where {@code T} can be a reified type. If the key is enum, it will be a {@code Partial<Record<?, ?>>}</li>
* </ul>
*
* <p><a href="https://github.com/cuzfrog/SharedType">SharedType Website</a></p>
Expand Down
23 changes: 23 additions & 0 deletions client-test/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ mod types;

#[cfg(test)]
mod tests {
use std::collections::HashMap;

use super::types::*;

#[test]
Expand Down Expand Up @@ -51,4 +53,25 @@ mod tests {
print!("{}", &json);
assert_eq!(&json, r#"{"directRef":{"directRef":null,"arrayRef":[]},"arrayRef":[{"directRef":null,"arrayRef":[]}]}"#);
}

#[test]
fn map_class() {
let mut map_class = MapClass {
mapField: HashMap::new(),
enumKeyMapField: HashMap::new(),
customMapField: HashMap::new(),
nestedMapField: HashMap::new(),
};

map_class.mapField.insert(33, String::from("v1"));
map_class.nestedMapField.insert(String::from("m1"), HashMap::new());

let json = serde_json::to_string(&map_class).unwrap();

let map_class_deser: MapClass = serde_json::from_str(&json).unwrap();
assert_eq!(map_class_deser, map_class);

print!("{}", &json);
assert_eq!(&json, r#"{"mapField":{"33":"v1"},"enumKeyMapField":{},"customMapField":{},"nestedMapField":{"m1":{}}}"#);
}
}
17 changes: 16 additions & 1 deletion client-test/typescript/tests/types.java17.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DependencyClassA, DependencyClassB, DependencyClassC, EnumGalaxy, EnumSize, EnumTShirt, JavaRecord, AnotherJavaClass, RecursiveClass } from "../src/index.java17.js";
import type { DependencyClassA, DependencyClassB, DependencyClassC, EnumGalaxy, EnumSize, EnumTShirt, JavaRecord, AnotherJavaClass, RecursiveClass, MapClass } from "../src/index.java17.js";

export const list1: EnumGalaxy[] = ["Andromeda", "MilkyWay", "Triangulum"];
export const record1: Record<EnumTShirt, number> = {
Expand Down Expand Up @@ -68,3 +68,18 @@ export const recursiveClass: RecursiveClass = {
},
arrayRef: [],
}

export const mapClass: MapClass = {
mapField: {},
enumKeyMapField: {
1: "1",
},
customMapField: {
55: "abc",
},
nestedMapField: {
m1: {
v: 1
}
}
}
11 changes: 9 additions & 2 deletions doc/Development.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Internal types also have javadoc for more information.
#### Project structure
* `annotation` contains the annotation type `@SharedType` as client code compile-time dependency.
* `processor` contains annotation processor logic, put on client's annotation processing path.
* `internal` shared domain types among `processor` and `it`. This is done via build-helper-maven-plugin.
* `internal` shared domain types among `processor` and `it`. This is done via build-helper-maven-plugin. IDE visibility can be controlled by maven profile.
* `it` contains integration tests, which do metadata verification by deserializing metadata objects.
* `java8` contains major types for tests.
* `java17` uses symlink to reuse types in `java8` then does more type checks, e.g. for Java `record`.
Expand All @@ -27,6 +27,13 @@ Optionally mount tmpfs to save your disk by:
```bash
./mount-tmpfs.sh
```

### Maven profiles
* `dev` and `release` - control whether to include test maven modules during build.
* `it` - enable integration test profile. `internal` folder is shared source between `processor` and `it`,
IDE may not able to properly resolve classes in `internal` folder for both modules.
Enable this profile to enable `it` modules in IDE, and disable it when developing against `processor` module.

## Development
### Run test
If you encounter compilation problems with your IDE, delegate compilation to maven.
Expand Down Expand Up @@ -58,7 +65,7 @@ Debug annotation processor by run maven build:
```bash
./mvnd <your args goes here>
```
Then attach your debugger on it.
Then attach your debugger on it. E.g. [IDEA run config](../.run/mvnd.run.xml).

Compile specific classes, e.g.:
```bash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,19 @@ public List<TypeVariableInfo> typeVariables() {
return typeVariables;
}

@Override
public List<TypeInfo> directSupertypes() {
return supertypes;
}

public Set<ConcreteTypeInfo> typeInfoSet() {
return typeInfoSet;
}

public boolean isMapType() {
return typeInfoSet.stream().anyMatch(ConcreteTypeInfo::isMapType);
}

/**
* Register a counterpart typeInfo.
* @see #typeInfoSet
Expand All @@ -71,7 +77,7 @@ public void linkTypeInfo(ConcreteTypeInfo typeInfo) {
public ClassDef reify(List<? extends TypeInfo> typeArgs) {
int l;
if ((l = typeArgs.size()) != typeVariables.size()) {
throw new IllegalArgumentException(String.format("Cannot reify %s against typeArgs: %s", this, typeArgs));
throw new IllegalArgumentException(String.format("Cannot reify %s against typeArgs: %s, type parameter sizes are different.", this, typeArgs));
}
if (l == 0) {
return this;
Expand Down Expand Up @@ -115,7 +121,7 @@ public boolean resolved() {
@Override
public String toString() {
List<String> rows = new ArrayList<>(components.size()+2);
rows.add(String.format("%s%s%s {", simpleName, typeVariablesToString(), supertypesToString()));
rows.add(String.format("%s%s%s {", qualifiedName, typeVariablesToString(), supertypesToString()));
rows.addAll(components.stream().map(f -> String.format(" %s", f)).collect(Collectors.toList()));
rows.add("}");
return String.join(System.lineSeparator(), rows);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;

import javax.annotation.Nullable;
import java.util.Collections;
Expand All @@ -27,6 +28,15 @@ public final class ConcreteTypeInfo implements TypeInfo {
private final String simpleName;
@Builder.Default
private final List<? extends TypeInfo> typeArgs = Collections.emptyList();
/** If this type is an Enum */
@Getter
private final boolean enumType;
/** If this type is map-like. */
@Getter
private final boolean mapType;
/** If this type is defined in global config as base Map type */
@Getter
private final boolean baseMapType;

/**
* Qualified names of types from where this typeInfo is strongly referenced, i.e. as a component type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

import javax.lang.model.type.TypeKind;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* @author Cause Chung
Expand Down Expand Up @@ -35,7 +37,6 @@ public final class Constants {
public static final ConcreteTypeInfo CLASS_TYPE_INFO = ConcreteTypeInfo.ofPredefined("java.lang.Class", "Class");
public static final ConcreteTypeInfo ENUM_TYPE_INFO = ConcreteTypeInfo.ofPredefined("java.lang.Enum", "Enum");
public static final ConcreteTypeInfo OPTIONAL_TYPE_INFO = ConcreteTypeInfo.ofPredefined("java.util.Optional", "Optional");
public static final ConcreteTypeInfo MAP_TYPE_INFO = ConcreteTypeInfo.ofPredefined("java.util.Map", "Map");

public static final Map<TypeKind, ConcreteTypeInfo> PRIMITIVES = new HashMap<>(8);
static {
Expand Down Expand Up @@ -65,8 +66,26 @@ public final class Constants {
PREDEFINED_OBJECT_TYPES.put("java.lang.Class", CLASS_TYPE_INFO);
PREDEFINED_OBJECT_TYPES.put("java.lang.Enum", ENUM_TYPE_INFO);
PREDEFINED_OBJECT_TYPES.put("java.util.Optional", OPTIONAL_TYPE_INFO);
// PREDEFINED_OBJECT_TYPES.put("java.util.Map", MAP_TYPE_INFO); // TODO: Map support
};
}

public static final Set<TypeInfo> STRING_AND_NUMBER_TYPES = new HashSet<>(14);
static {
STRING_AND_NUMBER_TYPES.add(STRING_TYPE_INFO);
STRING_AND_NUMBER_TYPES.add(BYTE_TYPE_INFO);
STRING_AND_NUMBER_TYPES.add(SHORT_TYPE_INFO);
STRING_AND_NUMBER_TYPES.add(INT_TYPE_INFO);
STRING_AND_NUMBER_TYPES.add(LONG_TYPE_INFO);
STRING_AND_NUMBER_TYPES.add(FLOAT_TYPE_INFO);
STRING_AND_NUMBER_TYPES.add(DOUBLE_TYPE_INFO);
STRING_AND_NUMBER_TYPES.add(CHAR_TYPE_INFO);
STRING_AND_NUMBER_TYPES.add(BOXED_BYTE_TYPE_INFO);
STRING_AND_NUMBER_TYPES.add(BOXED_SHORT_TYPE_INFO);
STRING_AND_NUMBER_TYPES.add(BOXED_INT_TYPE_INFO);
STRING_AND_NUMBER_TYPES.add(BOXED_LONG_TYPE_INFO);
STRING_AND_NUMBER_TYPES.add(BOXED_FLOAT_TYPE_INFO);
STRING_AND_NUMBER_TYPES.add(BOXED_DOUBLE_TYPE_INFO);
STRING_AND_NUMBER_TYPES.add(BOXED_CHAR_TYPE_INFO);
}

private Constants() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import lombok.experimental.SuperBuilder;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -37,6 +38,11 @@ public List<EnumValueInfo> components() {
return enumValueInfos;
}

@Override
public List<TypeInfo> directSupertypes() {
return Collections.emptyList();
}

@Override
public boolean resolved() {
return enumValueInfos.stream().allMatch(EnumValueInfo::resolved);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public interface TypeDef extends Serializable {

List<? extends ComponentInfo> components();

List<TypeInfo> directSupertypes();

/**
* @return true if all required types are resolved.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public interface TypeInfo extends Serializable {
*/
boolean resolved();

default boolean isEnumType() {
return false;
}

/**
* Replace type variables with type arguments.
* @param mappings key is a type variable e.g. T
Expand Down
4 changes: 2 additions & 2 deletions it/java17/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<name>SharedType Integration Test Java17</name>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package online.sharedtype.it.java8;

import java.util.HashMap;

final class CustomMap extends HashMap<Integer, String> {

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import lombok.RequiredArgsConstructor;
import online.sharedtype.SharedType;

@SharedType(rustMacroTraits = {"PartialEq", "Eq", "Hash", "serde::Serialize", "serde::Deserialize"})
@RequiredArgsConstructor
public enum EnumSize {
SMALL(1), MEDIUM(2), LARGE(3);
Expand Down
14 changes: 14 additions & 0 deletions it/java8/src/main/java/online/sharedtype/it/java8/MapClass.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package online.sharedtype.it.java8;

import online.sharedtype.SharedType;

import java.util.Map;
import java.util.concurrent.ConcurrentMap;

@SharedType(rustMacroTraits = {"PartialEq", "serde::Serialize", "serde::Deserialize"})
final class MapClass {
private ConcurrentMap<Integer, String> mapField;
private Map<EnumSize, String> enumKeyMapField;
private CustomMap customMapField;
private Map<String, Map<String, Integer>> nestedMapField;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
import online.sharedtype.SharedType;

@SharedType
public class TempClass extends SuperClassA {
public class TempClass {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package online.sharedtype.it;

import online.sharedtype.processor.domain.ClassDef;
import online.sharedtype.processor.domain.ConcreteTypeInfo;
import online.sharedtype.processor.domain.Constants;
import org.junit.jupiter.api.Test;

import static online.sharedtype.it.support.TypeDefDeserializer.deserializeTypeDef;
import static org.assertj.core.api.Assertions.assertThat;

final class MapClassIntegrationTest {
@Test
void mapClass() {
ClassDef classDef = (ClassDef)deserializeTypeDef("online.sharedtype.it.java8.MapClass.ser");
assertThat(classDef.components()).satisfiesExactly(
mapField -> {
assertThat(mapField.name()).isEqualTo("mapField");
ConcreteTypeInfo typeInfo = (ConcreteTypeInfo)mapField.type();
assertThat(typeInfo.isMapType()).isTrue();
assertThat(typeInfo.qualifiedName()).isEqualTo("java.util.concurrent.ConcurrentMap");
assertThat(typeInfo.typeArgs()).hasSize(2).satisfiesExactly(
keyType -> assertThat(keyType).isEqualTo(Constants.BOXED_INT_TYPE_INFO),
valueType -> assertThat(valueType).isEqualTo(Constants.STRING_TYPE_INFO)
);
},
enumKeyMapField -> {
assertThat(enumKeyMapField.name()).isEqualTo("enumKeyMapField");
ConcreteTypeInfo typeInfo = (ConcreteTypeInfo) enumKeyMapField.type();
assertThat(typeInfo.isMapType()).isTrue();
assertThat(typeInfo.qualifiedName()).isEqualTo("java.util.Map");
assertThat(typeInfo.typeArgs()).hasSize(2).satisfiesExactly(
keyType -> {
ConcreteTypeInfo keyTypeInfo = (ConcreteTypeInfo) keyType;
assertThat(keyTypeInfo.qualifiedName()).isEqualTo("online.sharedtype.it.java8.EnumSize");
},
valueType -> assertThat(valueType).isEqualTo(Constants.STRING_TYPE_INFO)
);
},
customMapField -> {
assertThat(customMapField.name()).isEqualTo("customMapField");
ConcreteTypeInfo typeInfo = (ConcreteTypeInfo) customMapField.type();
assertThat(typeInfo.isMapType()).isTrue();
assertThat(typeInfo.qualifiedName()).isEqualTo("online.sharedtype.it.java8.CustomMap");
assertThat(typeInfo.typeArgs()).hasSize(0);
},
nestedMapField -> {
assertThat(nestedMapField.name()).isEqualTo("nestedMapField");
ConcreteTypeInfo typeInfo = (ConcreteTypeInfo) nestedMapField.type();
assertThat(typeInfo.isMapType()).isTrue();
assertThat(typeInfo.qualifiedName()).isEqualTo("java.util.Map");
assertThat(typeInfo.typeArgs()).hasSize(2).satisfiesExactly(
keyType -> assertThat(keyType).isEqualTo(Constants.STRING_TYPE_INFO),
valueType -> {
ConcreteTypeInfo valueTypeInfo = (ConcreteTypeInfo) valueType;
assertThat(valueTypeInfo.isMapType()).isTrue();
assertThat(valueTypeInfo.qualifiedName()).isEqualTo("java.util.Map");
assertThat(valueTypeInfo.typeArgs()).hasSize(2).satisfiesExactly(
nestedKeyType -> assertThat(nestedKeyType).isEqualTo(Constants.STRING_TYPE_INFO),
nestedValueType -> assertThat(nestedValueType).isEqualTo(Constants.BOXED_INT_TYPE_INFO)
);
}
);
}
);
}
}
Loading