All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.infinispan.protostream.descriptors.FileDescriptor Maven / Gradle / Ivy
Go to download
ProtoStream is a serialization library based on Protocol buffers format for serializing structured data.
package org.infinispan.protostream.descriptors;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.infinispan.protostream.DescriptorParserException;
import org.infinispan.protostream.config.Configuration;
import org.infinispan.protostream.descriptors.namespace.FileNamespace;
import org.infinispan.protostream.descriptors.namespace.Namespace;
import org.infinispan.protostream.impl.Log;
/**
* Representation of a .proto file, including its dependencies.
*
* @author gustavonalle
* @author [email protected]
* @since 2.0
*/
public final class FileDescriptor {
public enum Syntax {
PROTO2("proto2"),
PROTO3("proto3");
private final String syntax;
Syntax(String syntax) {
this.syntax = syntax;
}
public static Syntax fromString(String syntax) {
if (syntax == null) {
throw new IllegalArgumentException("argument cannot be null");
}
for (Syntax s : values()) {
if (s.syntax.equals(syntax)) {
return s;
}
}
throw new IllegalArgumentException("Illegal syntax : '" + syntax + "'");
}
@Override
public String toString() {
return syntax;
}
}
private static final Log log = Log.LogFactory.getLog(FileDescriptor.class);
private final Syntax syntax;
protected Configuration configuration;
private final String name;
private final String packageName;
/**
* The imports. These are not transitive.
*/
private final List dependencies;
/**
* The public imports. These generate transitive dependencies.
*/
private final List publicDependencies;
private final List options;
private final List messageTypes;
private final List enumTypes;
private final List extendTypes;
private final Map extendDescriptors = new HashMap<>();
/**
* Files that directly depend on this one.
*/
private final Map dependants = new HashMap<>();
/**
* The types defined in this file or in the imported files.
*/
private FileNamespace fileNamespace;
/**
* The validation status of a .proto file. Only {@link Status#RESOLVED} files contribute to the current state (known
* types) of the {@link org.infinispan.protostream.SerializationContext}.
*/
private enum Status {
/**
* Not processed yet.
*/
UNRESOLVED,
/**
* Processed successfully.
*/
RESOLVED,
/**
* A possibly recoverable error was encountered during processing.
*/
ERROR,
/**
* An unrecoverable parsing error was encountered during processing.
*/
PARSING_ERROR;
boolean isResolved() {
return this == RESOLVED;
}
boolean isError() {
return this == ERROR || this == PARSING_ERROR;
}
}
private Status status;
/**
* When {@link #status} is equal to {@link Status#PARSING_ERROR}, this exception provides the cause.
*/
private DescriptorParserException parsingException;
private FileDescriptor(Builder builder) {
// Default to proto2 if no syntax was specified
syntax = builder.syntax == null ? Syntax.PROTO2 : builder.syntax;
name = builder.name;
packageName = builder.packageName;
dependencies = Collections.unmodifiableList(builder.dependencies);
publicDependencies = Collections.unmodifiableList(builder.publicDependencies);
options = Collections.unmodifiableList(builder.options);
enumTypes = Collections.unmodifiableList(builder.enumTypes);
messageTypes = Collections.unmodifiableList(builder.messageTypes);
extendTypes = Collections.unmodifiableList(builder.extendDescriptors);
parsingException = builder.parsingException;
status = parsingException != null ? Status.PARSING_ERROR : Status.UNRESOLVED;
}
public Configuration getConfiguration() {
return configuration;
}
/**
* This method is not part of the public API. May be removed in future versions.
*/
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}
public Map getDependants() {
return dependants;
}
public boolean isResolved() {
return status.isResolved();
}
public void markUnresolved() {
status = Status.UNRESOLVED;
}
void markError() {
// parsing errors are already considered fatal and final so should not be overwritten by a 'regular' error
if (status != Status.PARSING_ERROR) {
status = Status.ERROR;
}
}
/**
* Clear resolving errors of unresolved files. Parsing errors are not cleared. Transitions from ERROR status back to
* UNRESOLVED and propagates this recursively to all dependant FileDescriptors. All internal state acquired during
* type reference resolution is cleared for this file and dependants (recursively).
*/
public void clearErrors() {
if (status != Status.RESOLVED && status != Status.PARSING_ERROR) {
markUnresolved();
fileNamespace = null;
extendDescriptors.clear();
for (FileDescriptor fd : dependants.values()) {
fd.clearErrors();
}
dependants.clear();
}
}
public Namespace getExportedNamespace() {
if (status != Status.RESOLVED) {
throw new IllegalStateException("File '" + name + "' is not resolved yet");
}
return fileNamespace.getExportedNamespace();
}
/**
* Resolve type references across files and report semantic errors like duplicate type declarations, duplicate type
* ids or clashing enum value constants. Only {@link FileDescriptor.Status#UNRESOLVED} files are processed. Files
* with other states are ignored.
*/
public void resolveDependencies(ResolutionContext resolutionContext) throws DescriptorParserException {
resolveDependencies(resolutionContext, new HashSet<>());
}
private void resolveDependencies(ResolutionContext resolutionContext, Set processedFiles) throws DescriptorParserException {
if (status == Status.PARSING_ERROR) {
resolutionContext.handleError(this, parsingException);
return;
}
if (status != Status.UNRESOLVED) {
return;
}
if (log.isDebugEnabled()) {
log.debugf("Resolving dependencies of %s", name);
}
try {
List pubDeps = resolveImports(publicDependencies, resolutionContext, processedFiles);
List deps = resolveImports(dependencies, resolutionContext, processedFiles);
if (status.isError()) {
// no point going further if any of the imported files have errors
return;
}
fileNamespace = new FileNamespace(this, pubDeps, deps);
for (FileDescriptor fd : pubDeps) {
fd.dependants.put(name, this);
}
for (FileDescriptor fd : deps) {
fd.dependants.put(name, this);
}
for (Descriptor desc : messageTypes) {
collectDescriptors(desc, resolutionContext);
}
for (EnumDescriptor enumDesc : enumTypes) {
collectEnumDescriptors(enumDesc, resolutionContext);
}
for (ExtendDescriptor extendDescriptor : extendTypes) {
collectExtensions(extendDescriptor);
}
for (Descriptor descriptor : messageTypes) {
resolveFieldTypes(descriptor);
}
for (ExtendDescriptor extendDescriptor : extendTypes) {
resolveExtension(extendDescriptor);
}
status = Status.RESOLVED;
resolutionContext.flush();
resolutionContext.handleSuccess(this);
} catch (DescriptorParserException dpe) {
resolutionContext.handleError(this, dpe);
} catch (Exception e) {
resolutionContext.handleError(this, new DescriptorParserException(e));
} finally {
if (status != Status.RESOLVED) {
resolutionContext.clear();
}
}
}
/**
* Resolves imported file names to {@link FileDescriptor}s. Changes the status of current file to ERROR if any of the
* imported files (directly or indirectly) have any errors.
*
* @return the list of resolved {@link FileDescriptor}s
*/
private List resolveImports(List dependencies, ResolutionContext resolutionContext,
Set processedFiles) throws DescriptorParserException {
List fileDescriptors = new ArrayList<>(dependencies.size());
Set uniqueDependencies = new HashSet<>(dependencies.size());
for (String dependency : dependencies) {
if (!uniqueDependencies.add(dependency)) {
resolutionContext.handleError(this, new DescriptorParserException("Duplicate import : " + dependency));
continue;
}
FileDescriptor fd = resolutionContext.getFileDescriptorMap().get(dependency);
if (fd == null) {
resolutionContext.handleError(this, new DescriptorParserException("Import '" + dependency + "' not found"));
continue;
}
if (fd.status == Status.UNRESOLVED) {
if (!processedFiles.add(dependency)) {
resolutionContext.handleError(this, new DescriptorParserException("Cyclic import detected at " + name + ", import " + dependency));
continue;
}
fd.resolveDependencies(resolutionContext, processedFiles);
}
if (fd.status.isError()) {
resolutionContext.handleError(this, new DescriptorParserException("File " + name + " imports a file (" + fd.getName() + ") that has errors"));
continue;
}
fileDescriptors.add(fd);
}
return fileDescriptors;
}
private void collectDescriptors(Descriptor descriptor, ResolutionContext resolutionContext) {
descriptor.setFileDescriptor(this);
fileNamespace.put(descriptor.getFullName(), descriptor);
resolutionContext.addGenericDescriptor(descriptor);
for (Descriptor nested : descriptor.getNestedTypes()) {
collectDescriptors(nested, resolutionContext);
}
for (EnumDescriptor enumDescriptor : descriptor.getEnumTypes()) {
collectEnumDescriptors(enumDescriptor, resolutionContext);
}
}
private void collectEnumDescriptors(EnumDescriptor enumDescriptor, ResolutionContext resolutionContext) {
enumDescriptor.setFileDescriptor(this);
fileNamespace.put(enumDescriptor.getFullName(), enumDescriptor);
resolutionContext.addGenericDescriptor(enumDescriptor);
}
private void collectExtensions(ExtendDescriptor extendDescriptor) {
extendDescriptor.setFileDescriptor(this);
extendDescriptors.put(extendDescriptor.getFullName(), extendDescriptor);
}
private void resolveFieldTypes(Descriptor descriptor) {
for (FieldDescriptor fieldDescriptor : descriptor.getFields()) {
if (fieldDescriptor.getType() == null ||
fieldDescriptor.getType() == Type.GROUP ||
fieldDescriptor.getType() == Type.MESSAGE ||
fieldDescriptor.getType() == Type.ENUM) {
GenericDescriptor res = searchType(fieldDescriptor.getTypeName(), descriptor);
if (res instanceof EnumDescriptor) {
fieldDescriptor.setEnumType((EnumDescriptor) res);
} else if (res instanceof Descriptor) {
fieldDescriptor.setMessageType((Descriptor) res);
} else {
throw new DescriptorParserException("Failed to resolve type of field \"" + fieldDescriptor.getFullName()
+ "\". Type not found : " + fieldDescriptor.getTypeName());
}
}
}
for (Descriptor nested : descriptor.getNestedTypes()) {
resolveFieldTypes(nested);
}
}
private void resolveExtension(ExtendDescriptor extendDescriptor) {
GenericDescriptor res = searchType(extendDescriptor.getName(), null);
if (res == null) {
throw new DescriptorParserException("Extension error: type " + extendDescriptor.getName() + " not found");
}
if (res instanceof EnumDescriptor) {
throw new DescriptorParserException("Enumerations cannot be extended: " + extendDescriptor.getFullName());
}
extendDescriptor.setExtendedMessage((Descriptor) res);
}
private String getScopedName(String name) {
return packageName == null ? name : packageName.concat(".").concat(name);
}
private GenericDescriptor searchType(String name, Descriptor scope) {
GenericDescriptor fullyQualified = fileNamespace.get(getScopedName(name));
if (fullyQualified != null) {
return fullyQualified;
}
GenericDescriptor relativeName = fileNamespace.get(name);
if (relativeName != null) {
return relativeName;
}
if (scope != null) {
String searchScope = scope.getFullName().concat(".").concat(name);
GenericDescriptor o = fileNamespace.get(searchScope);
if (o != null) {
return o;
}
Descriptor containingType;
while ((containingType = scope.getContainingType()) != null) {
GenericDescriptor res = searchType(name, containingType);
if (res != null) {
return res;
}
}
}
return null;
}
public Syntax getSyntax() {
return syntax;
}
public String getName() {
return name;
}
public String getPackage() {
return packageName;
}
public List getOptions() {
return options;
}
public Option getOption(String name) {
for (Option o : options) {
if (o.getName().equals(name)) {
return o;
}
}
return null;
}
public List getEnumTypes() {
return enumTypes;
}
/**
* Top level message types defined in this file.
*/
public List getMessageTypes() {
return messageTypes;
}
public List getExtensionsTypes() {
return extendTypes;
}
/**
* All types defined in this file (both message and enum).
*/
public Map getTypes() {
if (status != Status.RESOLVED) {
throw new IllegalStateException("File '" + name + "' is not resolved yet");
}
return fileNamespace.getLocalNamespace().getTypes();
}
@Override
public String toString() {
return "FileDescriptor{" +
"name='" + name + '\'' +
", packageName='" + packageName + '\'' +
", status=" + status +
'}';
}
public static final class Builder {
private Syntax syntax = Syntax.PROTO2;
private String name;
private String packageName;
private List dependencies = Collections.emptyList();
private List publicDependencies = Collections.emptyList();
private List options = Collections.emptyList();
private List enumTypes = Collections.emptyList();
private List messageTypes = Collections.emptyList();
private List extendDescriptors = Collections.emptyList();
private DescriptorParserException parsingException;
public Builder withSyntax(String syntax) {
return withSyntax(Syntax.fromString(syntax));
}
public Builder withSyntax(Syntax syntax) {
this.syntax = syntax;
return this;
}
public Builder withName(String name) {
this.name = name;
return this;
}
public Builder withPackageName(String packageName) {
this.packageName = packageName;
return this;
}
public Builder withDependencies(List dependencies) {
this.dependencies = dependencies;
return this;
}
public Builder withPublicDependencies(List publicDependencies) {
this.publicDependencies = publicDependencies;
return this;
}
public Builder withExtendDescriptors(List extendDescriptors) {
this.extendDescriptors = extendDescriptors;
return this;
}
public Builder withOptions(List options) {
this.options = options;
return this;
}
public Builder withEnumTypes(List enumTypes) {
this.enumTypes = enumTypes;
return this;
}
public Builder withMessageTypes(List messageTypes) {
this.messageTypes = messageTypes;
return this;
}
public Builder withParsingException(DescriptorParserException parsingException) {
this.parsingException = parsingException;
return this;
}
public FileDescriptor build() {
return new FileDescriptor(this);
}
}
}