Java tutorial
/** * 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; } }