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