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
Users need to implement a marshaller object that interacts with a field writer/reader in order to serialize
state.
package org.infinispan.protostream.descriptors;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
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,
PROTO3;
public static Syntax fromString(String syntax) {
return Syntax.valueOf(syntax.toUpperCase());
}
@Override
public String toString() {
return name().toLowerCase();
}
}
private static final Log log = Log.LogFactory.getLog(FileDescriptor.class);
private final Syntax syntax;
private 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;
/**
* 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 final 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 = List.copyOf(builder.dependencies);
publicDependencies = List.copyOf(builder.publicDependencies);
options = List.copyOf(builder.options);
enumTypes = List.copyOf(builder.enumTypes);
messageTypes = List.copyOf(builder.messageTypes);
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 Collection getDependencies() {
return Stream.concat(dependencies.stream(), publicDependencies.stream()).toList();
}
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;
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 (Descriptor descriptor : messageTypes) {
resolveFieldTypes(descriptor);
}
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) {
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) {
fileNamespace.put(enumDescriptor.getFullName(), enumDescriptor);
resolutionContext.addGenericDescriptor(enumDescriptor);
}
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()
+ "\" in \"" + name + "\". Type not found : " + fieldDescriptor.getTypeName());
}
}
}
for (Descriptor nested : descriptor.getNestedTypes()) {
resolveFieldTypes(nested);
}
}
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;
}
/**
* 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();
}
public void checkCompatibility(FileDescriptor that, boolean strict, List errors) {
for(Descriptor dThat : that.getMessageTypes()) {
messageTypes.stream().filter(d -> d.getName().equals(dThat.getName())).findFirst().ifPresent(d -> d.checkCompatibility(dThat, strict, errors));
}
for(EnumDescriptor eThat : that.getEnumTypes()) {
enumTypes.stream().filter(e -> e.getName().equals(eThat.getName())).findFirst().ifPresent(e -> e.checkCompatibility(eThat, strict, errors));
}
}
public void checkCompatibility(FileDescriptor that, boolean strict) {
List errors = new ArrayList<>();
checkCompatibility(that, strict, errors);
if (!errors.isEmpty()) {
throw Log.LOG.incompatibleSchemaChanges(String.join("\n", errors));
}
}
public void parseAnnotations() {
if (configuration == null) {
throw new IllegalStateException("FileDescriptor.setConfiguration() must be invoked before parsing the annotations");
}
messageTypes.forEach(descriptor -> descriptor.setFileDescriptor(this));
enumTypes.forEach(enumDescriptor -> enumDescriptor.setFileDescriptor(this));
}
@Override
public String toString() {
return "FileDescriptor{" +
"name='" + name + '\'' +
", packageName='" + packageName + '\'' +
", status=" + status +
'}';
}
public static String fullName(String parent, String name) {
return parent == null ? name : parent + '.' + name;
}
public static final class Builder implements MessageContainer, OptionContainer, EnumContainer {
private Syntax syntax = Syntax.PROTO2;
private String name;
private String packageName;
private List dependencies = new ArrayList<>();
private List publicDependencies = new ArrayList<>();
private List options = new ArrayList<>();
private List enumTypes = new ArrayList<>();
private List messageTypes = new ArrayList<>();
private DescriptorParserException parsingException;
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;
}
@Override
public String getFullName() {
return packageName;
}
public Builder withDependencies(List dependencies) {
this.dependencies = dependencies;
return this;
}
public Builder addDependency(String dependency) {
this.dependencies.add(dependency);
return this;
}
public Builder withPublicDependencies(List publicDependencies) {
this.publicDependencies = publicDependencies;
return this;
}
public Builder addPublicDependency(String dependency) {
this.publicDependencies.add(dependency);
return this;
}
public Builder withOptions(List options) {
this.options = options;
return this;
}
@Override
public Builder addOption(Option option) {
this.options.add(option);
return this;
}
public Builder withEnumTypes(List enumTypes) {
this.enumTypes = enumTypes;
return this;
}
@Override
public Builder addEnum(EnumDescriptor.Builder enumDescriptor) {
this.enumTypes.add(enumDescriptor.withFullName(fullName(packageName, enumDescriptor.getName())).build());
return this;
}
public Builder withMessageTypes(List messageTypes) {
this.messageTypes = messageTypes;
return this;
}
@Override
public Builder addMessage(Descriptor.Builder message) {
this.messageTypes.add(message.withFullName(fullName(packageName, message.getName())).build());
return this;
}
public Builder withParsingException(DescriptorParserException parsingException) {
this.parsingException = parsingException;
return this;
}
public FileDescriptor build() {
Set optionNames = new HashSet<>(options.size());
for (Option option : options) {
if (!optionNames.add(option.getName())) {
throw new DescriptorParserException(name + ": Option \"" + option.getName() + "\" was already set.");
}
}
return new FileDescriptor(this);
}
}
}