1/*
2 * Copyright (C) 2011 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package benchmarks.regression;
18
19import com.google.caliper.BeforeExperiment;
20import com.google.caliper.Param;
21import java.io.IOException;
22import java.io.InputStream;
23import java.io.InputStreamReader;
24import java.io.Reader;
25import java.io.StringReader;
26import java.io.StringWriter;
27import javax.xml.parsers.DocumentBuilderFactory;
28import javax.xml.parsers.SAXParserFactory;
29import org.json.JSONArray;
30import org.json.JSONObject;
31import org.xml.sax.InputSource;
32import org.xml.sax.helpers.DefaultHandler;
33import org.xmlpull.v1.XmlPullParser;
34
35/**
36 * Measure throughput of various parsers.
37 *
38 * <p>This benchmark requires that ParseBenchmarkData.zip is on the classpath.
39 * That file contains Twitter feed data, which is representative of what
40 * applications will be parsing.
41 */
42public final class ParseBenchmark {
43
44    @Param Document document;
45    @Param Api api;
46
47    private enum Document {
48        TWEETS,
49        READER_SHORT,
50        READER_LONG
51    }
52
53    private enum Api {
54        ANDROID_STREAM("json") {
55            @Override Parser newParser() {
56                return new AndroidStreamParser();
57            }
58        },
59        ORG_JSON("json") {
60            @Override Parser newParser() {
61                return new OrgJsonParser();
62            }
63        },
64        XML_PULL("xml") {
65            @Override Parser newParser() {
66                return new GeneralXmlPullParser();
67            }
68        },
69        XML_DOM("xml") {
70            @Override Parser newParser() {
71                return new XmlDomParser();
72            }
73        },
74        XML_SAX("xml") {
75            @Override Parser newParser() {
76                return new XmlSaxParser();
77            }
78        };
79
80        final String extension;
81
82        private Api(String extension) {
83            this.extension = extension;
84        }
85
86        abstract Parser newParser();
87    }
88
89    private String text;
90    private Parser parser;
91
92    @BeforeExperiment
93    protected void setUp() throws Exception {
94        text = resourceToString("/" + document.name() + "." + api.extension);
95        parser = api.newParser();
96    }
97
98    public void timeParse(int reps) throws Exception {
99        for (int i = 0; i < reps; i++) {
100            parser.parse(text);
101        }
102    }
103
104    private static String resourceToString(String path) throws Exception {
105        InputStream in = ParseBenchmark.class.getResourceAsStream(path);
106        if (in == null) {
107            throw new IllegalArgumentException("No such file: " + path);
108        }
109
110        Reader reader = new InputStreamReader(in, "UTF-8");
111        char[] buffer = new char[8192];
112        StringWriter writer = new StringWriter();
113        int count;
114        while ((count = reader.read(buffer)) != -1) {
115            writer.write(buffer, 0, count);
116        }
117        reader.close();
118        return writer.toString();
119    }
120
121    interface Parser {
122        void parse(String data) throws Exception;
123    }
124
125    private static class AndroidStreamParser implements Parser {
126        @Override public void parse(String data) throws Exception {
127            android.util.JsonReader jsonReader
128                    = new android.util.JsonReader(new StringReader(data));
129            readToken(jsonReader);
130            jsonReader.close();
131        }
132
133        public void readObject(android.util.JsonReader reader) throws IOException {
134            reader.beginObject();
135            while (reader.hasNext()) {
136                reader.nextName();
137                readToken(reader);
138            }
139            reader.endObject();
140        }
141
142        public void readArray(android.util.JsonReader reader) throws IOException {
143            reader.beginArray();
144            while (reader.hasNext()) {
145                readToken(reader);
146            }
147            reader.endArray();
148        }
149
150        private void readToken(android.util.JsonReader reader) throws IOException {
151            switch (reader.peek()) {
152            case BEGIN_ARRAY:
153                readArray(reader);
154                break;
155            case BEGIN_OBJECT:
156                readObject(reader);
157                break;
158            case BOOLEAN:
159                reader.nextBoolean();
160                break;
161            case NULL:
162                reader.nextNull();
163                break;
164            case NUMBER:
165                reader.nextLong();
166                break;
167            case STRING:
168                reader.nextString();
169                break;
170            default:
171                throw new IllegalArgumentException("Unexpected token" + reader.peek());
172            }
173        }
174    }
175
176    private static class OrgJsonParser implements Parser {
177        @Override public void parse(String data) throws Exception {
178            if (data.startsWith("[")) {
179                new JSONArray(data);
180            } else if (data.startsWith("{")) {
181                new JSONObject(data);
182            } else {
183                throw new IllegalArgumentException();
184            }
185        }
186    }
187
188    private static class GeneralXmlPullParser implements Parser {
189        @Override public void parse(String data) throws Exception {
190            XmlPullParser xmlParser = android.util.Xml.newPullParser();
191            xmlParser.setInput(new StringReader(data));
192            xmlParser.nextTag();
193            while (xmlParser.next() != XmlPullParser.END_DOCUMENT) {
194                xmlParser.getName();
195                xmlParser.getText();
196            }
197        }
198    }
199
200    private static class XmlDomParser implements Parser {
201        @Override public void parse(String data) throws Exception {
202            DocumentBuilderFactory.newInstance().newDocumentBuilder()
203                    .parse(new InputSource(new StringReader(data)));
204        }
205    }
206
207    private static class XmlSaxParser implements Parser {
208        @Override public void parse(String data) throws Exception {
209            SAXParserFactory.newInstance().newSAXParser().parse(
210                    new InputSource(new StringReader(data)), new DefaultHandler());
211        }
212    }
213}
214