001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2010-2010 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.xmla.impl;
011
012import mondrian.olap.Util;
013import mondrian.util.ArrayStack;
014import mondrian.xmla.SaxWriter;
015
016import java.io.IOException;
017import java.io.OutputStream;
018import java.util.Arrays;
019
020/**
021 * Implementation of <code>SaxWriter</code> which, perversely, generates a
022 * JSON (JavaScript Object Notation) document.
023 *
024 * @author jhyde
025 */
026class JsonSaxWriter implements SaxWriter {
027    private final StringBuilder buf = new StringBuilder();
028    private int indent;
029    private String[] indentStrings = INITIAL_INDENT_STRINGS;
030    private String indentString = indentStrings[0];
031    private final ArrayStack<Frame> stack = new ArrayStack<Frame>();
032    private OutputStream outputStream;
033
034    private static final String[] INITIAL_INDENT_STRINGS = {
035        "",
036        "  ",
037        "    ",
038        "      ",
039        "        ",
040        "          ",
041        "            ",
042        "              ",
043        "                ",
044        "                  ",
045    };
046
047    /**
048     * Creates a JsonSaxWriter.
049     *
050     * @param outputStream Output stream
051     */
052    public JsonSaxWriter(OutputStream outputStream) {
053        this.outputStream = outputStream;
054    }
055
056    public void startDocument() {
057        stack.push(new Frame(null));
058    }
059
060    public void endDocument() {
061        stack.pop();
062        flush();
063    }
064
065    public void startSequence(String name, String subName) {
066        comma();
067        buf.append(indentString);
068        if (name == null) {
069            name = subName;
070        }
071        if (stack.peek().name != null) {
072            assert name.equals(stack.peek().name)
073                : "In sequence [" + stack.peek() + "], element name ["
074                  + name + "]";
075            buf.append("[");
076        } else {
077            Util.quoteForMdx(buf, name);
078            buf.append(": [");
079        }
080
081        assert subName != null;
082        stack.push(new Frame(subName));
083        indent();
084    }
085
086    public void endSequence() {
087        assert stack.peek() != null : "not in sequence";
088        stack.pop();
089        outdent();
090
091        buf.append("\n");
092        buf.append(indentString);
093        buf.append("]");
094    }
095
096    public void startElement(String name) {
097        comma();
098        buf.append(indentString);
099        if (stack.peek().name != null) {
100            assert name.equals(stack.peek().name)
101                : "In sequence [" + stack.peek() + "], element name ["
102                  + name + "]";
103            buf.append("{");
104        } else {
105            Util.quoteForMdx(buf, name);
106            buf.append(": {");
107        }
108
109        stack.push(new Frame(null));
110        indent();
111    }
112
113    public void startElement(String name, Object... attrs) {
114        startElement(name);
115        for (int i = 0; i < attrs.length;) {
116            if (i > 0) {
117                buf.append(",\n");
118            } else {
119                buf.append("\n");
120            }
121            String attr = (String) attrs[i++];
122            buf.append(indentString);
123            Util.quoteForMdx(buf, attr);
124            buf.append(": ");
125            Object value = attrs[i++];
126            value(value);
127        }
128        stack.peek().ordinal = attrs.length / 2;
129    }
130
131    public void endElement() {
132        Frame prev = stack.pop();
133        assert prev.name == null
134            : "Ended an element, but in sequence " + prev.name;
135        buf.append("\n");
136        outdent();
137        buf.append(indentString);
138        buf.append("}");
139    }
140
141    public void element(String name, Object... attrs) {
142        startElement(name, attrs);
143        endElement();
144    }
145
146    public void characters(String data) {
147        throw new UnsupportedOperationException();
148    }
149
150    public void textElement(String name, Object data) {
151        comma();
152        buf.append(indentString);
153        Util.quoteForMdx(buf, name);
154        buf.append(": ");
155        value(data);
156    }
157
158    public void completeBeforeElement(String tagName) {
159        throw new UnsupportedOperationException();
160    }
161
162    public void verbatim(String text) {
163        throw new UnsupportedOperationException();
164    }
165
166    public void flush() {
167        try {
168            outputStream.write(buf.toString().substring(1).getBytes());
169        } catch (IOException e) {
170            throw Util.newError(e, "While encoding JSON response");
171        }
172    }
173
174    // helper methods
175
176    private void indent() {
177        ++indent;
178        if (indent >= indentStrings.length) {
179            final int newLength = indentStrings.length * 2 + 1;
180            final int INDENT = 2;
181            assert indentStrings[1].length() == INDENT;
182            char[] chars = new char[newLength * INDENT];
183            Arrays.fill(chars, ' ');
184            String s = new String(chars);
185            indentStrings = new String[newLength];
186            for (int i = 0; i < newLength; ++i) {
187                indentStrings[i] = s.substring(0, i * INDENT);
188            }
189        }
190        indentString = indentStrings[indent];
191    }
192
193    private void outdent() {
194        indentString = indentStrings[--indent];
195    }
196
197    /**
198     * Writes a value with appropriate quoting for a JavaScript constant
199     * of that type.
200     *
201     * <p>Examples: {@code "a \"quoted\" string"} (string),
202     * {@code 12} (int), {@code 12.345} (float), {@code null} (null value).
203     *
204     * @param value Value
205     */
206    private void value(Object value) {
207        if (value instanceof String) {
208            String s = (String) value;
209            Util.quoteForMdx(buf, s);
210        } else {
211            buf.append(value);
212        }
213    }
214
215    private void comma() {
216        if (stack.peek().ordinal++ > 0) {
217            buf.append(",\n");
218        } else {
219            buf.append("\n");
220        }
221    }
222
223    private static class Frame {
224        final String name;
225        int ordinal;
226
227        Frame(String name) {
228            this.name = name;
229        }
230    }
231}
232
233// End JsonSaxWriter.java