package com.philemonworks.critter.proto;
import com.google.common.base.Optional;
import com.squareup.protoparser.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Definitions holds all Protocolbuffers message definitions needed to inspect serialized data.
* <p/>
* Created by emicklei on 01/03/16.
*/
public class Definitions {
private Map<String, MessageElement> elementMap = new HashMap<String, MessageElement>();
public boolean isEmpty() {
return this.elementMap.isEmpty();
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append('[');
for (String messageName : elementMap.keySet()) {
sb.append(messageName);
sb.append(",");
}
sb.append(']');
return sb.toString();
}
// TEMP
public String explainAll() {
StringBuilder sb = new StringBuilder();
for (String messageName : elementMap.keySet()) {
sb.append("\n-----------\n");
sb.append(messageName);
sb.append("\n-----------\n");
for (String path : this.allPathsOfMessage(messageName)) {
sb.append(path);
sb.append("\n");
}
}
return sb.toString();
}
public List<String> allPathsOfMessage(String messageName) {
List<String> paths = new ArrayList<String>();
this.appendPathTo("", messageName, paths);
return paths;
}
private void appendPathTo(String path, String messageName, List<String> paths) {
MessageElement m = this.elementMap.get(messageName);
if (null == m) {
return;
}
for (FieldElement each : m.fields()) {
String nextPath = path + "." + each.name();
if (FieldElement.Label.REPEATED == each.label()) {
nextPath += ".0";
}
if (each.type() instanceof DataType.NamedType) {
DataType.NamedType namedType = (DataType.NamedType) each.type();
String qName = this.qualifiedNameInNamespaceOf(namedType.name(), messageName);
this.appendPathTo(nextPath, qName, paths);
}
if (each.type() instanceof DataType.ScalarType) {
// end of path
paths.add(nextPath);
}
}
}
/**
* Returns a new Inspector of data that uses (one of) my definitions.
*
* @param messageName
* @return
*/
public Inspector newInspector(String messageName) {
Inspector i = new Inspector();
i.messageName = messageName;
i.messageDefinitions = this;
return i;
}
protected Optional<FieldElement> fieldElementNamed(String messageName, String fieldName) {
MessageElement m = this.elementMap.get(messageName);
if (null == m) {
return Optional.absent();
}
for (FieldElement each : m.fields()) {
if (each.name().equals(fieldName)) {
return Optional.of(each);
}
}
return Optional.absent();
}
public Optional<MessageElement> messageElementNamed(String messageName) {
MessageElement m = this.elementMap.get(messageName);
if (null == m) {
return Optional.absent();
}
return Optional.of(m);
}
/**
* Different strategies are used here to workaround the fact that typeName is unqualified.
* <p/>
* qualifiedNameInNamespaceOf
*
* @param typeName
* @param qualifiedName
* @return
*/
String qualifiedNameInNamespaceOf(String typeName, String qualifiedName) {
// can be nested message of qualifiedName
String nested = qualifiedName + "." + typeName;
if (this.elementMap.containsKey(nested)) {
return nested;
}
// can be message in the parent namespace of qualifiedName
// take largest namespace part
String namespace = qualifiedName.substring(0, qualifiedName.lastIndexOf("."));
String namespaceSibling = namespace + "." + typeName;
if (this.elementMap.containsKey(namespaceSibling)) {
return namespaceSibling;
}
// can be message in the root namespace of qualifiedName
// take smallest namespace part
namespace = qualifiedName.substring(0, qualifiedName.indexOf("."));
namespaceSibling = namespace + "." + typeName;
if (this.elementMap.containsKey(namespaceSibling)) {
return namespaceSibling;
}
// give up
return typeName;
}
/**
* Reads all ProtocolBuffers message definitions from an Inputstream on a specification resource.
*
* @param is
* @return
*/
public boolean read(InputStream is) {
try {
ProtoFile pf = ProtoParser.parseUtf8("inspector", is);
for (TypeElement each : pf.typeElements()) {
if (each instanceof MessageElement) {
this.insert((MessageElement) each);
}
}
return true;
} catch (Exception ex) {
return false;
}
}
/**
* Inserts a new elements and all its nested elements (will override all).
*
* @param element
*/
private void insert(MessageElement element) {
this.elementMap.put(element.qualifiedName(), element);
for (TypeElement each : element.nestedElements()) {
if (each instanceof MessageElement) {
insert((MessageElement) each);
return;
}
}
}
}