/* * Copyright 2013 Esri. * * 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 com.esri.gpt.framework.dcat; import com.esri.gpt.framework.dcat.adaptors.DcatRecordAdaptor; import com.esri.gpt.framework.dcat.dcat.DcatRecord; import com.esri.gpt.framework.dcat.dcat.DcatRecordList; import com.esri.gpt.framework.dcat.json.JsonArray; import com.esri.gpt.framework.dcat.json.JsonAttribute; import com.esri.gpt.framework.dcat.json.JsonAttributes; import com.esri.gpt.framework.dcat.json.JsonRecord; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * DCAT parser. */ public class DcatParser { private static final Logger LOGGER = Logger.getLogger(DcatParser.class.getCanonicalName()); private JsonReader jsonReader; /** * Creates instance of the parser. * * @param input input stream to parse */ public DcatParser(InputStream input) throws UnsupportedEncodingException { this.jsonReader = new JsonReader(new InputStreamReader(input, "UTF-8")); } /** * Creates instance of the parser. * * @param reader data reader */ public DcatParser(Reader reader) { this.jsonReader = new JsonReader(reader); } /** * Closes parser. */ public void close() throws IOException { jsonReader.close(); } /** * Parses data (event driven style). * * @param listener event listener * @throws DcatParseException if parsing fails */ public void parse(final Listener listener) throws DcatParseException, IOException { ListenerInternal localListener = new ListenerInternal() { @Override public boolean onRecord(DcatRecord record) { LOGGER.log(Level.FINEST, record != null ? record.toString() : "<empty record>"); listener.onRecord(record); return true; } }; parse(localListener); } /** * Parses data (DOM building style). * * @param policy limit policy * @return list of records. * @throws DcatParseException if parsing fails */ public DcatRecordList parse(final LimitPolicy policy) throws DcatParseException, IOException { final DcatRecordListImpl list = new DcatRecordListImpl(); DcatParser.ListenerInternal listener = new DcatParser.ListenerInternal() { private void parse(final ListenerInternal listener) throws DcatParseException, IOException { if (!jsonReader.hasNext()) { throw new DcatParseException("No more data available."); } JsonToken token = jsonReader.peek(); if (token != JsonToken.BEGIN_ARRAY) { throw new DcatParseException("No array found."); } parseRecords(listener); } @Override public boolean onRecord(DcatRecord record) { list.add(record); if (policy == null || list.size() >= policy.getLimit()) { return true; } else { policy.onLimit(list); return false; } } }; parse(listener); return list; } /** * Parses DCAT using internal listener. * * @param listener internal listener * @throws DcatParseException if parsing DCAT fails */ void parse(final ListenerInternal listener) throws DcatParseException, IOException { if (!jsonReader.hasNext()) { throw new DcatParseException("No more data available."); } JsonToken token = jsonReader.peek(); if (token != JsonToken.BEGIN_ARRAY) { throw new DcatParseException("No array found."); } jsonReader.beginArray(); parseRecords(listener); } /** * Parses DCAT records using internal listener. * * @param listener internal listener * @throws DcatParseException if parsing DCAT fails */ void parseRecords(ListenerInternal listener) throws DcatParseException, IOException { while (jsonReader.hasNext()) { JsonToken token = jsonReader.peek(); switch (token) { case BEGIN_OBJECT: jsonReader.beginObject(); if (!parseRecord(listener)) { return; } break; default: throw new DcatParseException("Unexpected token in the data: " + token); } } jsonReader.endArray(); } private boolean parseRecord(ListenerInternal listener) throws DcatParseException, IOException { JsonRecord record = new JsonRecord(); while (jsonReader.hasNext()) { JsonToken token = jsonReader.peek(); switch (token) { case NAME: parseAttribute(record); break; default: throw new DcatParseException("Unexpected token in the data: " + token); } } jsonReader.endObject(); return listener.onRecord(new DcatRecordAdaptor(record)); } private void parseAttribute(JsonRecord record) throws DcatParseException, IOException { String attrName = jsonReader.nextName(); while (jsonReader.hasNext()) { JsonToken token = jsonReader.peek(); switch (token) { case STRING: record.put(attrName, new JsonAttribute(jsonReader.nextString())); return; case NUMBER: record.put(attrName, new JsonAttribute(jsonReader.nextDouble())); return; case BOOLEAN: record.put(attrName, new JsonAttribute(jsonReader.nextBoolean())); return; case BEGIN_ARRAY: if ("distribution".equals(attrName)) { jsonReader.beginArray(); parseDistributions(record.getDistribution()); jsonReader.endArray(); } else if ("keyword".equals(attrName)) { jsonReader.beginArray(); parseKeywords(record.getKeywords()); jsonReader.endArray(); } else { // skip jsonReader.skipValue(); } return; default: throw new DcatParseException("Unexpected token in the data: " + token); } } } private void parseKeywords(List<JsonAttribute> keywords) throws DcatParseException, IOException { while (jsonReader.hasNext()) { JsonToken token = jsonReader.peek(); switch (token) { case END_ARRAY: jsonReader.endArray(); break; case STRING: keywords.add(new JsonAttribute(jsonReader.nextString())); break; case NUMBER: keywords.add(new JsonAttribute(jsonReader.nextDouble())); break; case BOOLEAN: keywords.add(new JsonAttribute(jsonReader.nextBoolean())); break; default: throw new DcatParseException("Unexpected token in the data: " + token); } } } private void parseDistributions(JsonArray<JsonAttributes> distributions) throws DcatParseException, IOException { while (jsonReader.hasNext()) { JsonToken token = jsonReader.peek(); switch (token) { case BEGIN_OBJECT: jsonReader.beginObject(); parseDistribution(distributions); jsonReader.endObject(); break; default: throw new DcatParseException("Unexpected token in the data: " + token); } } } private void parseDistribution(JsonArray<JsonAttributes> distributions) throws DcatParseException, IOException { JsonAttributes attributes = new JsonAttributes(); while (jsonReader.hasNext()) { JsonToken token = jsonReader.peek(); switch (token) { case NAME: parseAttribute(attributes); break; default: throw new DcatParseException("Unexpected token in the data: " + token); } } distributions.add(attributes); } private void parseAttribute(JsonAttributes attributes) throws DcatParseException, IOException { String attrName = jsonReader.nextName(); while (jsonReader.hasNext()) { JsonToken token = jsonReader.peek(); switch (token) { case STRING: attributes.put(attrName, new JsonAttribute(jsonReader.nextString())); return; case NUMBER: attributes.put(attrName, new JsonAttribute(jsonReader.nextDouble())); return; case BOOLEAN: attributes.put(attrName, new JsonAttribute(jsonReader.nextBoolean())); return; default: throw new DcatParseException("Unexpected token in the data: " + token); } } } /** * Record listener. */ public static interface Listener { /** * Called upon a single record parsed. * * @param record record */ void onRecord(DcatRecord record); } /** * Limit policy. */ public static interface LimitPolicy { /** * Gets size limit. * * @return size limit */ long getLimit(); /** * Called when size reached limit. * * @param recordsList current list of records */ void onLimit(DcatRecordList recordsList); } static interface ListenerInternal { boolean onRecord(DcatRecord record); } private static class DcatRecordListImpl extends ArrayList<DcatRecord> implements DcatRecordList { } }