com.doitnext.jsonschema.generator.SchemaGen.java Source code

Java tutorial

Introduction

Here is the source code for com.doitnext.jsonschema.generator.SchemaGen.java

Source

/**
 * Copyright (C) 2013 Steve Owens (DoItNext.com) http://www.doitnext.com
 *
 * 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.doitnext.jsonschema.generator;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;

import com.doitnext.jsonschema.annotations.JsonSchemaClass;
import com.doitnext.jsonschema.annotations.JsonSchemaProperty;

/**
 * Generates a JSON Schema from an annotated class.
 * 
 * @author Steve Owens (steve@doitnext.com)
 *
 */
public class SchemaGen {

    private SchemaGen() {
        // Hidden constructor.  This is a static class.
    }

    /**
     * Converts a class defintiion to a JSON Schema definition.
     * 
     * @param classz the annotated class for which to generate a JSON schema.
     * @param uriPrefix any classes that this class aggregates will be documented in
     *     a separate schema and a $ref will be used for the properties herein.
     *     The URI base is the prefix to apply to the references.
     * @return the JSON schema as a string.
     */
    public static String toSchema(Class<?> classz, String uriPrefix) {
        StringBuilder sb = new StringBuilder();
        Map<String, String> declarations = new HashMap<String, String>();
        toSchema(classz, sb, null, declarations, uriPrefix);
        return sb.toString();
    }

    private static void toSchema(Class<?> classz, StringBuilder sb, JsonSchemaProperty propertyDescriptor,
            Map<String, String> declarations, String uriPrefix) {
        sb.append("{");
        if (!handleSimpleType(classz, sb, propertyDescriptor, declarations, uriPrefix)) {
            if (!handleArrayType(classz, sb, propertyDescriptor, declarations, uriPrefix)) {
                handleObjectType(classz, sb, propertyDescriptor, declarations, true, uriPrefix);
            }
        }
        sb.append("}");
    }

    private static boolean handleSimpleType(Class<?> classz, StringBuilder sb,
            JsonSchemaProperty propertyDescriptor, Map<String, String> declarations, String uriPrefix) {
        boolean result = false;
        if (classz.equals(String.class)) {
            sb.append("\"type\":\"string\"");
            result = true;
        } else if (classz.equals(Float.class)) {
            sb.append("\"type\":\"number\"");
            result = true;
        } else if (classz.equals(Double.class)) {
            sb.append("\"type\":\"number\"");
            result = true;
        } else if (classz.equals(Integer.class)) {
            sb.append("\"type\":\"integer\"");
            result = true;
        } else if (classz.equals(Long.class)) {
            sb.append("\"type\":\"long\"");
            result = true;
        } else if (classz.equals(Boolean.class)) {
            sb.append("\"type\":\"boolean\"");
            result = true;
        }
        if (result) {
            Map<String, Boolean> flags = new HashMap<String, Boolean>();
            flags.put("hasTitle", false);
            flags.put("hasDescription", false);
            flags.put("hasType", true);
            flags.put("hasEnum", false);
            if (handlePropertyDescriptor(sb, propertyDescriptor, flags, ", ", result, uriPrefix)) {
                result = true;
            }
        }

        return result;
    }

    private static boolean handleArrayType(Class<?> classz, StringBuilder sb, JsonSchemaProperty propertyDescriptor,
            Map<String, String> declarations, String uriPrefix) {
        boolean result = false;
        Map<String, Boolean> flags = new HashMap<String, Boolean>();
        flags.put("hasTitle", false);
        flags.put("hasDescription", false);
        flags.put("hasType", false);
        flags.put("hasEnum", false);

        Class<?> itemClass = null;
        String prepend = ", ";
        if (classz.isArray()) {
            sb.append("\"type\":\"array\"");
            itemClass = classz.getComponentType();
            flags.put("hasType", true);
            result = true;
        } else if (Collection.class.isAssignableFrom(classz)) {
            if (propertyDescriptor.collectionContains().equals(JsonSchemaProperty.DEFAULT.class))
                throw new IllegalArgumentException(String.format(
                        "The property descriptor is not valid.  You must specify collectionContains for collection type properties. %s",
                        propertyDescriptor));
            itemClass = propertyDescriptor.collectionContains();
            sb.append("\"type\":\"array\"");
            flags.put("hasType", false);
            result = true;
        }
        if (result == true) {

            handlePropertyDescriptor(sb, propertyDescriptor, flags, prepend, result, uriPrefix);

            boolean inline = true;
            StringBuilder sb2 = new StringBuilder();
            sb2.append("{");
            if (!handleSimpleType(itemClass, sb2, null, declarations, uriPrefix)) {
                if (!handleArrayType(itemClass, sb2, propertyDescriptor, declarations, uriPrefix)) {
                    inline = false;
                    handleObjectType(itemClass, sb2, propertyDescriptor, declarations, false, uriPrefix);
                }
            }
            sb2.append("}");

            if (inline) {
                sb.append(prepend);
                sb.append("\"items\":");
                sb.append(sb2.toString());
            } else {
                String id = null;
                JsonSchemaClass jsc = itemClass.getAnnotation(JsonSchemaClass.class);
                if (jsc != null)
                    id = jsc.id();
                else
                    id = itemClass.getName();
                declarations.put(id, sb2.toString());

                sb.append(prepend);
                sb.append("\"items\":{\"$ref\":\"" + uriPrefix);
                sb.append(id);
                sb.append("\"}");
            }
        }
        return result;
    }

    private static boolean handlePropertyDescriptor(StringBuilder sb, JsonSchemaProperty propertyDescriptor,
            Map<String, Boolean> flags, String prepend, boolean result, String uriPrefix) {
        if (propertyDescriptor != null) {
            if (result == true)
                prepend = ", ";

            if (!flags.get("hasTitle") && !StringUtils.isEmpty(propertyDescriptor.title())) {
                sb.append(prepend);
                sb.append("\"title\":\"" + propertyDescriptor.title() + "\"");
                prepend = ", ";
                flags.put("hasTitle", true);
            }
            if (!flags.get("hasDescription") && !StringUtils.isEmpty(propertyDescriptor.description())) {
                sb.append(prepend);
                sb.append("\"description\":\"" + propertyDescriptor.description() + "\"");
                prepend = ",";
                flags.put("hasDescription", true);
            }
            if (!flags.get("hasEnum") && propertyDescriptor.enumValues().length > 0) {
                sb.append(prepend);
                sb.append("\"enum\":[");
                String prepend2 = "";
                for (String value : propertyDescriptor.enumValues()) {
                    sb.append(prepend2);
                    sb.append("\"");
                    sb.append(value);
                    sb.append("\"");
                    prepend2 = ", ";
                }
                sb.append("]");
                prepend = ", ";
                flags.put("hasEnum", true);
            }
            if (!StringUtils.isEmpty(propertyDescriptor.defaultValue())) {
                sb.append(prepend);
                sb.append("\"defaultValue\":\"" + propertyDescriptor.defaultValue() + "\"");
                prepend = ", ";
            }
            if (propertyDescriptor.dependencies().length > 0) {
                sb.append(prepend);
                sb.append("\"dependencies\":[");
                String prepend2 = "";
                for (String value : propertyDescriptor.dependencies()) {
                    sb.append(prepend2);
                    sb.append("\"");
                    sb.append(value);
                    sb.append("\"");
                    prepend2 = ", ";
                }
                sb.append("]");
                prepend = ", ";
            }
            if (!StringUtils.isEmpty(propertyDescriptor.exclusiveMaximum())) {
                sb.append(prepend);
                sb.append("\"exclusiveMaximum\":\"" + propertyDescriptor.exclusiveMaximum() + "\"");
                prepend = ", ";
            }
            if (!StringUtils.isEmpty(propertyDescriptor.exclusiveMinimum())) {
                sb.append(prepend);
                sb.append("\"exclusiveMinimum\":\"" + propertyDescriptor.exclusiveMinimum() + "\"");
                prepend = ", ";
            }
            if (!StringUtils.isEmpty(propertyDescriptor.maximum())) {
                sb.append(prepend);
                sb.append("\"maximum\":\"" + propertyDescriptor.maximum() + "\"");
                prepend = ", ";
            }
            if (!StringUtils.isEmpty(propertyDescriptor.minimum())) {
                sb.append(prepend);
                sb.append("\"minimum\":\"" + propertyDescriptor.minimum() + "\"");
                prepend = ", ";
            }
            if (!StringUtils.isEmpty(propertyDescriptor.maxItems())) {
                sb.append(prepend);
                sb.append("\"maxItems\":\"" + propertyDescriptor.maxItems() + "\"");
                prepend = ", ";
            }
            if (!StringUtils.isEmpty(propertyDescriptor.minItems())) {
                sb.append(prepend);
                sb.append("\"minItems\":\"" + propertyDescriptor.minItems() + "\"");
                prepend = ", ";
            }
            if (!StringUtils.isEmpty(propertyDescriptor.maxLength())) {
                sb.append(prepend);
                sb.append("\"maxLength\":\"" + propertyDescriptor.maxLength() + "\"");
                prepend = ", ";
            }
            if (!StringUtils.isEmpty(propertyDescriptor.minLength())) {
                sb.append(prepend);
                sb.append("\"minLength\":\"" + propertyDescriptor.minLength() + "\"");
                prepend = ", ";
            }
            if (!StringUtils.isEmpty(propertyDescriptor.pattern())) {
                sb.append(prepend);
                sb.append("\"patern\":\"" + propertyDescriptor.pattern() + "\"");
                prepend = ", ";
            }
            if (!StringUtils.isEmpty(propertyDescriptor.format())) {
                sb.append(prepend);
                sb.append("\"format\":\"" + propertyDescriptor.format() + "\"");
                prepend = ", ";
            }
            sb.append(prepend);
            sb.append("\"required\":" + Boolean.toString(propertyDescriptor.required()));
            prepend = ", ";

            result = true;
        }
        return result;
    }

    private static boolean handleProperties(Class<?> classz, StringBuilder sb, Map<String, String> declarations,
            String uriPrefix) {
        boolean result = false;
        String prepend = "";
        Set<String> processedFields = new HashSet<String>();
        sb.append("{");
        for (Field field : classz.getFields()) {
            JsonSchemaProperty propertyDecl = field.getAnnotation(JsonSchemaProperty.class);
            if (propertyDecl != null) {
                Class<?> propClassz = field.getType();
                StringBuilder sb2 = new StringBuilder();
                boolean inline = true;
                sb2.append("{");
                if (!handleSimpleType(propClassz, sb2, propertyDecl, declarations, uriPrefix)) {
                    if (!handleArrayType(propClassz, sb2, propertyDecl, declarations, uriPrefix)) {
                        inline = false;
                    }
                }
                sb2.append("}");
                if (inline) {
                    sb.append(prepend);
                    sb.append("\"");
                    sb.append(propertyDecl.name());
                    sb.append("\":");
                    sb.append(sb2.toString());
                    prepend = ", ";
                } else {
                    String id = null;
                    JsonSchemaClass jsc = propClassz.getAnnotation(JsonSchemaClass.class);
                    if (jsc != null)
                        id = jsc.id();
                    else
                        id = propClassz.getName();
                    declarations.put(id, sb2.toString());

                    sb.append(prepend);
                    sb.append("\"");
                    sb.append(propertyDecl.name());
                    sb.append("\":{\"$ref\":\"");
                    if (!StringUtils.isEmpty(uriPrefix))
                        sb.append(uriPrefix);
                    sb.append(id);
                    sb.append("\"}");
                    prepend = ", ";
                }
                processedFields.add(propertyDecl.name());
            }
        }
        for (Method method : classz.getMethods()) {
            JsonSchemaProperty propertyDecl = method.getAnnotation(JsonSchemaProperty.class);
            if (propertyDecl != null && !processedFields.contains(propertyDecl.name())) {
                Class<?> propClassz = method.getReturnType();
                StringBuilder sb2 = new StringBuilder();
                boolean inline = true;
                sb2.append("{");
                if (!handleSimpleType(propClassz, sb2, propertyDecl, declarations, uriPrefix)) {
                    if (!handleArrayType(propClassz, sb2, propertyDecl, declarations, uriPrefix)) {
                        inline = false;
                    }
                }
                sb2.append("}");
                if (inline) {
                    sb.append(prepend);
                    sb.append("\"");
                    sb.append(propertyDecl.name());
                    sb.append("\":");
                    sb.append(sb2.toString());
                    prepend = ", ";
                } else {
                    String id = null;
                    JsonSchemaClass jsc = propClassz.getAnnotation(JsonSchemaClass.class);
                    if (jsc != null)
                        id = jsc.id();
                    else
                        id = propClassz.getName();
                    declarations.put(id, sb2.toString());

                    sb.append(prepend);
                    sb.append("\"");
                    sb.append(propertyDecl.name());
                    sb.append("\":{\"$ref\":\"");
                    if (!StringUtils.isEmpty(uriPrefix))
                        sb.append(uriPrefix);
                    sb.append(id);
                    sb.append("\"}");
                    prepend = ", ";
                }
                processedFields.add(propertyDecl.name());
            }
        }
        sb.append("}");
        return result;
    }

    private static boolean handleObjectType(Class<?> classz, StringBuilder sb,
            JsonSchemaProperty propertyDescriptor, Map<String, String> declarations, boolean topLevel,
            String uriPrefix) {
        boolean result = false;
        String prepend = "";
        Map<String, Boolean> flags = new HashMap<String, Boolean>();
        flags.put("hasTitle", false);
        flags.put("hasDescription", false);
        flags.put("hasType", false);
        flags.put("hasEnum", false);

        if (classz.isEnum()) {
            sb.append("\"type\":\"string\",");
            sb.append("\"enum\":[");
            for (Enum<?> c : (Enum<?>[]) classz.getEnumConstants()) {
                sb.append("\"");
                sb.append(c.toString());
                sb.append("\", ");
            }
            sb.append("]");
            flags.put("hasType", true);
            flags.put("hasEnum", true);
            result = true;
        }
        if (!flags.get("hasType")) {
            sb.append(prepend);
            sb.append("\"type\":\"object\"");
            prepend = ", ";
            flags.put("hasType", true);
            result = true;
        }
        if (handlePropertyDescriptor(sb, propertyDescriptor, flags, prepend, result, uriPrefix)) {
            result = true;
        }
        JsonSchemaClass annotation = classz.getAnnotation(JsonSchemaClass.class);
        if (annotation != null) {
            if (result == true)
                prepend = ", ";

            if (!StringUtils.isEmpty(annotation.id())) {
                sb.append(prepend);
                sb.append("\"id\":\"" + annotation.id() + "\"");
                prepend = ", ";
            }
            if (!flags.get("hasTitle") && !StringUtils.isEmpty(annotation.title())) {
                sb.append(prepend);
                sb.append("\"title\":\"" + annotation.title() + "\"");
                prepend = ", ";
            }
            if (!flags.get("hasDescription") && !StringUtils.isEmpty(annotation.description())) {
                sb.append(prepend);
                sb.append("\"description\":\"" + annotation.description() + "\"");
                prepend = ", ";
            }
            result = true;
        }

        sb.append(prepend);
        sb.append("\"properties\":");
        handleProperties(classz, sb, declarations, uriPrefix);
        prepend = ", ";
        return result;
    }

}