Convert thrifty-java-codegen to Kotlin (#173)
As part of the prep work for implementing Kotlin codegen, we're converting the compiler to Kotlin. This is the first portion to be converted; thrifty-schema is next, in stages.
This commit is contained in:
Родитель
e1b1f702e0
Коммит
6d8187bd36
|
@ -28,6 +28,8 @@ allprojects {
|
|||
version VERSION
|
||||
|
||||
project.ext {
|
||||
kotlin_version = '1.2.41'
|
||||
|
||||
libraries = [
|
||||
guava: [
|
||||
'com.google.guava:guava:23.0'
|
||||
|
@ -41,6 +43,10 @@ allprojects {
|
|||
'com.squareup:javapoet:1.11.1'
|
||||
],
|
||||
|
||||
kotlin: [
|
||||
"org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
],
|
||||
|
||||
testing: [
|
||||
'junit:junit:4.12',
|
||||
'org.mockito:mockito-core:1.10.19',
|
||||
|
@ -62,6 +68,7 @@ buildscript {
|
|||
|
||||
dependencies {
|
||||
classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.14'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.41"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
GROUP=com.microsoft.thrifty
|
||||
VERSION=0.4.4-SNAPSHOT
|
||||
VERSION=0.5.0-SNAPSHOT
|
||||
|
||||
POM_URL=https://github.com/microsoft/thrifty/
|
||||
|
||||
|
|
|
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-all.zip
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
description = 'Converts Thrifty Schemas into Java source files'
|
||||
|
||||
dependencies {
|
||||
|
@ -26,6 +29,8 @@ dependencies {
|
|||
api libraries.okio
|
||||
api libraries.javaPoet
|
||||
|
||||
api libraries.kotlin
|
||||
|
||||
implementation project(':thrifty-runtime')
|
||||
|
||||
testImplementation libraries.testing
|
||||
|
|
|
@ -1,435 +0,0 @@
|
|||
/*
|
||||
* Thrifty
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the License);
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
||||
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.microsoft.thrifty.gen;
|
||||
|
||||
import com.microsoft.thrifty.schema.BuiltinType;
|
||||
import com.microsoft.thrifty.schema.Constant;
|
||||
import com.microsoft.thrifty.schema.EnumMember;
|
||||
import com.microsoft.thrifty.schema.EnumType;
|
||||
import com.microsoft.thrifty.schema.ListType;
|
||||
import com.microsoft.thrifty.schema.MapType;
|
||||
import com.microsoft.thrifty.schema.NamespaceScope;
|
||||
import com.microsoft.thrifty.schema.Schema;
|
||||
import com.microsoft.thrifty.schema.ServiceType;
|
||||
import com.microsoft.thrifty.schema.SetType;
|
||||
import com.microsoft.thrifty.schema.StructType;
|
||||
import com.microsoft.thrifty.schema.ThriftType;
|
||||
import com.microsoft.thrifty.schema.TypedefType;
|
||||
import com.microsoft.thrifty.schema.parser.ConstValueElement;
|
||||
import com.squareup.javapoet.CodeBlock;
|
||||
import com.squareup.javapoet.NameAllocator;
|
||||
import com.squareup.javapoet.ParameterizedTypeName;
|
||||
import com.squareup.javapoet.TypeName;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
final class ConstantBuilder {
|
||||
private final TypeResolver typeResolver;
|
||||
private final Schema schema;
|
||||
|
||||
ConstantBuilder(TypeResolver typeResolver, Schema schema) {
|
||||
this.typeResolver = typeResolver;
|
||||
this.schema = schema;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void generateFieldInitializer(
|
||||
final CodeBlock.Builder initializer,
|
||||
final NameAllocator allocator,
|
||||
final AtomicInteger scope,
|
||||
final String name,
|
||||
final ThriftType tt,
|
||||
final ConstValueElement value,
|
||||
final boolean needsDeclaration) {
|
||||
|
||||
tt.getTrueType().accept(new SimpleVisitor<Void>() {
|
||||
@Override
|
||||
public Void visitBuiltin(ThriftType builtinType) {
|
||||
CodeBlock init = renderConstValue(initializer, allocator, scope, tt, value);
|
||||
initializer.addStatement("$L = $L", name, init);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitEnum(EnumType userType) {
|
||||
CodeBlock item = renderConstValue(initializer, allocator, scope, tt, value);
|
||||
|
||||
initializer.addStatement("$L = $L", name, item);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitList(ListType listType) {
|
||||
List<ConstValueElement> list = (List<ConstValueElement>) value.value();
|
||||
ThriftType elementType = listType.elementType().getTrueType();
|
||||
TypeName elementTypeName = typeResolver.getJavaClass(elementType);
|
||||
TypeName genericName = ParameterizedTypeName.get(TypeNames.LIST, elementTypeName);
|
||||
TypeName listImplName = typeResolver.listOf(elementTypeName);
|
||||
generateSingleElementCollection(elementType, genericName, listImplName, list);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitSet(SetType setType) {
|
||||
List<ConstValueElement> set = (List<ConstValueElement>) value.value();
|
||||
ThriftType elementType = setType.elementType().getTrueType();
|
||||
TypeName elementTypeName = typeResolver.getJavaClass(elementType);
|
||||
TypeName genericName = ParameterizedTypeName.get(TypeNames.SET, elementTypeName);
|
||||
TypeName setImplName = typeResolver.setOf(elementTypeName);
|
||||
generateSingleElementCollection(elementType, genericName, setImplName, set);
|
||||
return null;
|
||||
}
|
||||
|
||||
private void generateSingleElementCollection(
|
||||
ThriftType elementType,
|
||||
TypeName genericName,
|
||||
TypeName collectionImplName,
|
||||
List<ConstValueElement> values) {
|
||||
if (needsDeclaration) {
|
||||
initializer.addStatement("$T $N = new $T()",
|
||||
genericName, name, collectionImplName);
|
||||
} else {
|
||||
initializer.addStatement("$N = new $T()", name, collectionImplName);
|
||||
}
|
||||
|
||||
for (ConstValueElement element : values) {
|
||||
CodeBlock elementName = renderConstValue(initializer, allocator, scope, elementType, element);
|
||||
initializer.addStatement("$N.add($L)", name, elementName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitMap(MapType mapType) {
|
||||
Map<ConstValueElement, ConstValueElement> map =
|
||||
(Map<ConstValueElement, ConstValueElement>) value.value();
|
||||
ThriftType keyType = mapType.keyType().getTrueType();
|
||||
ThriftType valueType = mapType.valueType().getTrueType();
|
||||
|
||||
TypeName keyTypeName = typeResolver.getJavaClass(keyType);
|
||||
TypeName valueTypeName = typeResolver.getJavaClass(valueType);
|
||||
TypeName mapImplName = typeResolver.mapOf(keyTypeName, valueTypeName);
|
||||
|
||||
if (needsDeclaration) {
|
||||
initializer.addStatement("$T $N = new $T()",
|
||||
ParameterizedTypeName.get(TypeNames.MAP, keyTypeName, valueTypeName),
|
||||
name,
|
||||
mapImplName);
|
||||
} else {
|
||||
initializer.addStatement("$N = new $T()", name, mapImplName);
|
||||
}
|
||||
|
||||
for (Map.Entry<ConstValueElement, ConstValueElement> entry : map.entrySet()) {
|
||||
CodeBlock keyName = renderConstValue(initializer, allocator, scope, keyType, entry.getKey());
|
||||
CodeBlock valueName = renderConstValue(initializer, allocator, scope, valueType, entry.getValue());
|
||||
initializer.addStatement("$N.put($L, $L)", name, keyName, valueName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitStruct(StructType userType) {
|
||||
// TODO: this
|
||||
throw new UnsupportedOperationException("struct-type default values are not yet implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitTypedef(TypedefType typedefType) {
|
||||
throw new AssertionError("Should not be possible!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitService(ServiceType serviceType) {
|
||||
throw new AssertionError("Should not be possible!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitVoid(BuiltinType voidType) {
|
||||
throw new AssertionError("Should not be possible!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CodeBlock renderConstValue(
|
||||
final CodeBlock.Builder block,
|
||||
final NameAllocator allocator,
|
||||
final AtomicInteger scope,
|
||||
final ThriftType type,
|
||||
final ConstValueElement value) {
|
||||
return type.accept(new ConstRenderingVisitor(block, allocator, scope, type, value));
|
||||
}
|
||||
|
||||
private class ConstRenderingVisitor implements ThriftType.Visitor<CodeBlock> {
|
||||
final CodeBlock.Builder block;
|
||||
final NameAllocator allocator;
|
||||
final AtomicInteger scope;
|
||||
final ThriftType type;
|
||||
final ConstValueElement value;
|
||||
|
||||
ConstRenderingVisitor(
|
||||
CodeBlock.Builder block,
|
||||
NameAllocator allocator,
|
||||
AtomicInteger scope,
|
||||
ThriftType type,
|
||||
ConstValueElement value) {
|
||||
this.block = block;
|
||||
this.allocator = allocator;
|
||||
this.scope = scope;
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private Object getNumberLiteral(ConstValueElement element) {
|
||||
if (!element.isInt()) {
|
||||
throw new AssertionError("Expected an int or double, got: " + element.kind());
|
||||
}
|
||||
|
||||
if (element.thriftText().startsWith("0x") || element.thriftText().startsWith("0X")) {
|
||||
return element.thriftText();
|
||||
} else {
|
||||
return element.getAsInt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock visitBool(BuiltinType boolType) {
|
||||
String name;
|
||||
if (value.isIdentifier()
|
||||
&& ("true".equals(value.getAsString()) || "false".equals(value.getAsString()))) {
|
||||
name = "true".equals(value.value()) ? "true" : "false";
|
||||
} else if (value.isInt()) {
|
||||
name = ((Long) value.value()) == 0L ? "false" : "true";
|
||||
} else {
|
||||
return constantOrError("Invalid boolean constant");
|
||||
}
|
||||
|
||||
return CodeBlock.builder().add(name).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock visitByte(BuiltinType byteType) {
|
||||
if (value.isInt()) {
|
||||
return CodeBlock.builder().add("(byte) $L", getNumberLiteral(value)).build();
|
||||
} else {
|
||||
return constantOrError("Invalid byte constant");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock visitI16(BuiltinType i16Type) {
|
||||
if (value.isInt()) {
|
||||
return CodeBlock.builder().add("(short) $L", getNumberLiteral(value)).build();
|
||||
} else {
|
||||
return constantOrError("Invalid i16 constant");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock visitI32(BuiltinType i32Type) {
|
||||
if (value.isInt()) {
|
||||
return CodeBlock.builder().add("$L", getNumberLiteral(value)).build();
|
||||
} else {
|
||||
return constantOrError("Invalid i32 constant");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock visitI64(BuiltinType i64Type) {
|
||||
if (value.isInt()) {
|
||||
return CodeBlock.builder().add("$LL", getNumberLiteral(value)).build();
|
||||
} else {
|
||||
return constantOrError("Invalid i64 constant");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock visitDouble(BuiltinType doubleType) {
|
||||
if (value.isInt() || value.isDouble()) {
|
||||
return CodeBlock.builder().add("(double) $L", value.getAsDouble()).build();
|
||||
} else {
|
||||
return constantOrError("Invalid double constant");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock visitString(BuiltinType stringType) {
|
||||
if (value.isString()) {
|
||||
return CodeBlock.builder().add("$S", value.getAsString()).build();
|
||||
} else {
|
||||
return constantOrError("Invalid string constant");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock visitBinary(BuiltinType binaryType) {
|
||||
throw new UnsupportedOperationException("Binary literals are not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock visitVoid(BuiltinType voidType) {
|
||||
throw new AssertionError("Void literals are meaningless, what are you even doing");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock visitEnum(EnumType enumType) {
|
||||
// TODO(ben): Figure out how to handle const references
|
||||
EnumMember member;
|
||||
try {
|
||||
if (value.kind() == ConstValueElement.Kind.INTEGER) {
|
||||
member = enumType.findMemberById(value.getAsInt());
|
||||
} else if (value.kind() == ConstValueElement.Kind.IDENTIFIER) {
|
||||
String id = value.getAsString();
|
||||
|
||||
// Remove the enum name prefix, assuming it is present
|
||||
int ix = id.lastIndexOf('.');
|
||||
if (ix != -1) {
|
||||
id = id.substring(ix + 1);
|
||||
}
|
||||
|
||||
member = enumType.findMemberByName(id);
|
||||
} else {
|
||||
throw new AssertionError(
|
||||
"Constant value kind " + value.kind() + " is not possibly an enum; validation bug");
|
||||
}
|
||||
} catch (NoSuchElementException e) {
|
||||
throw new IllegalStateException(
|
||||
"No enum member in " + enumType.name() + " with value " + value.value());
|
||||
}
|
||||
|
||||
return CodeBlock.builder()
|
||||
.add("$T.$L", typeResolver.getJavaClass(enumType), member.name())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock visitList(ListType listType) {
|
||||
if (value.isList()) {
|
||||
if (value.getAsList().isEmpty()) {
|
||||
TypeName elementType = typeResolver.getJavaClass(listType.elementType());
|
||||
return CodeBlock.builder()
|
||||
.add("$T.<$T>emptyList()", TypeNames.COLLECTIONS, elementType)
|
||||
.build();
|
||||
}
|
||||
return visitCollection(listType, "list", "unmodifiableList");
|
||||
} else {
|
||||
return constantOrError("Invalid list constant");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock visitSet(SetType setType) {
|
||||
if (value.isList()) { // not a typo; ConstantValueElement.Kind.LIST covers lists and sets.
|
||||
if (value.getAsList().isEmpty()) {
|
||||
TypeName elementType = typeResolver.getJavaClass(setType.elementType());
|
||||
return CodeBlock.builder()
|
||||
.add("$T.<$T>emptySet()", TypeNames.COLLECTIONS, elementType)
|
||||
.build();
|
||||
}
|
||||
return visitCollection(setType, "set", "unmodifiableSet");
|
||||
} else {
|
||||
return constantOrError("Invalid set constant");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock visitMap(MapType mapType) {
|
||||
if (value.isMap()) {
|
||||
if (value.getAsMap().isEmpty()) {
|
||||
TypeName keyType = typeResolver.getJavaClass(mapType.keyType());
|
||||
TypeName valueType = typeResolver.getJavaClass(mapType.valueType());
|
||||
return CodeBlock.builder()
|
||||
.add("$T.<$T, $T>emptyMap()", TypeNames.COLLECTIONS, keyType, valueType)
|
||||
.build();
|
||||
}
|
||||
return visitCollection(mapType, "map", "unmodifiableMap");
|
||||
} else {
|
||||
return constantOrError("Invalid map constant");
|
||||
}
|
||||
}
|
||||
|
||||
private CodeBlock visitCollection(
|
||||
ThriftType type,
|
||||
String tempName,
|
||||
String method) {
|
||||
String name = allocator.newName(tempName, scope.getAndIncrement());
|
||||
generateFieldInitializer(block, allocator, scope, name, type, value, true);
|
||||
return CodeBlock.builder().add("$T.$L($N)", TypeNames.COLLECTIONS, method, name).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock visitStruct(StructType userType) {
|
||||
throw new IllegalStateException("nested structs not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock visitTypedef(TypedefType typedefType) {
|
||||
return typedefType.oldType().accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock visitService(ServiceType serviceType) {
|
||||
throw new IllegalStateException("constants cannot be services");
|
||||
}
|
||||
|
||||
private CodeBlock constantOrError(String error) {
|
||||
error += ": " + value.value() + " at " + value.location();
|
||||
|
||||
if (!value.isIdentifier()) {
|
||||
throw new IllegalStateException(error);
|
||||
}
|
||||
|
||||
ThriftType expectedType = type.getTrueType();
|
||||
|
||||
String name = value.getAsString();
|
||||
String expectedProgram = null;
|
||||
int ix = name.indexOf('.');
|
||||
if (ix != -1) {
|
||||
expectedProgram = name.substring(0, ix);
|
||||
name = name.substring(ix + 1);
|
||||
}
|
||||
|
||||
for (Constant constant : schema.constants()) {
|
||||
if (!constant.name().equals(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ThriftType constantType = constant.type().getTrueType();
|
||||
if (!constantType.equals(expectedType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO(ben): Think of a more systematic way to know what Program owns
|
||||
// a thrift element - pointer-to-parent, probably.
|
||||
String programName = constant.location().getProgramName();
|
||||
if (expectedProgram != null && !programName.equals(expectedProgram)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String packageName = constant.getNamespaceFor(NamespaceScope.JAVA);
|
||||
return CodeBlock.builder().add(packageName + ".Constants." + name).build();
|
||||
}
|
||||
|
||||
throw new IllegalStateException(error);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,303 +0,0 @@
|
|||
/*
|
||||
* Thrifty
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the License);
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
||||
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.microsoft.thrifty.gen;
|
||||
|
||||
import com.microsoft.thrifty.Adapter;
|
||||
import com.microsoft.thrifty.TType;
|
||||
import com.microsoft.thrifty.protocol.Protocol;
|
||||
import com.microsoft.thrifty.schema.BuiltinType;
|
||||
import com.microsoft.thrifty.schema.EnumType;
|
||||
import com.microsoft.thrifty.schema.ListType;
|
||||
import com.microsoft.thrifty.schema.MapType;
|
||||
import com.microsoft.thrifty.schema.NamespaceScope;
|
||||
import com.microsoft.thrifty.schema.ServiceType;
|
||||
import com.microsoft.thrifty.schema.SetType;
|
||||
import com.microsoft.thrifty.schema.StructType;
|
||||
import com.microsoft.thrifty.schema.ThriftType;
|
||||
import com.microsoft.thrifty.schema.TypedefType;
|
||||
import com.microsoft.thrifty.schema.UserType;
|
||||
import com.squareup.javapoet.MethodSpec;
|
||||
import com.squareup.javapoet.NameAllocator;
|
||||
import com.squareup.javapoet.ParameterizedTypeName;
|
||||
import com.squareup.javapoet.TypeName;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
|
||||
/**
|
||||
* Generates Java code to read a field's value from an open Protocol object.
|
||||
*
|
||||
* Assumptions:
|
||||
* We are inside of {@link Adapter#read(Protocol)}. Further, we are
|
||||
* inside of a single case block for a single field. There are variables
|
||||
* in scope named "protocol" and "builder", representing the connection and
|
||||
* the struct builder.
|
||||
*/
|
||||
class GenerateReaderVisitor implements ThriftType.Visitor<Void> {
|
||||
private Deque<String> nameStack = new ArrayDeque<>();
|
||||
private TypeResolver resolver;
|
||||
private NameAllocator nameAllocator;
|
||||
private MethodSpec.Builder read;
|
||||
private String fieldName;
|
||||
private ThriftType fieldType;
|
||||
private int scope;
|
||||
|
||||
GenerateReaderVisitor(TypeResolver resolver, MethodSpec.Builder read, String fieldName, ThriftType fieldType) {
|
||||
this.resolver = resolver;
|
||||
this.read = read;
|
||||
this.fieldName = fieldName;
|
||||
this.fieldType = fieldType;
|
||||
}
|
||||
|
||||
void generate() {
|
||||
byte fieldTypeCode = resolver.getTypeCode(fieldType);
|
||||
if (fieldTypeCode == TType.ENUM) {
|
||||
// Enums are I32 on the wire
|
||||
fieldTypeCode = TType.I32;
|
||||
}
|
||||
String codeName = TypeNames.getTypeCodeName(fieldTypeCode);
|
||||
read.beginControlFlow("if (field.typeId == $T.$L)", TypeNames.TTYPE, codeName);
|
||||
|
||||
nameStack.push("value");
|
||||
fieldType.accept(this);
|
||||
nameStack.pop();
|
||||
|
||||
useReadValue("value");
|
||||
|
||||
read.nextControlFlow("else");
|
||||
read.addStatement("$T.skip(protocol, field.typeId)", TypeNames.PROTO_UTIL);
|
||||
read.endControlFlow();
|
||||
|
||||
}
|
||||
|
||||
protected void useReadValue(String localName) {
|
||||
read.addStatement("builder.$N($N)", fieldName, localName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitBool(BuiltinType boolType) {
|
||||
read.addStatement("$T $N = protocol.readBool()", TypeNames.BOOLEAN.unbox(), nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitByte(BuiltinType bytetype) {
|
||||
read.addStatement("$T $N = protocol.readByte()", TypeNames.BYTE.unbox(), nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitI16(BuiltinType i16Type) {
|
||||
read.addStatement("$T $N = protocol.readI16()", TypeNames.SHORT.unbox(), nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitI32(BuiltinType i32Type) {
|
||||
read.addStatement("$T $N = protocol.readI32()", TypeNames.INTEGER.unbox(), nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitI64(BuiltinType i64Type) {
|
||||
read.addStatement("$T $N = protocol.readI64()", TypeNames.LONG.unbox(), nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitDouble(BuiltinType doubleType) {
|
||||
read.addStatement("$T $N = protocol.readDouble()", TypeNames.DOUBLE.unbox(), nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitString(BuiltinType stringType) {
|
||||
read.addStatement("$T $N = protocol.readString()", TypeNames.STRING, nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitBinary(BuiltinType binaryType) {
|
||||
read.addStatement("$T $N = protocol.readBinary()", TypeNames.BYTE_STRING, nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitVoid(BuiltinType voidType) {
|
||||
throw new AssertionError("Cannot read void");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitEnum(EnumType enumType) {
|
||||
String target = nameStack.peek();
|
||||
String qualifiedJavaName = getFullyQualifiedJavaName(enumType);
|
||||
String intName = "i32_" + scope;
|
||||
|
||||
read.addStatement("int $L = protocol.readI32()", intName);
|
||||
read.addStatement("$1L $2N = $1L.findByValue($3L)", qualifiedJavaName, target, intName);
|
||||
read.beginControlFlow("if ($N == null)", target);
|
||||
read.addStatement(
|
||||
"throw new $1T($2T.PROTOCOL_ERROR, $3S + $4L)",
|
||||
TypeNames.THRIFT_EXCEPTION,
|
||||
TypeNames.THRIFT_EXCEPTION_KIND,
|
||||
"Unexpected value for enum-type " + enumType.name() + ": ",
|
||||
intName);
|
||||
read.endControlFlow();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitList(ListType listType) {
|
||||
initNameAllocator();
|
||||
|
||||
TypeName elementType = resolver.getJavaClass(listType.elementType().getTrueType());
|
||||
TypeName genericListType = ParameterizedTypeName.get(TypeNames.LIST, elementType);
|
||||
TypeName listImplType = resolver.listOf(elementType);
|
||||
|
||||
String listInfo = "listMetadata" + scope;
|
||||
String idx = "i" + scope;
|
||||
String item = "item" + scope;
|
||||
|
||||
read.addStatement("$T $N = protocol.readListBegin()", TypeNames.LIST_META, listInfo);
|
||||
read.addStatement("$T $N = new $T($N.size)", genericListType, nameStack.peek(), listImplType, listInfo);
|
||||
read.beginControlFlow("for (int $1N = 0; $1N < $2N.size; ++$1N)", idx, listInfo);
|
||||
|
||||
++scope;
|
||||
nameStack.push(item);
|
||||
|
||||
listType.elementType().getTrueType().accept(this);
|
||||
|
||||
nameStack.pop();
|
||||
--scope;
|
||||
|
||||
read.addStatement("$N.add($N)", nameStack.peek(), item);
|
||||
read.endControlFlow();
|
||||
read.addStatement("protocol.readListEnd()");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitSet(SetType setType) {
|
||||
initNameAllocator();
|
||||
|
||||
TypeName elementType = resolver.getJavaClass(setType.elementType().getTrueType());
|
||||
TypeName genericSetType = ParameterizedTypeName.get(TypeNames.SET, elementType);
|
||||
TypeName setImplType = resolver.setOf(elementType);
|
||||
|
||||
String setInfo = "setMetadata" + scope;
|
||||
String idx = "i" + scope;
|
||||
String item = "item" + scope;
|
||||
|
||||
read.addStatement("$T $N = protocol.readSetBegin()", TypeNames.SET_META, setInfo);
|
||||
read.addStatement("$T $N = new $T($N.size)", genericSetType, nameStack.peek(), setImplType, setInfo);
|
||||
read.beginControlFlow("for (int $1N = 0; $1N < $2N.size; ++$1N)", idx, setInfo);
|
||||
|
||||
++scope;
|
||||
nameStack.push(item);
|
||||
|
||||
setType.elementType().accept(this);
|
||||
|
||||
nameStack.pop();
|
||||
--scope;
|
||||
|
||||
read.addStatement("$N.add($N)", nameStack.peek(), item);
|
||||
read.endControlFlow();
|
||||
read.addStatement("protocol.readSetEnd()");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitMap(MapType mapType) {
|
||||
initNameAllocator();
|
||||
|
||||
TypeName keyType = resolver.getJavaClass(mapType.keyType().getTrueType());
|
||||
TypeName valueType = resolver.getJavaClass(mapType.valueType().getTrueType());
|
||||
TypeName genericMapType = ParameterizedTypeName.get(TypeNames.MAP, keyType, valueType);
|
||||
TypeName mapImplType = resolver.mapOf(keyType, valueType);
|
||||
|
||||
String mapInfo = "mapMetadata" + scope;
|
||||
String idx = "i" + scope;
|
||||
String key = "key" + scope;
|
||||
String value = "value" + scope;
|
||||
++scope;
|
||||
|
||||
read.addStatement("$T $N = protocol.readMapBegin()", TypeNames.MAP_META, mapInfo);
|
||||
read.addStatement("$T $N = new $T($N.size)", genericMapType, nameStack.peek(), mapImplType, mapInfo);
|
||||
read.beginControlFlow("for (int $1N = 0; $1N < $2N.size; ++$1N)", idx, mapInfo);
|
||||
|
||||
nameStack.push(key);
|
||||
mapType.keyType().accept(this);
|
||||
nameStack.pop();
|
||||
|
||||
nameStack.push(value);
|
||||
mapType.valueType().accept(this);
|
||||
nameStack.pop();
|
||||
|
||||
read.addStatement("$N.put($N, $N)", nameStack.peek(), key, value);
|
||||
|
||||
read.endControlFlow();
|
||||
read.addStatement("protocol.readMapEnd()");
|
||||
|
||||
--scope;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitStruct(StructType userType) {
|
||||
String qualifiedJavaName = getFullyQualifiedJavaName(userType);
|
||||
read.addStatement("$1L $2N = $1L.ADAPTER.read(protocol)", qualifiedJavaName, nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitTypedef(TypedefType typedefType) {
|
||||
// throw AssertionError?
|
||||
typedefType.getTrueType().accept(this);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitService(ServiceType serviceType) {
|
||||
throw new AssertionError("Cannot read a service");
|
||||
}
|
||||
|
||||
private String getFullyQualifiedJavaName(UserType type) {
|
||||
if (type.isBuiltin() || type.isList() || type.isMap() || type.isSet() || type.isTypedef()) {
|
||||
throw new AssertionError("Only user and enum types are supported");
|
||||
}
|
||||
|
||||
String packageName = type.getNamespaceFor(NamespaceScope.JAVA);
|
||||
return packageName + "." + type.name();
|
||||
}
|
||||
|
||||
private void initNameAllocator() {
|
||||
if (nameAllocator == null) {
|
||||
nameAllocator = new NameAllocator();
|
||||
nameAllocator.newName("protocol", "protocol");
|
||||
nameAllocator.newName("builder", "builder");
|
||||
nameAllocator.newName("value", "value");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,285 +0,0 @@
|
|||
/*
|
||||
* Thrifty
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the License);
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
||||
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.microsoft.thrifty.gen;
|
||||
|
||||
import com.microsoft.thrifty.Adapter;
|
||||
import com.microsoft.thrifty.TType;
|
||||
import com.microsoft.thrifty.protocol.Protocol;
|
||||
import com.microsoft.thrifty.schema.BuiltinType;
|
||||
import com.microsoft.thrifty.schema.EnumType;
|
||||
import com.microsoft.thrifty.schema.ListType;
|
||||
import com.microsoft.thrifty.schema.MapType;
|
||||
import com.microsoft.thrifty.schema.NamespaceScope;
|
||||
import com.microsoft.thrifty.schema.ServiceType;
|
||||
import com.microsoft.thrifty.schema.SetType;
|
||||
import com.microsoft.thrifty.schema.StructType;
|
||||
import com.microsoft.thrifty.schema.ThriftType;
|
||||
import com.microsoft.thrifty.schema.TypedefType;
|
||||
import com.squareup.javapoet.MethodSpec;
|
||||
import com.squareup.javapoet.NameAllocator;
|
||||
import com.squareup.javapoet.ParameterizedTypeName;
|
||||
import com.squareup.javapoet.TypeName;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* Generates Java code to write the value of a field in a {@link Adapter#write}
|
||||
* implementation.
|
||||
*
|
||||
* Handles nested values like lists, sets, maps, and user types.
|
||||
*/
|
||||
class GenerateWriterVisitor implements ThriftType.Visitor<Void> {
|
||||
private TypeResolver resolver;
|
||||
|
||||
/**
|
||||
* The write method under construction.
|
||||
*/
|
||||
private MethodSpec.Builder write;
|
||||
|
||||
/**
|
||||
* The name of the {@link Protocol} parameter to {@linkplain #write}.
|
||||
*/
|
||||
private String proto;
|
||||
|
||||
/**
|
||||
* A stack of names, with the topmost name being the one currently
|
||||
* being written/assigned.
|
||||
*/
|
||||
private Deque<String> nameStack = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* A count of nested scopes. Used to prevent name clashes for iterator
|
||||
* and temporary names used when writing nested collections.
|
||||
*/
|
||||
private int scopeLevel;
|
||||
|
||||
private NameAllocator nameAllocator;
|
||||
|
||||
/**
|
||||
* Creates a new GenerateWriterVisitor.
|
||||
*
|
||||
* @param write the {@link Adapter#write} method under construction
|
||||
* @param proto the name of the {@link Protocol} parameter to the write method
|
||||
* @param subject the name of the struct parameter to the write method
|
||||
* @param fieldName the Java name of the field being written
|
||||
*/
|
||||
GenerateWriterVisitor(
|
||||
TypeResolver resolver,
|
||||
MethodSpec.Builder write,
|
||||
String proto,
|
||||
String subject,
|
||||
String fieldName) {
|
||||
this.resolver = resolver;
|
||||
this.write = write;
|
||||
this.proto = proto;
|
||||
nameStack.push(subject + "." + fieldName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitBool(BuiltinType boolType) {
|
||||
write.addStatement("$N.writeBool($L)", proto, nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitByte(BuiltinType byteType) {
|
||||
write.addStatement("$N.writeByte($L)", proto, nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitI16(BuiltinType i16Type) {
|
||||
write.addStatement("$N.writeI16($L)", proto, nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitI32(BuiltinType i32Type) {
|
||||
write.addStatement("$N.writeI32($L)", proto, nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitI64(BuiltinType i64Type) {
|
||||
write.addStatement("$N.writeI64($L)", proto, nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitDouble(BuiltinType doubleType) {
|
||||
write.addStatement("$N.writeDouble($L)", proto, nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitString(BuiltinType stringType) {
|
||||
write.addStatement("$N.writeString($L)", proto, nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitBinary(BuiltinType binaryType) {
|
||||
write.addStatement("$N.writeBinary($L)", proto, nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitVoid(BuiltinType voidType) {
|
||||
throw new AssertionError("Fields cannot be void");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitEnum(EnumType enumType) {
|
||||
write.addStatement("$N.writeI32($L.value)", proto, nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitList(ListType listType) {
|
||||
visitSingleElementCollection(
|
||||
listType.elementType().getTrueType(),
|
||||
"writeListBegin",
|
||||
"writeListEnd");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitSet(SetType setType) {
|
||||
visitSingleElementCollection(
|
||||
setType.elementType().getTrueType(),
|
||||
"writeSetBegin",
|
||||
"writeSetEnd");
|
||||
return null;
|
||||
}
|
||||
|
||||
private void visitSingleElementCollection(ThriftType elementType, String beginMethod, String endMethod) {
|
||||
initCollectionHelpers();
|
||||
String tag = "item" + scopeLevel;
|
||||
String item = nameAllocator.newName(tag, tag);
|
||||
|
||||
TypeName javaClass = resolver.getJavaClass(elementType);
|
||||
byte typeCode = resolver.getTypeCode(elementType);
|
||||
if (typeCode == TType.ENUM) {
|
||||
typeCode = TType.I32;
|
||||
}
|
||||
String typeCodeName = TypeNames.getTypeCodeName(typeCode);
|
||||
|
||||
write.addStatement(
|
||||
"$N.$L($T.$L, $L.size())",
|
||||
proto,
|
||||
beginMethod,
|
||||
TypeNames.TTYPE,
|
||||
typeCodeName,
|
||||
nameStack.peek());
|
||||
|
||||
write.beginControlFlow("for ($T $N : $L)", javaClass, item, nameStack.peek());
|
||||
|
||||
scopeLevel++;
|
||||
nameStack.push(item);
|
||||
elementType.accept(this);
|
||||
nameStack.pop();
|
||||
scopeLevel--;
|
||||
write.endControlFlow();
|
||||
|
||||
write.addStatement("$N.$L()", proto, endMethod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitMap(MapType mapType) {
|
||||
initCollectionHelpers();
|
||||
String entryTag = "entry" + scopeLevel;
|
||||
String keyTag = "key" + scopeLevel;
|
||||
String valueTag = "value" + scopeLevel;
|
||||
|
||||
String entryName = nameAllocator.newName(entryTag, entryTag);
|
||||
String keyName = nameAllocator.newName(keyTag, keyTag);
|
||||
String valueName = nameAllocator.newName(valueTag, valueTag);
|
||||
ThriftType kt = mapType.keyType().getTrueType();
|
||||
ThriftType vt = mapType.valueType().getTrueType();
|
||||
|
||||
byte keyTypeCode = resolver.getTypeCode(kt);
|
||||
byte valTypeCode = resolver.getTypeCode(vt);
|
||||
|
||||
if (keyTypeCode == TType.ENUM) {
|
||||
keyTypeCode = TType.I32;
|
||||
}
|
||||
|
||||
if (valTypeCode == TType.ENUM) {
|
||||
valTypeCode = TType.I32;
|
||||
}
|
||||
|
||||
write.addStatement(
|
||||
"$1N.writeMapBegin($2T.$3L, $2T.$4L, $5L.size())",
|
||||
proto,
|
||||
TypeNames.TTYPE,
|
||||
TypeNames.getTypeCodeName(keyTypeCode),
|
||||
TypeNames.getTypeCodeName(valTypeCode),
|
||||
nameStack.peek());
|
||||
|
||||
TypeName keyTypeName = resolver.getJavaClass(kt);
|
||||
TypeName valueTypeName = resolver.getJavaClass(vt);
|
||||
TypeName entry = ParameterizedTypeName.get(TypeNames.MAP_ENTRY, keyTypeName, valueTypeName);
|
||||
write.beginControlFlow("for ($T $N : $L.entrySet())", entry, entryTag, nameStack.peek());
|
||||
write.addStatement("$T $N = $N.getKey()", keyTypeName, keyName, entryName);
|
||||
write.addStatement("$T $N = $N.getValue()", valueTypeName, valueName, entryName);
|
||||
|
||||
scopeLevel++;
|
||||
nameStack.push(keyName);
|
||||
kt.accept(this);
|
||||
nameStack.pop();
|
||||
|
||||
nameStack.push(valueName);
|
||||
vt.accept(this);
|
||||
nameStack.pop();
|
||||
scopeLevel--;
|
||||
|
||||
write.endControlFlow();
|
||||
write.addStatement("$N.writeMapEnd()", proto);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitStruct(StructType structType) {
|
||||
String javaName = structType.getNamespaceFor(NamespaceScope.JAVA) + "." + structType.name();
|
||||
write.addStatement("$L.ADAPTER.write($N, $L)", javaName, proto, nameStack.peek());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitTypedef(TypedefType typedefType) {
|
||||
typedefType.getTrueType().accept(this);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitService(ServiceType serviceType) {
|
||||
throw new AssertionError("Cannot write a service");
|
||||
}
|
||||
|
||||
private void initCollectionHelpers() {
|
||||
if (nameAllocator == null) {
|
||||
nameAllocator = new NameAllocator();
|
||||
nameAllocator.newName(proto, proto);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,423 +0,0 @@
|
|||
/*
|
||||
* Thrifty
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the License);
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
||||
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.microsoft.thrifty.gen;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.microsoft.thrifty.TType;
|
||||
import com.microsoft.thrifty.ThriftException;
|
||||
import com.microsoft.thrifty.schema.BuiltinType;
|
||||
import com.microsoft.thrifty.schema.Field;
|
||||
import com.microsoft.thrifty.schema.NamespaceScope;
|
||||
import com.microsoft.thrifty.schema.ServiceMethod;
|
||||
import com.microsoft.thrifty.schema.ServiceType;
|
||||
import com.microsoft.thrifty.schema.ThriftType;
|
||||
import com.squareup.javapoet.AnnotationSpec;
|
||||
import com.squareup.javapoet.ClassName;
|
||||
import com.squareup.javapoet.CodeBlock;
|
||||
import com.squareup.javapoet.FieldSpec;
|
||||
import com.squareup.javapoet.MethodSpec;
|
||||
import com.squareup.javapoet.NameAllocator;
|
||||
import com.squareup.javapoet.ParameterSpec;
|
||||
import com.squareup.javapoet.ParameterizedTypeName;
|
||||
import com.squareup.javapoet.TypeName;
|
||||
import com.squareup.javapoet.TypeSpec;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
final class ServiceBuilder {
|
||||
private final TypeResolver typeResolver;
|
||||
private final ConstantBuilder constantBuilder;
|
||||
private final FieldNamer fieldNamer;
|
||||
|
||||
ServiceBuilder(TypeResolver typeResolver, ConstantBuilder constantBuilder, FieldNamer fieldNamer) {
|
||||
this.typeResolver = typeResolver;
|
||||
this.constantBuilder = constantBuilder;
|
||||
this.fieldNamer = fieldNamer;
|
||||
}
|
||||
|
||||
TypeSpec buildServiceInterface(ServiceType service) {
|
||||
TypeSpec.Builder serviceSpec = TypeSpec.interfaceBuilder(service.name())
|
||||
.addModifiers(Modifier.PUBLIC);
|
||||
|
||||
if (!Strings.isNullOrEmpty(service.documentation())) {
|
||||
serviceSpec.addJavadoc(service.documentation());
|
||||
}
|
||||
|
||||
if (service.isDeprecated()) {
|
||||
serviceSpec.addAnnotation(AnnotationSpec.builder(Deprecated.class).build());
|
||||
}
|
||||
|
||||
if (service.extendsService() != null) {
|
||||
ThriftType superType = service.extendsService().getTrueType();
|
||||
TypeName superTypeName = typeResolver.getJavaClass(superType);
|
||||
serviceSpec.addSuperinterface(superTypeName);
|
||||
}
|
||||
|
||||
for (ServiceMethod method : service.methods()) {
|
||||
NameAllocator allocator = new NameAllocator();
|
||||
int tag = 0;
|
||||
|
||||
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(method.name())
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT);
|
||||
|
||||
if (method.hasJavadoc()) {
|
||||
methodBuilder.addJavadoc(method.documentation());
|
||||
}
|
||||
|
||||
for (Field field : method.parameters()) {
|
||||
String fieldName = fieldNamer.getName(field);
|
||||
String name = allocator.newName(fieldName, ++tag);
|
||||
ThriftType paramType = field.type().getTrueType();
|
||||
TypeName paramTypeName = typeResolver.getJavaClass(paramType);
|
||||
|
||||
methodBuilder.addParameter(paramTypeName, name);
|
||||
|
||||
}
|
||||
|
||||
String callbackName = allocator.newName("callback", ++tag);
|
||||
|
||||
ThriftType returnType = method.returnType();
|
||||
TypeName returnTypeName;
|
||||
if (returnType.equals(BuiltinType.VOID)) {
|
||||
returnTypeName = TypeName.VOID.box();
|
||||
} else {
|
||||
returnTypeName = typeResolver.getJavaClass(returnType.getTrueType());
|
||||
}
|
||||
|
||||
TypeName callbackInterfaceName = ParameterizedTypeName.get(
|
||||
TypeNames.SERVICE_CALLBACK, returnTypeName);
|
||||
|
||||
methodBuilder.addParameter(callbackInterfaceName, callbackName);
|
||||
|
||||
serviceSpec.addMethod(methodBuilder.build());
|
||||
}
|
||||
|
||||
return serviceSpec.build();
|
||||
}
|
||||
|
||||
TypeSpec buildService(ServiceType service, TypeSpec serviceInterface) {
|
||||
String packageName = service.getNamespaceFor(NamespaceScope.JAVA);
|
||||
TypeName interfaceTypeName = ClassName.get(packageName, serviceInterface.name);
|
||||
TypeSpec.Builder builder = TypeSpec.classBuilder(service.name() + "Client")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addSuperinterface(interfaceTypeName);
|
||||
|
||||
if (service.extendsService() != null) {
|
||||
ThriftType type = service.extendsService();
|
||||
String typeName = type.name() + "Client";
|
||||
String ns = ((ServiceType) type).getNamespaceFor(NamespaceScope.JAVA);
|
||||
TypeName javaClass = ClassName.get(ns, typeName);
|
||||
builder.superclass(javaClass);
|
||||
} else {
|
||||
builder.superclass(TypeNames.SERVICE_CLIENT_BASE);
|
||||
}
|
||||
|
||||
builder.addMethod(MethodSpec.constructorBuilder()
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addParameter(TypeNames.PROTOCOL, "protocol")
|
||||
.addParameter(TypeNames.SERVICE_CLIENT_LISTENER, "listener")
|
||||
.addStatement("super(protocol, listener)")
|
||||
.build());
|
||||
|
||||
int i = 0;
|
||||
for (MethodSpec methodSpec : serviceInterface.methodSpecs) {
|
||||
ServiceMethod serviceMethod = service.methods().get(i++);
|
||||
TypeSpec call = buildCallSpec(serviceMethod);
|
||||
builder.addType(call);
|
||||
|
||||
MethodSpec.Builder meth = MethodSpec.methodBuilder(methodSpec.name)
|
||||
.addAnnotation(Override.class)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addParameters(methodSpec.parameters)
|
||||
.addExceptions(methodSpec.exceptions);
|
||||
|
||||
CodeBlock.Builder body = CodeBlock.builder()
|
||||
.add("$[this.enqueue(new $N(", call);
|
||||
|
||||
boolean first = true;
|
||||
for (ParameterSpec parameter : methodSpec.parameters) {
|
||||
if (first) {
|
||||
body.add("$N", parameter.name);
|
||||
first = false;
|
||||
} else {
|
||||
body.add(", $N", parameter.name);
|
||||
}
|
||||
}
|
||||
|
||||
body.add("));\n$]");
|
||||
|
||||
meth.addCode(body.build());
|
||||
|
||||
builder.addMethod(meth.build());
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private TypeSpec buildCallSpec(ServiceMethod method) {
|
||||
String name = method.name();
|
||||
if (Character.isLowerCase(name.charAt(0))) {
|
||||
if (name.length() > 1) {
|
||||
name = Character.toUpperCase(name.charAt(0)) + name.substring(1);
|
||||
} else {
|
||||
name = name.toUpperCase(Locale.US);
|
||||
}
|
||||
|
||||
name += "Call";
|
||||
}
|
||||
|
||||
ThriftType returnType = method.returnType();
|
||||
TypeName returnTypeName = returnType.equals(BuiltinType.VOID)
|
||||
? TypeName.VOID.box()
|
||||
: typeResolver.getJavaClass(returnType.getTrueType());
|
||||
TypeName callbackTypeName = ParameterizedTypeName.get(TypeNames.SERVICE_CALLBACK, returnTypeName);
|
||||
TypeName superclass = ParameterizedTypeName.get(TypeNames.SERVICE_METHOD_CALL, returnTypeName);
|
||||
|
||||
boolean hasReturnType = !returnTypeName.equals(TypeName.VOID.box());
|
||||
|
||||
TypeSpec.Builder callBuilder = TypeSpec.classBuilder(name)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
|
||||
.superclass(superclass);
|
||||
|
||||
// Set up fields
|
||||
for (Field field : method.parameters()) {
|
||||
TypeName javaType = typeResolver.getJavaClass(field.type().getTrueType());
|
||||
|
||||
callBuilder.addField(FieldSpec.builder(javaType, fieldNamer.getName(field))
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
|
||||
.build());
|
||||
}
|
||||
|
||||
// Ctor
|
||||
callBuilder.addMethod(buildCallCtor(method, callbackTypeName));
|
||||
|
||||
// Send
|
||||
callBuilder.addMethod(buildSendMethod(method));
|
||||
|
||||
// Receive
|
||||
callBuilder.addMethod(buildReceiveMethod(method, hasReturnType));
|
||||
|
||||
return callBuilder.build();
|
||||
}
|
||||
|
||||
private MethodSpec buildCallCtor(ServiceMethod method, TypeName callbackTypeName) {
|
||||
NameAllocator allocator = new NameAllocator();
|
||||
AtomicInteger scope = new AtomicInteger(0);
|
||||
MethodSpec.Builder ctor = MethodSpec.constructorBuilder()
|
||||
.addStatement(
|
||||
"super($S, $T.$L, callback)",
|
||||
method.name(),
|
||||
TypeNames.TMESSAGE_TYPE,
|
||||
method.oneWay() ? "ONEWAY" : "CALL");
|
||||
|
||||
for (Field field : method.parameters()) {
|
||||
String fieldName = fieldNamer.getName(field);
|
||||
TypeName javaType = typeResolver.getJavaClass(field.type().getTrueType());
|
||||
|
||||
ctor.addParameter(javaType, fieldName);
|
||||
|
||||
if (field.required() && field.defaultValue() == null) {
|
||||
ctor.addStatement("if ($L == null) throw new NullPointerException($S)", fieldName, fieldName);
|
||||
ctor.addStatement("this.$1L = $1L", fieldName);
|
||||
} else if (field.defaultValue() != null) {
|
||||
ctor.beginControlFlow("if ($L != null)", fieldName);
|
||||
ctor.addStatement("this.$1L = $1L", fieldName);
|
||||
ctor.nextControlFlow("else");
|
||||
|
||||
CodeBlock.Builder init = CodeBlock.builder();
|
||||
constantBuilder.generateFieldInitializer(
|
||||
init,
|
||||
allocator,
|
||||
scope,
|
||||
"this." + fieldName,
|
||||
field.type().getTrueType(),
|
||||
field.defaultValue(),
|
||||
false);
|
||||
ctor.addCode(init.build());
|
||||
|
||||
ctor.endControlFlow();
|
||||
} else {
|
||||
ctor.addStatement("this.$1L = $1L", fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
ctor.addParameter(callbackTypeName, "callback");
|
||||
|
||||
return ctor.build();
|
||||
}
|
||||
|
||||
private MethodSpec buildSendMethod(ServiceMethod method) {
|
||||
MethodSpec.Builder send = MethodSpec.methodBuilder("send")
|
||||
.addAnnotation(Override.class)
|
||||
.addModifiers(Modifier.PROTECTED)
|
||||
.addParameter(TypeNames.PROTOCOL, "protocol")
|
||||
.addException(TypeNames.IO_EXCEPTION);
|
||||
|
||||
send.addStatement("protocol.writeStructBegin($S)", "args");
|
||||
|
||||
for (Field field : method.parameters()) {
|
||||
String fieldName = fieldNamer.getName(field);
|
||||
boolean optional = !field.required();
|
||||
final ThriftType tt = field.type().getTrueType();
|
||||
byte typeCode = typeResolver.getTypeCode(tt);
|
||||
|
||||
// Enums are written/read as i32
|
||||
if (typeCode == TType.ENUM) {
|
||||
typeCode = TType.I32;
|
||||
}
|
||||
|
||||
if (optional) {
|
||||
send.beginControlFlow("if (this.$L != null)", fieldName);
|
||||
}
|
||||
|
||||
send.addStatement("protocol.writeFieldBegin($S, $L, $T.$L)",
|
||||
field.name(), // send the Thrift name, not the fieldNamer output
|
||||
field.id(),
|
||||
TypeNames.TTYPE,
|
||||
TypeNames.getTypeCodeName(typeCode));
|
||||
|
||||
tt.accept(new GenerateWriterVisitor(typeResolver, send, "protocol", "this", fieldName));
|
||||
|
||||
send.addStatement("protocol.writeFieldEnd()");
|
||||
|
||||
if (optional) {
|
||||
send.endControlFlow();
|
||||
}
|
||||
}
|
||||
|
||||
send.addStatement("protocol.writeFieldStop()");
|
||||
send.addStatement("protocol.writeStructEnd()");
|
||||
|
||||
return send.build();
|
||||
}
|
||||
|
||||
private MethodSpec buildReceiveMethod(ServiceMethod method, boolean hasReturnType) {
|
||||
final MethodSpec.Builder recv = MethodSpec.methodBuilder("receive")
|
||||
.addAnnotation(Override.class)
|
||||
.addModifiers(Modifier.PROTECTED)
|
||||
.addParameter(TypeNames.PROTOCOL, "protocol")
|
||||
.addParameter(TypeNames.MESSAGE_METADATA, "metadata")
|
||||
.addException(TypeNames.EXCEPTION);
|
||||
|
||||
if (hasReturnType) {
|
||||
TypeName retTypeName = typeResolver.getJavaClass(method.returnType().getTrueType());
|
||||
recv.returns(retTypeName);
|
||||
recv.addStatement("$T result = null", retTypeName);
|
||||
} else {
|
||||
recv.returns(TypeName.VOID.box());
|
||||
}
|
||||
|
||||
for (Field field : method.exceptions()) {
|
||||
String fieldName = fieldNamer.getName(field);
|
||||
TypeName exceptionTypeName = typeResolver.getJavaClass(field.type().getTrueType());
|
||||
recv.addStatement("$T $L = null", exceptionTypeName, fieldName);
|
||||
}
|
||||
|
||||
recv.addStatement("protocol.readStructBegin()")
|
||||
.beginControlFlow("while (true)")
|
||||
.addStatement("$T field = protocol.readFieldBegin()", TypeNames.FIELD_METADATA)
|
||||
.beginControlFlow("if (field.typeId == $T.STOP)", TypeNames.TTYPE)
|
||||
.addStatement("break")
|
||||
.endControlFlow()
|
||||
.beginControlFlow("switch (field.fieldId)");
|
||||
|
||||
if (hasReturnType) {
|
||||
ThriftType type = method.returnType().getTrueType();
|
||||
recv.beginControlFlow("case 0:");
|
||||
|
||||
new GenerateReaderVisitor(typeResolver, recv, "result", type) {
|
||||
@Override
|
||||
protected void useReadValue(String localName) {
|
||||
recv.addStatement("result = $N", localName);
|
||||
}
|
||||
}.generate();
|
||||
|
||||
recv.endControlFlow();
|
||||
recv.addStatement("break");
|
||||
}
|
||||
|
||||
for (Field field : method.exceptions()) {
|
||||
final String fieldName = fieldNamer.getName(field);
|
||||
recv.beginControlFlow("case $L:", field.id());
|
||||
|
||||
new GenerateReaderVisitor(typeResolver, recv, fieldName, field.type().getTrueType()) {
|
||||
@Override
|
||||
protected void useReadValue(String localName) {
|
||||
recv.addStatement("$N = $N", fieldName, localName);
|
||||
}
|
||||
}.generate();
|
||||
|
||||
recv.endControlFlow();
|
||||
recv.addStatement("break");
|
||||
}
|
||||
|
||||
recv.addStatement("default: $T.skip(protocol, field.typeId); break", TypeNames.PROTO_UTIL);
|
||||
recv.endControlFlow(); // end switch
|
||||
recv.addStatement("protocol.readFieldEnd()");
|
||||
recv.endControlFlow(); // end while
|
||||
recv.addStatement("protocol.readStructEnd()");
|
||||
|
||||
boolean isInControlFlow = false;
|
||||
if (hasReturnType) {
|
||||
recv.beginControlFlow("if (result != null)");
|
||||
recv.addStatement("return result");
|
||||
isInControlFlow = true;
|
||||
}
|
||||
|
||||
for (Field field : method.exceptions()) {
|
||||
String fieldName = fieldNamer.getName(field);
|
||||
if (isInControlFlow) {
|
||||
recv.nextControlFlow("else if ($L != null)", fieldName);
|
||||
} else {
|
||||
recv.beginControlFlow("if ($L != null)", fieldName);
|
||||
isInControlFlow = true;
|
||||
}
|
||||
recv.addStatement("throw $L", fieldName);
|
||||
}
|
||||
|
||||
if (isInControlFlow) {
|
||||
recv.nextControlFlow("else");
|
||||
}
|
||||
|
||||
if (hasReturnType) {
|
||||
// In this branch, no return type was received, nor were
|
||||
// any declared exceptions received. This is a failure.
|
||||
recv.addStatement(
|
||||
"throw new $T($T.$L, $S)",
|
||||
TypeNames.THRIFT_EXCEPTION,
|
||||
TypeNames.THRIFT_EXCEPTION_KIND,
|
||||
ThriftException.Kind.MISSING_RESULT.name(),
|
||||
"Missing result");
|
||||
} else {
|
||||
// No return is expected, and no exceptions were received.
|
||||
// Success!
|
||||
recv.addStatement("return null");
|
||||
}
|
||||
|
||||
if (isInControlFlow) {
|
||||
recv.endControlFlow();
|
||||
}
|
||||
|
||||
return recv.build();
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
* Thrifty
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the License);
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
||||
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.microsoft.thrifty.gen;
|
||||
|
||||
import com.microsoft.thrifty.schema.BuiltinType;
|
||||
import com.microsoft.thrifty.schema.ThriftType;
|
||||
|
||||
/**
|
||||
* A visitor that collapses the various builtin visit methods into
|
||||
* one; useful when you don't need to distinguish between specific
|
||||
* builtin types.
|
||||
*
|
||||
* @param <T> the type of value returned by visit methods.
|
||||
*/
|
||||
abstract class SimpleVisitor<T> implements ThriftType.Visitor<T> {
|
||||
|
||||
public abstract T visitBuiltin(ThriftType builtinType);
|
||||
|
||||
@Override
|
||||
public T visitBool(BuiltinType boolType) {
|
||||
return visitBuiltin(boolType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visitByte(BuiltinType byteType) {
|
||||
return visitBuiltin(byteType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visitI16(BuiltinType i16Type) {
|
||||
return visitBuiltin(i16Type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visitI32(BuiltinType i32Type) {
|
||||
return visitBuiltin(i32Type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visitI64(BuiltinType i64Type) {
|
||||
return visitBuiltin(i64Type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visitDouble(BuiltinType doubleType) {
|
||||
return visitBuiltin(doubleType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visitString(BuiltinType stringType) {
|
||||
return visitBuiltin(stringType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visitBinary(BuiltinType binaryType) {
|
||||
return visitBuiltin(binaryType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visitVoid(BuiltinType voidType) {
|
||||
return visitBuiltin(voidType);
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,148 +0,0 @@
|
|||
/*
|
||||
* Thrifty
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the License);
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
||||
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.microsoft.thrifty.gen;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.microsoft.thrifty.Adapter;
|
||||
import com.microsoft.thrifty.StructBuilder;
|
||||
import com.microsoft.thrifty.TType;
|
||||
import com.microsoft.thrifty.ThriftException;
|
||||
import com.microsoft.thrifty.protocol.FieldMetadata;
|
||||
import com.microsoft.thrifty.protocol.ListMetadata;
|
||||
import com.microsoft.thrifty.protocol.MapMetadata;
|
||||
import com.microsoft.thrifty.protocol.MessageMetadata;
|
||||
import com.microsoft.thrifty.protocol.Protocol;
|
||||
import com.microsoft.thrifty.protocol.SetMetadata;
|
||||
import com.microsoft.thrifty.service.AsyncClientBase;
|
||||
import com.microsoft.thrifty.service.MethodCall;
|
||||
import com.microsoft.thrifty.service.ServiceMethodCallback;
|
||||
import com.microsoft.thrifty.service.TMessageType;
|
||||
import com.microsoft.thrifty.util.ObfuscationUtil;
|
||||
import com.microsoft.thrifty.util.ProtocolUtil;
|
||||
import com.squareup.javapoet.ClassName;
|
||||
import com.squareup.javapoet.TypeName;
|
||||
import okio.ByteString;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ProtocolException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* JavaPoet type names used for code generation.
|
||||
*/
|
||||
final class TypeNames {
|
||||
static final TypeName BOOLEAN = ClassName.BOOLEAN.box();
|
||||
static final TypeName BYTE = ClassName.BYTE.box();
|
||||
static final TypeName SHORT = ClassName.SHORT.box();
|
||||
static final TypeName INTEGER = ClassName.INT.box();
|
||||
static final TypeName LONG = ClassName.LONG.box();
|
||||
static final TypeName DOUBLE = ClassName.DOUBLE.box();
|
||||
static final TypeName VOID = ClassName.VOID; // Don't box void, it is only used for methods returning nothing.
|
||||
|
||||
static final ClassName COLLECTIONS = ClassName.get(Collections.class);
|
||||
static final ClassName STRING = ClassName.get(String.class);
|
||||
static final ClassName LIST = ClassName.get(List.class);
|
||||
static final ClassName MAP = ClassName.get(Map.class);
|
||||
static final ClassName MAP_ENTRY = ClassName.get(Map.Entry.class);
|
||||
static final ClassName SET = ClassName.get(Set.class);
|
||||
static final ClassName BYTE_STRING = ClassName.get(ByteString.class);
|
||||
static final ClassName STRING_BUILDER = ClassName.get(StringBuilder.class);
|
||||
|
||||
static final ClassName LIST_META = ClassName.get(ListMetadata.class);
|
||||
static final ClassName SET_META = ClassName.get(SetMetadata.class);
|
||||
static final ClassName MAP_META = ClassName.get(MapMetadata.class);
|
||||
|
||||
static final ClassName PROTOCOL = ClassName.get(Protocol.class);
|
||||
static final ClassName PROTO_UTIL = ClassName.get(ProtocolUtil.class);
|
||||
static final ClassName PROTOCOL_EXCEPTION = ClassName.get(ProtocolException.class);
|
||||
static final ClassName IO_EXCEPTION = ClassName.get(IOException.class);
|
||||
static final ClassName EXCEPTION = ClassName.get(Exception.class);
|
||||
static final ClassName TTYPE = ClassName.get(TType.class);
|
||||
static final ClassName TMESSAGE_TYPE = ClassName.get(TMessageType.class);
|
||||
|
||||
static final ClassName THRIFT_EXCEPTION = ClassName.get(ThriftException.class);
|
||||
static final ClassName THRIFT_EXCEPTION_KIND = ClassName.get(ThriftException.Kind.class);
|
||||
|
||||
static final ClassName BUILDER = ClassName.get(StructBuilder.class);
|
||||
static final ClassName ADAPTER = ClassName.get(Adapter.class);
|
||||
|
||||
static final ClassName FIELD_METADATA = ClassName.get(FieldMetadata.class);
|
||||
static final ClassName MESSAGE_METADATA = ClassName.get(MessageMetadata.class);
|
||||
|
||||
static final ClassName NOT_NULL = ClassName.get("android.support.annotation", "NonNull");
|
||||
static final ClassName NULLABLE = ClassName.get("android.support.annotation", "Nullable");
|
||||
|
||||
static final ClassName SERVICE_CALLBACK = ClassName.get(ServiceMethodCallback.class);
|
||||
static final ClassName SERVICE_CLIENT_BASE = ClassName.get(AsyncClientBase.class);
|
||||
static final ClassName SERVICE_CLIENT_LISTENER = ClassName.get(AsyncClientBase.Listener.class);
|
||||
static final ClassName SERVICE_METHOD_CALL = ClassName.get(MethodCall.class);
|
||||
|
||||
static final ClassName PARCEL = ClassName.get("android.os", "Parcel");
|
||||
static final ClassName PARCELABLE = ClassName.get("android.os", "Parcelable");
|
||||
static final ClassName PARCELABLE_CREATOR = ClassName.get("android.os", "Parcelable", "Creator");
|
||||
|
||||
static final ClassName OBFUSCATION_UTIL = ClassName.get(ObfuscationUtil.class);
|
||||
|
||||
/**
|
||||
* A mapping of {@link TType} constant values to their Java names.
|
||||
*/
|
||||
private static final ImmutableMap<Byte, String> TTYPE_NAMES;
|
||||
|
||||
static {
|
||||
ImmutableMap.Builder<Byte, String> map = ImmutableMap.builder();
|
||||
map.put(TType.BOOL, "BOOL");
|
||||
map.put(TType.BYTE, "BYTE");
|
||||
map.put(TType.I16, "I16");
|
||||
map.put(TType.I32, "I32");
|
||||
map.put(TType.I64, "I64");
|
||||
map.put(TType.DOUBLE, "DOUBLE");
|
||||
map.put(TType.STRING, "STRING");
|
||||
map.put(TType.ENUM, "ENUM");
|
||||
map.put(TType.STRUCT, "STRUCT");
|
||||
map.put(TType.LIST, "LIST");
|
||||
map.put(TType.SET, "SET");
|
||||
map.put(TType.MAP, "MAP");
|
||||
map.put(TType.VOID, "VOID");
|
||||
map.put(TType.STOP, "STOP");
|
||||
TTYPE_NAMES = map.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link TType} member name corresponding to the given type-code.
|
||||
*
|
||||
* @param code the code whose name is needed
|
||||
* @return the TType member name as a string
|
||||
*/
|
||||
static String getTypeCodeName(byte code) {
|
||||
if (!TTYPE_NAMES.containsKey(code)) {
|
||||
throw new NoSuchElementException("not a TType member: " + code);
|
||||
}
|
||||
return TTYPE_NAMES.get(code);
|
||||
}
|
||||
|
||||
private TypeNames() {
|
||||
// no instances
|
||||
}
|
||||
}
|
|
@ -1,278 +0,0 @@
|
|||
/*
|
||||
* Thrifty
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the License);
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
||||
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.microsoft.thrifty.gen;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.microsoft.thrifty.TType;
|
||||
import com.microsoft.thrifty.schema.BuiltinType;
|
||||
import com.microsoft.thrifty.schema.EnumType;
|
||||
import com.microsoft.thrifty.schema.ListType;
|
||||
import com.microsoft.thrifty.schema.MapType;
|
||||
import com.microsoft.thrifty.schema.NamespaceScope;
|
||||
import com.microsoft.thrifty.schema.ServiceType;
|
||||
import com.microsoft.thrifty.schema.SetType;
|
||||
import com.microsoft.thrifty.schema.StructType;
|
||||
import com.microsoft.thrifty.schema.ThriftType;
|
||||
import com.microsoft.thrifty.schema.TypedefType;
|
||||
import com.microsoft.thrifty.schema.UserType;
|
||||
import com.squareup.javapoet.ClassName;
|
||||
import com.squareup.javapoet.ParameterizedTypeName;
|
||||
import com.squareup.javapoet.TypeName;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Utility for getting JavaPoet {@link TypeName} and {@link TType} codes from
|
||||
* {@link ThriftType} instances.
|
||||
*/
|
||||
final class TypeResolver {
|
||||
private final Map<String, ClassName> nameCache = new LinkedHashMap<>();
|
||||
|
||||
private ClassName listClass = ClassName.get(ArrayList.class);
|
||||
private ClassName setClass = ClassName.get(HashSet.class);
|
||||
private ClassName mapClass = ClassName.get(HashMap.class);
|
||||
|
||||
void setListClass(ClassName listClass) {
|
||||
this.listClass = listClass;
|
||||
}
|
||||
|
||||
void setSetClass(ClassName setClass) {
|
||||
this.setClass = setClass;
|
||||
}
|
||||
|
||||
void setMapClass(ClassName mapClass) {
|
||||
this.mapClass = mapClass;
|
||||
}
|
||||
|
||||
byte getTypeCode(ThriftType thriftType) {
|
||||
return thriftType.getTrueType().accept(TYPE_CODE_VISITOR);
|
||||
}
|
||||
|
||||
TypeName getJavaClass(ThriftType thriftType) {
|
||||
return thriftType.accept(typeNameVisitor);
|
||||
}
|
||||
|
||||
ParameterizedTypeName listOf(TypeName elementType) {
|
||||
return ParameterizedTypeName.get(listClass, elementType);
|
||||
}
|
||||
|
||||
ParameterizedTypeName setOf(TypeName elementType) {
|
||||
return ParameterizedTypeName.get(setClass, elementType);
|
||||
}
|
||||
|
||||
ParameterizedTypeName mapOf(TypeName keyType, TypeName valueType) {
|
||||
return ParameterizedTypeName.get(mapClass, keyType, valueType);
|
||||
}
|
||||
|
||||
/**
|
||||
* A Visitor that converts a {@link ThriftType} into a {@link TypeName}.
|
||||
*/
|
||||
private final ThriftType.Visitor<TypeName> typeNameVisitor = new ThriftType.Visitor<TypeName>() {
|
||||
@Override
|
||||
public TypeName visitVoid(BuiltinType voidType) {
|
||||
return TypeNames.VOID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeName visitBool(BuiltinType boolType) {
|
||||
return TypeNames.BOOLEAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeName visitByte(BuiltinType byteType) {
|
||||
return TypeNames.BYTE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeName visitI16(BuiltinType i16Type) {
|
||||
return TypeNames.SHORT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeName visitI32(BuiltinType i32Type) {
|
||||
return TypeNames.INTEGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeName visitI64(BuiltinType i64Type) {
|
||||
return TypeNames.LONG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeName visitDouble(BuiltinType doubleType) {
|
||||
return TypeNames.DOUBLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeName visitString(BuiltinType stringType) {
|
||||
return TypeNames.STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeName visitBinary(BuiltinType binaryType) {
|
||||
return TypeNames.BYTE_STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeName visitEnum(EnumType enumType) {
|
||||
return visitUserType(enumType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeName visitList(ListType listType) {
|
||||
ThriftType elementType = listType.elementType().getTrueType();
|
||||
TypeName elementTypeName = elementType.accept(this);
|
||||
return ParameterizedTypeName.get(TypeNames.LIST, elementTypeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeName visitSet(SetType setType) {
|
||||
ThriftType elementType = setType.elementType().getTrueType();
|
||||
TypeName elementTypeName = elementType.accept(this);
|
||||
return ParameterizedTypeName.get(TypeNames.SET, elementTypeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeName visitMap(MapType mapType) {
|
||||
ThriftType keyType = mapType.keyType().getTrueType();
|
||||
ThriftType valueType = mapType.valueType().getTrueType();
|
||||
|
||||
TypeName keyTypeName = keyType.accept(this);
|
||||
TypeName valueTypeName = valueType.accept(this);
|
||||
return ParameterizedTypeName.get(TypeNames.MAP, keyTypeName, valueTypeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeName visitStruct(StructType structType) {
|
||||
return visitUserType(structType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeName visitTypedef(TypedefType typedefType) {
|
||||
return typedefType.oldType().accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeName visitService(ServiceType serviceType) {
|
||||
return visitUserType(serviceType);
|
||||
}
|
||||
|
||||
private TypeName visitUserType(UserType userType) {
|
||||
String packageName = userType.getNamespaceFor(NamespaceScope.JAVA);
|
||||
if (Strings.isNullOrEmpty(packageName)) {
|
||||
throw new AssertionError("Missing namespace. Did you forget to add 'namespace java'?");
|
||||
}
|
||||
|
||||
String key = packageName + "##" + userType.name();
|
||||
return nameCache.computeIfAbsent(key, k -> ClassName.get(packageName, userType.name()));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A Visitor that converts a {@link ThriftType} into a {@link TType}
|
||||
* constant value.
|
||||
*/
|
||||
private static final ThriftType.Visitor<Byte> TYPE_CODE_VISITOR = new ThriftType.Visitor<Byte>() {
|
||||
@Override
|
||||
public Byte visitBool(BuiltinType boolType) {
|
||||
return TType.BOOL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte visitByte(BuiltinType byteType) {
|
||||
return TType.BYTE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte visitI16(BuiltinType i16Type) {
|
||||
return TType.I16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte visitI32(BuiltinType i32Type) {
|
||||
return TType.I32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte visitI64(BuiltinType i64Type) {
|
||||
return TType.I64;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte visitDouble(BuiltinType doubleType) {
|
||||
return TType.DOUBLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte visitString(BuiltinType stringType) {
|
||||
return TType.STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte visitBinary(BuiltinType binaryType) {
|
||||
return TType.STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte visitVoid(BuiltinType voidType) {
|
||||
return TType.VOID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte visitEnum(EnumType userType) {
|
||||
return TType.ENUM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte visitList(ListType listType) {
|
||||
return TType.LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte visitSet(SetType setType) {
|
||||
return TType.SET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte visitMap(MapType mapType) {
|
||||
return TType.MAP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte visitStruct(StructType userType) {
|
||||
return TType.STRUCT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte visitTypedef(TypedefType typedefType) {
|
||||
return typedefType.oldType().accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte visitService(ServiceType serviceType) {
|
||||
throw new AssertionError("Services do not have typecodes");
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,370 @@
|
|||
/*
|
||||
* Thrifty
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the License);
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
||||
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.microsoft.thrifty.gen
|
||||
|
||||
import com.microsoft.thrifty.schema.BuiltinType
|
||||
import com.microsoft.thrifty.schema.EnumType
|
||||
import com.microsoft.thrifty.schema.ListType
|
||||
import com.microsoft.thrifty.schema.MapType
|
||||
import com.microsoft.thrifty.schema.NamespaceScope
|
||||
import com.microsoft.thrifty.schema.Schema
|
||||
import com.microsoft.thrifty.schema.ServiceType
|
||||
import com.microsoft.thrifty.schema.SetType
|
||||
import com.microsoft.thrifty.schema.StructType
|
||||
import com.microsoft.thrifty.schema.ThriftType
|
||||
import com.microsoft.thrifty.schema.TypedefType
|
||||
import com.microsoft.thrifty.schema.parser.ConstValueElement
|
||||
import com.squareup.javapoet.CodeBlock
|
||||
import com.squareup.javapoet.NameAllocator
|
||||
import com.squareup.javapoet.ParameterizedTypeName
|
||||
import com.squareup.javapoet.TypeName
|
||||
import java.util.NoSuchElementException
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
internal class ConstantBuilder(
|
||||
private val typeResolver: TypeResolver,
|
||||
private val schema: Schema
|
||||
) {
|
||||
|
||||
fun generateFieldInitializer(
|
||||
initializer: CodeBlock.Builder,
|
||||
allocator: NameAllocator,
|
||||
scope: AtomicInteger,
|
||||
name: String,
|
||||
tt: ThriftType,
|
||||
value: ConstValueElement,
|
||||
needsDeclaration: Boolean) {
|
||||
|
||||
tt.trueType.accept(object : SimpleVisitor<Unit>() {
|
||||
override fun visitBuiltin(builtinType: ThriftType) {
|
||||
val init = renderConstValue(initializer, allocator, scope, tt, value)
|
||||
initializer.addStatement("\$L = \$L", name, init)
|
||||
}
|
||||
|
||||
override fun visitEnum(userType: EnumType) {
|
||||
val item = renderConstValue(initializer, allocator, scope, tt, value)
|
||||
|
||||
initializer.addStatement("\$L = \$L", name, item)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun visitList(listType: ListType) {
|
||||
val list = value.value() as List<ConstValueElement>
|
||||
val elementType = listType.elementType().trueType
|
||||
val elementTypeName = typeResolver.getJavaClass(elementType)
|
||||
val genericName = ParameterizedTypeName.get(TypeNames.LIST, elementTypeName)
|
||||
val listImplName = typeResolver.listOf(elementTypeName)
|
||||
generateSingleElementCollection(elementType, genericName, listImplName, list)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun visitSet(setType: SetType) {
|
||||
val set = value.value() as List<ConstValueElement>
|
||||
val elementType = setType.elementType().trueType
|
||||
val elementTypeName = typeResolver.getJavaClass(elementType)
|
||||
val genericName = ParameterizedTypeName.get(TypeNames.SET, elementTypeName)
|
||||
val setImplName = typeResolver.setOf(elementTypeName)
|
||||
generateSingleElementCollection(elementType, genericName, setImplName, set)
|
||||
}
|
||||
|
||||
private fun generateSingleElementCollection(
|
||||
elementType: ThriftType,
|
||||
genericName: TypeName,
|
||||
collectionImplName: TypeName,
|
||||
values: List<ConstValueElement>) {
|
||||
if (needsDeclaration) {
|
||||
initializer.addStatement("\$T \$N = new \$T()",
|
||||
genericName, name, collectionImplName)
|
||||
} else {
|
||||
initializer.addStatement("\$N = new \$T()", name, collectionImplName)
|
||||
}
|
||||
|
||||
for (element in values) {
|
||||
val elementName = renderConstValue(initializer, allocator, scope, elementType, element)
|
||||
initializer.addStatement("\$N.add(\$L)", name, elementName)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun visitMap(mapType: MapType) {
|
||||
val map = value.value() as Map<ConstValueElement, ConstValueElement>
|
||||
val keyType = mapType.keyType().trueType
|
||||
val valueType = mapType.valueType().trueType
|
||||
|
||||
val keyTypeName = typeResolver.getJavaClass(keyType)
|
||||
val valueTypeName = typeResolver.getJavaClass(valueType)
|
||||
val mapImplName = typeResolver.mapOf(keyTypeName, valueTypeName)
|
||||
|
||||
if (needsDeclaration) {
|
||||
initializer.addStatement("\$T \$N = new \$T()",
|
||||
ParameterizedTypeName.get(TypeNames.MAP, keyTypeName, valueTypeName),
|
||||
name,
|
||||
mapImplName)
|
||||
} else {
|
||||
initializer.addStatement("\$N = new \$T()", name, mapImplName)
|
||||
}
|
||||
|
||||
for ((key, value1) in map) {
|
||||
val keyName = renderConstValue(initializer, allocator, scope, keyType, key)
|
||||
val valueName = renderConstValue(initializer, allocator, scope, valueType, value1)
|
||||
initializer.addStatement("\$N.put(\$L, \$L)", name, keyName, valueName)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitStruct(userType: StructType) {
|
||||
// TODO: this
|
||||
throw UnsupportedOperationException("struct-type default values are not yet implemented")
|
||||
}
|
||||
|
||||
override fun visitTypedef(typedefType: TypedefType) {
|
||||
throw AssertionError("Should not be possible!")
|
||||
}
|
||||
|
||||
override fun visitService(serviceType: ServiceType) {
|
||||
throw AssertionError("Should not be possible!")
|
||||
}
|
||||
|
||||
override fun visitVoid(voidType: BuiltinType) {
|
||||
throw AssertionError("Should not be possible!")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun renderConstValue(
|
||||
block: CodeBlock.Builder,
|
||||
allocator: NameAllocator,
|
||||
scope: AtomicInteger,
|
||||
type: ThriftType,
|
||||
value: ConstValueElement): CodeBlock {
|
||||
return type.accept(ConstRenderingVisitor(block, allocator, scope, type, value))
|
||||
}
|
||||
|
||||
private inner class ConstRenderingVisitor(
|
||||
internal val block: CodeBlock.Builder,
|
||||
internal val allocator: NameAllocator,
|
||||
internal val scope: AtomicInteger,
|
||||
internal val type: ThriftType,
|
||||
internal val value: ConstValueElement
|
||||
) : ThriftType.Visitor<CodeBlock> {
|
||||
|
||||
private fun getNumberLiteral(element: ConstValueElement): Any {
|
||||
if (!element.isInt) {
|
||||
throw AssertionError("Expected an int or double, got: " + element.kind())
|
||||
}
|
||||
|
||||
return if (element.thriftText().startsWith("0x") || element.thriftText().startsWith("0X")) {
|
||||
element.thriftText()
|
||||
} else {
|
||||
element.asInt
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitBool(boolType: BuiltinType): CodeBlock {
|
||||
val name: String
|
||||
if (value.isIdentifier && ("true" == value.asString || "false" == value.asString)) {
|
||||
name = if ("true" == value.value()) "true" else "false"
|
||||
} else if (value.isInt) {
|
||||
name = if (value.value() as Long == 0L) "false" else "true"
|
||||
} else {
|
||||
return constantOrError("Invalid boolean constant")
|
||||
}
|
||||
|
||||
return CodeBlock.builder().add(name).build()
|
||||
}
|
||||
|
||||
override fun visitByte(byteType: BuiltinType): CodeBlock {
|
||||
return if (value.isInt) {
|
||||
CodeBlock.builder().add("(byte) \$L", getNumberLiteral(value)).build()
|
||||
} else {
|
||||
constantOrError("Invalid byte constant")
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitI16(i16Type: BuiltinType): CodeBlock {
|
||||
return if (value.isInt) {
|
||||
CodeBlock.builder().add("(short) \$L", getNumberLiteral(value)).build()
|
||||
} else {
|
||||
constantOrError("Invalid i16 constant")
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitI32(i32Type: BuiltinType): CodeBlock {
|
||||
return if (value.isInt) {
|
||||
CodeBlock.builder().add("\$L", getNumberLiteral(value)).build()
|
||||
} else {
|
||||
constantOrError("Invalid i32 constant")
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitI64(i64Type: BuiltinType): CodeBlock {
|
||||
return if (value.isInt) {
|
||||
CodeBlock.builder().add("\$LL", getNumberLiteral(value)).build()
|
||||
} else {
|
||||
constantOrError("Invalid i64 constant")
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitDouble(doubleType: BuiltinType): CodeBlock {
|
||||
return if (value.isInt || value.isDouble) {
|
||||
CodeBlock.builder().add("(double) \$L", value.asDouble).build()
|
||||
} else {
|
||||
constantOrError("Invalid double constant")
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitString(stringType: BuiltinType): CodeBlock {
|
||||
return if (value.isString) {
|
||||
CodeBlock.builder().add("\$S", value.asString).build()
|
||||
} else {
|
||||
constantOrError("Invalid string constant")
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitBinary(binaryType: BuiltinType): CodeBlock {
|
||||
throw UnsupportedOperationException("Binary literals are not supported")
|
||||
}
|
||||
|
||||
override fun visitVoid(voidType: BuiltinType): CodeBlock {
|
||||
throw AssertionError("Void literals are meaningless, what are you even doing")
|
||||
}
|
||||
|
||||
override fun visitEnum(enumType: EnumType): CodeBlock {
|
||||
// TODO(ben): Figure out how to handle const references
|
||||
try {
|
||||
val member = when (value.kind()) {
|
||||
ConstValueElement.Kind.INTEGER ->
|
||||
enumType.findMemberById(value.asInt)
|
||||
|
||||
ConstValueElement.Kind.IDENTIFIER -> {
|
||||
// Remove the enum name prefix, assuming it is present
|
||||
val id = value.asString.split(".").last()
|
||||
|
||||
enumType.findMemberByName(id)
|
||||
}
|
||||
|
||||
else -> throw AssertionError(
|
||||
"Constant value kind " + value.kind() + " is not possibly an enum; validation bug")
|
||||
}
|
||||
|
||||
return CodeBlock.builder()
|
||||
.add("\$T.\$L", typeResolver.getJavaClass(enumType), member.name())
|
||||
.build()
|
||||
|
||||
} catch (e: NoSuchElementException) {
|
||||
throw IllegalStateException(
|
||||
"No enum member in " + enumType.name() + " with value " + value.value())
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitList(listType: ListType): CodeBlock {
|
||||
if (value.isList) {
|
||||
if (value.asList.isEmpty()) {
|
||||
val elementType = typeResolver.getJavaClass(listType.elementType())
|
||||
return CodeBlock.builder()
|
||||
.add("\$T.<\$T>emptyList()", TypeNames.COLLECTIONS, elementType)
|
||||
.build()
|
||||
}
|
||||
return visitCollection(listType, "list", "unmodifiableList")
|
||||
} else {
|
||||
return constantOrError("Invalid list constant")
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitSet(setType: SetType): CodeBlock {
|
||||
if (value.isList) { // not a typo; ConstantValueElement.Kind.LIST covers lists and sets.
|
||||
if (value.asList.isEmpty()) {
|
||||
val elementType = typeResolver.getJavaClass(setType.elementType())
|
||||
return CodeBlock.builder()
|
||||
.add("\$T.<\$T>emptySet()", TypeNames.COLLECTIONS, elementType)
|
||||
.build()
|
||||
}
|
||||
return visitCollection(setType, "set", "unmodifiableSet")
|
||||
} else {
|
||||
return constantOrError("Invalid set constant")
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitMap(mapType: MapType): CodeBlock {
|
||||
if (value.isMap) {
|
||||
if (value.asMap.isEmpty()) {
|
||||
val keyType = typeResolver.getJavaClass(mapType.keyType())
|
||||
val valueType = typeResolver.getJavaClass(mapType.valueType())
|
||||
return CodeBlock.builder()
|
||||
.add("\$T.<\$T, \$T>emptyMap()", TypeNames.COLLECTIONS, keyType, valueType)
|
||||
.build()
|
||||
}
|
||||
return visitCollection(mapType, "map", "unmodifiableMap")
|
||||
} else {
|
||||
return constantOrError("Invalid map constant")
|
||||
}
|
||||
}
|
||||
|
||||
private fun visitCollection(
|
||||
type: ThriftType,
|
||||
tempName: String,
|
||||
method: String): CodeBlock {
|
||||
val name = allocator.newName(tempName, scope.getAndIncrement())
|
||||
generateFieldInitializer(block, allocator, scope, name, type, value, true)
|
||||
return CodeBlock.builder().add("\$T.\$L(\$N)", TypeNames.COLLECTIONS, method, name).build()
|
||||
}
|
||||
|
||||
override fun visitStruct(userType: StructType): CodeBlock {
|
||||
throw IllegalStateException("nested structs not implemented")
|
||||
}
|
||||
|
||||
override fun visitTypedef(typedefType: TypedefType): CodeBlock {
|
||||
return typedefType.oldType().accept(this)
|
||||
}
|
||||
|
||||
override fun visitService(serviceType: ServiceType): CodeBlock {
|
||||
throw IllegalStateException("constants cannot be services")
|
||||
}
|
||||
|
||||
private fun constantOrError(error: String): CodeBlock {
|
||||
val message = "$error: ${value.value()} + at ${value.location()}"
|
||||
|
||||
if (!value.isIdentifier) {
|
||||
throw IllegalStateException(message)
|
||||
}
|
||||
|
||||
val expectedType = type.trueType
|
||||
|
||||
var name = value.asString
|
||||
val ix = name.indexOf('.')
|
||||
var expectedProgram: String? = null
|
||||
if (ix != -1) {
|
||||
expectedProgram = name.substring(0, ix)
|
||||
name = name.substring(ix + 1)
|
||||
}
|
||||
|
||||
// TODO(ben): Think of a more systematic way to know what [Program] owns a thrift element
|
||||
val c = schema.constants()
|
||||
.asSequence()
|
||||
.filter { it.name() == name }
|
||||
.filter { it.type().trueType == expectedType }
|
||||
.filter { expectedProgram == null || it.location().programName == expectedProgram }
|
||||
.firstOrNull() ?: throw IllegalStateException(message)
|
||||
|
||||
val packageName = c.getNamespaceFor(NamespaceScope.JAVA)
|
||||
return CodeBlock.builder().add("$packageName.Constants.$name").build()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,23 +18,17 @@
|
|||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.microsoft.thrifty.gen;
|
||||
package com.microsoft.thrifty.gen
|
||||
|
||||
import com.microsoft.thrifty.schema.Field;
|
||||
import com.microsoft.thrifty.schema.FieldNamingPolicy;
|
||||
import com.microsoft.thrifty.schema.Field
|
||||
import com.microsoft.thrifty.schema.FieldNamingPolicy
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
internal class FieldNamer(
|
||||
private val namingPolicy: FieldNamingPolicy
|
||||
) {
|
||||
private val nameCache = mutableMapOf<Field, String>()
|
||||
|
||||
class FieldNamer {
|
||||
private final Map<Field, String> nameCache = new LinkedHashMap<>();
|
||||
private final FieldNamingPolicy namingPolicy;
|
||||
|
||||
FieldNamer(FieldNamingPolicy namingPolicy) {
|
||||
this.namingPolicy = namingPolicy;
|
||||
}
|
||||
|
||||
String getName(Field field) {
|
||||
return nameCache.computeIfAbsent(field, k -> namingPolicy.apply(field.name()));
|
||||
fun getName(field: Field) = nameCache.computeIfAbsent(field) {
|
||||
namingPolicy.apply(it.name())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* Thrifty
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the License);
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
||||
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.microsoft.thrifty.gen
|
||||
|
||||
import com.microsoft.thrifty.Adapter
|
||||
import com.microsoft.thrifty.schema.BuiltinType
|
||||
import com.microsoft.thrifty.schema.EnumType
|
||||
import com.microsoft.thrifty.schema.ListType
|
||||
import com.microsoft.thrifty.schema.MapType
|
||||
import com.microsoft.thrifty.schema.NamespaceScope
|
||||
import com.microsoft.thrifty.schema.ServiceType
|
||||
import com.microsoft.thrifty.schema.SetType
|
||||
import com.microsoft.thrifty.schema.StructType
|
||||
import com.microsoft.thrifty.schema.ThriftType
|
||||
import com.microsoft.thrifty.schema.TypedefType
|
||||
import com.microsoft.thrifty.schema.UserType
|
||||
import com.squareup.javapoet.MethodSpec
|
||||
import com.squareup.javapoet.ParameterizedTypeName
|
||||
|
||||
import java.util.ArrayDeque
|
||||
import java.util.Deque
|
||||
|
||||
/**
|
||||
* Generates Java code to read a field's value from an open Protocol object.
|
||||
*
|
||||
* Assumptions:
|
||||
* We are inside of [Adapter.read]. Further, we are
|
||||
* inside of a single case block for a single field. There are variables
|
||||
* in scope named "protocol" and "builder", representing the connection and
|
||||
* the struct builder.
|
||||
*/
|
||||
internal open class GenerateReaderVisitor(
|
||||
private val resolver: TypeResolver,
|
||||
private val read: MethodSpec.Builder,
|
||||
private val fieldName: String,
|
||||
private val fieldType: ThriftType
|
||||
) : ThriftType.Visitor<Unit> {
|
||||
|
||||
private val nameStack: Deque<String> = ArrayDeque<String>()
|
||||
|
||||
private var scope: Int = 0
|
||||
|
||||
fun generate() {
|
||||
val fieldTypeCode = resolver.getTypeCode(fieldType)
|
||||
val codeName = TypeNames.getTypeCodeName(fieldTypeCode)
|
||||
read.beginControlFlow("if (field.typeId == \$T.\$L)", TypeNames.TTYPE, codeName)
|
||||
|
||||
nameStack.push("value")
|
||||
fieldType.accept(this)
|
||||
nameStack.pop()
|
||||
|
||||
useReadValue("value")
|
||||
|
||||
read.nextControlFlow("else")
|
||||
read.addStatement("\$T.skip(protocol, field.typeId)", TypeNames.PROTO_UTIL)
|
||||
read.endControlFlow()
|
||||
|
||||
}
|
||||
|
||||
protected open fun useReadValue(localName: String) {
|
||||
read.addStatement("builder.\$N(\$N)", fieldName, localName)
|
||||
}
|
||||
|
||||
override fun visitBool(boolType: BuiltinType) {
|
||||
read.addStatement("\$T \$N = protocol.readBool()", TypeNames.BOOLEAN.unbox(), nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitByte(bytetype: BuiltinType) {
|
||||
read.addStatement("\$T \$N = protocol.readByte()", TypeNames.BYTE.unbox(), nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitI16(i16Type: BuiltinType) {
|
||||
read.addStatement("\$T \$N = protocol.readI16()", TypeNames.SHORT.unbox(), nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitI32(i32Type: BuiltinType) {
|
||||
read.addStatement("\$T \$N = protocol.readI32()", TypeNames.INTEGER.unbox(), nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitI64(i64Type: BuiltinType) {
|
||||
read.addStatement("\$T \$N = protocol.readI64()", TypeNames.LONG.unbox(), nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitDouble(doubleType: BuiltinType) {
|
||||
read.addStatement("\$T \$N = protocol.readDouble()", TypeNames.DOUBLE.unbox(), nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitString(stringType: BuiltinType) {
|
||||
read.addStatement("\$T \$N = protocol.readString()", TypeNames.STRING, nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitBinary(binaryType: BuiltinType) {
|
||||
read.addStatement("\$T \$N = protocol.readBinary()", TypeNames.BYTE_STRING, nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitVoid(voidType: BuiltinType) {
|
||||
throw AssertionError("Cannot read void")
|
||||
}
|
||||
|
||||
override fun visitEnum(enumType: EnumType) {
|
||||
val target = nameStack.peek()
|
||||
val qualifiedJavaName = getFullyQualifiedJavaName(enumType)
|
||||
val intName = "i32_$scope"
|
||||
|
||||
read.addStatement("int \$L = protocol.readI32()", intName)
|
||||
read.addStatement("$1L $2N = $1L.findByValue($3L)", qualifiedJavaName, target, intName)
|
||||
read.beginControlFlow("if (\$N == null)", target!!)
|
||||
read.addStatement(
|
||||
"throw new $1T($2T.PROTOCOL_ERROR, $3S + $4L)",
|
||||
TypeNames.THRIFT_EXCEPTION,
|
||||
TypeNames.THRIFT_EXCEPTION_KIND,
|
||||
"Unexpected value for enum-type " + enumType.name() + ": ",
|
||||
intName)
|
||||
read.endControlFlow()
|
||||
}
|
||||
|
||||
override fun visitList(listType: ListType) {
|
||||
val elementType = resolver.getJavaClass(listType.elementType().trueType)
|
||||
val genericListType = ParameterizedTypeName.get(TypeNames.LIST, elementType)
|
||||
val listImplType = resolver.listOf(elementType)
|
||||
|
||||
val listInfo = "listMetadata$scope"
|
||||
val idx = "i$scope"
|
||||
val item = "item$scope"
|
||||
|
||||
read.addStatement("\$T \$N = protocol.readListBegin()", TypeNames.LIST_META, listInfo)
|
||||
read.addStatement("\$T \$N = new \$T(\$N.size)", genericListType, nameStack.peek(), listImplType, listInfo)
|
||||
read.beginControlFlow("for (int $1N = 0; $1N < $2N.size; ++$1N)", idx, listInfo)
|
||||
|
||||
pushScope {
|
||||
nameStack.push(item)
|
||||
|
||||
listType.elementType().trueType.accept(this)
|
||||
|
||||
nameStack.pop()
|
||||
}
|
||||
|
||||
read.addStatement("\$N.add(\$N)", nameStack.peek(), item)
|
||||
read.endControlFlow()
|
||||
read.addStatement("protocol.readListEnd()")
|
||||
}
|
||||
|
||||
override fun visitSet(setType: SetType) {
|
||||
val elementType = resolver.getJavaClass(setType.elementType().trueType)
|
||||
val genericSetType = ParameterizedTypeName.get(TypeNames.SET, elementType)
|
||||
val setImplType = resolver.setOf(elementType)
|
||||
|
||||
val setInfo = "setMetadata$scope"
|
||||
val idx = "i$scope"
|
||||
val item = "item$scope"
|
||||
|
||||
read.addStatement("\$T \$N = protocol.readSetBegin()", TypeNames.SET_META, setInfo)
|
||||
read.addStatement("\$T \$N = new \$T(\$N.size)", genericSetType, nameStack.peek(), setImplType, setInfo)
|
||||
read.beginControlFlow("for (int $1N = 0; $1N < $2N.size; ++$1N)", idx, setInfo)
|
||||
|
||||
pushScope {
|
||||
nameStack.push(item)
|
||||
|
||||
setType.elementType().accept(this)
|
||||
|
||||
nameStack.pop()
|
||||
}
|
||||
|
||||
read.addStatement("\$N.add(\$N)", nameStack.peek(), item)
|
||||
read.endControlFlow()
|
||||
read.addStatement("protocol.readSetEnd()")
|
||||
}
|
||||
|
||||
override fun visitMap(mapType: MapType) {
|
||||
val keyType = resolver.getJavaClass(mapType.keyType().trueType)
|
||||
val valueType = resolver.getJavaClass(mapType.valueType().trueType)
|
||||
val genericMapType = ParameterizedTypeName.get(TypeNames.MAP, keyType, valueType)
|
||||
val mapImplType = resolver.mapOf(keyType, valueType)
|
||||
|
||||
val mapInfo = "mapMetadata$scope"
|
||||
val idx = "i$scope"
|
||||
val key = "key$scope"
|
||||
val value = "value$scope"
|
||||
|
||||
pushScope {
|
||||
read.addStatement("\$T \$N = protocol.readMapBegin()", TypeNames.MAP_META, mapInfo)
|
||||
read.addStatement("\$T \$N = new \$T(\$N.size)", genericMapType, nameStack.peek(), mapImplType, mapInfo)
|
||||
read.beginControlFlow("for (int $1N = 0; $1N < $2N.size; ++$1N)", idx, mapInfo)
|
||||
|
||||
nameStack.push(key)
|
||||
mapType.keyType().accept(this)
|
||||
nameStack.pop()
|
||||
|
||||
nameStack.push(value)
|
||||
mapType.valueType().accept(this)
|
||||
nameStack.pop()
|
||||
|
||||
read.addStatement("\$N.put(\$N, \$N)", nameStack.peek(), key, value)
|
||||
|
||||
read.endControlFlow()
|
||||
read.addStatement("protocol.readMapEnd()")
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitStruct(userType: StructType) {
|
||||
val qualifiedJavaName = getFullyQualifiedJavaName(userType)
|
||||
read.addStatement("$1L $2N = $1L.ADAPTER.read(protocol)", qualifiedJavaName, nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitTypedef(typedefType: TypedefType) {
|
||||
// throw AssertionError?
|
||||
typedefType.trueType.accept(this)
|
||||
}
|
||||
|
||||
override fun visitService(serviceType: ServiceType) {
|
||||
throw AssertionError("Cannot read a service")
|
||||
}
|
||||
|
||||
private fun getFullyQualifiedJavaName(type: UserType): String {
|
||||
if (type.isBuiltin || type.isList || type.isMap || type.isSet || type.isTypedef) {
|
||||
throw AssertionError("Only user and enum types are supported")
|
||||
}
|
||||
|
||||
val packageName = type.getNamespaceFor(NamespaceScope.JAVA)
|
||||
return packageName + "." + type.name()
|
||||
}
|
||||
|
||||
private inline fun pushScope(fn: () -> Unit) {
|
||||
++scope
|
||||
try {
|
||||
fn()
|
||||
} finally {
|
||||
--scope
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* Thrifty
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the License);
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
||||
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.microsoft.thrifty.gen
|
||||
|
||||
import com.microsoft.thrifty.Adapter
|
||||
import com.microsoft.thrifty.protocol.Protocol
|
||||
import com.microsoft.thrifty.schema.BuiltinType
|
||||
import com.microsoft.thrifty.schema.EnumType
|
||||
import com.microsoft.thrifty.schema.ListType
|
||||
import com.microsoft.thrifty.schema.MapType
|
||||
import com.microsoft.thrifty.schema.NamespaceScope
|
||||
import com.microsoft.thrifty.schema.ServiceType
|
||||
import com.microsoft.thrifty.schema.SetType
|
||||
import com.microsoft.thrifty.schema.StructType
|
||||
import com.microsoft.thrifty.schema.ThriftType
|
||||
import com.microsoft.thrifty.schema.TypedefType
|
||||
import com.squareup.javapoet.MethodSpec
|
||||
import com.squareup.javapoet.NameAllocator
|
||||
import com.squareup.javapoet.ParameterizedTypeName
|
||||
import java.util.Deque
|
||||
|
||||
import java.util.LinkedList
|
||||
|
||||
/**
|
||||
* Generates Java code to write the value of a field in a [Adapter.write]
|
||||
* implementation.
|
||||
*
|
||||
* Handles nested values like lists, sets, maps, and user types.
|
||||
*
|
||||
* @param resolver the [TypeResolver] singleton
|
||||
* @param write the [Adapter.write] method under construction
|
||||
* @param proto the name of the [Protocol] parameter to the write method
|
||||
* @param subject the name of the struct parameter to the write method
|
||||
* @param fieldName the Java name of the field being written
|
||||
*/
|
||||
internal class GenerateWriterVisitor(
|
||||
private val resolver: TypeResolver,
|
||||
private val write: MethodSpec.Builder,
|
||||
private val proto: String,
|
||||
subject: String,
|
||||
fieldName: String
|
||||
) : ThriftType.Visitor<Unit> {
|
||||
|
||||
/**
|
||||
* A stack of names, with the topmost name being the one currently
|
||||
* being written/assigned.
|
||||
*/
|
||||
private val nameStack: Deque<String> = LinkedList<String>().apply {
|
||||
push("$subject.$fieldName")
|
||||
}
|
||||
|
||||
/**
|
||||
* A count of nested scopes. Used to prevent name clashes for iterator
|
||||
* and temporary names used when writing nested collections.
|
||||
*/
|
||||
private var scopeLevel: Int = 0
|
||||
|
||||
private val nameAllocator = NameAllocator().apply {
|
||||
newName(proto, proto)
|
||||
}
|
||||
|
||||
override fun visitBool(boolType: BuiltinType) {
|
||||
write.addStatement("\$N.writeBool(\$L)", proto, nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitByte(byteType: BuiltinType) {
|
||||
write.addStatement("\$N.writeByte(\$L)", proto, nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitI16(i16Type: BuiltinType) {
|
||||
write.addStatement("\$N.writeI16(\$L)", proto, nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitI32(i32Type: BuiltinType) {
|
||||
write.addStatement("\$N.writeI32(\$L)", proto, nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitI64(i64Type: BuiltinType) {
|
||||
write.addStatement("\$N.writeI64(\$L)", proto, nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitDouble(doubleType: BuiltinType) {
|
||||
write.addStatement("\$N.writeDouble(\$L)", proto, nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitString(stringType: BuiltinType) {
|
||||
write.addStatement("\$N.writeString(\$L)", proto, nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitBinary(binaryType: BuiltinType) {
|
||||
write.addStatement("\$N.writeBinary(\$L)", proto, nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitVoid(voidType: BuiltinType) {
|
||||
throw AssertionError("Fields cannot be void")
|
||||
}
|
||||
|
||||
override fun visitEnum(enumType: EnumType) {
|
||||
write.addStatement("\$N.writeI32(\$L.value)", proto, nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitList(listType: ListType) {
|
||||
visitSingleElementCollection(
|
||||
listType.elementType().trueType,
|
||||
"writeListBegin",
|
||||
"writeListEnd")
|
||||
}
|
||||
|
||||
override fun visitSet(setType: SetType) {
|
||||
visitSingleElementCollection(
|
||||
setType.elementType().trueType,
|
||||
"writeSetBegin",
|
||||
"writeSetEnd")
|
||||
}
|
||||
|
||||
private fun visitSingleElementCollection(elementType: ThriftType, beginMethod: String, endMethod: String) {
|
||||
val tag = "item$scopeLevel"
|
||||
val item = nameAllocator.newName(tag, tag)
|
||||
|
||||
val javaClass = resolver.getJavaClass(elementType)
|
||||
val typeCode = resolver.getTypeCode(elementType)
|
||||
val typeCodeName = TypeNames.getTypeCodeName(typeCode)
|
||||
|
||||
write.addStatement(
|
||||
"\$N.\$L(\$T.\$L, \$L.size())",
|
||||
proto,
|
||||
beginMethod,
|
||||
TypeNames.TTYPE,
|
||||
typeCodeName,
|
||||
nameStack.peek())
|
||||
|
||||
write.beginControlFlow("for (\$T \$N : \$L)", javaClass, item, nameStack.peek())
|
||||
|
||||
scope {
|
||||
nameStack.push(item)
|
||||
elementType.accept(this)
|
||||
nameStack.pop()
|
||||
}
|
||||
|
||||
write.endControlFlow()
|
||||
|
||||
write.addStatement("\$N.\$L()", proto, endMethod)
|
||||
}
|
||||
|
||||
override fun visitMap(mapType: MapType) {
|
||||
val entryTag = "entry$scopeLevel"
|
||||
val keyTag = "key$scopeLevel"
|
||||
val valueTag = "value$scopeLevel"
|
||||
|
||||
val entryName = nameAllocator.newName(entryTag, entryTag)
|
||||
val keyName = nameAllocator.newName(keyTag, keyTag)
|
||||
val valueName = nameAllocator.newName(valueTag, valueTag)
|
||||
val kt = mapType.keyType().trueType
|
||||
val vt = mapType.valueType().trueType
|
||||
|
||||
val keyTypeCode = resolver.getTypeCode(kt)
|
||||
val valTypeCode = resolver.getTypeCode(vt)
|
||||
|
||||
write.addStatement(
|
||||
"$1N.writeMapBegin($2T.$3L, $2T.$4L, $5L.size())",
|
||||
proto,
|
||||
TypeNames.TTYPE,
|
||||
TypeNames.getTypeCodeName(keyTypeCode),
|
||||
TypeNames.getTypeCodeName(valTypeCode),
|
||||
nameStack.peek())
|
||||
|
||||
val keyTypeName = resolver.getJavaClass(kt)
|
||||
val valueTypeName = resolver.getJavaClass(vt)
|
||||
val entry = ParameterizedTypeName.get(TypeNames.MAP_ENTRY, keyTypeName, valueTypeName)
|
||||
write.beginControlFlow("for (\$T \$N : \$L.entrySet())", entry, entryTag, nameStack.peek())
|
||||
write.addStatement("\$T \$N = \$N.getKey()", keyTypeName, keyName, entryName)
|
||||
write.addStatement("\$T \$N = \$N.getValue()", valueTypeName, valueName, entryName)
|
||||
|
||||
scope {
|
||||
nameStack.push(keyName)
|
||||
kt.accept(this)
|
||||
nameStack.pop()
|
||||
|
||||
nameStack.push(valueName)
|
||||
vt.accept(this)
|
||||
nameStack.pop()
|
||||
}
|
||||
|
||||
write.endControlFlow()
|
||||
write.addStatement("\$N.writeMapEnd()", proto)
|
||||
}
|
||||
|
||||
override fun visitStruct(structType: StructType) {
|
||||
val javaName = structType.getNamespaceFor(NamespaceScope.JAVA) + "." + structType.name()
|
||||
write.addStatement("\$L.ADAPTER.write(\$N, \$L)", javaName, proto, nameStack.peek())
|
||||
}
|
||||
|
||||
override fun visitTypedef(typedefType: TypedefType) {
|
||||
typedefType.trueType.accept(this)
|
||||
}
|
||||
|
||||
override fun visitService(serviceType: ServiceType) {
|
||||
throw AssertionError("Cannot write a service")
|
||||
}
|
||||
|
||||
private inline fun scope(fn: () -> Unit) {
|
||||
scopeLevel++
|
||||
try {
|
||||
fn()
|
||||
} finally {
|
||||
scopeLevel--
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,397 @@
|
|||
/*
|
||||
* Thrifty
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the License);
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
||||
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.microsoft.thrifty.gen
|
||||
|
||||
import com.microsoft.thrifty.ThriftException
|
||||
import com.microsoft.thrifty.schema.BuiltinType
|
||||
import com.microsoft.thrifty.schema.NamespaceScope
|
||||
import com.microsoft.thrifty.schema.ServiceMethod
|
||||
import com.microsoft.thrifty.schema.ServiceType
|
||||
import com.squareup.javapoet.AnnotationSpec
|
||||
import com.squareup.javapoet.ClassName
|
||||
import com.squareup.javapoet.CodeBlock
|
||||
import com.squareup.javapoet.FieldSpec
|
||||
import com.squareup.javapoet.MethodSpec
|
||||
import com.squareup.javapoet.NameAllocator
|
||||
import com.squareup.javapoet.ParameterizedTypeName
|
||||
import com.squareup.javapoet.TypeName
|
||||
import com.squareup.javapoet.TypeSpec
|
||||
|
||||
import javax.lang.model.element.Modifier
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
internal class ServiceBuilder(
|
||||
private val typeResolver: TypeResolver,
|
||||
private val constantBuilder: ConstantBuilder,
|
||||
private val fieldNamer: FieldNamer
|
||||
) {
|
||||
|
||||
fun buildServiceInterface(service: ServiceType): TypeSpec {
|
||||
val serviceSpec = TypeSpec.interfaceBuilder(service.name())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
|
||||
service.documentation()?.let {
|
||||
if (it.isNotEmpty()) {
|
||||
serviceSpec.addJavadoc(it)
|
||||
}
|
||||
}
|
||||
|
||||
if (service.isDeprecated) {
|
||||
serviceSpec.addAnnotation(AnnotationSpec.builder(Deprecated::class.java).build())
|
||||
}
|
||||
|
||||
if (service.extendsService() != null) {
|
||||
val superType = service.extendsService().trueType
|
||||
val superTypeName = typeResolver.getJavaClass(superType)
|
||||
serviceSpec.addSuperinterface(superTypeName)
|
||||
}
|
||||
|
||||
for (method in service.methods()) {
|
||||
val allocator = NameAllocator()
|
||||
var tag = 0
|
||||
|
||||
val methodBuilder = MethodSpec.methodBuilder(method.name())
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
|
||||
|
||||
if (method.hasJavadoc()) {
|
||||
methodBuilder.addJavadoc(method.documentation())
|
||||
}
|
||||
|
||||
for (field in method.parameters()) {
|
||||
val fieldName = fieldNamer.getName(field)
|
||||
val name = allocator.newName(fieldName, ++tag)
|
||||
val paramType = field.type().trueType
|
||||
val paramTypeName = typeResolver.getJavaClass(paramType)
|
||||
|
||||
methodBuilder.addParameter(paramTypeName, name)
|
||||
|
||||
}
|
||||
|
||||
val callbackName = allocator.newName("callback", ++tag)
|
||||
|
||||
val returnType = method.returnType()
|
||||
val returnTypeName = if (returnType == BuiltinType.VOID) {
|
||||
TypeName.VOID.box()
|
||||
} else {
|
||||
typeResolver.getJavaClass(returnType.trueType)
|
||||
}
|
||||
|
||||
val callbackInterfaceName = ParameterizedTypeName.get(
|
||||
TypeNames.SERVICE_CALLBACK, returnTypeName)
|
||||
|
||||
methodBuilder.addParameter(callbackInterfaceName, callbackName)
|
||||
|
||||
serviceSpec.addMethod(methodBuilder.build())
|
||||
}
|
||||
|
||||
return serviceSpec.build()
|
||||
}
|
||||
|
||||
fun buildService(service: ServiceType, serviceInterface: TypeSpec): TypeSpec {
|
||||
val packageName = service.getNamespaceFor(NamespaceScope.JAVA)
|
||||
val interfaceTypeName = ClassName.get(packageName, serviceInterface.name)
|
||||
val builder = TypeSpec.classBuilder(service.name() + "Client")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addSuperinterface(interfaceTypeName)
|
||||
|
||||
if (service.extendsService() != null) {
|
||||
val type = service.extendsService()
|
||||
val typeName = type.name() + "Client"
|
||||
val ns = (type as ServiceType).getNamespaceFor(NamespaceScope.JAVA)
|
||||
val javaClass = ClassName.get(ns, typeName)
|
||||
builder.superclass(javaClass)
|
||||
} else {
|
||||
builder.superclass(TypeNames.SERVICE_CLIENT_BASE)
|
||||
}
|
||||
|
||||
builder.addMethod(MethodSpec.constructorBuilder()
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addParameter(TypeNames.PROTOCOL, "protocol")
|
||||
.addParameter(TypeNames.SERVICE_CLIENT_LISTENER, "listener")
|
||||
.addStatement("super(protocol, listener)")
|
||||
.build())
|
||||
|
||||
for ((i, methodSpec) in serviceInterface.methodSpecs.withIndex()) {
|
||||
val serviceMethod = service.methods()[i]
|
||||
val call = buildCallSpec(serviceMethod)
|
||||
builder.addType(call)
|
||||
|
||||
val meth = MethodSpec.methodBuilder(methodSpec.name)
|
||||
.addAnnotation(Override::class.java)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addParameters(methodSpec.parameters)
|
||||
.addExceptions(methodSpec.exceptions)
|
||||
|
||||
val body = CodeBlock.builder()
|
||||
.add("$[this.enqueue(new \$N(", call)
|
||||
|
||||
for ((index, parameter) in methodSpec.parameters.withIndex()) {
|
||||
if (index == 0) {
|
||||
body.add("\$N", parameter.name)
|
||||
} else {
|
||||
body.add(", \$N", parameter.name)
|
||||
}
|
||||
}
|
||||
|
||||
body.add("));\n$]")
|
||||
|
||||
meth.addCode(body.build())
|
||||
|
||||
builder.addMethod(meth.build())
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
private fun buildCallSpec(method: ServiceMethod): TypeSpec {
|
||||
val name = "${method.name().capitalize()}Call"
|
||||
|
||||
val returnType = method.returnType()
|
||||
val returnTypeName = if (returnType == BuiltinType.VOID) {
|
||||
TypeName.VOID.box()
|
||||
} else {
|
||||
typeResolver.getJavaClass(returnType.trueType)
|
||||
}
|
||||
|
||||
val callbackTypeName = ParameterizedTypeName.get(TypeNames.SERVICE_CALLBACK, returnTypeName)
|
||||
val superclass = ParameterizedTypeName.get(TypeNames.SERVICE_METHOD_CALL, returnTypeName)
|
||||
|
||||
val hasReturnType = returnTypeName != TypeName.VOID.box()
|
||||
|
||||
val callBuilder = TypeSpec.classBuilder(name)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
|
||||
.superclass(superclass)
|
||||
|
||||
// Set up fields
|
||||
for (field in method.parameters()) {
|
||||
val javaType = typeResolver.getJavaClass(field.type().trueType)
|
||||
|
||||
callBuilder.addField(FieldSpec.builder(javaType, fieldNamer.getName(field))
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
|
||||
.build())
|
||||
}
|
||||
|
||||
// Ctor
|
||||
callBuilder.addMethod(buildCallCtor(method, callbackTypeName))
|
||||
|
||||
// Send
|
||||
callBuilder.addMethod(buildSendMethod(method))
|
||||
|
||||
// Receive
|
||||
callBuilder.addMethod(buildReceiveMethod(method, hasReturnType))
|
||||
|
||||
return callBuilder.build()
|
||||
}
|
||||
|
||||
private fun buildCallCtor(method: ServiceMethod, callbackTypeName: TypeName): MethodSpec {
|
||||
val allocator = NameAllocator()
|
||||
val scope = AtomicInteger(0)
|
||||
val ctor = MethodSpec.constructorBuilder()
|
||||
.addStatement(
|
||||
"super(\$S, \$T.\$L, callback)",
|
||||
method.name(),
|
||||
TypeNames.TMESSAGE_TYPE,
|
||||
if (method.oneWay()) "ONEWAY" else "CALL")
|
||||
|
||||
for (field in method.parameters()) {
|
||||
val fieldName = fieldNamer.getName(field)
|
||||
val javaType = typeResolver.getJavaClass(field.type().trueType)
|
||||
|
||||
ctor.addParameter(javaType, fieldName)
|
||||
|
||||
if (field.required() && field.defaultValue() == null) {
|
||||
ctor.addStatement("if (\$L == null) throw new NullPointerException(\$S)", fieldName, fieldName)
|
||||
ctor.addStatement("this.$1L = $1L", fieldName)
|
||||
} else if (field.defaultValue() != null) {
|
||||
ctor.beginControlFlow("if (\$L != null)", fieldName)
|
||||
ctor.addStatement("this.$1L = $1L", fieldName)
|
||||
ctor.nextControlFlow("else")
|
||||
|
||||
val init = CodeBlock.builder()
|
||||
constantBuilder.generateFieldInitializer(
|
||||
init,
|
||||
allocator,
|
||||
scope,
|
||||
"this.$fieldName",
|
||||
field.type().trueType,
|
||||
field.defaultValue()!!,
|
||||
false)
|
||||
ctor.addCode(init.build())
|
||||
|
||||
ctor.endControlFlow()
|
||||
} else {
|
||||
ctor.addStatement("this.$1L = $1L", fieldName)
|
||||
}
|
||||
}
|
||||
|
||||
ctor.addParameter(callbackTypeName, "callback")
|
||||
|
||||
return ctor.build()
|
||||
}
|
||||
|
||||
private fun buildSendMethod(method: ServiceMethod): MethodSpec {
|
||||
val send = MethodSpec.methodBuilder("send")
|
||||
.addAnnotation(Override::class.java)
|
||||
.addModifiers(Modifier.PROTECTED)
|
||||
.addParameter(TypeNames.PROTOCOL, "protocol")
|
||||
.addException(TypeNames.IO_EXCEPTION)
|
||||
|
||||
send.addStatement("protocol.writeStructBegin(\$S)", "args")
|
||||
|
||||
for (field in method.parameters()) {
|
||||
val fieldName = fieldNamer.getName(field)
|
||||
val optional = !field.required()
|
||||
val tt = field.type().trueType
|
||||
val typeCode = typeResolver.getTypeCode(tt)
|
||||
|
||||
if (optional) {
|
||||
send.beginControlFlow("if (this.\$L != null)", fieldName)
|
||||
}
|
||||
|
||||
send.addStatement("protocol.writeFieldBegin(\$S, \$L, \$T.\$L)",
|
||||
field.name(), // send the Thrift name, not the fieldNamer output
|
||||
field.id(),
|
||||
TypeNames.TTYPE,
|
||||
TypeNames.getTypeCodeName(typeCode))
|
||||
|
||||
tt.accept(GenerateWriterVisitor(typeResolver, send, "protocol", "this", fieldName))
|
||||
|
||||
send.addStatement("protocol.writeFieldEnd()")
|
||||
|
||||
if (optional) {
|
||||
send.endControlFlow()
|
||||
}
|
||||
}
|
||||
|
||||
send.addStatement("protocol.writeFieldStop()")
|
||||
send.addStatement("protocol.writeStructEnd()")
|
||||
|
||||
return send.build()
|
||||
}
|
||||
|
||||
private fun buildReceiveMethod(method: ServiceMethod, hasReturnType: Boolean): MethodSpec {
|
||||
val recv = MethodSpec.methodBuilder("receive")
|
||||
.addAnnotation(Override::class.java)
|
||||
.addModifiers(Modifier.PROTECTED)
|
||||
.addParameter(TypeNames.PROTOCOL, "protocol")
|
||||
.addParameter(TypeNames.MESSAGE_METADATA, "metadata")
|
||||
.addException(TypeNames.EXCEPTION)
|
||||
|
||||
if (hasReturnType) {
|
||||
val retTypeName = typeResolver.getJavaClass(method.returnType().trueType)
|
||||
recv.returns(retTypeName)
|
||||
recv.addStatement("\$T result = null", retTypeName)
|
||||
} else {
|
||||
recv.returns(TypeName.VOID.box())
|
||||
}
|
||||
|
||||
for (field in method.exceptions()) {
|
||||
val fieldName = fieldNamer.getName(field)
|
||||
val exceptionTypeName = typeResolver.getJavaClass(field.type().trueType)
|
||||
recv.addStatement("\$T \$L = null", exceptionTypeName, fieldName)
|
||||
}
|
||||
|
||||
recv.addStatement("protocol.readStructBegin()")
|
||||
.beginControlFlow("while (true)")
|
||||
.addStatement("\$T field = protocol.readFieldBegin()", TypeNames.FIELD_METADATA)
|
||||
.beginControlFlow("if (field.typeId == \$T.STOP)", TypeNames.TTYPE)
|
||||
.addStatement("break")
|
||||
.endControlFlow()
|
||||
.beginControlFlow("switch (field.fieldId)")
|
||||
|
||||
if (hasReturnType) {
|
||||
val type = method.returnType().trueType
|
||||
recv.beginControlFlow("case 0:")
|
||||
|
||||
object : GenerateReaderVisitor(typeResolver, recv, "result", type) {
|
||||
override fun useReadValue(localName: String) {
|
||||
recv.addStatement("result = \$N", localName)
|
||||
}
|
||||
}.generate()
|
||||
|
||||
recv.endControlFlow()
|
||||
recv.addStatement("break")
|
||||
}
|
||||
|
||||
for (field in method.exceptions()) {
|
||||
val fieldName = fieldNamer.getName(field)
|
||||
recv.beginControlFlow("case \$L:", field.id())
|
||||
|
||||
object : GenerateReaderVisitor(typeResolver, recv, fieldName, field.type().trueType) {
|
||||
override fun useReadValue(localName: String) {
|
||||
recv.addStatement("\$N = \$N", fieldName, localName)
|
||||
}
|
||||
}.generate()
|
||||
|
||||
recv.endControlFlow()
|
||||
recv.addStatement("break")
|
||||
}
|
||||
|
||||
recv.addStatement("default: \$T.skip(protocol, field.typeId); break", TypeNames.PROTO_UTIL)
|
||||
recv.endControlFlow() // end switch
|
||||
recv.addStatement("protocol.readFieldEnd()")
|
||||
recv.endControlFlow() // end while
|
||||
recv.addStatement("protocol.readStructEnd()")
|
||||
|
||||
var isInControlFlow = false
|
||||
if (hasReturnType) {
|
||||
recv.beginControlFlow("if (result != null)")
|
||||
recv.addStatement("return result")
|
||||
isInControlFlow = true
|
||||
}
|
||||
|
||||
for (field in method.exceptions()) {
|
||||
val fieldName = fieldNamer.getName(field)
|
||||
if (isInControlFlow) {
|
||||
recv.nextControlFlow("else if (\$L != null)", fieldName)
|
||||
} else {
|
||||
recv.beginControlFlow("if (\$L != null)", fieldName)
|
||||
isInControlFlow = true
|
||||
}
|
||||
recv.addStatement("throw \$L", fieldName)
|
||||
}
|
||||
|
||||
if (isInControlFlow) {
|
||||
recv.nextControlFlow("else")
|
||||
}
|
||||
|
||||
if (hasReturnType) {
|
||||
// In this branch, no return type was received, nor were
|
||||
// any declared exceptions received. This is a failure.
|
||||
recv.addStatement(
|
||||
"throw new \$T(\$T.\$L, \$S)",
|
||||
TypeNames.THRIFT_EXCEPTION,
|
||||
TypeNames.THRIFT_EXCEPTION_KIND,
|
||||
ThriftException.Kind.MISSING_RESULT.name,
|
||||
"Missing result")
|
||||
} else {
|
||||
// No return is expected, and no exceptions were received.
|
||||
// Success!
|
||||
recv.addStatement("return null")
|
||||
}
|
||||
|
||||
if (isInControlFlow) {
|
||||
recv.endControlFlow()
|
||||
}
|
||||
|
||||
return recv.build()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Thrifty
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the License);
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
||||
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.microsoft.thrifty.gen
|
||||
|
||||
import com.microsoft.thrifty.schema.BuiltinType
|
||||
import com.microsoft.thrifty.schema.ThriftType
|
||||
|
||||
/**
|
||||
* A visitor that collapses the various builtin visit methods into
|
||||
* one; useful when you don't need to distinguish between specific
|
||||
* builtin types.
|
||||
*
|
||||
* @param T the type of value returned by visit methods.
|
||||
*/
|
||||
internal abstract class SimpleVisitor<T> : ThriftType.Visitor<T> {
|
||||
|
||||
abstract fun visitBuiltin(builtinType: ThriftType): T
|
||||
|
||||
override fun visitBool(boolType: BuiltinType): T {
|
||||
return visitBuiltin(boolType)
|
||||
}
|
||||
|
||||
override fun visitByte(byteType: BuiltinType): T {
|
||||
return visitBuiltin(byteType)
|
||||
}
|
||||
|
||||
override fun visitI16(i16Type: BuiltinType): T {
|
||||
return visitBuiltin(i16Type)
|
||||
}
|
||||
|
||||
override fun visitI32(i32Type: BuiltinType): T {
|
||||
return visitBuiltin(i32Type)
|
||||
}
|
||||
|
||||
override fun visitI64(i64Type: BuiltinType): T {
|
||||
return visitBuiltin(i64Type)
|
||||
}
|
||||
|
||||
override fun visitDouble(doubleType: BuiltinType): T {
|
||||
return visitBuiltin(doubleType)
|
||||
}
|
||||
|
||||
override fun visitString(stringType: BuiltinType): T {
|
||||
return visitBuiltin(stringType)
|
||||
}
|
||||
|
||||
override fun visitBinary(binaryType: BuiltinType): T {
|
||||
return visitBuiltin(binaryType)
|
||||
}
|
||||
|
||||
override fun visitVoid(voidType: BuiltinType): T {
|
||||
return visitBuiltin(voidType)
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Thrifty
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the License);
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
||||
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.microsoft.thrifty.gen
|
||||
|
||||
import com.microsoft.thrifty.Adapter
|
||||
import com.microsoft.thrifty.Obfuscated
|
||||
import com.microsoft.thrifty.Redacted
|
||||
import com.microsoft.thrifty.StructBuilder
|
||||
import com.microsoft.thrifty.TType
|
||||
import com.microsoft.thrifty.ThriftException
|
||||
import com.microsoft.thrifty.ThriftField
|
||||
import com.microsoft.thrifty.protocol.FieldMetadata
|
||||
import com.microsoft.thrifty.protocol.ListMetadata
|
||||
import com.microsoft.thrifty.protocol.MapMetadata
|
||||
import com.microsoft.thrifty.protocol.MessageMetadata
|
||||
import com.microsoft.thrifty.protocol.Protocol
|
||||
import com.microsoft.thrifty.protocol.SetMetadata
|
||||
import com.microsoft.thrifty.service.AsyncClientBase
|
||||
import com.microsoft.thrifty.service.MethodCall
|
||||
import com.microsoft.thrifty.service.ServiceMethodCallback
|
||||
import com.microsoft.thrifty.service.TMessageType
|
||||
import com.microsoft.thrifty.util.ObfuscationUtil
|
||||
import com.microsoft.thrifty.util.ProtocolUtil
|
||||
import com.squareup.javapoet.ClassName
|
||||
import okio.ByteString
|
||||
|
||||
import java.io.IOException
|
||||
import java.net.ProtocolException
|
||||
import java.util.Collections
|
||||
import java.util.NoSuchElementException
|
||||
|
||||
/**
|
||||
* JavaPoet type names used for code generation.
|
||||
*/
|
||||
internal object TypeNames {
|
||||
val BOOLEAN = ClassName.BOOLEAN.box()
|
||||
val BYTE = ClassName.BYTE.box()
|
||||
val SHORT = ClassName.SHORT.box()
|
||||
val INTEGER = ClassName.INT.box()
|
||||
val LONG = ClassName.LONG.box()
|
||||
val DOUBLE = ClassName.DOUBLE.box()
|
||||
val VOID = ClassName.VOID // Don't box void, it is only used for methods returning nothing.
|
||||
|
||||
val COLLECTIONS = classNameOf<Collections>()
|
||||
val STRING = classNameOf<String>()
|
||||
val LIST = classNameOf<List<*>>()
|
||||
val MAP = classNameOf<Map<*, *>>()
|
||||
val MAP_ENTRY = classNameOf<Map.Entry<*, *>>()
|
||||
val SET = classNameOf<Set<*>>()
|
||||
val BYTE_STRING = classNameOf<ByteString>()
|
||||
val STRING_BUILDER = classNameOf<StringBuilder>()
|
||||
val ILLEGAL_STATE_EXCEPTION = classNameOf<java.lang.IllegalStateException>()
|
||||
val ILLEGAL_ARGUMENT_EXCEPTION = classNameOf<java.lang.IllegalArgumentException>()
|
||||
val NULL_POINTER_EXCEPTION = classNameOf<java.lang.NullPointerException>()
|
||||
|
||||
val ARRAY_LIST = classNameOf<ArrayList<*>>()
|
||||
val HASH_SET = classNameOf<HashSet<*>>()
|
||||
val HASH_MAP = classNameOf<HashMap<*, *>>()
|
||||
|
||||
val LIST_META = classNameOf<ListMetadata>()
|
||||
val SET_META = classNameOf<SetMetadata>()
|
||||
val MAP_META = classNameOf<MapMetadata>()
|
||||
|
||||
val PROTOCOL = classNameOf<Protocol>()
|
||||
val PROTO_UTIL = classNameOf<ProtocolUtil>()
|
||||
val PROTOCOL_EXCEPTION = classNameOf<ProtocolException>()
|
||||
val IO_EXCEPTION = classNameOf<IOException>()
|
||||
val EXCEPTION = classNameOf<Exception>()
|
||||
val TTYPE = classNameOf<TType>()
|
||||
val TMESSAGE_TYPE = classNameOf<TMessageType>()
|
||||
|
||||
val THRIFT_EXCEPTION = classNameOf<ThriftException>()
|
||||
val THRIFT_EXCEPTION_KIND = classNameOf<ThriftException.Kind>()
|
||||
|
||||
val BUILDER = classNameOf<StructBuilder<*>>()
|
||||
val ADAPTER = classNameOf<Adapter<*, *>>()
|
||||
|
||||
val FIELD_METADATA = classNameOf<FieldMetadata>()
|
||||
val MESSAGE_METADATA = classNameOf<MessageMetadata>()
|
||||
|
||||
val OVERRIDE = classNameOf<Override>()
|
||||
val DEPRECATED = classNameOf<java.lang.Deprecated>()
|
||||
val SUPPRESS_WARNINGS = classNameOf<java.lang.SuppressWarnings>()
|
||||
val REDACTED = classNameOf<Redacted>()
|
||||
val OBFUSCATED = classNameOf<Obfuscated>()
|
||||
val THRIFT_FIELD = classNameOf<ThriftField>()
|
||||
|
||||
val NOT_NULL = ClassName.get("android.support.annotation", "NonNull")
|
||||
val NULLABLE = ClassName.get("android.support.annotation", "Nullable")
|
||||
|
||||
val SERVICE_CALLBACK = classNameOf<ServiceMethodCallback<*>>()
|
||||
val SERVICE_CLIENT_BASE = classNameOf<AsyncClientBase>()
|
||||
val SERVICE_CLIENT_LISTENER = classNameOf<AsyncClientBase.Listener>()
|
||||
val SERVICE_METHOD_CALL = classNameOf<MethodCall<*>>()
|
||||
|
||||
val PARCEL = ClassName.get("android.os", "Parcel")
|
||||
val PARCELABLE = ClassName.get("android.os", "Parcelable")
|
||||
val PARCELABLE_CREATOR = ClassName.get("android.os", "Parcelable", "Creator")
|
||||
|
||||
val OBFUSCATION_UTIL = classNameOf<ObfuscationUtil>()
|
||||
|
||||
/**
|
||||
* Gets the [TType] member name corresponding to the given type-code.
|
||||
*
|
||||
* @param code the code whose name is needed
|
||||
* @return the TType member name as a string
|
||||
*/
|
||||
fun getTypeCodeName(code: Byte): String {
|
||||
return when(code) {
|
||||
TType.BOOL -> "BOOL"
|
||||
TType.BYTE -> "BYTE"
|
||||
TType.I16 -> "I16"
|
||||
TType.I32 -> "I32"
|
||||
TType.I64 -> "I64"
|
||||
TType.DOUBLE -> "DOUBLE"
|
||||
TType.STRING -> "STRING"
|
||||
TType.ENUM -> "ENUM"
|
||||
TType.STRUCT -> "STRUCT"
|
||||
TType.LIST -> "LIST"
|
||||
TType.SET -> "SET"
|
||||
TType.MAP -> "MAP"
|
||||
TType.VOID -> "VOID"
|
||||
TType.STOP -> "STOP"
|
||||
else -> throw NoSuchElementException("not a TType member: $code")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <reified T> classNameOf(): ClassName = ClassName.get(T::class.java)
|
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* Thrifty
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the License);
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
||||
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.microsoft.thrifty.gen
|
||||
|
||||
import com.microsoft.thrifty.TType
|
||||
import com.microsoft.thrifty.schema.BuiltinType
|
||||
import com.microsoft.thrifty.schema.EnumType
|
||||
import com.microsoft.thrifty.schema.ListType
|
||||
import com.microsoft.thrifty.schema.MapType
|
||||
import com.microsoft.thrifty.schema.NamespaceScope
|
||||
import com.microsoft.thrifty.schema.ServiceType
|
||||
import com.microsoft.thrifty.schema.SetType
|
||||
import com.microsoft.thrifty.schema.StructType
|
||||
import com.microsoft.thrifty.schema.ThriftType
|
||||
import com.microsoft.thrifty.schema.TypedefType
|
||||
import com.microsoft.thrifty.schema.UserType
|
||||
import com.squareup.javapoet.ClassName
|
||||
import com.squareup.javapoet.ParameterizedTypeName
|
||||
import com.squareup.javapoet.TypeName
|
||||
|
||||
import java.util.ArrayList
|
||||
import java.util.HashMap
|
||||
import java.util.HashSet
|
||||
import java.util.LinkedHashMap
|
||||
|
||||
/**
|
||||
* Utility for getting JavaPoet [TypeName] and [TType] codes from
|
||||
* [ThriftType] instances.
|
||||
*/
|
||||
internal class TypeResolver {
|
||||
|
||||
private var listClass = classNameOf<ArrayList<*>>()
|
||||
private var setClass = classNameOf<HashSet<*>>()
|
||||
private var mapClass = classNameOf<HashMap<*, *>>()
|
||||
|
||||
fun setListClass(listClass: ClassName) {
|
||||
this.listClass = listClass
|
||||
}
|
||||
|
||||
fun setSetClass(setClass: ClassName) {
|
||||
this.setClass = setClass
|
||||
}
|
||||
|
||||
fun setMapClass(mapClass: ClassName) {
|
||||
this.mapClass = mapClass
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [TType] constant representing the type-code for the given
|
||||
* [thriftType]. Note that this method will substitute [TType.I32] for
|
||||
* [TType.ENUM], because enums are always represented as I32 in encodings
|
||||
* and in generated code.
|
||||
*/
|
||||
fun getTypeCode(thriftType: ThriftType): Byte {
|
||||
val typeCode = thriftType.trueType.accept(TypeCodeVisitor)
|
||||
return if (typeCode == TType.ENUM) {
|
||||
TType.I32
|
||||
} else {
|
||||
typeCode
|
||||
}
|
||||
}
|
||||
|
||||
fun getJavaClass(thriftType: ThriftType): TypeName {
|
||||
return thriftType.accept(TypeNameVisitor)
|
||||
}
|
||||
|
||||
fun listOf(elementType: TypeName): ParameterizedTypeName {
|
||||
return ParameterizedTypeName.get(listClass, elementType)
|
||||
}
|
||||
|
||||
fun setOf(elementType: TypeName): ParameterizedTypeName {
|
||||
return ParameterizedTypeName.get(setClass, elementType)
|
||||
}
|
||||
|
||||
fun mapOf(keyType: TypeName, valueType: TypeName): ParameterizedTypeName {
|
||||
return ParameterizedTypeName.get(mapClass, keyType, valueType)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Visitor that converts a [ThriftType] into a [TypeName].
|
||||
*/
|
||||
private object TypeNameVisitor : ThriftType.Visitor<TypeName> {
|
||||
private val nameCache = LinkedHashMap<String, ClassName>()
|
||||
|
||||
override fun visitVoid(voidType: BuiltinType): TypeName {
|
||||
return TypeNames.VOID
|
||||
}
|
||||
|
||||
override fun visitBool(boolType: BuiltinType): TypeName {
|
||||
return TypeNames.BOOLEAN
|
||||
}
|
||||
|
||||
override fun visitByte(byteType: BuiltinType): TypeName {
|
||||
return TypeNames.BYTE
|
||||
}
|
||||
|
||||
override fun visitI16(i16Type: BuiltinType): TypeName {
|
||||
return TypeNames.SHORT
|
||||
}
|
||||
|
||||
override fun visitI32(i32Type: BuiltinType): TypeName {
|
||||
return TypeNames.INTEGER
|
||||
}
|
||||
|
||||
override fun visitI64(i64Type: BuiltinType): TypeName {
|
||||
return TypeNames.LONG
|
||||
}
|
||||
|
||||
override fun visitDouble(doubleType: BuiltinType): TypeName {
|
||||
return TypeNames.DOUBLE
|
||||
}
|
||||
|
||||
override fun visitString(stringType: BuiltinType): TypeName {
|
||||
return TypeNames.STRING
|
||||
}
|
||||
|
||||
override fun visitBinary(binaryType: BuiltinType): TypeName {
|
||||
return TypeNames.BYTE_STRING
|
||||
}
|
||||
|
||||
override fun visitEnum(enumType: EnumType): TypeName {
|
||||
return visitUserType(enumType)
|
||||
}
|
||||
|
||||
override fun visitList(listType: ListType): TypeName {
|
||||
val elementType = listType.elementType().trueType
|
||||
val elementTypeName = elementType.accept(this)
|
||||
return ParameterizedTypeName.get(TypeNames.LIST, elementTypeName)
|
||||
}
|
||||
|
||||
override fun visitSet(setType: SetType): TypeName {
|
||||
val elementType = setType.elementType().trueType
|
||||
val elementTypeName = elementType.accept(this)
|
||||
return ParameterizedTypeName.get(TypeNames.SET, elementTypeName)
|
||||
}
|
||||
|
||||
override fun visitMap(mapType: MapType): TypeName {
|
||||
val keyType = mapType.keyType().trueType
|
||||
val valueType = mapType.valueType().trueType
|
||||
|
||||
val keyTypeName = keyType.accept(this)
|
||||
val valueTypeName = valueType.accept(this)
|
||||
return ParameterizedTypeName.get(TypeNames.MAP, keyTypeName, valueTypeName)
|
||||
}
|
||||
|
||||
override fun visitStruct(structType: StructType): TypeName {
|
||||
return visitUserType(structType)
|
||||
}
|
||||
|
||||
override fun visitTypedef(typedefType: TypedefType): TypeName {
|
||||
return typedefType.oldType().accept(this)
|
||||
}
|
||||
|
||||
override fun visitService(serviceType: ServiceType): TypeName {
|
||||
return visitUserType(serviceType)
|
||||
}
|
||||
|
||||
private fun visitUserType(userType: UserType): TypeName {
|
||||
val packageName = userType.getNamespaceFor(NamespaceScope.JAVA)
|
||||
if (packageName.isNullOrEmpty()) {
|
||||
throw AssertionError("Missing namespace. Did you forget to add 'namespace java'?")
|
||||
}
|
||||
|
||||
val key = "$packageName##${userType.name()}"
|
||||
return nameCache.computeIfAbsent(key) { ClassName.get(packageName, userType.name()) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Visitor that converts a [ThriftType] into a [TType]
|
||||
* constant value.
|
||||
*/
|
||||
private object TypeCodeVisitor : ThriftType.Visitor<Byte> {
|
||||
override fun visitBool(boolType: BuiltinType): Byte? {
|
||||
return TType.BOOL
|
||||
}
|
||||
|
||||
override fun visitByte(byteType: BuiltinType): Byte? {
|
||||
return TType.BYTE
|
||||
}
|
||||
|
||||
override fun visitI16(i16Type: BuiltinType): Byte? {
|
||||
return TType.I16
|
||||
}
|
||||
|
||||
override fun visitI32(i32Type: BuiltinType): Byte? {
|
||||
return TType.I32
|
||||
}
|
||||
|
||||
override fun visitI64(i64Type: BuiltinType): Byte? {
|
||||
return TType.I64
|
||||
}
|
||||
|
||||
override fun visitDouble(doubleType: BuiltinType): Byte? {
|
||||
return TType.DOUBLE
|
||||
}
|
||||
|
||||
override fun visitString(stringType: BuiltinType): Byte? {
|
||||
return TType.STRING
|
||||
}
|
||||
|
||||
override fun visitBinary(binaryType: BuiltinType): Byte? {
|
||||
return TType.STRING
|
||||
}
|
||||
|
||||
override fun visitVoid(voidType: BuiltinType): Byte? {
|
||||
return TType.VOID
|
||||
}
|
||||
|
||||
override fun visitEnum(userType: EnumType): Byte? {
|
||||
return TType.ENUM
|
||||
}
|
||||
|
||||
override fun visitList(listType: ListType): Byte? {
|
||||
return TType.LIST
|
||||
}
|
||||
|
||||
override fun visitSet(setType: SetType): Byte? {
|
||||
return TType.SET
|
||||
}
|
||||
|
||||
override fun visitMap(mapType: MapType): Byte? {
|
||||
return TType.MAP
|
||||
}
|
||||
|
||||
override fun visitStruct(userType: StructType): Byte? {
|
||||
return TType.STRUCT
|
||||
}
|
||||
|
||||
override fun visitTypedef(typedefType: TypedefType): Byte? {
|
||||
return typedefType.oldType().accept(this)
|
||||
}
|
||||
|
||||
override fun visitService(serviceType: ServiceType): Byte? {
|
||||
throw AssertionError("Services do not have typecodes")
|
||||
}
|
||||
}
|
|
@ -1,475 +0,0 @@
|
|||
/*
|
||||
* Thrifty
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the License);
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
||||
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.microsoft.thrifty.gen;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.microsoft.thrifty.schema.Loader;
|
||||
import com.microsoft.thrifty.schema.Schema;
|
||||
import com.squareup.javapoet.JavaFile;
|
||||
import okio.BufferedSink;
|
||||
import okio.Okio;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import javax.tools.JavaFileObject;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.common.truth.Truth.assertAbout;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
|
||||
import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;
|
||||
|
||||
/**
|
||||
* These tests ensure that various constructs produce valid Java code.
|
||||
* They don't test *anything* about the correctness of the code!
|
||||
*
|
||||
* Semantic tests can be found in {@code thrifty-integration-tests}.
|
||||
*/
|
||||
public class ThriftyCodeGeneratorTest {
|
||||
@Rule public TemporaryFolder tmp = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void redactedToStringCompiles() throws Exception {
|
||||
String thrift = Joiner.on('\n').join(
|
||||
"namespace java test",
|
||||
"",
|
||||
"struct Foo {",
|
||||
" 1: required list<string> (python.immutable) ssn (redacted)",
|
||||
"}");
|
||||
|
||||
Schema schema = parse("foo.thrift", thrift);
|
||||
|
||||
ThriftyCodeGenerator gen = new ThriftyCodeGenerator(schema);
|
||||
ImmutableList<JavaFile> javaFiles = gen.generateTypes();
|
||||
|
||||
assertThat(javaFiles).hasSize(1);
|
||||
|
||||
assertAbout(javaSource())
|
||||
.that(javaFiles.get(0).toJavaFileObject())
|
||||
.compilesWithoutError();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enumGeneration() throws Exception {
|
||||
String thrift = Joiner.on('\n').join(
|
||||
"namespace java enums",
|
||||
"",
|
||||
"// A generated enum",
|
||||
"enum BuildStatus {",
|
||||
" OK = 0,",
|
||||
" FAIL = 1",
|
||||
"}");
|
||||
|
||||
Schema schema = parse("enum.thrift", thrift);
|
||||
ThriftyCodeGenerator gen = new ThriftyCodeGenerator(schema);
|
||||
ImmutableList<JavaFile> java = gen.generateTypes();
|
||||
|
||||
assertThat(java).hasSize(1);
|
||||
|
||||
assertAbout(javaSource())
|
||||
.that(java.get(0).toJavaFileObject())
|
||||
.compilesWithoutError();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fieldWithConstInitializer() throws Exception {
|
||||
String thrift = Joiner.on('\n').join(
|
||||
"namespace java fields",
|
||||
"",
|
||||
"const i32 TEST_CONST = 5",
|
||||
"",
|
||||
"struct HasDefaultValue {",
|
||||
" 1: required i32 foo = TEST_CONST",
|
||||
"}");
|
||||
|
||||
Schema schema = parse("fields.thrift", thrift);
|
||||
ThriftyCodeGenerator gen = new ThriftyCodeGenerator(schema);
|
||||
ImmutableList<JavaFile> java = gen.generateTypes();
|
||||
List<JavaFileObject> jfos = new ArrayList<>();
|
||||
|
||||
boolean found = false;
|
||||
for (JavaFile javaFile : java) {
|
||||
if (javaFile.toString().contains("foo = fields.Constants.TEST_CONST;")) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
assertThat(found).named("Const reference was found in field assignment").isTrue();
|
||||
|
||||
assertThat(java).hasSize(2);
|
||||
for (JavaFile javaFile : java) {
|
||||
jfos.add(javaFile.toJavaFileObject());
|
||||
}
|
||||
|
||||
assertAbout(javaSources())
|
||||
.that(jfos)
|
||||
.compilesWithoutError();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deprecatedStructWithComment() throws Exception {
|
||||
String thrift = Joiner.on('\n').join(
|
||||
"namespace java deprecated",
|
||||
"",
|
||||
"/** @deprecated */",
|
||||
"struct Foo {}"
|
||||
);
|
||||
|
||||
Schema schema = parse("dep.thrift", thrift);
|
||||
ThriftyCodeGenerator gen = new ThriftyCodeGenerator(schema);
|
||||
ImmutableList<JavaFile> java = gen.generateTypes();
|
||||
List<JavaFileObject> jfos = new ArrayList<>(java.size());
|
||||
|
||||
for (JavaFile javaFile : java) {
|
||||
jfos.add(javaFile.toJavaFileObject());
|
||||
}
|
||||
|
||||
assertAbout(javaSources()).that(jfos).compilesWithoutError();
|
||||
|
||||
String file = java.get(0).toString();
|
||||
|
||||
assertThat(file).contains("@Deprecated"); // note the change in case
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deprecatedStructWithAnnotation() throws Exception {
|
||||
String thrift = Joiner.on('\n').join(
|
||||
"namespace java deprecated",
|
||||
"",
|
||||
"struct Foo {} (deprecated)"
|
||||
);
|
||||
|
||||
Schema schema = parse("dep.thrift", thrift);
|
||||
ThriftyCodeGenerator gen = new ThriftyCodeGenerator(schema);
|
||||
ImmutableList<JavaFile> java = gen.generateTypes();
|
||||
List<JavaFileObject> jfos = new ArrayList<>(java.size());
|
||||
|
||||
for (JavaFile javaFile : java) {
|
||||
jfos.add(javaFile.toJavaFileObject());
|
||||
}
|
||||
|
||||
assertAbout(javaSources()).that(jfos).compilesWithoutError();
|
||||
|
||||
String file = java.get(0).toString();
|
||||
|
||||
assertThat(file).contains("@Deprecated");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deprecatedEnum() throws Exception {
|
||||
String thrift = Joiner.on('\n').join(
|
||||
"namespace java deprecated",
|
||||
"",
|
||||
"enum Foo {ONE = 1} (deprecated)"
|
||||
);
|
||||
|
||||
Schema schema = parse("enum.thrift", thrift);
|
||||
ThriftyCodeGenerator gen = new ThriftyCodeGenerator(schema);
|
||||
ImmutableList<JavaFile> javaFiles = gen.generateTypes();
|
||||
JavaFile file = javaFiles.get(0);
|
||||
|
||||
String java = file.toString();
|
||||
|
||||
assertThat(java).contains("@Deprecated\npublic enum Foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deprecatedEnumMember() throws Exception {
|
||||
String thrift = Joiner.on('\n').join(
|
||||
"namespace java deprecated",
|
||||
"",
|
||||
"enum Foo {",
|
||||
" ONE = 1 (deprecated)",
|
||||
"}"
|
||||
);
|
||||
|
||||
Schema schema = parse("enum.thrift", thrift);
|
||||
ThriftyCodeGenerator gen = new ThriftyCodeGenerator(schema);
|
||||
ImmutableList<JavaFile> javaFiles = gen.generateTypes();
|
||||
JavaFile file = javaFiles.get(0);
|
||||
|
||||
String java = file.toString();
|
||||
|
||||
assertThat(java).contains("@Deprecated\n ONE(1)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stringConstantsAreNotUnboxed() throws Exception {
|
||||
String thrift = "" +
|
||||
"namespace java string_consts\n" +
|
||||
"\n" +
|
||||
"const string STR = 'foo'";
|
||||
|
||||
// This check validates that we can successfully compile a string constant,
|
||||
// and that we don't regress on issue #77.
|
||||
//
|
||||
// The regression here would be if an UnsupportedOperationException were thrown,
|
||||
// due to a logic bug where we attempt to unbox TypeNames.STRING.
|
||||
compile("string_consts.thrift", thrift);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void byteConstants() throws Exception {
|
||||
String thrift = "" +
|
||||
"namespace java byte_consts\n" +
|
||||
"\n" +
|
||||
"const i8 I8 = 123";
|
||||
|
||||
JavaFile file = compile("bytes.thrift", thrift).get(0);
|
||||
assertThat(file.toString()).isEqualTo("" +
|
||||
"package byte_consts;\n" +
|
||||
"\n" +
|
||||
"public final class Constants {\n" +
|
||||
" public static final byte I8 = (byte) 123;\n" +
|
||||
"\n" +
|
||||
" private Constants() {\n" +
|
||||
" // no instances\n" +
|
||||
" }\n" +
|
||||
"}\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shortConstants() throws Exception {
|
||||
String thrift = "" +
|
||||
"namespace java short_consts\n" +
|
||||
"\n" +
|
||||
"const i16 INT = 0xFF";
|
||||
|
||||
JavaFile file = compile("shorts.thrift", thrift).get(0);
|
||||
assertThat(file.toString()).isEqualTo("" +
|
||||
"package short_consts;\n" +
|
||||
"\n" +
|
||||
"public final class Constants {\n" +
|
||||
" public static final short INT = (short) 0xFF;\n" +
|
||||
"\n" +
|
||||
" private Constants() {\n" +
|
||||
" // no instances\n" +
|
||||
" }\n" +
|
||||
"}\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void intConstants() throws Exception {
|
||||
String thrift = "" +
|
||||
"namespace java int_consts\n" +
|
||||
"\n" +
|
||||
"const i32 INT = 12345";
|
||||
|
||||
JavaFile file = compile("ints.thrift", thrift).get(0);
|
||||
assertThat(file.toString()).isEqualTo("" +
|
||||
"package int_consts;\n" +
|
||||
"\n" +
|
||||
"public final class Constants {\n" +
|
||||
" public static final int INT = 12345;\n" +
|
||||
"\n" +
|
||||
" private Constants() {\n" +
|
||||
" // no instances\n" +
|
||||
" }\n" +
|
||||
"}\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void longConstants() throws Exception {
|
||||
String thrift = "" +
|
||||
"namespace java long_consts\n" +
|
||||
"\n" +
|
||||
"const i64 LONG = 0xFFFFFFFFFF";
|
||||
|
||||
JavaFile file = compile("longs.thrift", thrift).get(0);
|
||||
assertThat(file.toString()).isEqualTo("" +
|
||||
"package long_consts;\n" +
|
||||
"\n" +
|
||||
"public final class Constants {\n" +
|
||||
" public static final long LONG = 0xFFFFFFFFFFL;\n" +
|
||||
"\n" +
|
||||
" private Constants() {\n" +
|
||||
" // no instances\n" +
|
||||
" }\n" +
|
||||
"}\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void numberEqualityWarningsAreSuppressedForI32() throws Exception {
|
||||
String thrift = "" +
|
||||
"namespace java number_equality\n" +
|
||||
"\n" +
|
||||
"struct HasNumber {\n" +
|
||||
" 1: optional i32 n;\n" +
|
||||
"}";
|
||||
|
||||
String expectedEqualsMethod = "" +
|
||||
" @Override\n" +
|
||||
" @SuppressWarnings(\"NumberEquality\")\n" +
|
||||
" public boolean equals(Object other) {\n" +
|
||||
" if (this == other) return true;\n" +
|
||||
" if (other == null) return false;\n" +
|
||||
" if (!(other instanceof HasNumber)) return false;\n" +
|
||||
" HasNumber that = (HasNumber) other;\n" +
|
||||
" return (this.n == that.n || (this.n != null && this.n.equals(that.n)));\n" +
|
||||
" }\n";
|
||||
|
||||
JavaFile file = compile("numberEquality.thrift", thrift).get(0);
|
||||
|
||||
assertThat(file.toString()).contains(expectedEqualsMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constantsWithSigilsInJavadoc() throws Exception {
|
||||
String thrift = "" +
|
||||
"namespace java sigils.consts\n" +
|
||||
"\n" +
|
||||
"// This comment has $Dollar $Signs\n" +
|
||||
"const i32 INT = 12345";
|
||||
|
||||
String expectedFormat = "" +
|
||||
"package sigils.consts;\n" +
|
||||
"\n" +
|
||||
"public final class Constants {\n" +
|
||||
" /**\n" +
|
||||
" * This comment has $Dollar $Signs\n" +
|
||||
" *\n" +
|
||||
" *\n" +
|
||||
" * Generated from: %s at 4:1\n" +
|
||||
" */\n" +
|
||||
" public static final int INT = 12345;\n" +
|
||||
"\n" +
|
||||
" private Constants() {\n" +
|
||||
" // no instances\n" +
|
||||
" }\n" +
|
||||
"}\n";
|
||||
|
||||
File thriftFile = tmp.newFile("sigils_consts.thrift");
|
||||
JavaFile javaFile = compile(thriftFile, thrift).get(0);
|
||||
|
||||
String javaText = javaFile.toString();
|
||||
String expected = String.format(expectedFormat, thriftFile.getAbsolutePath());
|
||||
|
||||
assertThat(javaText).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enumsWithSigilsInJavadoc() throws Exception {
|
||||
String thrift = "" +
|
||||
"namespace java sigils.enums\n" +
|
||||
"\n" +
|
||||
"// $Sigil here\n" +
|
||||
"enum TestEnum {\n" +
|
||||
" // $Good, here's another\n" +
|
||||
" FOO\n" +
|
||||
"}\n";
|
||||
|
||||
String expected = "" +
|
||||
"package sigils.enums;\n" +
|
||||
"\n" +
|
||||
"/**\n" +
|
||||
" * $Sigil here\n" +
|
||||
" */\n" +
|
||||
"public enum TestEnum {\n" +
|
||||
" /**\n" +
|
||||
" * $Good, here's another\n" +
|
||||
" */\n" +
|
||||
" FOO(0);\n" +
|
||||
"\n" +
|
||||
" public final int value;\n" +
|
||||
"\n" +
|
||||
" TestEnum(int value) {\n" +
|
||||
" this.value = value;\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
" public static TestEnum findByValue(int value) {\n" +
|
||||
" switch (value) {\n" +
|
||||
" case 0: return FOO;\n" +
|
||||
" default: return null;\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}\n";
|
||||
|
||||
File thriftFile = tmp.newFile("sigil_enums.thrift");
|
||||
JavaFile javaFile = compile(thriftFile, thrift).get(0);
|
||||
|
||||
assertThat(javaFile.toString()).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void structsWithSigilsInJavadoc() throws Exception {
|
||||
String thrift = "" +
|
||||
"namespace java sigils.structs\n" +
|
||||
"\n" +
|
||||
"// $A $B $C $D $E\n" +
|
||||
"struct Foo {\n" +
|
||||
" // $F $G $H $I $J\n" +
|
||||
" 1: required string bar\n" +
|
||||
"}";
|
||||
|
||||
String expectedClassJavadoc = "" +
|
||||
"/**\n" +
|
||||
" * $A $B $C $D $E\n" +
|
||||
" */\n" +
|
||||
"public final class Foo implements Struct {\n";
|
||||
|
||||
String expectedFieldJavadoc = "" +
|
||||
" /**\n" +
|
||||
" * $F $G $H $I $J\n" +
|
||||
" */\n" +
|
||||
" @ThriftField(\n" +
|
||||
" fieldId = 1,\n" +
|
||||
" isRequired = true\n" +
|
||||
" )\n" +
|
||||
" public final String bar;\n";
|
||||
|
||||
File thriftFile = tmp.newFile("sigil_enums.thrift");
|
||||
JavaFile javaFile = compile(thriftFile, thrift).get(0);
|
||||
assertThat(javaFile.toString()).contains(expectedClassJavadoc);
|
||||
assertThat(javaFile.toString()).contains(expectedFieldJavadoc);
|
||||
}
|
||||
|
||||
private ImmutableList<JavaFile> compile(String filename, String text) throws Exception {
|
||||
Schema schema = parse(filename, text);
|
||||
ThriftyCodeGenerator gen = new ThriftyCodeGenerator(schema).emitFileComment(false);
|
||||
return gen.generateTypes();
|
||||
}
|
||||
|
||||
private ImmutableList<JavaFile> compile(File file, String text) throws Exception {
|
||||
Schema schema = parse(file, text);
|
||||
ThriftyCodeGenerator gen = new ThriftyCodeGenerator(schema).emitFileComment(false);
|
||||
return gen.generateTypes();
|
||||
}
|
||||
|
||||
private Schema parse(String filename, String text) throws Exception {
|
||||
return parse(tmp.newFile(filename), text);
|
||||
}
|
||||
|
||||
private Schema parse(File file, String text) throws Exception {
|
||||
try (BufferedSink sink = Okio.buffer(Okio.sink(file))) {
|
||||
sink.writeUtf8(text);
|
||||
sink.flush();
|
||||
}
|
||||
|
||||
Loader loader = new Loader();
|
||||
loader.addThriftFile(file.toPath().toAbsolutePath().normalize());
|
||||
|
||||
return loader.load();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,503 @@
|
|||
/*
|
||||
* Thrifty
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the License);
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
||||
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
*
|
||||
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.microsoft.thrifty.gen
|
||||
|
||||
import com.microsoft.thrifty.schema.Loader
|
||||
import com.microsoft.thrifty.schema.Schema
|
||||
import com.squareup.javapoet.JavaFile
|
||||
import okio.Okio
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
|
||||
import javax.tools.JavaFileObject
|
||||
import java.io.File
|
||||
import java.util.ArrayList
|
||||
|
||||
import com.google.common.truth.Truth.assertAbout
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.google.testing.compile.JavaSourceSubjectFactory.javaSource
|
||||
import com.google.testing.compile.JavaSourcesSubjectFactory.javaSources
|
||||
|
||||
/**
|
||||
* These tests ensure that various constructs produce valid Java code.
|
||||
* They don't test *anything* about the correctness of the code!
|
||||
*
|
||||
* Semantic tests can be found in `thrifty-integration-tests`.
|
||||
*/
|
||||
class ThriftyCodeGeneratorTest {
|
||||
@get:Rule val tmp = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun redactedToStringCompiles() {
|
||||
val thrift = """
|
||||
namespace java test
|
||||
|
||||
struct foo {
|
||||
1: required list<string> (python.immutable) ssn (redacted)
|
||||
}
|
||||
"""
|
||||
|
||||
val schema = parse("foo.thrift", thrift)
|
||||
|
||||
val gen = ThriftyCodeGenerator(schema)
|
||||
val javaFiles = gen.generateTypes()
|
||||
|
||||
assertThat(javaFiles).hasSize(1)
|
||||
|
||||
assertAbout(javaSource())
|
||||
.that(javaFiles[0].toJavaFileObject())
|
||||
.compilesWithoutError()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun enumGeneration() {
|
||||
val thrift = """
|
||||
namespace java enums
|
||||
|
||||
// a generated enum
|
||||
enum BuildStatus {
|
||||
OK = 0,
|
||||
FAIL = 1
|
||||
}
|
||||
"""
|
||||
|
||||
val schema = parse("enum.thrift", thrift)
|
||||
val gen = ThriftyCodeGenerator(schema)
|
||||
val java = gen.generateTypes()
|
||||
|
||||
assertThat(java).hasSize(1)
|
||||
|
||||
assertAbout(javaSource())
|
||||
.that(java[0].toJavaFileObject())
|
||||
.compilesWithoutError()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fieldWithConstInitializer() {
|
||||
val thrift = """
|
||||
namespace java fields
|
||||
|
||||
const i32 TEST_CONST = 5
|
||||
|
||||
struct HasDefaultValue {
|
||||
1: required i32 foo = TEST_CONST
|
||||
}
|
||||
"""
|
||||
|
||||
val schema = parse("fields.thrift", thrift)
|
||||
val gen = ThriftyCodeGenerator(schema)
|
||||
val java = gen.generateTypes()
|
||||
val jfos = ArrayList<JavaFileObject>()
|
||||
|
||||
var found = false
|
||||
for (javaFile in java) {
|
||||
if (javaFile.toString().contains("foo = fields.Constants.TEST_CONST;")) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
assertThat(found).named("Const reference was found in field assignment").isTrue()
|
||||
|
||||
assertThat(java).hasSize(2)
|
||||
for (javaFile in java) {
|
||||
jfos.add(javaFile.toJavaFileObject())
|
||||
}
|
||||
|
||||
assertAbout(javaSources())
|
||||
.that(jfos)
|
||||
.compilesWithoutError()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deprecatedStructWithComment() {
|
||||
val thrift = """
|
||||
namespace java deprecated
|
||||
|
||||
/** @deprecated */
|
||||
struct Foo {}
|
||||
"""
|
||||
|
||||
val schema = parse("dep.thrift", thrift)
|
||||
val gen = ThriftyCodeGenerator(schema)
|
||||
val java = gen.generateTypes()
|
||||
val jfos = ArrayList<JavaFileObject>(java.size)
|
||||
|
||||
for (javaFile in java) {
|
||||
jfos.add(javaFile.toJavaFileObject())
|
||||
}
|
||||
|
||||
assertAbout(javaSources()).that(jfos).compilesWithoutError()
|
||||
|
||||
val file = java[0].toString()
|
||||
|
||||
assertThat(file).contains("@Deprecated") // note the change in case
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deprecatedStructWithAnnotation() {
|
||||
val thrift = """
|
||||
namespace java deprecated
|
||||
|
||||
struct Foo {} (deprecated)
|
||||
"""
|
||||
|
||||
val schema = parse("dep.thrift", thrift)
|
||||
val gen = ThriftyCodeGenerator(schema)
|
||||
val java = gen.generateTypes()
|
||||
val jfos = ArrayList<JavaFileObject>(java.size)
|
||||
|
||||
for (javaFile in java) {
|
||||
jfos.add(javaFile.toJavaFileObject())
|
||||
}
|
||||
|
||||
assertAbout(javaSources()).that(jfos).compilesWithoutError()
|
||||
|
||||
val file = java[0].toString()
|
||||
|
||||
assertThat(file).contains("@Deprecated")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deprecatedEnum() {
|
||||
val thrift = """
|
||||
namespace java deprecated
|
||||
|
||||
enum Foo {ONE = 1} (deprecated)
|
||||
"""
|
||||
|
||||
val schema = parse("enum.thrift", thrift)
|
||||
val gen = ThriftyCodeGenerator(schema)
|
||||
val javaFiles = gen.generateTypes()
|
||||
val file = javaFiles[0]
|
||||
|
||||
val java = file.toString()
|
||||
|
||||
assertThat(java).contains("@Deprecated\npublic enum Foo")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deprecatedEnumMember() {
|
||||
val thrift = """
|
||||
namespace java deprecated
|
||||
|
||||
enum Foo {
|
||||
ONE = 1 (deprecated)
|
||||
}
|
||||
"""
|
||||
|
||||
val schema = parse("enum.thrift", thrift)
|
||||
val gen = ThriftyCodeGenerator(schema)
|
||||
val javaFiles = gen.generateTypes()
|
||||
val file = javaFiles[0]
|
||||
|
||||
val java = file.toString()
|
||||
|
||||
assertThat(java).contains("@Deprecated\n ONE(1)")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stringConstantsAreNotUnboxed() {
|
||||
val thrift = """
|
||||
namespace java string_consts
|
||||
|
||||
const string STR = 'foo'
|
||||
"""
|
||||
|
||||
// This check validates that we can successfully compile a string constant,
|
||||
// and that we don't regress on issue #77.
|
||||
//
|
||||
// The regression here would be if an UnsupportedOperationException were thrown,
|
||||
// due to a logic bug where we attempt to unbox TypeNames.STRING.
|
||||
compile("string_consts.thrift", thrift)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun byteConstants() {
|
||||
val thrift = """
|
||||
namespace java byte_consts
|
||||
|
||||
const i8 I8 = 123
|
||||
"""
|
||||
|
||||
val file = compile("bytes.thrift", thrift)[0]
|
||||
assertThat(file.toString()).isEqualTo("""
|
||||
package byte_consts;
|
||||
|
||||
public final class Constants {
|
||||
public static final byte I8 = (byte) 123;
|
||||
|
||||
private Constants() {
|
||||
// no instances
|
||||
}
|
||||
}
|
||||
|
||||
""".trimRawString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shortConstants() {
|
||||
val thrift = """
|
||||
namespace java short_consts
|
||||
|
||||
const i16 INT = 0xFF
|
||||
"""
|
||||
|
||||
val file = compile("shorts.thrift", thrift)[0]
|
||||
assertThat(file.toString()).isEqualTo("""
|
||||
package short_consts;
|
||||
|
||||
public final class Constants {
|
||||
public static final short INT = (short) 0xFF;
|
||||
|
||||
private Constants() {
|
||||
// no instances
|
||||
}
|
||||
}
|
||||
|
||||
""".trimRawString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun intConstants() {
|
||||
val thrift = """
|
||||
namespace java int_consts
|
||||
|
||||
const i32 INT = 12345
|
||||
"""
|
||||
|
||||
val file = compile("ints.thrift", thrift)[0]
|
||||
assertThat(file.toString()).isEqualTo("""
|
||||
package int_consts;
|
||||
|
||||
public final class Constants {
|
||||
public static final int INT = 12345;
|
||||
|
||||
private Constants() {
|
||||
// no instances
|
||||
}
|
||||
}
|
||||
|
||||
""".trimRawString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun longConstants() {
|
||||
val thrift = """
|
||||
namespace java long_consts
|
||||
|
||||
const i64 LONG = 0xFFFFFFFFFF
|
||||
"""
|
||||
|
||||
val file = compile("longs.thrift", thrift)[0]
|
||||
assertThat(file.toString()).isEqualTo("""
|
||||
package long_consts;
|
||||
|
||||
public final class Constants {
|
||||
public static final long LONG = 0xFFFFFFFFFFL;
|
||||
|
||||
private Constants() {
|
||||
// no instances
|
||||
}
|
||||
}
|
||||
|
||||
""".trimRawString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun numberEqualityWarningsAreSuppressedForI32() {
|
||||
val thrift = """
|
||||
namespace java number_equality
|
||||
|
||||
struct HasNumber {
|
||||
1: optional i32 n;
|
||||
}
|
||||
"""
|
||||
|
||||
val expectedEqualsMethod = """
|
||||
@Override
|
||||
@SuppressWarnings("NumberEquality")
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) return true;
|
||||
if (other == null) return false;
|
||||
if (!(other instanceof HasNumber)) return false;
|
||||
HasNumber that = (HasNumber) other;
|
||||
return (this.n == that.n || (this.n != null && this.n.equals(that.n)));
|
||||
}
|
||||
""".trimStart('\n')
|
||||
|
||||
val file = compile("numberEquality.thrift", thrift)[0]
|
||||
|
||||
assertThat(file.toString()).contains(expectedEqualsMethod)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun constantsWithSigilsInJavadoc() {
|
||||
val thrift = """
|
||||
namespace java sigils.consts
|
||||
|
||||
// This comment has ${"$"}Dollar ${"$"}Signs
|
||||
const i32 INT = 12345
|
||||
"""
|
||||
|
||||
val expectedFormat = """
|
||||
package sigils.consts;
|
||||
|
||||
public final class Constants {
|
||||
/**
|
||||
* This comment has ${"$"}Dollar ${"$"}Signs
|
||||
*
|
||||
*
|
||||
* Generated from: %s at 4:1
|
||||
*/
|
||||
public static final int INT = 12345;
|
||||
|
||||
private Constants() {
|
||||
// no instances
|
||||
}
|
||||
}
|
||||
|
||||
""".trimRawString()
|
||||
|
||||
val thriftFile = tmp.newFile("sigils_consts.thrift")
|
||||
val javaFile = compile(thriftFile, thrift)[0]
|
||||
|
||||
val javaText = javaFile.toString()
|
||||
val expected = String.format(expectedFormat, thriftFile.absolutePath)
|
||||
|
||||
assertThat(javaText).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun enumsWithSigilsInJavadoc() {
|
||||
val thrift = """
|
||||
namespace java sigils.enums
|
||||
|
||||
// ${"$"}Sigil here
|
||||
enum TestEnum {
|
||||
// ${"$"}Good, here's another
|
||||
FOO
|
||||
}
|
||||
"""
|
||||
|
||||
val expected = """
|
||||
package sigils.enums;
|
||||
|
||||
/**
|
||||
* ${"$"}Sigil here
|
||||
*/
|
||||
public enum TestEnum {
|
||||
/**
|
||||
* ${"$"}Good, here's another
|
||||
*/
|
||||
FOO(0);
|
||||
|
||||
public final int value;
|
||||
|
||||
TestEnum(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static TestEnum findByValue(int value) {
|
||||
switch (value) {
|
||||
case 0: return FOO;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
""".trimRawString()
|
||||
|
||||
val thriftFile = tmp.newFile("sigil_enums.thrift")
|
||||
val javaFile = compile(thriftFile, thrift)[0]
|
||||
|
||||
assertThat(javaFile.toString()).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun structsWithSigilsInJavadoc() {
|
||||
val thrift = """
|
||||
namespace java sigils.structs
|
||||
|
||||
// ${"$"}A ${"$"}B ${"$"}C ${"$"}D ${"$"}E
|
||||
struct Foo {
|
||||
// ${"$"}F ${"$"}G ${"$"}H ${"$"}I ${"$"}J
|
||||
1: required string bar
|
||||
}
|
||||
"""
|
||||
|
||||
val expectedClassJavadoc = """
|
||||
/**
|
||||
* ${"$"}A ${"$"}B ${"$"}C ${"$"}D ${"$"}E
|
||||
*/
|
||||
public final class Foo implements Struct {
|
||||
""".trimRawString()
|
||||
|
||||
val expectedFieldJavadoc = """
|
||||
/**
|
||||
* ${"$"}F ${"$"}G ${"$"}H ${"$"}I ${"$"}J
|
||||
*/
|
||||
@ThriftField(
|
||||
fieldId = 1,
|
||||
isRequired = true
|
||||
)
|
||||
public final String bar;
|
||||
"""
|
||||
|
||||
val thriftFile = tmp.newFile("sigil_enums.thrift")
|
||||
val javaFile = compile(thriftFile, thrift)[0]
|
||||
assertThat(javaFile.toString()).contains(expectedClassJavadoc)
|
||||
assertThat(javaFile.toString()).contains(expectedFieldJavadoc)
|
||||
}
|
||||
|
||||
private fun compile(filename: String, text: String): List<JavaFile> {
|
||||
val schema = parse(filename, text)
|
||||
val gen = ThriftyCodeGenerator(schema).emitFileComment(false)
|
||||
return gen.generateTypes()
|
||||
}
|
||||
|
||||
private fun compile(file: File, text: String): List<JavaFile> {
|
||||
val schema = parse(file, text)
|
||||
val gen = ThriftyCodeGenerator(schema).emitFileComment(false)
|
||||
return gen.generateTypes()
|
||||
}
|
||||
|
||||
private fun parse(filename: String, text: String): Schema {
|
||||
return parse(tmp.newFile(filename), text)
|
||||
}
|
||||
|
||||
private fun parse(file: File, text: String): Schema {
|
||||
|
||||
val trimmed = text.trimStart('\n').trimIndent()
|
||||
|
||||
Okio.buffer(Okio.sink(file)).use { sink ->
|
||||
sink.writeUtf8(trimmed)
|
||||
sink.flush()
|
||||
}
|
||||
|
||||
val loader = Loader()
|
||||
loader.addThriftFile(file.toPath().toAbsolutePath().normalize())
|
||||
|
||||
return loader.load()
|
||||
}
|
||||
|
||||
private fun String.trimRawString() = this.trimStart('\n').trimIndent()
|
||||
}
|
Загрузка…
Ссылка в новой задаче