Mercurial > hg > thermostat-ng
view common/src/main/java/com/redhat/thermostat/common/yaml/YamlToJson.java @ 28:00540d33ce40 version 0.1.1
Changes to SwaggerCombine for web-gateway schema API
This patch modifies the thermostat-common module by upgrading the
functionality of SwaggerCombine and the YAML parser (which is NOT a full
YAML parser, but only the subset we use) to handle the use cases
about to be implemented in the web-gateway.
- better parsing of YAML arrays
- output and @atfile support in SwaggerCombine
- misc bug fixes
The version number is also ungraded to 0.1.1
Reviewed-by: sgehwolf
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-October/025364.html
author | Simon Tooke <stooke@redhat.com> |
---|---|
date | Wed, 18 Oct 2017 10:28:09 -0400 |
parents | 33cf0b946e3d |
children |
line wrap: on
line source
/* * Copyright 2012-2017 Red Hat, Inc. * * This file is part of Thermostat. * * Thermostat is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * Thermostat is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Thermostat; see the file COPYING. If not see * <http://www.gnu.org/licenses/>. * * Linking this code with other modules is making a combined work * based on this code. Thus, the terms and conditions of the GNU * General Public License cover the whole combination. * * As a special exception, the copyright holders of this code give * you permission to link this code with independent modules to * produce an executable, regardless of the license terms of these * independent modules, and to copy and distribute the resulting * executable under terms of your choice, provided that you also * meet, for each linked independent module, the terms and conditions * of the license of that module. An independent module is a module * which is not derived from or based on this code. If you modify * this code, you may extend this exception to your version of the * library, but you are not obligated to do so. If you do not wish * to do so, delete this exception statement from your version. */ package com.redhat.thermostat.common.yaml; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.redhat.thermostat.common.json.JsonUtil; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Utility class to read YAML files and convert to GSON objects * * Limitations: * Only enough code to read Swagger YAML is implemented * $ref only works within the same document, not across the network */ public class YamlToJson { public YamlToJson() { } /** * read YAML file and convert to JSON object * @param fn input file * @return output JSON object * @throws IOException if a syntax error, or an I/O error */ public JsonObject yamlToJsonObject(final File fn) throws IOException { try (final Reader in = new FileReader(fn)) { return yamlToJsonObject(in); } } /** * parse YAML string to JSON object * @param yaml YAML string to parse * @return output JSON object * @throws IOException if an I/O error */ public JsonObject yamlToJsonObject(final String yaml) throws IOException { try (final Reader in = new StringReader(yaml)) { return yamlToJsonObject(in); } } /** * read YAML and convert to JSON object * @param in Reader providing YAML input * @return output JSON object * @throws IOException if a syntax error, or an I/O error */ public JsonObject yamlToJsonObject(final Reader in) throws IOException { try (final PushableReader pr = new PushableReader(in instanceof BufferedReader ? (BufferedReader)in : new BufferedReader(in))) { final JsonObject obj = new JsonObject(); yamlToJson(pr, obj, 0); pr.close(); return obj; } } private void yamlToJson(PushableReader in, JsonElement parent, int currentIndent) throws IOException { for (String s = in.readLine(); s != null; s = in.readLine()) { YamlLine line = new YamlLine(s); if (line.indent == 0 && line.value == null && line.name == null) { // blsnk or unparseable line continue; } if (line.indent < currentIndent) { in.push(s); return; } final JsonElement child; if (line.hasColon) { if (line.value != null && !line.value.isEmpty()) { if (">-".equals(line.value)) { //the next few lines are the value for the current line child = makeUnquotedPrimitive(absorb(in, Math.max(currentIndent, line.indent))); } else if (line.value.startsWith("[")) { // [ array elements ] (may be on separate lines in.push(line.value); child = absorbArray(in, line.indent); } else { JsonElement ce = makeUnquotedPrimitive(line.value); if (parent.isJsonArray()) { JsonObject childobj = new JsonObject(); childobj.add(makeUnquotedString(line.name), ce); child = childobj; } else { child = ce; } } } else { // no value specified; this could be the start of an object or array final String next = in.readLine(); boolean isArray = false; if (next != null) { YamlLine nextLine = new YamlLine(next); isArray = nextLine.hasDashPrefix; in.push(next); } child = isArray ? new JsonArray() : new JsonObject(); yamlToJson(in, child, line.indent); } } else { child = (line.value != null) ? makeUnquotedPrimitive(line.value) : new JsonPrimitive(""); } if (parent.isJsonArray()) { JsonArray parray = parent.getAsJsonArray(); if (line.hasDashPrefix) { parray.add(child); } else { // the current element should be an object JsonElement currentArrayElement = parray.get(parray.size() - 1); if (currentArrayElement.isJsonPrimitive()) { System.err.println("Array member is being treated as object and primitive"); } else if (currentArrayElement.isJsonObject()) { if (child.isJsonPrimitive()) { currentArrayElement.getAsJsonObject().add(makeUnquotedString(line.name), child); } else if (child.isJsonObject()) { if (line.value.isEmpty()) { currentArrayElement.getAsJsonObject().add(makeUnquotedString(line.name), child); } else { JsonUtil.addAll(currentArrayElement.getAsJsonObject(), child.getAsJsonObject(), true); } } } else if (currentArrayElement.isJsonArray()) { currentArrayElement.getAsJsonArray().add(child); } } } else if (parent.isJsonObject()) { JsonObject pobject = parent.getAsJsonObject(); pobject.add(makeUnquotedString(line.name), child); } else { System.err.println("Error: parent is neither object nor array"); } currentIndent = line.indent; } } // allows for arrays on one line: foo: [one, two, threee] private JsonArray absorbArray(PushableReader in, int currentIndent) throws IOException { // create an array and read primitives into it until we hit ']' final JsonArray array = new JsonArray(); in.resetTokenizer(); // skip past '[' in.nextTokenOnLine(); String t = in.nextTokenOnLine(); while (t != null) { if ("]".equals(t)) { break; } else { array.add(makeUnquotedPrimitive(t)); } t = in.nextTokenOnLine(); } if (!"]".equals(t)) { // read following lines as name value pairs for (String s = in.readLine(); s != null; s = in.readLine()) { YamlLine line = new YamlLine(s); if (line.indent == 0 && line.value == null && line.name == null) { // blsnk or unparseable line continue; } if (line.indent < currentIndent) { in.push(s); return array; } if (line.value != null) { if (line.name != null) { JsonObject obj = new JsonObject(); obj.add(makeUnquotedString(line.name), makeUnquotedPrimitive(line.value)); array.add(obj); } else if ("]".equals(line.value)) { break; } else { array.add(makeUnquotedPrimitive(t)); } } } } return array; } private static String makeUnquotedString(final String s) { String ret = s; if (s.length() > 2) { if ((s.charAt(0) == '\'') && (s.charAt(s.length() - 1) == '\'') || (s.charAt(0) == '"') && (s.charAt(s.length() - 1) == '"')) { ret = s.substring(1, s.length() - 1); } } return ret; } private static JsonPrimitive makeUnquotedPrimitive(final String s) { final String uqString = makeUnquotedString(s); // anything quoted will return a string, otherwise try to parse if (uqString.length() == s.length()) { if ("true".equals(uqString)) return new JsonPrimitive(true); else if ("false".equals(uqString)) return new JsonPrimitive(false); else if ("null".equals(uqString)) return null; else { // test for number final char c0 = uqString.charAt(0); if (Character.isDigit(c0) || c0 == '+' || c0 == '-') { try { return new JsonPrimitive(new Long(uqString)); } catch (NumberFormatException ignored) { // fall through to return String } } } } else if (s.length() >= 4) { // avoid strings that aren't long enough to have surrounding quotes plus escaped quote // convert '' to ' in single quoted strings, "" to " in double quoted strings final char quotechar = s.charAt(0); String ns = uqString.replaceAll("[" + quotechar + "][" + quotechar + "]", "" + quotechar); return new JsonPrimitive(ns); } return new JsonPrimitive(uqString); } private static String absorb(PushableReader in, int currentIndent) throws IOException { // append lines until the indentation is the same as the currentIndent StringBuilder a = new StringBuilder(); for (String s = in.readLine(); s != null; s = in.readLine()) { int wsCount; for (wsCount = 0; wsCount < s.length(); wsCount++) { if (!Character.isWhitespace(s.charAt(wsCount))) { break; } } if (wsCount > currentIndent) { a.append(a.length() == 0 ? s.trim() : ' ' + s.trim()); } else { in.push(s); break; } } return a.toString(); } /** * YamlLine - parse a single line of YAML file */ static class YamlLine { int indent; String name; String value; boolean hasColon; boolean hasDashPrefix; private static final String regexpr = "^([\\s]*)([-]?[\\s]*)([^:]+)([:]?)(.*)$"; private static final Pattern p = Pattern.compile(regexpr); YamlLine(String line) throws IOException { Matcher m = p.matcher(line); if (m.matches()) { final String s1 = m.group(1); // leading whitespace final String s2 = m.group(2); // optional '-' indicating array element final String s3 = m.group(3); // key final String s4 = m.group(4); // ':' final String s5 = m.group(5).trim(); // value or optional '>-' hasColon = !(s4 == null || s4.isEmpty()); hasDashPrefix = !(s2 == null || s2.isEmpty()); indent = s1.length() + (hasDashPrefix ? s2.length() : 0); if (hasColon) { name = s3.trim(); value = s5.trim(); } else { name = null; value = s3 + s5; value = value.trim(); } } else { if (line.isEmpty()) { name = null; value = null; indent = 0; } else { throw new IOException("parse error in YAML '" + line + "'"); } } } } static class PushableReader implements AutoCloseable { final Stack<String> stack = new Stack<>(); final BufferedReader in; Matcher matcher; static final Pattern pattern = Pattern.compile("(\\[|\\]|[\"'\\w]+)"); PushableReader(BufferedReader in) { this.in = in; } String readLine() throws IOException { if (stack.isEmpty()) { return in.readLine(); } else { return stack.pop(); } } void push(String s) { stack.push(s); } void resetTokenizer() { matcher = null; } @Override public void close() throws IOException { in.close(); } String nextToken() throws IOException { // tokens are separated by space or comma if (matcher == null) { final String line = readLine(); matcher = (line != null) ? pattern.matcher(line) : null; } if (matcher != null) { if (matcher.find()) { return matcher.group(); } else { // ran out of tokens - try next line matcher = null; return nextToken(); } } return null; } String nextTokenOnLine() throws IOException { // tokens are separated by space or comma if (matcher == null) { final String line = readLine(); matcher = (line != null) ? pattern.matcher(line) : null; } if (matcher != null) { if (matcher.find()) { return matcher.group(); } else { matcher = null; } } return null; } } /** * command line utility to convert YAML to JSON * @param args command line arguments [--pretty] [--help] infile1 [infile2] ... */ public static void main(String[] args) { boolean pretty = false; for (final String fn : args) { if ("--pretty".equals(fn)) { pretty = true; } else if ("--help".equals("fn")) { System.err.println("YamlToJason [--pretty] [--help] infile1 [infile2] ..."); } else { try { final YamlToJson y2j = new YamlToJson(); final File fin = new File(fn); final JsonObject json = y2j.yamlToJsonObject(fin); if (pretty) { final GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setPrettyPrinting(); final Gson gson = gsonBuilder.create(); System.out.println(gson.toJson(json)); } else { System.out.println(json.toString()); } } catch (IOException e) { e.printStackTrace(); } } } } }