view common/src/main/java/com/redhat/thermostat/common/yaml/YamlToJson.java @ 25:dd0992bd51aa

Add Maven Central upload, refactor lang-schema and json libs to thermostat-common library - merge common-json and common-lang-schema packages into thermostat- common. - add targets to generate Javadoc and source jars, and modify sources to remove some Javadoc warnings. - add targets to sign and deploy thermostat-common to Maven Central. Reviewed-by: sgehwolf, neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-September/025214.html
author Simon Tooke <stooke@redhat.com>
date Fri, 29 Sep 2017 14:48:00 -0400
parents common/json/src/main/java/com/redhat/thermostat/common/yaml/YamlToJson.java@ab2706b9b1e3
children 33cf0b946e3d
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.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;

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.io.StringWriter;
import java.io.Writer;
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() {
    }

    public JsonObject yamlToJsonObject(final File fn) throws IOException {
        try (final Reader in = new FileReader(fn)) {
            return yamlToJsonObject(in);
        }
    }

    public JsonObject yamlToJsonObject(final String yaml) throws IOException {
        try (final Reader in = new StringReader(yaml)) {
            return yamlToJsonObject(in);
        }
    }

    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, currentIndent));
                    } 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()) {
                if (line.hasDashPrefix) {
                    JsonArray parray = parent.getAsJsonArray();
                    parray.add(child);
                } else {
                    System.err.println("Array member must begin with dash: " + s);
                }
            } 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;
        }
    }

    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
                    }

                }
            }
        }
        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;

        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);
        }

        @Override
        public void close() throws IOException {
            in.close();
        }
    }

    /*
     * utility function to convert YAML file to JSON stdout
     */
    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();
                }
            }
        }
    }
}