ProgramLoader.java
/*
* Copyright 2016 Providence Authors
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.morimekta.providence.reflect;
import net.morimekta.providence.PMessage;
import net.morimekta.providence.PMessageVariant;
import net.morimekta.providence.PType;
import net.morimekta.providence.descriptor.PAnnotation;
import net.morimekta.providence.descriptor.PContainer;
import net.morimekta.providence.descriptor.PDeclaredDescriptor;
import net.morimekta.providence.descriptor.PDescriptor;
import net.morimekta.providence.descriptor.PDescriptorProvider;
import net.morimekta.providence.descriptor.PEnumDescriptor;
import net.morimekta.providence.descriptor.PField;
import net.morimekta.providence.descriptor.PInterfaceDescriptorProvider;
import net.morimekta.providence.descriptor.PMessageDescriptor;
import net.morimekta.providence.descriptor.PPrimitive;
import net.morimekta.providence.descriptor.PRequirement;
import net.morimekta.providence.descriptor.PService;
import net.morimekta.providence.descriptor.PServiceMethod;
import net.morimekta.providence.descriptor.PServiceProvider;
import net.morimekta.providence.descriptor.PStructDescriptor;
import net.morimekta.providence.descriptor.PStructDescriptorProvider;
import net.morimekta.providence.reflect.contained.CConst;
import net.morimekta.providence.reflect.contained.CEnumDescriptor;
import net.morimekta.providence.reflect.contained.CEnumValue;
import net.morimekta.providence.reflect.contained.CException;
import net.morimekta.providence.reflect.contained.CExceptionDescriptor;
import net.morimekta.providence.reflect.contained.CField;
import net.morimekta.providence.reflect.contained.CInterface;
import net.morimekta.providence.reflect.contained.CInterfaceDescriptor;
import net.morimekta.providence.reflect.contained.CProgram;
import net.morimekta.providence.reflect.contained.CService;
import net.morimekta.providence.reflect.contained.CServiceMethod;
import net.morimekta.providence.reflect.contained.CStruct;
import net.morimekta.providence.reflect.contained.CStructDescriptor;
import net.morimekta.providence.reflect.contained.CUnion;
import net.morimekta.providence.reflect.contained.CUnionDescriptor;
import net.morimekta.providence.reflect.model.AnnotationDeclaration;
import net.morimekta.providence.reflect.model.ConstDeclaration;
import net.morimekta.providence.reflect.model.Declaration;
import net.morimekta.providence.reflect.model.EnumDeclaration;
import net.morimekta.providence.reflect.model.EnumValueDeclaration;
import net.morimekta.providence.reflect.model.FieldDeclaration;
import net.morimekta.providence.reflect.model.IncludeDeclaration;
import net.morimekta.providence.reflect.model.MessageDeclaration;
import net.morimekta.providence.reflect.model.MethodDeclaration;
import net.morimekta.providence.reflect.model.NamespaceDeclaration;
import net.morimekta.providence.reflect.model.ProgramDeclaration;
import net.morimekta.providence.reflect.model.ServiceDeclaration;
import net.morimekta.providence.reflect.model.TypedefDeclaration;
import net.morimekta.providence.reflect.parser.ThriftException;
import net.morimekta.providence.reflect.parser.ThriftParser;
import net.morimekta.providence.reflect.parser.ThriftToken;
import net.morimekta.providence.reflect.util.ConstValueProvider;
import net.morimekta.providence.types.TypeReference;
import net.morimekta.providence.types.TypeRegistry;
import net.morimekta.util.Strings;
import net.morimekta.util.collect.UnmodifiableList;
import net.morimekta.util.collect.UnmodifiableMap;
import net.morimekta.util.lexer.UncheckedLexerException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import static net.morimekta.providence.reflect.util.ReflectionUtils.isApacheThriftFile;
import static net.morimekta.providence.reflect.util.ReflectionUtils.isProvidenceFile;
import static net.morimekta.providence.reflect.util.ReflectionUtils.isThriftBasedFileSyntax;
import static net.morimekta.providence.reflect.util.ReflectionUtils.longestCommonPrefixPath;
import static net.morimekta.providence.reflect.util.ReflectionUtils.stripCommonPrefix;
import static net.morimekta.providence.types.TypeReference.parseType;
import static net.morimekta.providence.types.TypeReference.ref;
import static net.morimekta.util.FileUtil.readCanonicalPath;
import static net.morimekta.util.collect.UnmodifiableList.copyOf;
import static net.morimekta.util.collect.UnmodifiableList.listOf;
/**
* Class that loads programs and packages them into a contained type structure.
*/
public class ProgramLoader {
private final GlobalRegistry globalRegistry;
private final ThriftParser thriftParser;
private final ThriftParser providenceParser;
/**
* Constructor with lax and default behavior.
*/
public ProgramLoader() {
this(false,
false,
true);
}
/**
* Constructor with specified options.
*
* @param requireFieldId If field IDs are required.
* @param requireEnumValue If enum values are required.
* @param allowLanguageReservedNames If language-reserved names are allowed.
*/
public ProgramLoader(boolean requireFieldId,
boolean requireEnumValue,
boolean allowLanguageReservedNames) {
this(new GlobalRegistry(),
requireFieldId,
requireEnumValue,
allowLanguageReservedNames);
}
private ProgramLoader(@Nonnull GlobalRegistry registry,
boolean requireFieldId,
boolean requireEnumValue,
boolean allowLanguageReservedNames) {
this.globalRegistry = registry;
this.thriftParser = new ThriftParser(requireFieldId,
requireEnumValue,
allowLanguageReservedNames,
false);
this.providenceParser = new ThriftParser(requireFieldId,
requireEnumValue,
allowLanguageReservedNames,
true);
}
public ProgramRegistry load(File file) throws IOException {
return load(file.toPath());
}
/**
* Load a thrift definition from file including all it's dependencies.
*
* @param file The file to load.
* @return The loaded contained document.
* @throws IOException If the file could not be read or parsed.
*/
public ProgramRegistry load(Path file) throws IOException {
try {
file = readCanonicalPath(file.toAbsolutePath().normalize());
if (!Files.exists(file)) {
throw new IllegalArgumentException("No such file " + file);
}
if (!Files.isRegularFile(file)) {
throw new IllegalArgumentException(
"Unable to load thrift program: " + file + " is not a file.");
}
return loadInternal(file, listOf());
} catch (ThriftException e) {
if (e.getFile() == null) {
e.setFile(file.getFileName().toString());
}
throw e;
}
}
/**
* Load internally.
*
* @param inFile The canonical normalized recolved path to parse.
* @param loadStack The files loaded as part of getting here.
* @return The loaded program.
* @throws IOException If loading the program failed.
*/
private ProgramRegistry loadInternal(Path inFile, List<Path> loadStack) throws IOException {
loadStack = new ArrayList<>(loadStack);
loadStack.add(inFile);
ProgramRegistry registry = this.globalRegistry.registryForPath(inFile.toString());
if (registry.getProgram() != null) {
return registry;
}
ProgramDeclaration programType;
try (InputStream in = new BufferedInputStream(Files.newInputStream(inFile))) {
if (isThriftBasedFileSyntax(inFile)) {
if (isProvidenceFile(inFile)) {
programType = providenceParser.parse(in, inFile);
} else {
programType = thriftParser.parse(in, inFile);
}
} else {
throw new IllegalArgumentException("Not a known providence source format: " + inFile);
}
}
for (IncludeDeclaration include : programType.getIncludes()) {
String includePath = include.getFilePath();
Path location = inFile.getParent().resolve(includePath).normalize();
if (!Files.exists(location)) {
throw new ThriftException(
include.getFilePathToken(),
"No such file: " + location.toString());
}
Path canonicalPath = readCanonicalPath(location.toAbsolutePath().normalize());
if (!Files.exists(canonicalPath)) {
throw new ThriftException(
include.getFilePathToken(),
"No such file: " + canonicalPath.toString());
}
if (!Files.isRegularFile(canonicalPath)) {
throw new ThriftException(
include.getFilePathToken(),
"Not a file: " + canonicalPath.toString());
}
if (loadStack.contains(canonicalPath)) {
// circular includes.
// Only show the circular includes, not the path to get there.
while (!loadStack.get(0).equals(canonicalPath)) {
loadStack.remove(0);
}
loadStack.add(canonicalPath);
List<String> loadStackAsString = loadStack.stream().map(Path::toString).collect(Collectors.toList());
String prefix = longestCommonPrefixPath(loadStackAsString);
if (prefix.length() > 0) {
loadStackAsString = stripCommonPrefix(loadStackAsString);
}
throw new ThriftException(
include.getFilePathToken(),
"Circular includes: " + String.join(" -> ", loadStackAsString));
}
if (isThriftBasedFileSyntax(canonicalPath)) {
// Apache thrift cannot include providence files.
if (isApacheThriftFile(inFile) &&
isProvidenceFile(canonicalPath)) {
throw new ThriftException(
include.getFilePathToken(),
"Not a thrift file: " + location.getFileName().toString());
}
} else {
throw new ThriftException(
include.getFilePathToken(),
"Not a valid include format: " + location.getFileName().toString());
}
registry.addInclude(include.getProgramName(), loadInternal(location, copyOf(loadStack)));
}
// Now everything it depends on is loaded.
CProgram program = convert(inFile, programType);
registry.setProgramType(programType);
registry.setProgram(program);
return registry;
}
/**
* @return The local registry.
*/
public GlobalRegistry getGlobalRegistry() {
return globalRegistry;
}
/**
* Convert document model to declared document.
*
* @param path The program file path.
* @param program Program model to convert.
* @return The declared thrift document.
* @throws ThriftException When validation or const parsing fails.
*/
private CProgram convert(Path path, ProgramDeclaration program) throws ThriftException {
UnmodifiableList.Builder<PDeclaredDescriptor<?>> declaredTypes = UnmodifiableList.builder();
UnmodifiableList.Builder<CConst> constants = UnmodifiableList.builder();
UnmodifiableMap.Builder<String, String> typedefs = UnmodifiableMap.builder();
UnmodifiableList.Builder<CService> services = UnmodifiableList.builder();
ProgramRegistry registry = globalRegistry.registryForPath(path.toString());
Path dir = path.getParent();
for (IncludeDeclaration include : program.getIncludes()) {
Path includePath = dir.resolve(include.getFilePath());
try {
includePath = readCanonicalPath(includePath);
} catch (IOException e) {
throw new ThriftException(include.getFilePathToken(),
"Bad include path: %s", e.getMessage());
}
registry.addInclude(include.getProgramName(), globalRegistry.registryForPath(includePath.toString()));
}
String fileName = path.getFileName().toString();
for (Declaration declaration : program.getDeclarationList()) {
if (declaration instanceof EnumDeclaration) {
registerEnum(program, (EnumDeclaration) declaration, declaredTypes, registry);
} else if (declaration instanceof MessageDeclaration) {
registerMessage(fileName, program, (MessageDeclaration) declaration, declaredTypes, registry);
} else if (declaration instanceof ServiceDeclaration) {
registerService(fileName, program, (ServiceDeclaration) declaration, services, registry);
} else if (declaration instanceof TypedefDeclaration) {
String targetType = ((TypedefDeclaration) declaration).getType();
typedefs.put(declaration.getName(), targetType);
registry.registerTypedef(
ref(program.getProgramName(), declaration.getName()),
parseType(program.getProgramName(), targetType));
} else if (declaration instanceof ConstDeclaration) {
TypeReference constRef = ref(program.getProgramName(), declaration.getName());
TypeReference typeRef = parseType(program.getProgramName(), ((ConstDeclaration) declaration).getType());
ConstValueProvider valueProvider = new ConstValueProvider(
registry,
program.getProgramName(),
typeRef,
((ConstDeclaration) declaration).getValueTokens());
PDescriptorProvider typeProvider = registry.getTypeProvider(typeRef);
constants.add(new CConst(declaration.getDocumentation(),
program.getProgramName(),
declaration.getName(),
typeProvider,
valueProvider,
makeAnnoatations(declaration.getAnnotations())));
registry.registerConstant(constRef, valueProvider);
}
}
try {
for (Declaration declaration : program.getDeclarationList()) {
TypeReference reference = ref(program.getProgramName(), declaration.getName());
if (declaration instanceof EnumDeclaration) {
validateEnum((EnumDeclaration) declaration);
} else if (declaration instanceof MessageDeclaration) {
validateMessage(fileName, program, registry, (MessageDeclaration) declaration);
} else if (declaration instanceof ServiceDeclaration) {
PService service = registry.requireService(reference);
service.getExtendsService();
for (PServiceMethod method : service.getMethods()) {
method.getResponseType();
method.getResponseType();
}
} else if (declaration instanceof TypedefDeclaration) {
registry.getTypeProvider(reference).descriptor();
} else if (declaration instanceof ConstDeclaration) {
try {
registry.getConstantValue(reference);
} catch (UncheckedLexerException e) {
throw e.getCause();
}
}
}
} catch (ThriftException e) {
throw e;
} catch (Exception e) {
throw new ThriftException(e, e.getMessage());
}
return new CProgram(path.toString(),
program.getDocumentation(),
program.getProgramName(),
makeNamespaces(program.getNamespaces()),
getIncludedProgramNames(program),
program.getIncludes()
.stream()
.map(IncludeDeclaration::getFilePath)
.collect(Collectors.toSet()),
typedefs.build(),
declaredTypes.build(),
services.build(),
constants.build());
}
private void validateEnum(EnumDeclaration declaration) throws ThriftException {
Map<String, EnumValueDeclaration> forName = new HashMap<>();
Map<Integer, EnumValueDeclaration> forId = new HashMap<>();
for (EnumValueDeclaration val : declaration.getValues()) {
String normalizedName = Strings.c_case(val.getName()).toUpperCase(Locale.US);
if (forName.containsKey(normalizedName)) {
ThriftToken otherName = forName.get(normalizedName).getNameToken();
throw new ThriftException(val.getNameToken(), "Enum value with name already exists at line %d", otherName.lineNo());
}
forName.put(normalizedName, val);
if (val.getIdToken() != null) {
int id = val.getId();
if (forId.containsKey(id)) {
ThriftToken otherId = forId.get(id).getIdToken();
throw new ThriftException(val.getNameToken(), "Enum value with ID %d already exists at line %d",
id, otherId.lineNo());
}
forId.put(id, val);
}
}
}
private Map<String, String> makeNamespaces(List<NamespaceDeclaration> namespaces) {
Map<String, String> map = new HashMap<>();
for (NamespaceDeclaration nd : namespaces) {
map.put(nd.getLanguage(), nd.getNamespace());
}
return map;
}
private void validateMessage(String fileName,
ProgramDeclaration program,
ProgramRegistry registry,
MessageDeclaration mt) throws ThriftException {
PMessageDescriptor<?> descriptor = registry.requireMessageType(
ref(program.getProgramName(), mt.getName()));
try {
CInterfaceDescriptor iFace = (CInterfaceDescriptor) descriptor.getImplementing();
if (iFace != null) {
iFace.addPossibleType(descriptor);
}
} catch (ClassCastException e) {
throw new ThriftException(mt.getImplementing(),
"Bad implements type: %s is not an interface.",
mt.getImplementing())
.setFile(fileName);
} catch (IllegalArgumentException | IllegalStateException e) {
// No such type.
throw new ThriftException(mt.getImplementing(), e.getMessage())
.setFile(fileName);
}
Map<String, FieldDeclaration> forName = new HashMap<>();
Map<Integer, FieldDeclaration> forId = new HashMap<>();
for (PField<?> field : descriptor.getFields()) {
FieldDeclaration ft = findField(mt.getFields(), field.getName());
if (ft == null) throw new IllegalArgumentException("Impossible");
String normalizedName = Strings.camelCase(ft.getName()).toUpperCase(Locale.US);
if (forName.containsKey(normalizedName)) {
ThriftToken other = forName.get(normalizedName).getNameToken();
throw new ThriftException(ft.getNameToken(),
"Field with name '%s' already exists on line %d",
ft.getName(),
other.lineNo())
.setFile(fileName);
}
forName.put(normalizedName, ft);
if (ft.getIdToken() != null) {
int id = ft.getId();
if (forId.containsKey(id)) {
ThriftToken other = forId.get(id).getIdToken();
throw new ThriftException(ft.getIdToken(),
"Field with id %d already exists on line %d",
ft.getId(),
other.lineNo())
.setFile(fileName);
}
forId.put(id, ft);
}
try {
field.getDescriptor();
} catch (IllegalArgumentException | IllegalStateException e) {
// Unknown field type.
ThriftToken type1 = ft.getTypeToken();
throw new ThriftException(type1, e.getMessage())
.initCause(e)
.setFile(fileName);
}
try {
field.getArgumentsType();
} catch (IllegalArgumentException | IllegalStateException e) {
AnnotationDeclaration args = findAnnotation(ft.getAnnotations(), PAnnotation.ARGUMENTS_TYPE);
if (args == null || args.getValueToken() == null) throw new IllegalStateException("");
throw new ThriftException(args.getValueToken(),
e.getMessage())
.initCause(e)
.setFile(fileName);
}
AnnotationDeclaration refEnum = findAnnotation(ft.getAnnotations(), PAnnotation.REF_ENUM);
if (refEnum != null) {
ThriftToken refEnumValue = refEnum.getValueToken();
if (refEnum.getValue().isEmpty()) {
throw new ThriftException(refEnumValue,
"Empty type '%s' for ref.enum for '%s' in %s",
refEnum.getValue(),
field.getName(),
descriptor.getName())
.setFile(fileName);
}
if (PPrimitive.findByName(refEnum.getValue()) != null) {
throw new ThriftException(refEnumValue,
"Primitive type '%s' for ref.enum for '%s' in %s",
refEnum.getValue(),
field.getName(),
descriptor.getName())
.setFile(fileName);
}
try {
PDeclaredDescriptor<?> dd = registry
.getDeclaredType(parseType(program.getProgramName(), refEnum.getValue()))
.orElseThrow(() -> new ThriftException(refEnumValue,
"Unknown ref.enum type '%s' for '%s' in %s",
refEnum, field.getName(),
descriptor.getName())
.setFile(fileName));
if (!(dd instanceof PEnumDescriptor)) {
throw new ThriftException(refEnumValue,
"'%s' is not an enum for ref.enum '%s' in %s",
refEnum.getValue(),
field.getName(),
descriptor.getName())
.setFile(fileName);
}
} catch (IllegalArgumentException e) {
throw new ThriftException(refEnumValue,
e.getMessage());
}
}
field.getDefaultValue();
}
try {
switch (descriptor.getVariant()) {
case STRUCT: {
CStructDescriptor sd = (CStructDescriptor) descriptor;
if (sd.getImplementing() != null) {
CInterfaceDescriptor id = sd.getImplementing();
for (PField<?> iField : id.getFields()) {
CField<?> found = sd.findFieldByName(iField.getName());
FieldDeclaration ft = null;
for (FieldDeclaration f : mt.getFields()) {
if (f.getName().equals(iField.getName())) {
ft = f;
break;
}
}
if (found == null || ft == null) {
throw new ThriftException(mt.getVariantToken(),
"Missing interface field '%s' in %s implementing %s",
iField.getName(),
sd.getName(),
id.getQualifiedName(program.getProgramName()))
.setFile(fileName);
}
if (!found.getDescriptor().equals(iField.getDescriptor())) {
throw new ThriftException(ft.getTypeToken(),
"Type mismatch for field '%s' in %s implementing %s, %s != %s",
iField.getName(),
sd.getName(),
id.getQualifiedName(program.getProgramName()),
found.getDescriptor().getQualifiedName(),
iField.getDescriptor().getQualifiedName())
.setFile(fileName);
}
if (found.getRequirement() != iField.getRequirement()) {
throw new ThriftException(ft.getRequirementToken(),
"Requirement mismatch for field '%s' in %s implementing %s, %s != %s",
iField.getName(),
sd.getName(),
id.getQualifiedName(program.getProgramName()),
found.getRequirement(),
iField.getRequirement())
.setFile(fileName);
}
}
}
break;
}
case UNION: {
CUnionDescriptor sd = (CUnionDescriptor) descriptor;
if (sd.getImplementing() != null) {
CInterfaceDescriptor id = sd.getImplementing();
for (CField<?> field : sd.getFields()) {
FieldDeclaration cft = mt.getFields()
.stream().filter(f -> f.getName().equals(field.getName()))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Unable to find field source"));
PDescriptor pd = field.getDescriptor();
if (pd.getType() != PType.MESSAGE) {
throw new ThriftException(cft.getTypeToken(),
"Field %s in union %s of %s is not a message.",
field.getName(),
sd.getQualifiedName(),
id.getQualifiedName(program.getProgramName()))
.setFile(fileName);
}
PMessageDescriptor<?> pmd = (PMessageDescriptor<?>) pd;
if (pmd.getImplementing() == null ||
!pmd.getImplementing().equals(id)) {
throw new ThriftException(cft.getTypeToken(),
"Field '%s' in union %s of %s does not implement required interface.",
field.getName(),
sd.getQualifiedName(),
id.getQualifiedName(program.getProgramName()))
.setFile(fileName);
}
}
}
break;
}
}
} catch (IllegalArgumentException | IllegalStateException e) {
throw new ThriftException(mt.getVariantToken(), e.getMessage())
.initCause(e)
.setFile(fileName);
}
}
private void registerService(String path,
ProgramDeclaration program,
ServiceDeclaration serviceType,
UnmodifiableList.Builder<CService> services,
ProgramRegistry registry)
throws ThriftException {
UnmodifiableList.Builder<CServiceMethod> methodBuilder = UnmodifiableList.builder();
TypeReference serviceRef = ref(program.getProgramName(), serviceType.getName());
for (MethodDeclaration sm : serviceType.getMethods()) {
List<CField<CStruct>> rqFields = new ArrayList<>();
CStructDescriptor request;
boolean protoStub = false;
if (sm.getRequestTypeToken() != null) {
TypeReference requestRef = ref(program.getProgramName(), sm.getRequestTypeToken().toString());
try {
PDescriptor descriptor = globalRegistry.requireDeclaredType(requestRef);
if (!(descriptor instanceof CStructDescriptor)) {
throw new ThriftException(
sm.getRequestTypeToken(),
"Not a struct type for proto stub method request type");
}
request = (CStructDescriptor) descriptor;
protoStub = true;
} catch (IllegalArgumentException e) {
// Not a declared type.
throw new ThriftException(sm.getRequestTypeToken(), e.getMessage()).initCause(e);
}
} else {
for (FieldDeclaration field : sm.getParams()) {
rqFields.add(makeField(registry,
path,
program.getProgramName(),
field,
PMessageVariant.STRUCT,
null));
}
request = new CStructDescriptor(null,
program.getProgramName(),
serviceType.getName() + '.' +
sm.getName() + ".request",
rqFields,
null,
null);
}
CUnionDescriptor response = null;
if (!sm.isOneWay()) {
List<CField<CUnion>> rsFields = new ArrayList<>();
CField<CUnion> success = new CField<>(
null,
0,
PRequirement.OPTIONAL,
"success",
registry.getTypeProvider(parseType(program.getProgramName(), sm.getReturnType())),
null,
null,
makeAnnoatations(sm.getAnnotations()),
null);
if (protoStub && !(success.getDescriptor() instanceof PStructDescriptor)) {
throw new ThriftException(
// TODO: Point to whole of return type?
sm.getReturnTypeTokens().get(0),
"Response type not a struct on proto stub method");
}
rsFields.add(success);
if (sm.getThrowing() != null) {
for (FieldDeclaration field : sm.getThrowing()) {
rsFields.add(makeField(registry,
path,
program.getProgramName(),
field,
PMessageVariant.UNION,
null));
}
}
response = new CUnionDescriptor(null,
program.getProgramName(),
serviceType.getName() + '.' +
sm.getName() + ".response",
rsFields,
null,
null);
} else if (protoStub) {
throw new ThriftException(
sm.getRequestTypeToken(),
"Proto stubs may not be oneway");
}
CServiceMethod method = new CServiceMethod(
sm.getDocumentation(),
sm.getName(),
sm.isOneWay(),
protoStub,
request,
response,
makeAnnoatations(sm.getAnnotations()),
registry.getServiceProvider(serviceRef));
methodBuilder.add(method);
} // for each method
PServiceProvider extendsProvider = null;
if (serviceType.getExtending() != null) {
extendsProvider = registry.getServiceProvider(
parseType(program.getProgramName(), serviceType.getExtending()));
}
CService service = new CService(serviceType.getDocumentation(),
program.getProgramName(),
serviceType.getName(),
extendsProvider,
methodBuilder.build(),
makeAnnoatations(serviceType.getAnnotations()));
services.add(service);
registry.registerService(service);
}
private void registerMessage(String path,
ProgramDeclaration program,
MessageDeclaration messageType,
UnmodifiableList.Builder<PDeclaredDescriptor<?>> declaredTypes,
ProgramRegistry registry) throws ThriftException {
PDescriptorProvider implementing = null;
if (messageType.getImplementing() != null) {
implementing = registry.getTypeProvider(
parseType(program.getProgramName(), messageType.getImplementing().toString()));
}
PMessageDescriptor<?> type;
switch (messageType.getVariant()) {
case STRUCT: {
List<CField<CStruct>> fields = new ArrayList<>();
for (FieldDeclaration field : messageType.getFields()) {
fields.add(makeField(registry,
path,
program.getProgramName(),
field,
messageType.getVariant(),
implementing));
}
type = new CStructDescriptor(messageType.getDocumentation(),
program.getProgramName(),
messageType.getName(),
fields,
makeAnnoatations(messageType.getAnnotations()),
implementing);
break;
}
case UNION: {
List<CField<CUnion>> fields = new ArrayList<>();
for (FieldDeclaration field : messageType.getFields()) {
fields.add(makeField(registry,
path,
program.getProgramName(),
field,
messageType.getVariant(),
implementing));
}
type = new CUnionDescriptor(messageType.getDocumentation(),
program.getProgramName(),
messageType.getName(),
fields,
makeAnnoatations(messageType.getAnnotations()),
implementing);
break;
}
case EXCEPTION: {
List<CField<CException>> fields = new ArrayList<>();
for (FieldDeclaration field : messageType.getFields()) {
fields.add(makeField(registry,
path,
program.getProgramName(),
field,
messageType.getVariant(),
implementing));
}
type = new CExceptionDescriptor(messageType.getDocumentation(),
program.getProgramName(),
messageType.getName(),
fields,
makeAnnoatations(messageType.getAnnotations()));
break;
}
case INTERFACE: {
List<CField<CInterface>> fields = new ArrayList<>();
for (FieldDeclaration field : messageType.getFields()) {
fields.add(makeField(registry,
path,
program.getProgramName(),
field,
messageType.getVariant(),
implementing));
}
type = new CInterfaceDescriptor(messageType.getDocumentation(),
program.getProgramName(),
messageType.getName(),
fields,
makeAnnoatations(messageType.getAnnotations()));
break;
}
default:
throw new UnsupportedOperationException(
"Unhandled message variant " + messageType.getVariant());
}
declaredTypes.add(type);
registry.registerType(type);
}
private void registerEnum(ProgramDeclaration program,
EnumDeclaration enumType,
UnmodifiableList.Builder<PDeclaredDescriptor<?>> declaredTypes,
ProgramRegistry registry) {
int nextValue = PEnumDescriptor.DEFAULT_FIRST_VALUE;
CEnumDescriptor type = new CEnumDescriptor(enumType.getDocumentation(),
program.getProgramName(),
enumType.getName(),
makeAnnoatations(enumType.getAnnotations()));
List<CEnumValue> values = new ArrayList<>();
for (EnumValueDeclaration value : enumType.getValues()) {
int v = value.getId() > 0 ? value.getId() : nextValue;
nextValue = v + 1;
values.add(new CEnumValue(value.getDocumentation(),
value.getId(),
value.getName(),
type,
makeAnnoatations(value.getAnnotations())));
}
type.setValues(values);
declaredTypes.add(type);
registry.registerType(type);
}
private FieldDeclaration findField(Collection<FieldDeclaration> fields, String name) {
for (FieldDeclaration field : fields) {
if (field.getName().equals(name)) {
return field;
}
}
return null;
}
private Set<String> getIncludedProgramNames(ProgramDeclaration document) throws ThriftException {
Set<String> out = new TreeSet<>();
for (IncludeDeclaration include : document.getIncludes()) {
String program = include.getProgramName();
if (out.contains(program)) {
throw new ThriftException(
include.getProgramNameToken() != null ? include.getProgramNameToken() : include.getFilePathToken(),
"Including multiple programs of name " + program);
}
out.add(program);
}
return out;
}
@SuppressWarnings("rawtypes")
private <M extends PMessage<M>>
CField<M> makeField(@Nonnull TypeRegistry registry,
@Nonnull String fileName,
@Nonnull String programName,
@Nonnull FieldDeclaration field,
@Nonnull PMessageVariant variant,
@Nullable PDescriptorProvider implementing)
throws ThriftException {
TypeReference reference = parseType(programName, field.getType());
PDescriptorProvider type = registry.getTypeProvider(
reference, makeAnnoatations(field.getAnnotations()));
ConstValueProvider defaultValue = null;
if (field.getDefaultValueTokens() != null) {
defaultValue = new ConstValueProvider(registry,
programName,
reference,
field.getDefaultValueTokens());
}
PStructDescriptorProvider<?> argumentsProvider = null;
AnnotationDeclaration arguments = findAnnotation(field.getAnnotations(), PAnnotation.ARGUMENTS_TYPE);
if (arguments != null) {
if (arguments.getValue().isEmpty()) {
throw new ThriftException(arguments.getValueToken(),
"Empty " + PAnnotation.ARGUMENTS_TYPE.tag + " annotation.")
.setFile(fileName);
}
if (PPrimitive.findByName(arguments.getValue()) != null) {
throw new ThriftException(arguments.getValueToken(),
"Primitive " + arguments.getValue() + " not allowed as argument type")
.setFile(fileName);
}
PDescriptorProvider desc = registry.getTypeProvider(parseType(programName, arguments.getValue()),
makeAnnoatations(field.getAnnotations()));
argumentsProvider = new PStructDescriptorProvider() {
@Nonnull
@Override
public PStructDescriptor<?> descriptor() {
try {
return (PStructDescriptor<?>) desc.descriptor();
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
e.getMessage()
.replace(" in program '", " for argument type in program '"),
e);
} catch (ClassCastException e) {
throw new IllegalStateException(
"'" + arguments.getValue() + "' is not a struct for argument type in program '" + programName + "'", e);
}
}
};
}
AnnotationDeclaration refEnum = findAnnotation(field.getAnnotations(), PAnnotation.REF_ENUM);
if (refEnum != null && refEnum.getValue().isEmpty()) {
throw new ThriftException(refEnum.getValueToken(),
"Empty " + PAnnotation.REF_ENUM.tag + " annotation.")
.setFile(fileName);
}
AnnotationDeclaration container = findAnnotation(field.getAnnotations(), PAnnotation.CONTAINER);
if (container != null) {
if (container.getValue().isEmpty()) {
throw new ThriftException(container.getValueToken(),
"Empty " + PAnnotation.CONTAINER.tag + " annotation.")
.setFile(fileName);
}
if (!PContainer.isValid(container.getValue())) {
throw new ThriftException(container.getValueToken(),
"Invalid " + PAnnotation.CONTAINER.tag + " annotation," +
" must be one of 'ordered', 'sorted' or 'default'.")
.setFile(fileName);
}
}
PRequirement requirement = field.getRequirement();
if (variant == PMessageVariant.UNION) {
if (requirement == PRequirement.REQUIRED) {
throw new ThriftException(
field.getRequirementToken(), "Required field declaration in union");
}
requirement = PRequirement.OPTIONAL;
}
return new CField<>(field.getDocumentation(),
field.getId(),
requirement,
field.getName(),
type,
argumentsProvider,
defaultValue,
makeAnnoatations(field.getAnnotations()),
implementing);
}
private AnnotationDeclaration findAnnotation(List<AnnotationDeclaration> annotations, PAnnotation annotation) {
for (AnnotationDeclaration annotationDeclaration : annotations) {
if (annotationDeclaration.getTag().equals(annotation.tag)) {
return annotationDeclaration;
}
}
return null;
}
private Map<String, String> makeAnnoatations(List<AnnotationDeclaration> annotations) {
Map<String, String> map = new HashMap<>();
for (AnnotationDeclaration annotation : annotations) {
map.put(annotation.getTag(), annotation.getValue());
}
return map;
}
}