Package com.stanfy.gsonxml

Source Code of com.stanfy.gsonxml.XmlReader$Creator

package com.stanfy.gsonxml;

import com.google.gson.JsonSyntaxException;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.io.Reader;

/**
* Reads XML as JSON.
* @author Roman Mazur (Stanfy - http://stanfy.com)
*/
public class XmlReader extends JsonReader {

  /** Internal token type. */
  private static final int START_TAG = 1, END_TAG = 2, VALUE = 3, IGNORE = -1;

  /** Scope. */
  private static enum Scope {
    /** We are inside an object. Next token should be {@link JsonToken#NAME} or {@link JsonToken#END_OBJECT}. */
    INSIDE_OBJECT(false),
    /** We are inside an array. Next token should be {@link JsonToken#BEGIN_OBJECT} or {@link JsonToken#END_ARRAY}. */
    INSIDE_ARRAY(true),
    /** We are inside automatically added array. Next token should be {@link JsonToken#BEGIN_OBJECT} or {@link JsonToken#END_ARRAY}. */
    INSIDE_EMBEDDED_ARRAY(true),
    /** We are inside primitive embedded array. Child scope can be #PRIMITIVE_VALUE only. */
    INSIDE_PRIMITIVE_EMBEDDED_ARRAY(true),
    /** We are inside primitive array. Child scope can be #PRIMITIVE_VALUE only. */
    INSIDE_PRIMITIVE_ARRAY(true),
    /** We are inside primitive value. Next token should be {@link JsonToken#STRING} or {@link JsonToken#END_ARRAY}. */
    PRIMITIVE_VALUE(false),
    /** New start tag met, we returned {@link JsonToken#NAME}. Object, array, or value can go next. */
    NAME(false);

    /** Inside array flag. */
    final boolean insideArray;

    private Scope(final boolean insideArray) {
      this.insideArray = insideArray;
    }
  }

  /** XML parser. */
  private final XmlPullParser xmlParser;

  /** Option. */
  final Options options;

  /** Tokens pool. */
  private final RefsPool<TokenRef> tokensPool = new RefsPool<TokenRef>(new Creator<TokenRef>() {
    public TokenRef create() { return new TokenRef(); }
  });
  /** Values pool. */
  private final RefsPool<ValueRef> valuesPool = new RefsPool<ValueRef>(new Creator<ValueRef>() {
    public ValueRef create() { return new ValueRef(); }
  });

  /** Tokens queue. */
  private TokenRef tokensQueue, tokensQueueStart;
  /** Values queue. */
  private ValueRef valuesQueue, valuesQueueStart;

  private JsonToken expectedToken;

  /** State. */
  private boolean endReached, firstStart = true, lastTextWhiteSpace = false;

  /** Stack of scopes. */
  private final Stack<Scope> scopeStack = new Stack<Scope>();
  /** Stack of last closed tags. */
  private final Stack<ClosedTag> closeStack = new Stack<ClosedTag>();

  /** Current token. */
  private JsonToken token;

  /** Counter for "$". */
  private int textNameCounter = 0;

  /** Skipping state flag. */
  private boolean skipping;

  /** Last XML token info. */
  private final XmlTokenInfo xmlToken = new XmlTokenInfo();

  /** Attributes. */
  private final AttributesData attributes = new AttributesData(10);

  public XmlReader(final Reader in, final XmlParserCreator creator, final Options options) {
    super(in);
    this.xmlParser = creator.createParser();
    this.options = options;
    this.xmlToken.type = IGNORE;
    try {
      this.xmlParser.setInput(in);
      this.xmlParser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, options.namespaces);
    } catch (final XmlPullParserException e) {
      throw new RuntimeException(e);
    }
  }

  @SuppressWarnings("unused")
  private CharSequence dump() {
    return new StringBuilder()
      .append("Scopes: ").append(scopeStack).append('\n')
      .append("Closed tags: ").append(closeStack).append('\n')
      .append("Token: ").append(token).append('\n')
      .append("Tokens queue: ").append(tokensQueueStart).append('\n')
      .append("Values queue: ").append(valuesQueueStart).append('\n');
  }

  @Override
  public String toString() { return "--- XmlReader ---\n" + dump(); }

  private JsonToken peekNextToken() { return tokensQueueStart != null ? tokensQueueStart.token : null; }

  private JsonToken nextToken() {
    final TokenRef ref = tokensQueueStart;
    if (ref == null) {
      return JsonToken.END_DOCUMENT;
    }

    tokensQueueStart = ref.next;
    if (ref == tokensQueue) { tokensQueue = null; }
    tokensPool.release(ref);
    return ref.token;
  }

  private ValueRef nextValue() {
    final ValueRef ref = valuesQueueStart;
    if (ref == null) { throw new IllegalStateException("No value can be given"); }
    if (ref == valuesQueue) { valuesQueue = null; }
    valuesPool.release(ref);
    valuesQueueStart = ref.next;
    return ref;
  }

  private void expect(final JsonToken token) throws IOException {
    final JsonToken actual = peek();
    this.token = null;
    if (actual != token) { throw new IllegalStateException(token + " expected, but met " + actual + "\n" + dump()); }
  }

  @Override
  public void beginObject() throws IOException {
    expectedToken = JsonToken.BEGIN_OBJECT;
    expect(expectedToken);
  }
  @Override
  public void endObject() throws IOException {
    expectedToken = JsonToken.END_OBJECT;
    expect(expectedToken);
  }
  @Override
  public void beginArray() throws IOException {
    expectedToken = JsonToken.BEGIN_ARRAY;
    expect(expectedToken);
  }
  @Override
  public void endArray() throws IOException {
    expectedToken = JsonToken.END_ARRAY;
    expect(expectedToken);
  }

  @Override
  public boolean hasNext() throws IOException {
    peek();
    return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY;
  }

  @Override
  public void skipValue() throws IOException {
    skipping = true;
    try {
      int count = 0;
      do {
        final JsonToken token = peek();
        if (token == JsonToken.BEGIN_ARRAY || token == JsonToken.BEGIN_OBJECT) {
          count++;
        } else if (token == JsonToken.END_ARRAY || token == JsonToken.END_OBJECT) {
          count--;
        } else if (valuesQueue != null) {
          nextValue(); // pull ignored value
        }
        this.token = null; // advance
      } while (count != 0);
    } finally {
      skipping = false;
    }
  }

  private void adaptCurrentToken() throws XmlPullParserException, IOException {
    if (token == expectedToken) { return; }
    if (expectedToken != JsonToken.BEGIN_ARRAY) { return; }

    switch (token) {

    case BEGIN_OBJECT:

      token = JsonToken.BEGIN_ARRAY;

      final Scope lastScope = scopeStack.peek();

      if (peekNextToken() == JsonToken.NAME) {
        if (options.sameNameList) {
          // we are replacing current scope with INSIDE_EMBEDDED_ARRAY
          scopeStack.cleanup(1);

          // use it as a field
          pushToQueue(JsonToken.BEGIN_OBJECT);

          scopeStack.push(Scope.INSIDE_EMBEDDED_ARRAY);
          scopeStack.push(Scope.INSIDE_OBJECT);
          if (lastScope == Scope.NAME) {
            scopeStack.push(Scope.NAME);
          }
        } else {
          // ignore name
          nextToken();
          nextValue();

          int pushPos = scopeStack.size();
          if (options.primitiveArrays && peekNextToken() == null) {
            // pull what next: it can be either primitive or object
            fillQueues(true);
          }
          pushPos = scopeStack.cleanup(3, pushPos);

          if (options.primitiveArrays && peekNextToken() == JsonToken.STRING) {
            // primitive
            scopeStack.pushAt(pushPos, Scope.INSIDE_PRIMITIVE_ARRAY);
          } else {
            // object (if array it will be adapted again)
            scopeStack.pushAt(pushPos, Scope.INSIDE_ARRAY);
            if (scopeStack.size() <= pushPos + 1 || scopeStack.get(pushPos + 1) != Scope.INSIDE_OBJECT) {
              scopeStack.pushAt(pushPos + 1, Scope.INSIDE_OBJECT);
            }
            if (peekNextToken() != JsonToken.BEGIN_OBJECT) {
              pushToQueue(JsonToken.BEGIN_OBJECT);
            }
          }

        }
      }
      break;

    case STRING:
      token = JsonToken.BEGIN_ARRAY;
      if (options.sameNameList) {

        if (options.primitiveArrays) {
          // we have array of primitives
          pushToQueue(JsonToken.STRING);
          scopeStack.push(Scope.INSIDE_PRIMITIVE_EMBEDDED_ARRAY);
        } else {
          // pass value as a text node inside of an object
          String value = nextValue().value;
          pushToQueue(JsonToken.END_OBJECT);
          pushToQueue(JsonToken.STRING);
          pushToQueue(JsonToken.NAME);
          pushToQueue(JsonToken.BEGIN_OBJECT);
          pushToQueue(value);
          pushToQueue("$");
          scopeStack.push(Scope.INSIDE_EMBEDDED_ARRAY);
        }

      } else {
        // we have an empty list
        pushToQueue(JsonToken.END_ARRAY);
      }
      break;

    default:
    }

  }

  @Override
  public JsonToken peek() throws IOException {
    if (expectedToken == null && firstStart) { return JsonToken.BEGIN_OBJECT; }

    if (token != null) {
      try {
        adaptCurrentToken();
      } catch (final XmlPullParserException e) {
        throw new JsonSyntaxException("XML parsing exception", e);
      }
      expectedToken = null;
      return token;
    }

    try {

      fillQueues(false);
      expectedToken = null;

      return token = nextToken();

    } catch (final XmlPullParserException e) {
      throw new JsonSyntaxException("XML parsing exception", e);
    }
  }

  @Override
  public String nextString() throws IOException {
    expect(JsonToken.STRING);
    return nextValue().value;
  }
  @Override
  public boolean nextBoolean() throws IOException {
    expect(JsonToken.BOOLEAN);
    final String value = nextValue().value;
    if ("true".equalsIgnoreCase(value)) {
      return true;
    }
    if ("false".equalsIgnoreCase(value)) {
      return true;
    }
    throw new IOException("Cannot parse <" + value + "> to boolean");
  }
  @Override
  public double nextDouble() throws IOException {
    expect(JsonToken.STRING);
    return Double.parseDouble(nextValue().value);
  }
  @Override
  public int nextInt() throws IOException {
    expect(JsonToken.STRING);
    return Integer.parseInt(nextValue().value);
  }
  @Override
  public long nextLong() throws IOException {
    expect(JsonToken.STRING);
    return Long.parseLong(nextValue().value);
  }
  @Override
  public String nextName() throws IOException {
    expectedToken = JsonToken.NAME;
    expect(JsonToken.NAME);
    return nextValue().value;
  }


  private XmlTokenInfo nextXmlInfo() throws IOException, XmlPullParserException {
    final int type = xmlParser.next();

    final XmlTokenInfo info = this.xmlToken;
    info.clear();

    switch (type) {

    case XmlPullParser.START_TAG:
      info.type = START_TAG;
      info.name = xmlParser.getName();
      info.ns = xmlParser.getNamespace();
      final int aCount = xmlParser.getAttributeCount();
      if (aCount > 0) {
        attributes.fill(xmlParser);
        info.attributesData = attributes;
      }
      break;

    case XmlPullParser.END_TAG:
      info.type = END_TAG;
      info.name = xmlParser.getName();
      info.ns = xmlParser.getNamespace();
      break;

    case XmlPullParser.TEXT:
      final String text = xmlParser.getText().trim();
      if (text.length() == 0) {
        lastTextWhiteSpace = true;
        info.type = IGNORE;
        return info;
      }
      lastTextWhiteSpace = false;
      info.type = VALUE;
      info.value = text;
      break;


    case XmlPullParser.END_DOCUMENT:
      endReached = true;
      // fall through

    default:
      info.type = IGNORE;
    }

    return info;
  }

  private void addToQueue(final JsonToken token) {
    final TokenRef tokenRef = tokensPool.get();
    tokenRef.token = token;
    tokenRef.next = null;

    if (tokensQueue == null) {
      tokensQueue = tokenRef;
      tokensQueueStart = tokenRef;
    } else {
      tokensQueue.next = tokenRef;
      tokensQueue = tokenRef;
    }
  }
  private void pushToQueue(final JsonToken token) {
    final TokenRef tokenRef = tokensPool.get();
    tokenRef.token = token;
    tokenRef.next = null;

    if (tokensQueueStart == null) {
      tokensQueueStart = tokenRef;
      tokensQueue = tokenRef;
    } else {
      tokenRef.next = tokensQueueStart;
      tokensQueueStart = tokenRef;
    }
  }
  private void addToQueue(final String value) {
    final ValueRef valueRef = valuesPool.get();
    valueRef.value = value.trim();
    valueRef.next = null;

    if (valuesQueue == null) {
      valuesQueue = valueRef;
      valuesQueueStart = valueRef;
    } else {
      valuesQueue.next = valueRef;
      valuesQueue = valueRef;
    }
  }
  private void pushToQueue(final String value) {
    final ValueRef valueRef = valuesPool.get();
    valueRef.value = value;
    valueRef.next = null;

    if (valuesQueueStart == null) {
      valuesQueue = valueRef;
      valuesQueueStart = valueRef;
    } else {
      valueRef.next = valuesQueueStart;
      valuesQueueStart = valueRef;
    }
  }
  private void addToQueue(final AttributesData attrData) throws IOException, XmlPullParserException {
    final int count = attrData.count;
    for (int i = 0; i < count; i++) {
      addToQueue(JsonToken.NAME);
      addToQueue("@" + attrData.getName(i));
      addToQueue(JsonToken.STRING);
      addToQueue(attrData.values[i]);
    }
  }

  private void fillQueues(boolean force) throws IOException, XmlPullParserException {

    boolean mustRepeat = force;

    while ((tokensQueue == null && !endReached) || mustRepeat) {
      final XmlTokenInfo xml = nextXmlInfo();
      if (endReached) {
        if (!options.skipRoot) { addToQueue(JsonToken.END_OBJECT); }
        break;
      }
      if (xml.type == IGNORE) { continue; }

      mustRepeat = false;

      switch (xml.type) {
      case START_TAG:
        if (firstStart) {
          firstStart = false;
          processRoot(xml);
        } else {
          processStart(xml);
        }
        break;
      case VALUE:
        mustRepeat = processText(xml);
        break;
      case END_TAG:
        processEnd(xml);
        break;
      default:
      }

      if (!mustRepeat && skipping) { break; }
    }
  }

  private void processRoot(final XmlTokenInfo xml) throws IOException, XmlPullParserException {
    if (!options.skipRoot) {

      addToQueue(expectedToken);
      scopeStack.push(Scope.INSIDE_OBJECT);
      processStart(xml);

    } else if (xml.attributesData != null) {

      addToQueue(JsonToken.BEGIN_OBJECT);
      scopeStack.push(Scope.INSIDE_OBJECT);
      addToQueue(xml.attributesData);

    } else {

      switch (expectedToken) {
      case BEGIN_OBJECT:
        addToQueue(JsonToken.BEGIN_OBJECT);
        scopeStack.push(Scope.INSIDE_OBJECT);
        break;
      case BEGIN_ARRAY:
        addToQueue(JsonToken.BEGIN_ARRAY);
        scopeStack.push(options.rootArrayPrimitive ? Scope.INSIDE_PRIMITIVE_ARRAY : Scope.INSIDE_ARRAY);
        break;
      default:
        throw new IllegalStateException("First expectedToken=" + expectedToken + " (not begin_object/begin_array)");
      }

    }
  }

  private void processStart(final XmlTokenInfo xml) throws IOException, XmlPullParserException {

    boolean processTagName = true;

    Scope lastScope = scopeStack.peek();

    if (options.sameNameList && lastScope.insideArray && closeStack.size() > 0) {
      ClosedTag lastClosedInfo = closeStack.peek();
      if (lastClosedInfo.depth == xmlParser.getDepth()) {
        String currentName = options.namespaces ? xml.getName(xmlParser) : xml.name;
        if (!currentName.equals(lastClosedInfo.name)) {
          // close the previous array
          addToQueue(JsonToken.END_ARRAY);
          fixScopeStack();
          lastScope = scopeStack.peek();
        }
      }
    }

    switch (lastScope) {

    case INSIDE_PRIMITIVE_ARRAY:
    case INSIDE_PRIMITIVE_EMBEDDED_ARRAY:
      processTagName = false;
      scopeStack.push(Scope.PRIMITIVE_VALUE);
      break;

    case INSIDE_EMBEDDED_ARRAY:
    case INSIDE_ARRAY:
      processTagName = false;
      // fall through

    case NAME:
      addToQueue(JsonToken.BEGIN_OBJECT);
      scopeStack.push(Scope.INSIDE_OBJECT);
      break;

    default:
    }

    if (processTagName) {                 // ignore tag name inside the array
      scopeStack.push(Scope.NAME);
      addToQueue(JsonToken.NAME);
      addToQueue(xml.getName(xmlParser));
      lastTextWhiteSpace = true;           // if tag is closed immediately we'll add empty value to the queue
    }

    if (xml.attributesData != null) {
      lastScope = scopeStack.peek();
      if (lastScope == Scope.PRIMITIVE_VALUE) { throw new IllegalStateException("Attributes data in primitive scope"); }
      if (lastScope == Scope.NAME) {
        addToQueue(JsonToken.BEGIN_OBJECT);
        scopeStack.push(Scope.INSIDE_OBJECT);
      }
      // attributes, as fields
      addToQueue(xml.attributesData);
    }
  }

  private boolean processText(final XmlTokenInfo xml) {
    switch (scopeStack.peek()) {

    case PRIMITIVE_VALUE:
      addTextToQueue(xml.value, false);
      return false;

    case NAME:
      addTextToQueue(xml.value, true);
      return true;

    case INSIDE_OBJECT:
      String name = "$";
      if (textNameCounter > 0) { name += textNameCounter; }
      textNameCounter++;
      addToQueue(JsonToken.NAME);
      addToQueue(name);
      addTextToQueue(xml.value, false);
      return false;

    default:
      throw new JsonSyntaxException("Cannot process text '" + xml.value + "' inside scope " + scopeStack.peek());
    }
  }

  private void addTextToQueue(final String value, final boolean canBeAppended) {
    if (canBeAppended && tokensQueue != null && tokensQueue.token == JsonToken.STRING) {
      if (value.length() > 0) {
        valuesQueue.value += " " + value;
      }
    } else {
      addToQueue(JsonToken.STRING);
      addToQueue(value);
    }
  }

  private void fixScopeStack() {
    scopeStack.fix(Scope.NAME);
  }

  private void processEnd(final XmlTokenInfo xml) throws IOException, XmlPullParserException {
    switch (scopeStack.peek()) {

    case INSIDE_OBJECT:
      addToQueue(JsonToken.END_OBJECT);
      textNameCounter = 0;
      fixScopeStack();
      break;

    case PRIMITIVE_VALUE:
      scopeStack.drop();
      break;

    case INSIDE_PRIMITIVE_EMBEDDED_ARRAY:
    case INSIDE_EMBEDDED_ARRAY:
      addToQueue(JsonToken.END_ARRAY);
      addToQueue(JsonToken.END_OBJECT);
      fixScopeStack(); // auto-close embedded array
      fixScopeStack(); // close current object scope
      break;

    case INSIDE_PRIMITIVE_ARRAY:
    case INSIDE_ARRAY:
      addToQueue(JsonToken.END_ARRAY);
      fixScopeStack();
      break;

    case NAME:
      if (lastTextWhiteSpace) {
        addTextToQueue("", true);
      }
      fixScopeStack();
      break;

    default:
      // nothing
    }

    if (options.sameNameList) {
      int stackSize = xmlParser.getDepth();
      final String name = options.namespaces ? xml.getName(xmlParser) : xml.name;
      final Stack<ClosedTag> closeStack = this.closeStack;
      boolean nameChange = false;
      while (closeStack.size() > 0 && closeStack.peek().depth > stackSize) {
        closeStack.drop();
      }
      if (closeStack.size() == 0 || closeStack.peek().depth < stackSize) {
        closeStack.push(new ClosedTag(stackSize, name));
      } else {
        closeStack.peek().name = name;
      }
    }
  }

  private static final class TokenRef {
    JsonToken token;
    TokenRef next;
    @Override
    public String toString() {
      return token + ", " + next;
    }
  }
  private static final class ValueRef {
    String value;
    ValueRef next;
    @Override
    public String toString() {
      return value + ", " + next;
    }
  }

  static String nameWithNs(final String name, final String namespace, final XmlPullParser parser) throws XmlPullParserException {
    String result = name;
    String ns = namespace;
    if (ns != null && ns.length() > 0) {
      if (parser != null) {
        final int count = parser.getNamespaceCount(parser.getDepth());
        for (int i = 0; i < count; i++) {
          if (ns.equals(parser.getNamespaceUri(i))) {
            ns = parser.getNamespacePrefix(i);
            break;
          }
        }
      }
      result = "<" + ns + ">" + result;
    }
    return result;
  }

  private static final class XmlTokenInfo {
    int type;
    String name, value, ns;

    AttributesData attributesData;

    public void clear() {
      type = IGNORE;
      name = null;
      value = null;
      ns = null;
      attributesData = null;
    }

    @Override
    public String toString() {
      return "xml "
          + (type == START_TAG ? "start" : type == END_TAG ? "end" : "value")
          + " <" + ns + ":" + name + ">=" + value + (attributesData != null ? ", " + attributesData : "");
    }

    public String getName(final XmlPullParser parser) throws IOException, XmlPullParserException {
      return nameWithNs(name, ns, parser);
    }
  }

  private final class AttributesData {
    String[] names, values, ns;

    int count = 0;

    public AttributesData(final int capacity) {
      createArrays(capacity);
    }

    private void createArrays(final int capacity) {
      this.names = new String[capacity];
      this.values = new String[capacity];
      this.ns = new String[capacity];
    }

    public void fill(final XmlPullParser parser) {
      final int aCount = parser.getAttributeCount();
      if (aCount > names.length) {
        createArrays(aCount);
      }

      count = aCount;
      for (int i = 0; i < aCount; i++) {
        names[i] = parser.getAttributeName(i);
        if (options.namespaces) {
          ns[i] = parser.getAttributePrefix(i);
        }
        values[i] = parser.getAttributeValue(i);
      }
    }

    public String getName(final int i) throws IOException, XmlPullParserException {
      return nameWithNs(names[i], ns[i], null);
    }

  }

  /** Xml reader options. */
  public static class Options {
    /** Options. */
    boolean primitiveArrays, skipRoot, sameNameList, namespaces, rootArrayPrimitive;
  }

  /** Closed tag data. */
  private static class ClosedTag {
    int depth;
    String name;

    public ClosedTag(final int depth, final String name) {
      this.depth = depth;
      this.name = name;
    }

    @Override
    public String toString() {
      return "'" + name + "'/" + depth;
    }
  }

  /** Pool for  */
  private static final class RefsPool<T> {

    /** Max count. */
    private static final int SIZE = 32;

    /** Factory instance. */
    private final Creator<T> creator;

    /** Pool. */
    private final Object[] store = new Object[SIZE];

    /** Store length. */
    private int len = 0;

    public RefsPool(final Creator<T> factory) {
      this.creator = factory;
    }

    /** Get value from pool or create it. */
    public T get() {
      if (len == 0) { return creator.create(); }
      return (T)store[--len];
    }

    /** Return value to the pool. */
    public void release(final T obj) {
      if (len < SIZE) {
        store[len++] = obj;
      }
    }

  }

  /** Factory. */
  private interface Creator<T> { T create(); }

}
TOP

Related Classes of com.stanfy.gsonxml.XmlReader$Creator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.