package protobuf.codec.xml; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Map; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import org.apache.commons.codec.binary.Base64; import protobuf.codec.AbstractCodec; import protobuf.codec.Codec.Feature; import protobuf.codec.ParseException; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.ByteString; import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.ExtensionRegistry.ExtensionInfo; import com.google.protobuf.Message; import com.google.protobuf.Message.Builder; /** * XML Reader * @author sijuv * */ public class XmlReader { public static Message parse(Builder builder, XMLStreamReader xmlreader, ExtensionRegistry extnRegistry, Map<Feature, Object> featureMap) throws IOException, XMLStreamException { xmlreader.nextTag(); // Start document. String rootElemName = xmlreader.getLocalName(); if (!(rootElemName.equals(XmlCodec.getRootElementName(builder)))) { throw new IllegalArgumentException("The root element name does not match the required pattern"); } while (xmlreader.getEventType() != XMLStreamConstants.END_DOCUMENT) { parseElement(builder, xmlreader, extnRegistry, featureMap); xmlreader.next(); } return builder.build(); } /** Parse the element */ private static Builder parseElement(Builder builder, XMLStreamReader xmlreader, ExtensionRegistry extnRegistry, Map<Feature, Object> featureMap) throws XMLStreamException, IOException { int event = -1; while ((event = xmlreader.next()) != XMLStreamConstants.END_ELEMENT) { switch (event) { case XMLStreamConstants.START_ELEMENT: String fieldName = xmlreader.getLocalName(); fieldName = AbstractCodec.stripFieldName(fieldName, featureMap); fieldName = AbstractCodec.substituteFieldNameForReading(fieldName, featureMap); FieldDescriptor field = null; if (AbstractCodec.isExtensionFieldName(fieldName, featureMap)) { String extnName = AbstractCodec.parseExtensionFieldName(fieldName, featureMap); ExtensionInfo extnInfo = extnRegistry.findExtensionByName(extnName); if (extnInfo == null) { //Skip the extension element while (!xmlreader.isEndElement() || !(xmlreader.isEndElement() && AbstractCodec.isExtensionFieldName(xmlreader.getLocalName(), featureMap))) { xmlreader.next(); } continue; } field = extnInfo.descriptor; } else if (AbstractCodec.isFieldNameUnknownField(fieldName, featureMap)) { xmlreader.next(); String unknownFieldsText = xmlreader.getText(); if (AbstractCodec.supportUnknownFields(featureMap)) { AbstractCodec.mergeUnknownFieldsFromString(builder, extnRegistry, unknownFieldsText); } xmlreader.next(); continue; } else { field = builder.getDescriptorForType().findFieldByName(fieldName); } if (field == null) { throw new ParseException("Field cannot be null, processing fieldName " + fieldName); } setFields(builder, field, xmlreader, extnRegistry, featureMap); break; } } return builder; } private static Builder setFields(Builder builder, FieldDescriptor field, XMLStreamReader xmlreader, ExtensionRegistry extnRegistry, Map<Feature, Object> featureMap) throws IOException, XMLStreamException { Object value = getValue(builder, field, xmlreader, extnRegistry, featureMap); if (value == null) { // What to do in case of null values ? protobuf does not allow null. } else { if (field.isRepeated()) { builder.addRepeatedField(field, value); } else { try { builder.setField(field, value); } catch (IllegalArgumentException e) { throw new ParseException(String.format("Error while setting value [%s] on field [%s] ", value, field.getFullName()), e); } } } return builder; } private static Object getValue(Builder builder, FieldDescriptor field, XMLStreamReader xmlreader, ExtensionRegistry extnRegistry, Map<Feature, Object> featureMap) throws XMLStreamException, IOException { Object value = null; String fieldValue = null; switch (field.getJavaType()) { case INT: xmlreader.next();// Move past fieldValue = xmlreader.getText(); value = Integer.parseInt(fieldValue); xmlreader.next(); break; case BOOLEAN: xmlreader.next();// Move past fieldValue = xmlreader.getText(); value = Boolean.valueOf(fieldValue); xmlreader.next(); break; case DOUBLE: xmlreader.next();// Move past fieldValue = xmlreader.getText(); value = Double.valueOf(fieldValue); xmlreader.next(); break; case FLOAT: xmlreader.next();// Move past fieldValue = xmlreader.getText(); value = Float.valueOf(fieldValue); xmlreader.next(); break; case LONG: xmlreader.next();// Move past fieldValue = xmlreader.getText(); value = Long.valueOf(fieldValue); xmlreader.next(); break; case STRING: xmlreader.next();// Move past fieldValue = xmlreader.getText(); value = fieldValue; xmlreader.next(); break; case ENUM: xmlreader.next();// Move past fieldValue = xmlreader.getText(); value = field.getEnumType().findValueByName(fieldValue); xmlreader.next(); break; case MESSAGE: Builder newBuilder = null; if (field.isExtension()) { newBuilder = extnRegistry.findExtensionByName(field.getFullName()).defaultInstance.toBuilder(); } else { newBuilder = builder.newBuilderForField(field); } value = parseElement(newBuilder, xmlreader, extnRegistry, featureMap).build(); break; case BYTE_STRING: xmlreader.next();// Move past value = ByteString.copyFrom(Base64.decodeBase64(xmlreader.getText())); xmlreader.next(); break; default: throw new UnsupportedEncodingException(String.format( "Unsupported java type [%s]", field.getJavaType())); } return value; } }