JacksonStreamSource.java

/*
 * #%L
 * wcm.io
 * %%
 * Copyright (C) 2014 wcm.io
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package io.wcm.caravan.io.jsontransform.source;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;

import org.osgi.annotation.versioning.ProviderType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;

import io.wcm.caravan.io.jsontransform.element.JsonElement;

/**
 * JSON source using the Jackson streaming parser.
 */
@ProviderType
public final class JacksonStreamSource implements Source {

  private static final Logger LOG = LoggerFactory.getLogger(JacksonStreamSource.class);

  private static final JsonFactory JSON_FACTORY = new JsonFactory();
  private final JsonParser parser;

  private JsonElement current;

  /**
   * @param input The input stream
   * @throws JsonParseException Error reading json
   * @throws IOException Error reading stream
   */
  public JacksonStreamSource(final InputStream input) throws JsonParseException, IOException {
    parser = JSON_FACTORY.createParser(input);
  }

  @Override
  public boolean hasNext() {
    parseJsonElementIfNeeded();
    return current != null;
  }

  @Override
  public JsonElement next() {
    parseJsonElementIfNeeded();
    JsonElement next = current;
    current = null;
    return next;
  }

  private void parseJsonElementIfNeeded() {
    if (current == null) {
      try {
        JsonToken token = parser.nextToken();
        if (token != null) {
          current = parseJsonElement(token);
        }
      }
      catch (IOException ex) {
        LOG.error("Error reading JSON stream", ex);
      }
    }
  }

  private JsonElement parseJsonElement(final JsonToken token) throws IOException {
    String key = parser.getCurrentName();
    if (JsonToken.START_OBJECT.equals(token)) {
      return JsonElement.DEFAULT_START_OBJECT;
    }
    else if (JsonToken.END_OBJECT.equals(token)) {
      return JsonElement.DEFAULT_END_OBJECT;
    }
    else if (JsonToken.START_ARRAY.equals(token)) {
      return JsonElement.DEFAULT_START_ARRAY;
    }
    else if (JsonToken.END_ARRAY.equals(token)) {
      return JsonElement.DEFAULT_END_ARRAY;
    }
    else if (JsonToken.FIELD_NAME.equals(token)) {
      return handleFieldName(key);
    }
    return JsonElement.value(key, parseValue(token));
  }

  private JsonElement handleFieldName(String key) throws IOException, JsonParseException {
    JsonToken next = parser.nextToken();
    if (JsonToken.START_OBJECT.equals(next)) {
      return JsonElement.startObject(key);
    }
    else if (JsonToken.START_ARRAY.equals(next)) {
      return JsonElement.startArray(key);
    }
    else {
      return JsonElement.value(key, parseValue(next));
    }
  }

  private Object parseValue(final JsonToken token) throws IOException {
    if (token.isBoolean()) {
      return parser.getBooleanValue();
    }
    else if (token.isNumeric()) {
      return new BigDecimal(parser.getText());
    }
    else {
      return parser.getText();
    }
  }

  @Override
  public void close() throws IOException {
    parser.close();
  }

}