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:
Ben Bader 2018-06-03 22:38:48 -07:00 коммит произвёл GitHub
Родитель e1b1f702e0
Коммит 6d8187bd36
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
23 изменённых файлов: 3284 добавлений и 3623 удалений

Просмотреть файл

@ -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
gradle/wrapper/gradle-wrapper.properties поставляемый
Просмотреть файл

@ -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()
}