Skip to content

Commit

Permalink
Add TypeShape.fromTree parser
Browse files Browse the repository at this point in the history
part of #629
  • Loading branch information
pawelprazak committed Aug 29, 2022
1 parent 61db065 commit 868c8db
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 24 deletions.
72 changes: 72 additions & 0 deletions sdk/java/pulumi/src/main/java/com/pulumi/core/TypeShape.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.reflect.TypeToken;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gson.Gson;
import com.pulumi.core.internal.annotations.InternalUse;

import javax.annotation.CheckReturnValue;
Expand All @@ -19,6 +20,7 @@
import java.util.Optional;

import static com.pulumi.core.internal.PulumiCollectors.toSingleton;
import static java.util.Objects.requireNonNullElse;
import static java.util.stream.Collectors.joining;

public final class TypeShape<T> {
Expand Down Expand Up @@ -131,6 +133,12 @@ public <U> boolean isAssignableFrom(TypeShape<U> other) {
return true;
}

/**
* Create a string representations of this {@link TypeShape}.
* The reverse operation is {@link #fromString(String)}.
*
* @return a string representation of this {@link TypeShape}
*/
public String asString() {
var builder = new StringBuilder();
builder.append(getTypeName());
Expand All @@ -150,12 +158,14 @@ public String toString() {
.toString();
}

@InternalUse
public static TypeShape<?> extract(Parameter parameter) {
var parameterClass = parameter.getType();
var token = TypeToken.of(parameter.getParameterizedType());
return extract(parameterClass, token);
}

@InternalUse
private static TypeShape<?> extract(Class<?> type, TypeToken<?> resolverToken) {
var builder = TypeShape.builder(type);
for (var param : type.getTypeParameters()) {
Expand All @@ -165,6 +175,68 @@ private static TypeShape<?> extract(Class<?> type, TypeToken<?> resolverToken) {
return builder.build();
}

/**
* Parse a tree representation of a Java generic type.
*
* @param refs the class references for the generic type
* @param treeJson the generic type tree shape as JSON
* @return a {@link TypeShape} described by the tree representation
*/
@InternalUse
public static TypeShape<?> fromTree(Class<?>[] refs, String treeJson) {
var gson = new Gson();
var tree = gson.fromJson(treeJson, List.class);
return fromTreeInner(refs, treeJson, requireNonNullElse(tree, List.of()));
}

@InternalUse
private static TypeShape<?> fromTreeInner(Class<?>[] refs, String treeJson, @SuppressWarnings("rawtypes") List tree) {
if (refs.length == 0) {
return TypeShape.Empty;
}
// make tree optional for simple cases
if (tree.isEmpty() && refs.length == 1) {
return TypeShape.of(refs[0]);
}
final int rootIndex;
var rootIndexObj = tree.get(0);
if (rootIndexObj instanceof Double) {
rootIndex = ((Double) rootIndexObj).intValue();
} else {
throw new IllegalArgumentException(String.format(
"Expected type (sub) tree root to contain Double, got: '%s'",
rootIndexObj == null ? "null" : rootIndexObj.getClass().getTypeName()
));
}
TypeShape.Builder<?> builder = TypeShape.builder(refs[rootIndex]);
for (int i = 1; i < tree.size(); i++) {
var obj = tree.get(i);
if (obj instanceof List){
//noinspection rawtypes
builder.addParameter(fromTreeInner(refs, treeJson, (List) obj));
continue;
}
if (obj instanceof Double) {
var index = ((Double) obj).intValue();
var maxIndex = refs.length - 1;
if (index > maxIndex) {
throw new IllegalArgumentException(String.format(
"Expected class reference with index '%d' that was referenced in the tree shape: '%s', " +
"but was not present in the refs, maxIndex was '%d'",
index, treeJson, maxIndex
));
}
builder.addParameter(refs[index]);
continue;
}
throw new IllegalArgumentException(String.format(
"Expected type shape tree to contain List or Double, got: '%s'",
obj == null ? "null" : obj.getClass().getTypeName()
));
}
return builder.build();
}

@InternalUse
public com.google.gson.reflect.TypeToken<?> toGSON() {
var paramTokens = this.parameters.stream()
Expand Down
57 changes: 57 additions & 0 deletions sdk/java/pulumi/src/test/java/com/pulumi/core/TypeShapeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.pulumi.core;

import com.google.gson.Gson;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.params.provider.Arguments.arguments;

class TypeShapeTest {

@Test
void testTypeShapeToGSONConversion() {
var token = TypeShape.list(Integer.class).toGSON().getType();
var string = "[1,2,3,4]";

var gson = new Gson();
List<Integer> result = gson.fromJson(string, token);

assertThat(result).isNotNull();
assertThat(result).containsExactly(1, 2, 3, 4);
}

public static Stream<Arguments> testTypeShapeFromTree() {
return Stream.of(
arguments("", new Class<?>[]{}, "java.lang.Void"),
arguments("", new Class<?>[]{Integer.class}, "java.lang.Integer"),
arguments("[0,1]", new Class<?>[]{List.class, Integer.class}, "java.util.List<java.lang.Integer>"),
arguments(
"[0,1,2]", new Class<?>[]{Either.class, Integer.class, String.class},
"com.pulumi.core.Either<java.lang.Integer,java.lang.String>"
),
arguments(
"[0,[0,[0,1]]]", new Class<?>[]{List.class, String.class},
"java.util.List<java.util.List<java.util.List<java.lang.String>>>"
),
arguments(
"[0,[1,2,[3,2]],[1,2,[3,2]]]",
new Class<?>[]{Either.class, Map.class, String.class, List.class},
"com.pulumi.core.Either<java.util.Map<java.lang.String,java.util.List<java.lang.String>>,java.util.Map<java.lang.String,java.util.List<java.lang.String>>>"
)
);
}

@ParameterizedTest
@MethodSource
void testTypeShapeFromTree(String treeJson, Class<?>[] refs, String expected) {
var result = TypeShape.fromTree(refs, treeJson);
assertThat(result.asString()).isEqualTo(expected);
}
}

This file was deleted.

0 comments on commit 868c8db

Please sign in to comment.