package com.fasterxml.jackson.dataformat.protobuf.schema;
import java.util.*;
import com.fasterxml.jackson.core.util.InternCache;
import com.squareup.protoparser.*;
public class TypeResolver
{
private final TypeResolver _parent;
private final String _contextName;
private Map<String,MessageElement> _declaredMessageTypes;
private Map<String,ProtobufEnum> _enumTypes;
private Map<String,ProtobufMessage> _resolvedMessageTypes;
protected TypeResolver(TypeResolver p, String name, Map<String,MessageElement> declaredMsgs,
Map<String,ProtobufEnum> enums)
{
_parent = p;
_contextName = name;
_enumTypes = enums;
if (declaredMsgs == null) {
declaredMsgs = Collections.emptyMap();
}
_declaredMessageTypes = declaredMsgs;
_resolvedMessageTypes = Collections.emptyMap();
}
public static ProtobufMessage resolve(Collection<TypeElement> nativeTypes, MessageElement rawType) {
final TypeResolver rootR = construct(null, null, nativeTypes);
return TypeResolver.construct(rootR, rawType.name(), rawType.nestedElements())
._resolve(rawType);
}
protected ProtobufMessage resolve(TypeResolver parent, MessageElement rawType)
{
return TypeResolver.construct(this, rawType.name(), rawType.nestedElements())
._resolve(rawType);
}
protected static TypeResolver construct(TypeResolver parent, String localName,
Collection<TypeElement> nativeTypes)
{
Map<String,MessageElement> declaredMsgs = null;
Map<String,ProtobufEnum> declaredEnums = new LinkedHashMap<>();
for (TypeElement nt : nativeTypes) {
if (nt instanceof MessageElement) {
if (declaredMsgs == null) {
declaredMsgs = new LinkedHashMap<String,MessageElement>();
}
declaredMsgs.put(nt.name(), (MessageElement) nt);
} else if (nt instanceof EnumElement) {
final ProtobufEnum enumType = constructEnum((EnumElement) nt);
declaredEnums.put(nt.name(), enumType);
if (parent != null) {
parent.addEnumType(_scopedName(localName, nt.name()), enumType);
}
}
}
return new TypeResolver(parent, localName, declaredMsgs, declaredEnums);
}
protected void addEnumType(String name, ProtobufEnum enumType) {
_enumTypes.put(name, enumType);
if (_parent != null) {
_parent.addEnumType(_scopedName(name), enumType);
}
}
protected static ProtobufEnum constructEnum(EnumElement nativeEnum)
{
final Map<String,Integer> valuesByName = new LinkedHashMap<String,Integer>();
boolean standard = true;
int exp = 0;
for (EnumConstantElement v : nativeEnum.constants()) {
int id = v.tag();
if (standard && (id != exp)) {
standard = false;
}
valuesByName.put(v.name(), id);
++exp;
}
String name = InternCache.instance.intern(nativeEnum.name());
return new ProtobufEnum(name, valuesByName, standard);
}
protected ProtobufMessage _resolve(MessageElement rawType)
{
List<FieldElement> rawFields = rawType.fields();
ProtobufField[] resolvedFields = new ProtobufField[rawFields.size()];
ProtobufMessage message = new ProtobufMessage(rawType.name(), resolvedFields);
_parent.addResolvedMessageType(rawType.name(), message);
int ix = 0;
for (FieldElement f : rawFields) {
final DataType fieldType = f.type();
FieldType type = FieldTypes.findType(fieldType);
ProtobufField pbf;
if (type != null) {
pbf = new ProtobufField(f, type);
} else if (fieldType instanceof DataType.NamedType) {
final String typeStr = ((DataType.NamedType) fieldType).name();
ProtobufField resolvedF = _findLocalResolved(f, typeStr);
if (resolvedF != null) {
pbf = resolvedF;
} else {
MessageElement nativeMt = _declaredMessageTypes.get(typeStr);
if (nativeMt != null) {
pbf = new ProtobufField(f, resolve(this, nativeMt));
} else {
resolvedF = (_parent == null) ? null : _parent._findAnyResolved(f, typeStr);
if (resolvedF != null) {
pbf = resolvedF;
} else {
StringBuilder enumStr = _knownEnums(new StringBuilder());
StringBuilder msgStr = _knownMsgs(new StringBuilder());
throw new IllegalArgumentException(String.format(
"Unknown protobuf field type '%s' for field '%s' of MessageType '%s"
+"' (known enum types: %s; known message types: %s)",
typeStr, f.name(), rawType.name(), enumStr, msgStr));
}
}
}
} else {
throw new IllegalArgumentException(String.format(
"Unrecognized DataType '%s' for field '%s'", fieldType.getClass().getName(), f.name()));
}
resolvedFields[ix++] = pbf;
}
ProtobufField first = (resolvedFields.length == 0) ? null : resolvedFields[0];
Arrays.sort(resolvedFields);
for (int i = 0, end = resolvedFields.length-1; i < end; ++i) {
resolvedFields[i].assignNext(resolvedFields[i+1]);
}
message.init(first);
return message;
}
protected void addResolvedMessageType(String name, ProtobufMessage toResolve) {
if (_resolvedMessageTypes.isEmpty()) {
_resolvedMessageTypes = new HashMap<String,ProtobufMessage>();
}
_resolvedMessageTypes.put(name, toResolve);
if (_parent != null) {
_parent.addResolvedMessageType(_scopedName(name), toResolve);
}
}
private ProtobufField _findAnyResolved(FieldElement nativeField, String typeStr)
{
for (TypeResolver r = this; r != null; r = r._parent) {
ProtobufField f = r._findLocalResolved(nativeField, typeStr);
if (f != null) {
return f;
}
f = r._findAndResolve(nativeField, typeStr);
if (f != null) {
return f;
}
}
return null;
}
private ProtobufField _findAndResolve(FieldElement nativeField, String typeStr)
{
MessageElement nativeMt = _declaredMessageTypes.get(typeStr);
if (nativeMt != null) {
return new ProtobufField(nativeField, resolve(this, nativeMt));
}
return null;
}
private StringBuilder _knownEnums(StringBuilder sb) {
if (_parent != null) {
sb = _parent._knownEnums(sb);
}
for (String name : _enumTypes.keySet()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(name);
}
return sb;
}
private StringBuilder _knownMsgs(StringBuilder sb) {
if (_parent != null) {
sb = _parent._knownMsgs(sb);
}
for (String name : _declaredMessageTypes.keySet()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(name);
}
return sb;
}
private ProtobufField _findLocalResolved(FieldElement nativeField, String typeStr)
{
ProtobufMessage msg = _resolvedMessageTypes.get(typeStr);
if (msg != null) {
return new ProtobufField(nativeField, msg);
}
ProtobufEnum et = _enumTypes.get(typeStr);
if (et != null) {
return new ProtobufField(nativeField, et);
}
return null;
}
private final String _scopedName(String localName) {
return _scopedName(_contextName, localName);
}
private final static String _scopedName(String contextName, String localName) {
return new StringBuilder(contextName).append('.').append(localName).toString();
}
}