diff --git a/src/main/java/com/google/api/generator/gapic/composer/ServiceClientClassComposer.java b/src/main/java/com/google/api/generator/gapic/composer/ServiceClientClassComposer.java index 9d13c1a2bf..e792c624fc 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/ServiceClientClassComposer.java +++ b/src/main/java/com/google/api/generator/gapic/composer/ServiceClientClassComposer.java @@ -20,6 +20,7 @@ import com.google.api.core.BetaApi; import com.google.api.gax.core.BackgroundResource; import com.google.api.gax.longrunning.OperationFuture; +import com.google.api.gax.paging.AbstractPage; import com.google.api.gax.paging.AbstractPagedListResponse; import com.google.api.gax.rpc.BidiStreamingCallable; import com.google.api.gax.rpc.ClientStreamingCallable; @@ -43,6 +44,7 @@ import com.google.api.generator.engine.ast.ReferenceConstructorExpr; import com.google.api.generator.engine.ast.ScopeNode; import com.google.api.generator.engine.ast.Statement; +import com.google.api.generator.engine.ast.SuperObjectValue; import com.google.api.generator.engine.ast.TernaryExpr; import com.google.api.generator.engine.ast.ThisObjectValue; import com.google.api.generator.engine.ast.TypeNode; @@ -75,6 +77,7 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.Generated; @@ -892,37 +895,43 @@ private static List createNestedPagingClasses( if (!method.isPaged()) { continue; } - nestedClasses.add(createNestedRpcPagedResponseClass(method, messageTypes, types)); + // Find the repeated field. + Message methodOutputMessage = messageTypes.get(method.outputType().reference().name()); + TypeNode repeatedResponseType = null; + for (Field field : methodOutputMessage.fields()) { + if (field.isRepeated() && !field.isMap()) { + Reference repeatedGenericRef = field.type().reference().generics().get(0); + repeatedResponseType = TypeNode.withReference(repeatedGenericRef); + break; + } + } + + Preconditions.checkNotNull( + repeatedResponseType, + String.format( + "No repeated field found on message %s for method %s", + methodOutputMessage.name(), method.name())); + + nestedClasses.add( + createNestedRpcPagedResponseClass(method, repeatedResponseType, messageTypes, types)); + nestedClasses.add( + createNestedRpcPageClass(method, repeatedResponseType, messageTypes, types)); } return nestedClasses; } private static ClassDefinition createNestedRpcPagedResponseClass( - Method method, Map messageTypes, Map types) { + Method method, + TypeNode repeatedResponseType, + Map messageTypes, + Map types) { Preconditions.checkState( method.isPaged(), String.format("Expected method %s to be paged", method.name())); String className = String.format("%sPagedResponse", JavaStyle.toUpperCamelCase(method.name())); TypeNode thisClassType = types.get(className); - // Find the repeated field. - Message methodOutputMessage = messageTypes.get(method.outputType().reference().name()); - TypeNode repeatedResponseType = null; - for (Field field : methodOutputMessage.fields()) { - if (field.isRepeated() && !field.isMap()) { - Reference repeatedGenericRef = field.type().reference().generics().get(0); - repeatedResponseType = TypeNode.withReference(repeatedGenericRef); - break; - } - } - - Preconditions.checkNotNull( - repeatedResponseType, - String.format( - "No repeated field found on message %s for method %s", - methodOutputMessage.name(), method.name())); - String upperJavaMethodName = JavaStyle.toUpperCamelCase(method.name()); TypeNode methodPageType = types.get(String.format("%sPage", upperJavaMethodName)); TypeNode classExtendsType = @@ -1108,6 +1117,153 @@ private static ClassDefinition createNestedRpcPagedResponseClass( .build(); } + private static ClassDefinition createNestedRpcPageClass( + Method method, + TypeNode repeatedResponseType, + Map messageTypes, + Map types) { + Preconditions.checkState( + method.isPaged(), String.format("Expected method %s to be paged", method.name())); + + String upperJavaMethodName = JavaStyle.toUpperCamelCase(method.name()); + String className = String.format("%sPage", upperJavaMethodName); + TypeNode classType = types.get(className); + TypeNode classExtendsType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(AbstractPage.class) + .setGenerics( + Arrays.asList( + method.inputType(), + method.outputType(), + repeatedResponseType, + classType) + .stream() + .map(t -> t.reference()) + .collect(Collectors.toList())) + .build()); + + // Private constructor. + VariableExpr contextVarExpr = + VariableExpr.withVariable( + Variable.builder() + .setName("context") + .setType( + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(PageContext.class) + .setGenerics( + Arrays.asList( + method.inputType(), + method.outputType(), + repeatedResponseType) + .stream() + .map(t -> t.reference()) + .collect(Collectors.toList())) + .build())) + .build()); + VariableExpr responseVarExpr = + VariableExpr.withVariable( + Variable.builder().setName("response").setType(method.outputType()).build()); + MethodDefinition privateCtor = + MethodDefinition.constructorBuilder() + .setScope(ScopeNode.PRIVATE) + .setReturnType(classType) + .setArguments( + Arrays.asList(contextVarExpr, responseVarExpr).stream() + .map(e -> e.toBuilder().setIsDecl(true).build()) + .collect(Collectors.toList())) + .setBody( + Arrays.asList( + ExprStatement.withExpr( + ReferenceConstructorExpr.superBuilder() + .setType(classExtendsType) + .setArguments(contextVarExpr, responseVarExpr) + .build()))) + .build(); + + // createEmptyPage method. + ValueExpr nullExpr = ValueExpr.withValue(NullObjectValue.create()); + MethodDefinition createEmptyPageMethod = + MethodDefinition.builder() + .setScope(ScopeNode.PRIVATE) + .setIsStatic(true) + .setReturnType(classType) + .setName("createEmptyPage") + .setReturnExpr( + NewObjectExpr.builder().setType(classType).setArguments(nullExpr, nullExpr).build()) + .build(); + + // createPage method. + MethodDefinition createPageMethod = + MethodDefinition.builder() + .setIsOverride(true) + .setScope(ScopeNode.PROTECTED) + .setReturnType(classType) + .setName("createPage") + .setArguments( + Arrays.asList(contextVarExpr, responseVarExpr).stream() + .map(e -> e.toBuilder().setIsDecl(true).build()) + .collect(Collectors.toList())) + .setReturnExpr( + NewObjectExpr.builder() + .setType(classType) + .setArguments(contextVarExpr, responseVarExpr) + .build()) + .build(); + + // createPageAsync method. + Function futureTypeFn = + t -> + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(ApiFuture.class) + .setGenerics(Arrays.asList(t.reference())) + .build()); + VariableExpr futureResponseVarExpr = + VariableExpr.withVariable( + Variable.builder() + .setName("futureResponse") + .setType(futureTypeFn.apply(method.outputType())) + .build()); + TypeNode futurePageType = futureTypeFn.apply(classType); + MethodDefinition createPageAsyncMethod = + MethodDefinition.builder() + .setIsOverride(true) + .setScope(ScopeNode.PUBLIC) + .setReturnType(futurePageType) + .setName("createPageAsync") + .setArguments( + Arrays.asList(contextVarExpr, futureResponseVarExpr).stream() + .map(e -> e.toBuilder().setIsDecl(true).build()) + .collect(Collectors.toList())) + .setReturnExpr( + MethodInvocationExpr.builder() + .setExprReferenceExpr( + ValueExpr.withValue(SuperObjectValue.withType(classExtendsType))) + .setMethodName("createPageAsync") + .setArguments(contextVarExpr, futureResponseVarExpr) + .setReturnType(futurePageType) + .build()) + .build(); + + // Build the class. + List javaMethods = new ArrayList<>(); + javaMethods.add(privateCtor); + javaMethods.add(createEmptyPageMethod); + javaMethods.add(createPageMethod); + javaMethods.add(createPageAsyncMethod); + + return ClassDefinition.builder() + .setIsNested(true) + .setScope(ScopeNode.PUBLIC) + .setIsStatic(true) + .setExtendsType(classExtendsType) + .setName(className) + .setMethods(javaMethods) + .build(); + } + private static Map createTypes( Service service, Map messageTypes) { Map types = new HashMap<>(); diff --git a/src/test/java/com/google/api/generator/gapic/composer/ServiceClientClassComposerTest.java b/src/test/java/com/google/api/generator/gapic/composer/ServiceClientClassComposerTest.java index 78a4edfd44..3f65027bd1 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/ServiceClientClassComposerTest.java +++ b/src/test/java/com/google/api/generator/gapic/composer/ServiceClientClassComposerTest.java @@ -70,6 +70,7 @@ public void generateServiceClasses() { + "import com.google.api.core.BetaApi;\n" + "import com.google.api.gax.core.BackgroundResource;\n" + "import com.google.api.gax.longrunning.OperationFuture;\n" + + "import com.google.api.gax.paging.AbstractPage;\n" + "import com.google.api.gax.paging.AbstractPagedListResponse;\n" + "import com.google.api.gax.rpc.BidiStreamingCallable;\n" + "import com.google.api.gax.rpc.ClientStreamingCallable;\n" @@ -475,5 +476,34 @@ public void generateServiceClasses() { + " super(page, PagedExpandFixedSizeCollection.createEmptyCollection());\n" + " }\n" + " }\n" + + "\n" + + " public static class PagedExpandPage\n" + + " extends AbstractPage {\n" + + "\n" + + " private PagedExpandPage(\n" + + " PageContext context,\n" + + " PagedExpandResponse response) {\n" + + " super(context, response);\n" + + " }\n" + + "\n" + + " private static PagedExpandPage createEmptyPage() {\n" + + " return new PagedExpandPage(null, null);\n" + + " }\n" + + "\n" + + " @Override\n" + + " protected PagedExpandPage createPage(\n" + + " PageContext context,\n" + + " PagedExpandResponse response) {\n" + + " return new PagedExpandPage(context, response);\n" + + " }\n" + + "\n" + + " @Override\n" + + " public ApiFuture createPageAsync(\n" + + " PageContext context,\n" + + " ApiFuture futureResponse) {\n" + + " return super.createPageAsync(context, futureResponse);\n" + + " }\n" + + " }\n" + "}\n"; }