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.Param;
20import com.google.caliper.Runner;
21import com.google.caliper.SimpleBenchmark;
22import java.io.IOException;
23import java.io.InputStream;
24import java.io.InputStreamReader;
25import java.io.Reader;
26import java.io.StringReader;
27import java.io.StringWriter;
28import javax.xml.parsers.DocumentBuilderFactory;
29import javax.xml.parsers.SAXParserFactory;
30import org.json.JSONArray;
31import org.json.JSONObject;
32import org.xml.sax.InputSource;
33import org.xml.sax.helpers.DefaultHandler;
34import org.xmlpull.v1.XmlPullParser;
35
36/**
37 * Measure throughput of various parsers.
38 *
39 * <p>This benchmark requires that ParseBenchmarkData.zip is on the classpath.
40 * That file contains Twitter feed data, which is representative of what
41 * applications will be parsing.
42 */
43public final class ParseBenchmark extends SimpleBenchmark {
44
45    @Param Document document;
46    @Param Api api;
47
48    private enum Document {
49        TWEETS,
50        READER_SHORT,
51        READER_LONG
52    }
53
54    private enum Api {
55        ANDROID_STREAM("json") {
56            @Override Parser newParser() {
57                return new AndroidStreamParser();
58            }
59        },
60        ORG_JSON("json") {
61            @Override Parser newParser() {
62                return new OrgJsonParser();
63            }
64        },
65        XML_PULL("xml") {
66            @Override Parser newParser() {
67                return new GeneralXmlPullParser();
68            }
69        },
70        XML_DOM("xml") {
71            @Override Parser newParser() {
72                return new XmlDomParser();
73            }
74        },
75        XML_SAX("xml") {
76            @Override Parser newParser() {
77                return new XmlSaxParser();
78            }
79        };
80
81        final String extension;
82
83        private Api(String extension) {
84            this.extension = extension;
85        }
86
87        abstract Parser newParser();
88    }
89
90    private String text;
91    private Parser parser;
92
93    @Override 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    public static void main(String... args) throws Exception {
105        Runner.main(ParseBenchmark.class, args);
106    }
107
108    private static String resourceToString(String path) throws Exception {
109        InputStream in = ParseBenchmark.class.getResourceAsStream(path);
110        if (in == null) {
111            throw new IllegalArgumentException("No such file: " + path);
112        }
113
114        Reader reader = new InputStreamReader(in, "UTF-8");
115        char[] buffer = new char[8192];
116        StringWriter writer = new StringWriter();
117        int count;
118        while ((count = reader.read(buffer)) != -1) {
119            writer.write(buffer, 0, count);
120        }
121        reader.close();
122        return writer.toString();
123    }
124
125    interface Parser {
126        void parse(String data) throws Exception;
127    }
128
129    private static class AndroidStreamParser implements Parser {
130        @Override public void parse(String data) throws Exception {
131            android.util.JsonReader jsonReader
132                    = new android.util.JsonReader(new StringReader(data));
133            readToken(jsonReader);
134            jsonReader.close();
135        }
136
137        public void readObject(android.util.JsonReader reader) throws IOException {
138            reader.beginObject();
139            while (reader.hasNext()) {
140                reader.nextName();
141                readToken(reader);
142            }
143            reader.endObject();
144        }
145
146        public void readArray(android.util.JsonReader reader) throws IOException {
147            reader.beginArray();
148            while (reader.hasNext()) {
149                readToken(reader);
150            }
151            reader.endArray();
152        }
153
154        private void readToken(android.util.JsonReader reader) throws IOException {
155            switch (reader.peek()) {
156            case BEGIN_ARRAY:
157                readArray(reader);
158                break;
159            case BEGIN_OBJECT:
160                readObject(reader);
161                break;
162            case BOOLEAN:
163                reader.nextBoolean();
164                break;
165            case NULL:
166                reader.nextNull();
167                break;
168            case NUMBER:
169                reader.nextLong();
170                break;
171            case STRING:
172                reader.nextString();
173                break;
174            default:
175                throw new IllegalArgumentException("Unexpected token" + reader.peek());
176            }
177        }
178    }
179
180    private static class OrgJsonParser implements Parser {
181        @Override public void parse(String data) throws Exception {
182            if (data.startsWith("[")) {
183                new JSONArray(data);
184            } else if (data.startsWith("{")) {
185                new JSONObject(data);
186            } else {
187                throw new IllegalArgumentException();
188            }
189        }
190    }
191
192    private static class GeneralXmlPullParser implements Parser {
193        @Override public void parse(String data) throws Exception {
194            XmlPullParser xmlParser = android.util.Xml.newPullParser();
195            xmlParser.setInput(new StringReader(data));
196            xmlParser.nextTag();
197            while (xmlParser.next() != XmlPullParser.END_DOCUMENT) {
198                xmlParser.getName();
199                xmlParser.getText();
200            }
201        }
202    }
203
204    private static class XmlDomParser implements Parser {
205        @Override public void parse(String data) throws Exception {
206            DocumentBuilderFactory.newInstance().newDocumentBuilder()
207                    .parse(new InputSource(new StringReader(data)));
208        }
209    }
210
211    private static class XmlSaxParser implements Parser {
212        @Override public void parse(String data) throws Exception {
213            SAXParserFactory.newInstance().newSAXParser().parse(
214                    new InputSource(new StringReader(data)), new DefaultHandler());
215        }
216    }
217}
218