com.helger.wsdlgen.exchange.InterfaceReader.java Source code

Java tutorial

Introduction

Here is the source code for com.helger.wsdlgen.exchange.InterfaceReader.java

Source

/**
 * Copyright (C) 2013-2015 Philip Helger
 * philip[at]helger[dot]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.helger.wsdlgen.exchange;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.helger.commons.CGlobal;
import com.helger.commons.annotations.ReturnsMutableCopy;
import com.helger.commons.charset.CCharset;
import com.helger.commons.io.IReadableResource;
import com.helger.commons.io.streams.NonBlockingStringReader;
import com.helger.commons.io.streams.StreamUtils;
import com.helger.commons.regex.RegExHelper;
import com.helger.commons.state.ETriState;
import com.helger.commons.string.StringHelper;
import com.helger.wsdlgen.model.WGInterface;
import com.helger.wsdlgen.model.WGMethod;
import com.helger.wsdlgen.model.type.IWGType;
import com.helger.wsdlgen.model.type.WGComplexType;
import com.helger.wsdlgen.model.type.WGComplexType.EComplexType;
import com.helger.wsdlgen.model.type.WGEnumEntry;
import com.helger.wsdlgen.model.type.WGSimpleType;
import com.helger.wsdlgen.model.type.WGTypeDef;

/**
 * Class for reading the DSL and populating a {@link WGInterface}.
 * 
 * @author Philip Helger
 */
public class InterfaceReader {
    private static final Logger s_aLogger = LoggerFactory.getLogger(InterfaceReader.class);

    @Nonnull
    private static String _preprocess(@Nonnull final List<String> aContent) {
        final List<String> aRest = new ArrayList<String>();
        for (final String sLine : aContent)
            if (!RegExHelper.stringMatchesPattern("\\s*//.*", sLine))
                aRest.add(sLine);
            else
                // To have the correct line numbers!
                aRest.add("");
        return StringHelper.getImploded(CGlobal.LINE_SEPARATOR, aRest);
    }

    @Nonnull
    private static ObjectMapper _createJSONFactory() {
        final ObjectMapper aObjectMapper = new ObjectMapper();
        // Necessary configuration to allow control characters inside of JSON
        // strings (like newline etc.)
        aObjectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
        // Feature that determines whether parser will allow use of unquoted field
        // names (which is allowed by Javascript, but not by JSON specification).
        aObjectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
        // Always use BigDecimal
        aObjectMapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
        // As of 2.1.4 BigDecimals are compacted by default - with this method
        // everything stays as it was
        aObjectMapper.setNodeFactory(JsonNodeFactory.withExactBigDecimals(true));
        return aObjectMapper;
    }

    @Nullable
    private static ObjectNode _readAsJSON(@Nonnull final IReadableResource aRes) {
        try {
            // Read line by line
            final List<String> aContent = StreamUtils.readStreamLines(aRes, CCharset.CHARSET_UTF_8_OBJ);
            // Preprocess content
            final String sPreprocessedContent = _preprocess(aContent);
            // Convert to JSON
            final JsonNode aJSON = _createJSONFactory().readTree(new NonBlockingStringReader(sPreprocessedContent));
            if (aJSON instanceof ObjectNode)
                return (ObjectNode) aJSON;

            s_aLogger.error("Parsed JSON is not an object!");
        } catch (final Exception ex) {
            s_aLogger.error("Failed to parse JSON", ex);
        }
        return null;
    }

    @Nonnull
    @ReturnsMutableCopy
    private static Map<String, JsonNode> _getAllChildren(@Nonnull final JsonNode aNode) {
        // Maintain order!!
        final Map<String, JsonNode> ret = new LinkedHashMap<String, JsonNode>();
        final Iterator<Map.Entry<String, JsonNode>> it = aNode.fields();
        while (it.hasNext()) {
            final Map.Entry<String, JsonNode> aEntry = it.next();
            ret.put(aEntry.getKey(), aEntry.getValue());
        }
        return ret;
    }

    private static String _cleanupText(@Nonnull final String sText) {
        return sText.replace("\r", "");
    }

    @Nullable
    private static String _getChildAsText(@Nonnull final ObjectNode aNode, final String sFieldName) {
        final JsonNode aChildNode = aNode.get(sFieldName);
        if (aChildNode == null)
            return null;
        return _cleanupText(aChildNode.asText());
    }

    @Nullable
    private static String _getDocumentation(@Nullable final String sDoc) {
        if (sDoc == null)
            return null;
        // Replace leading whitespaces after a line break
        return RegExHelper.stringReplacePattern("\\n\\s+", _cleanupText(sDoc), "\n");
    }

    @Nonnull
    private static ETriState _getTriState(@Nonnull final ObjectNode aType, final String sProperty) {
        final JsonNode aProp = aType.get(sProperty);
        return aProp == null ? ETriState.UNDEFINED : ETriState.valueOf(aProp.asBoolean());
    }

    @Nonnull
    private static WGTypeDef _readTypeDef(final WGInterface aInterface, final String sTypeChildName,
            final JsonNode aTypeChildNode) {
        if (aTypeChildNode.isTextual()) {
            // type only - no details
            final String sChildTypeName = aTypeChildNode.asText();
            final IWGType aChildType = aInterface.getTypeOfName(sChildTypeName);
            if (aChildType == null)
                throw new IllegalArgumentException(
                        "Property '" + sTypeChildName + "' has invalid type '" + sChildTypeName + "'");
            return new WGTypeDef(aChildType);
        }

        // All details
        final ObjectNode aType = (ObjectNode) aTypeChildNode;

        final String sChildTypeName = _getChildAsText(aType, "$type");
        final IWGType aChildType = aInterface.getTypeOfName(sChildTypeName);
        if (aChildType == null)
            throw new IllegalArgumentException(
                    "Property '" + sTypeChildName + "' has invalid type '" + sChildTypeName + "'");
        final WGTypeDef aTypeDef = new WGTypeDef(aChildType);
        aTypeDef.setDocumentation(_getDocumentation(_getChildAsText(aType, "$doc")));
        aTypeDef.setMin(_getChildAsText(aType, "$min"));
        aTypeDef.setMax(_getChildAsText(aType, "$max"));
        aTypeDef.setDefault(_getChildAsText(aType, "$default"));
        aTypeDef.setOptional(_getTriState(aType, "$optional"));
        return aTypeDef;
    }

    @Nullable
    public static WGInterface readInterface(@Nonnull final IReadableResource aRes) {
        final ObjectNode aInterfaceNode = _readAsJSON(aRes);
        if (aInterfaceNode == null)
            return null;

        final String sInterfaceName = _getChildAsText(aInterfaceNode, "$name");
        final String sInterfaceNamespace = _getChildAsText(aInterfaceNode, "$namespace");
        final String sInterfaceEndpoint = _getChildAsText(aInterfaceNode, "$endpoint");

        final WGInterface aInterface = new WGInterface(sInterfaceName, sInterfaceNamespace, sInterfaceEndpoint);
        // Dont't format global interface comment
        aInterface.setDocumentation(_getChildAsText(aInterfaceNode, "$doc"));

        // Add all types
        final ObjectNode aTypesNode = (ObjectNode) aInterfaceNode.get("$types");
        if (aTypesNode != null) {
            // Read all contained types
            for (final Map.Entry<String, JsonNode> aTypeEntry : _getAllChildren(aTypesNode).entrySet()) {
                final String sTypeName = aTypeEntry.getKey();
                final JsonNode aTypeNode = aTypeEntry.getValue();

                if (!sTypeName.startsWith("$")) {
                    final boolean bIsSimpleType = sTypeName.startsWith("#");
                    if (bIsSimpleType) {
                        // Simple type
                        final WGSimpleType aSimpleType = new WGSimpleType(sInterfaceNamespace,
                                sTypeName.substring(1));
                        final WGTypeDef aSimpleTypeDef = new WGTypeDef(aSimpleType);
                        for (final Map.Entry<String, JsonNode> aChildTypeEntry : _getAllChildren(aTypeNode)
                                .entrySet()) {
                            final String sTypeChildName = aChildTypeEntry.getKey();
                            final JsonNode aTypeChildNode = aChildTypeEntry.getValue();

                            if (sTypeChildName.equals("$doc"))
                                aSimpleTypeDef.setDocumentation(_getDocumentation(aTypeChildNode.asText()));
                            else if (sTypeChildName.equals("$extension")) {
                                final String sExtension = aTypeChildNode.asText();
                                final IWGType aExtensionType = aInterface.getTypeOfName(sExtension);
                                if (aExtensionType == null)
                                    throw new IllegalArgumentException("Simple type '" + aSimpleType.getName()
                                            + "' has invalid extension type '" + sExtension + "'");
                                aSimpleType.setExtension(aExtensionType);
                            } else if (sTypeChildName.equals("$restriction")) {
                                final String sRestriction = aTypeChildNode.asText();
                                final IWGType aRestrictionType = aInterface.getTypeOfName(sRestriction);
                                if (aRestrictionType == null)
                                    throw new IllegalArgumentException("Simple type '" + aSimpleType.getName()
                                            + "' has invalid restriction type '" + sRestriction + "'");
                                aSimpleType.setRestriction(aRestrictionType);
                            } else if (sTypeChildName.equals("$enum")) {
                                final ArrayNode aEntries = (ArrayNode) aTypeChildNode;
                                if (aEntries == null)
                                    throw new IllegalArgumentException(
                                            "Simple type '" + aSimpleType.getName() + "' has invalid enum entries");

                                // Convert all to string
                                final List<WGEnumEntry> aEnumEntries = new ArrayList<WGEnumEntry>();
                                for (final JsonNode aPropValue : aEntries) {
                                    if (aPropValue instanceof ArrayNode) {
                                        // [key, documentation]
                                        final ArrayNode aProvValueList = (ArrayNode) aPropValue;
                                        aEnumEntries.add(new WGEnumEntry(aProvValueList.get(0).asText(),
                                                aProvValueList.get(1).asText()));
                                    } else {
                                        // Just the key, without documentation
                                        aEnumEntries.add(new WGEnumEntry(aPropValue.asText()));
                                    }
                                }
                                aSimpleType.setEnumEntries(aEnumEntries);
                            } else if (sTypeChildName.equals("$maxlength")) {
                                final int nMaxLength = aTypeChildNode.asInt(-1);
                                if (nMaxLength <= 0)
                                    throw new IllegalArgumentException("Simple type '" + aSimpleType.getName()
                                            + "' has invalid maxLength definition '" + aTypeChildNode.asText()
                                            + "'");

                                aSimpleType.setMaxLength(nMaxLength);
                            } else if (!sTypeChildName.startsWith("$")) {
                                // Only attributes allowed!
                                if (!sTypeChildName.startsWith("@"))
                                    throw new IllegalArgumentException("Simple type '" + aSimpleType.getName()
                                            + "' may only have attributes and not '" + sTypeChildName + "'");

                                final WGTypeDef aTypeDef = _readTypeDef(aInterface, sTypeChildName, aTypeChildNode);
                                aSimpleType.addChildAttribute(sTypeChildName.substring(1), aTypeDef);
                            } else
                                throw new IllegalStateException("Unhandled simple type child: " + sTypeChildName);
                        }
                        aInterface.addType(aSimpleTypeDef);
                    } else {
                        // Complex type
                        final WGComplexType aComplexType = new WGComplexType(sInterfaceNamespace, sTypeName);
                        final WGTypeDef aComplexTypeDef = new WGTypeDef(aComplexType);
                        for (final Map.Entry<String, JsonNode> aChildTypeEntry : _getAllChildren(aTypeNode)
                                .entrySet()) {
                            final String sTypeChildName = aChildTypeEntry.getKey();
                            final JsonNode aTypeChildNode = aChildTypeEntry.getValue();

                            if (sTypeChildName.equals("$doc"))
                                aComplexTypeDef.setDocumentation(_getDocumentation(aTypeChildNode.asText()));
                            else if (sTypeChildName.equals("$type"))
                                aComplexType.setType(EComplexType.getFromTagNameOrThrow(aTypeChildNode.asText()));
                            else if (!sTypeChildName.startsWith("$")) {
                                final WGTypeDef aChildTypeDef = _readTypeDef(aInterface, sTypeChildName,
                                        aTypeChildNode);

                                // Attribute or element?
                                if (sTypeChildName.startsWith("@"))
                                    aComplexType.addChildAttribute(sTypeChildName.substring(1), aChildTypeDef);
                                else
                                    aComplexType.addChildElement(sTypeChildName, aChildTypeDef);
                            } else
                                throw new IllegalStateException("Unhandled complex type child: " + sTypeChildName);
                        }
                        aInterface.addType(aComplexTypeDef);
                    }
                } else
                    throw new IllegalStateException("Unhandled special type: " + sTypeName);
            }
        }

        // Add all methods
        for (final Map.Entry<String, JsonNode> aMethodEntry : _getAllChildren(aInterfaceNode).entrySet()) {
            final String sMethodName = aMethodEntry.getKey();

            if (!sMethodName.startsWith("$")) {
                final ObjectNode aMethodNode = (ObjectNode) aMethodEntry.getValue();
                final WGMethod aMethod = new WGMethod(sMethodName);

                // Input
                final JsonNode aInputNode = aMethodNode.get("$input");
                if (aInputNode != null && aInputNode.isObject()) {
                    aMethod.markHavingInput();
                    for (final Map.Entry<String, JsonNode> aEntry : _getAllChildren(aInputNode).entrySet()) {
                        final String sParamName = aEntry.getKey();
                        final String sParamType = aEntry.getValue().asText();
                        final IWGType aParamType = aInterface.getTypeOfName(sParamType);
                        if (aParamType == null)
                            throw new IllegalArgumentException("Input type '" + sParamType + "' of method '"
                                    + sMethodName + "' for element '" + sParamName + "' not found");
                        aMethod.addInputParam(sParamName, aParamType);
                    }
                }

                // Output
                final JsonNode aOutputNode = aMethodNode.get("$output");
                if (aOutputNode != null && aOutputNode.isObject()) {
                    aMethod.markHavingOutput();
                    for (final Map.Entry<String, JsonNode> aEntry : _getAllChildren(aOutputNode).entrySet()) {
                        final String sParamName = aEntry.getKey();
                        final String sParamType = aEntry.getValue().asText();
                        final IWGType aParamType = aInterface.getTypeOfName(sParamType);
                        if (aParamType == null)
                            throw new IllegalArgumentException("Output type '" + sParamType + "' of method '"
                                    + sMethodName + "' for element '" + sParamName + "' not found");
                        aMethod.addOutputParam(sParamName, aParamType);
                    }
                }

                // Fault
                final JsonNode aFaultNode = aMethodNode.get("$fault");
                if (aFaultNode != null && aFaultNode.isObject()) {
                    aMethod.markHavingFault();
                    for (final Map.Entry<String, JsonNode> aEntry : _getAllChildren(aFaultNode).entrySet()) {
                        final String sParamName = aEntry.getKey();
                        final String sParamType = aEntry.getValue().asText();
                        final IWGType aParamType = aInterface.getTypeOfName(sParamType);
                        if (aParamType == null)
                            throw new IllegalArgumentException("Fault type '" + sParamType + "' of method '"
                                    + sMethodName + "' for element '" + sParamName + "' not found");
                        aMethod.addFaultParam(sParamName, aParamType);
                    }
                }

                aInterface.addMethod(aMethod);
            }
        }

        return aInterface;
    }
}