
io.protostuff.compiler.parser.OptionsPostProcessor Maven / Gradle / Ivy
package io.protostuff.compiler.parser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import io.protostuff.compiler.model.Descriptor;
import io.protostuff.compiler.model.DescriptorType;
import io.protostuff.compiler.model.DynamicMessage;
import io.protostuff.compiler.model.Element;
import io.protostuff.compiler.model.Enum;
import io.protostuff.compiler.model.Field;
import io.protostuff.compiler.model.FieldType;
import io.protostuff.compiler.model.Message;
import io.protostuff.compiler.model.ProtobufConstants;
import io.protostuff.compiler.model.ScalarFieldType;
import io.protostuff.compiler.model.UserTypeContainer;
import static io.protostuff.compiler.parser.DefaultDescriptorProtoProvider.DESCRIPTOR_PROTO;
import static io.protostuff.compiler.parser.TypeResolverPostProcessor.createScopeLookupList;
/**
* @author Kostiantyn Shchepanovskyi
*/
public class OptionsPostProcessor implements ProtoContextPostProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(OptionsPostProcessor.class);
private final Provider descriptorProtoProvider;
@Inject
public OptionsPostProcessor(@Named(DESCRIPTOR_PROTO) Provider descriptorProtoProvider) {
this.descriptorProtoProvider = descriptorProtoProvider;
}
@Override
public void process(ProtoContext context) {
ProtoWalker.newInstance(context)
.onProto(this::processOptions)
.onMessage(this::processOptions)
.walk();
}
private void processOptions(ProtoContext context, Descriptor descriptor) {
DynamicMessage options = descriptor.getOptions();
if (options.isEmpty()) {
// nothing to check - skip this message
return;
}
String descriptorClassName = descriptor.getClass().getSimpleName();
String descriptorName = descriptor.getName();
LOGGER.trace("processing class={} name={}", descriptorClassName, descriptorName);
Message sourceMessage = findSourceMessage(context, descriptor.getDescriptorType());
processOptions(context, sourceMessage, descriptor, options);
}
private void processOptions(ProtoContext context, Message sourceMessage, Descriptor owningDescriptor, DynamicMessage options) {
ExtensionRegistry extensionRegistry = context.getExtensionRegistry();
Map extensionFields = extensionRegistry.getExtensionFields(sourceMessage);
Map fullyQualifiedNames = new HashMap<>();
for (Map.Entry entry : options.getFields()) {
DynamicMessage.Key key = entry.getKey();
DynamicMessage.Value value = entry.getValue();
if (key.isExtension()) {
String fullyQualifiedName = null;
Field extensionField = null;
if (key.getName().startsWith(".")) {
String name = key.getName();
if (extensionFields.containsKey(name)) {
fullyQualifiedName = name;
extensionField = extensionFields.get(fullyQualifiedName);
}
} else {
UserTypeContainer owningContainer = getOwningContainer(owningDescriptor);
Deque scopeLookupList = createScopeLookupList(owningContainer);
for (String scope : scopeLookupList) {
String name = scope + key.getName();
if (extensionFields.containsKey(name)) {
fullyQualifiedName = name;
extensionField = extensionFields.get(fullyQualifiedName);
break;
}
}
}
if (fullyQualifiedName == null) {
throw new ParserException(value, "Unknown option: '%s'", key.getName());
}
fullyQualifiedNames.put(key, fullyQualifiedName);
checkFieldValue(context, owningDescriptor, extensionField, value);
} else {
// check standard option
String fieldName = key.getName();
Field field = sourceMessage.getField(fieldName);
if (field == null) {
throw new ParserException(value, "Unknown option: '%s'", fieldName);
}
checkFieldValue(context, owningDescriptor, field, value);
}
}
for (Map.Entry entry : fullyQualifiedNames.entrySet()) {
options.normalizeName(entry.getKey(), entry.getValue());
}
}
private UserTypeContainer getOwningContainer(Descriptor descriptor) {
Element tmp = descriptor;
while (!(tmp instanceof UserTypeContainer)) {
tmp = tmp.getParent();
}
return (UserTypeContainer) tmp;
}
private void checkFieldValue(ProtoContext context, Descriptor descriptor, Field field, DynamicMessage.Value value) {
String fieldName = field.getName();
FieldType fieldType = field.getType();
DynamicMessage.Value.Type valueType = value.getType();
if (fieldType instanceof ScalarFieldType) {
ScalarFieldType scalarFieldType = (ScalarFieldType) fieldType;
if (!isAssignableFrom(scalarFieldType, valueType)) {
throw new ParserException(value, "Cannot set option '%s': expected %s value", fieldName, fieldType);
}
} else if (fieldType instanceof Enum) {
Enum anEnum = (Enum) fieldType;
Set allowedNames = anEnum.getConstantNames();
if (valueType != DynamicMessage.Value.Type.ENUM
|| !allowedNames.contains(value.getEnumName())) {
throw new ParserException(value, "Cannot set option '%s': expected enum = %s", fieldName, allowedNames);
}
} else if (fieldType instanceof Message) {
if (valueType != DynamicMessage.Value.Type.MESSAGE) {
throw new ParserException(value, "Cannot set option '%s': expected message value", fieldName);
}
Message message = (Message) fieldType;
processOptions(context, message, descriptor, value.getMessage());
} else {
throw new IllegalStateException("Unknown field type: " + fieldType);
}
}
private boolean isAssignableFrom(ScalarFieldType target, DynamicMessage.Value.Type valueType) {
switch (target) {
case INT32:
case INT64:
case UINT32:
case UINT64:
case SINT32:
case SINT64:
case FIXED32:
case FIXED64:
case SFIXED32:
case SFIXED64:
// TODO check if value fits into target type
return valueType == DynamicMessage.Value.Type.INTEGER;
case FLOAT:
case DOUBLE:
// TODO check if value fits into target type
return valueType == DynamicMessage.Value.Type.INTEGER
|| valueType == DynamicMessage.Value.Type.FLOAT;
case BOOL:
return valueType == DynamicMessage.Value.Type.BOOLEAN;
case STRING:
return valueType == DynamicMessage.Value.Type.STRING;
case BYTES:
return valueType == DynamicMessage.Value.Type.STRING;
default:
throw new IllegalStateException("Unknown field type: " + target);
}
}
private Message findSourceMessage(ProtoContext context, DescriptorType type) {
Message message = tryResolveFromContext(context, type);
if (message == null) {
ProtoContext descriptorProto = descriptorProtoProvider.get();
return tryResolveFromContext(descriptorProto, type);
}
return message;
}
private Message tryResolveFromContext(ProtoContext context, DescriptorType type) {
switch (type) {
case PROTO:
return context.resolve(Message.class, ProtobufConstants.MSG_FILE_OPTIONS);
case ENUM:
return context.resolve(Message.class, ProtobufConstants.MSG_ENUM_OPTIONS);
case ENUM_CONSTANT:
return context.resolve(Message.class, ProtobufConstants.MSG_ENUM_VALUE_OPTIONS);
case MESSAGE:
return context.resolve(Message.class, ProtobufConstants.MSG_MESSAGE_OPTIONS);
case MESSAGE_FIELD:
return context.resolve(Message.class, ProtobufConstants.MSG_FIELD_OPTIONS);
case GROUP:
// Groups are not fully supported. For simplicity, in this place we assume
// that only that options that are applicable for messages are also
// applicable for groups.
// But, actually it is invalid assumption, because both field and message
// options are applicable to groups.
return context.resolve(Message.class, ProtobufConstants.MSG_MESSAGE_OPTIONS);
case SERVICE:
return context.resolve(Message.class, ProtobufConstants.MSG_SERVICE_OPTIONS);
case SERVICE_METHOD:
return context.resolve(Message.class, ProtobufConstants.MSG_METHOD_OPTIONS);
default:
throw new IllegalStateException("Unknown descriptor type: " + type);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy