/* * Copyright 2011, 2012 Odysseus Software GmbH * * 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. */ package org.apache.synapse.commons.staxon.core.json.stream.util; import java.io.IOException; import java.util.Deque; import java.util.LinkedList; import java.util.Stack; import org.apache.synapse.commons.staxon.core.json.JsonXMLStreamWriter; import org.apache.synapse.commons.staxon.core.json.stream.JsonStreamTarget; import org.apache.synapse.commons.staxon.core.json.stream.JsonStreamToken; /** * Target filter to auto-insert array boundaries. * <p/> * Note: this class caches all events and flushes to the * underlying target after receiving the last close-object * event, which may cause memory issues for large documents. * Also, auto-recognition of array boundaries never creates * arrays with a single element. * <p/> * It is recommended to handle array boundaries via the * {@link JsonXMLStreamWriter#writeStartArray(String)} and * {@link JsonXMLStreamWriter#writeEndArray()} methods * or by producing <code><?xml-muliple ...?></code> * processing instructions. */ public class AutoArrayTarget implements JsonStreamTarget { /** * Event type */ static interface Event { JsonStreamToken token(); void write(JsonStreamTarget target) throws IOException; } static final Event START_OBJECT = new Event() { @Override public void write(JsonStreamTarget target) throws IOException { target.startObject(); } @Override public JsonStreamToken token() { return JsonStreamToken.START_OBJECT; } @Override public String toString() { return token().name(); } }; static final Event END_OBJECT = new Event() { @Override public void write(JsonStreamTarget target) throws IOException { target.endObject(); } @Override public JsonStreamToken token() { return JsonStreamToken.END_OBJECT; } @Override public String toString() { return token().name(); } }; static final Event END_ARRAY = new Event() { @Override public void write(JsonStreamTarget target) throws IOException { target.endArray(); } @Override public JsonStreamToken token() { return JsonStreamToken.END_ARRAY; } @Override public String toString() { return token().name(); } }; static final class NameEvent implements Event { final String name; boolean array; NameEvent(String name) { this.name = name; } @Override public void write(JsonStreamTarget target) throws IOException { target.name(name); if (array) { target.startArray(); } } @Override public JsonStreamToken token() { return JsonStreamToken.NAME; } public String name() { return name; } public boolean isArray() { return array; } public void setArray(boolean array) { this.array = array; } @Override public String toString() { if (array) { return token().name() + " = " + name + " " + JsonStreamToken.START_ARRAY; } else { return token().name() + " = " + name; } } } static final class ValueEvent implements Event { final Object value; ValueEvent(Object value) { this.value = value; } @Override public void write(JsonStreamTarget target) throws IOException { target.value(value); } @Override public JsonStreamToken token() { return JsonStreamToken.VALUE; } @Override public String toString() { return token().name() + " = " + value; } } /* * delegate target */ private final JsonStreamTarget delegate; /* * Event queue */ private final Deque<Event> events = new LinkedList<Event>(); /* * Field stack */ private final Stack<NameEvent> fields = new Stack<NameEvent>(); public AutoArrayTarget(JsonStreamTarget delegate) { this.delegate = delegate; } private void pushField(String name) { events.add(fields.push(new NameEvent(name))); } private void popField() { if (fields.pop().isArray()) { events.add(END_ARRAY); } } @Override public void name(String name) throws IOException { if (events.peekLast().token() == JsonStreamToken.START_OBJECT) { pushField(name); } else { if (name.equals(fields.peek().name())) { fields.peek().setArray(true); } else { popField(); pushField(name); } } } @Override public void value(Object value) throws IOException { events.add(new ValueEvent(value)); } @Override public void startObject() throws IOException { events.add(START_OBJECT); } @Override public void endObject() throws IOException { if (events.peekLast().token() != JsonStreamToken.START_OBJECT) { popField(); } events.add(END_OBJECT); if (fields.isEmpty()) { while (!events.isEmpty()) { events.pollFirst().write(delegate); } } } @Override public void startArray() throws IOException { if (fields.peek().isArray()) { throw new IllegalStateException(); } fields.peek().setArray(true); } @Override public void endArray() throws IOException { if (!fields.peek().isArray()) { throw new IllegalStateException(); } // array will be closed automatically } @Override public void close() throws IOException { while (!events.isEmpty()) { events.pollFirst().write(delegate); } delegate.close(); } @Override public void flush() throws IOException { delegate.flush(); } }