This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/master by this push: new 2ac4587 [CAMEL-13135] Refactor the json-simple-ordered module 2ac4587 is described below commit 2ac45879b53d157b90858df34129936f154692f9 Author: Guillaume Nodet <gno...@gmail.com> AuthorDate: Mon Jan 28 16:47:49 2019 +0100 [CAMEL-13135] Refactor the json-simple-ordered module --- camel-api/pom.xml | 2 +- camel-base/pom.xml | 2 +- .../runtimecatalog/impl/JSonSchemaHelper.java | 4 +- camel-core/pom.xml | 50 +- camel-management-api/pom.xml | 2 +- camel-management-impl/pom.xml | 2 +- camel-support/pom.xml | 2 +- {tooling => camel-util-json}/pom.xml | 56 +- .../camel/util/json/DeserializationException.java | 79 ++ .../java/org/apache/camel/util/json/JsonArray.java | 346 +++++++++ .../org/apache/camel/util/json/JsonObject.java | 655 ++++++++++++++++ .../java/org/apache/camel/util/json/Jsonable.java | 27 + .../java/org/apache/camel/util/json/Jsoner.java | 860 +++++++++++++++++++++ .../java/org/apache/camel/util/json/Yylex.java | 695 +++++++++++++++++ .../java/org/apache/camel/util/json/Yytoken.java | 92 +++ .../src/main/resources/META-INF/LICENSE.txt | 0 .../src/main/resources/META-INF/NOTICE.txt | 0 .../camel/util/json}/JsonSimpleOrderedTest.java | 4 +- .../src/test/resources/bean.json | 0 camel-util/pom.xml | 2 +- .../slack/SlackComponentVerifierExtension.java | 4 +- .../camel/component/slack/SlackConsumer.java | 21 +- .../camel/component/slack/SlackEndpoint.java | 18 +- platforms/camel-catalog/pom.xml | 11 +- .../karaf/features/src/main/resources/features.xml | 4 +- pom.xml | 1 + tooling/apt/pom.xml | 2 +- .../tools/apt/EndpointAnnotationProcessor.java | 4 +- .../camel/tools/apt/helper/JsonSchemaHelper.java | 4 +- tooling/json-simple-ordered/pom.xml | 120 --- .../json-simple-ordered/src/main/java/.gitignore | 2 - tooling/maven/camel-package-maven-plugin/pom.xml | 2 +- .../camel/maven/packaging/JSonSchemaHelper.java | 4 +- ...pdateSpringBootAutoConfigurationReadmeMojo.java | 8 +- tooling/pom.xml | 1 - 35 files changed, 2812 insertions(+), 274 deletions(-) diff --git a/camel-api/pom.xml b/camel-api/pom.xml index 01d7688..629f6bf 100644 --- a/camel-api/pom.xml +++ b/camel-api/pom.xml @@ -76,7 +76,7 @@ <dependency> <groupId>org.apache.camel</groupId> - <artifactId>json-simple-ordered</artifactId> + <artifactId>camel-util-json</artifactId> <version>${project.version}</version> </dependency> diff --git a/camel-base/pom.xml b/camel-base/pom.xml index df43949..23e484d 100644 --- a/camel-base/pom.xml +++ b/camel-base/pom.xml @@ -119,7 +119,7 @@ <!-- we shade our patched ordered json-simple parser --> <dependency> <groupId>org.apache.camel</groupId> - <artifactId>json-simple-ordered</artifactId> + <artifactId>camel-util-json</artifactId> <version>${project.version}</version> </dependency> diff --git a/camel-base/src/main/java/org/apache/camel/runtimecatalog/impl/JSonSchemaHelper.java b/camel-base/src/main/java/org/apache/camel/runtimecatalog/impl/JSonSchemaHelper.java index d923579..0032ac4 100644 --- a/camel-base/src/main/java/org/apache/camel/runtimecatalog/impl/JSonSchemaHelper.java +++ b/camel-base/src/main/java/org/apache/camel/runtimecatalog/impl/JSonSchemaHelper.java @@ -24,8 +24,8 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import org.json.simple.JsonObject; -import org.json.simple.Jsoner; +import org.apache.camel.util.json.JsonObject; +import org.apache.camel.util.json.Jsoner; public final class JSonSchemaHelper { diff --git a/camel-core/pom.xml b/camel-core/pom.xml index 2fc45a0..dc61898 100644 --- a/camel-core/pom.xml +++ b/camel-core/pom.xml @@ -230,7 +230,7 @@ <!-- we shade our patched ordered json-simple parser --> <dependency> <groupId>org.apache.camel</groupId> - <artifactId>json-simple-ordered</artifactId> + <artifactId>camel-util-json</artifactId> <version>${project.version}</version> </dependency> @@ -396,54 +396,6 @@ </executions> </plugin> - <!-- shade caffeine cache for faster Camel and spi-annotations as needed by everybody --> - <!-- shade json-simple for parsing Camel component JSon schema files --> - <!-- - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-shade-plugin</artifactId> - <executions> - <execution> - <phase>package</phase> - <goals> - <goal>shade</goal> - </goals> - <configuration> - <artifactSet> - <includes> - <include>com.github.ben-manes.caffeine:caffeine</include> - <include>org.apache.camel:json-simple-ordered</include> - <include>org.apache.camel:spi-annotations</include> - </includes> - <excludes> - <exclude>org.apache.camel:apt</exclude> - </excludes> - </artifactSet> - <relocations> - <relocation> - <pattern>com.github.benmanes.caffeine</pattern> - <shadedPattern>org.apache.camel.com.github.benmanes.caffeine</shadedPattern> - </relocation> - <relocation> - <pattern>org.json.simple</pattern> - <shadedPattern>org.apache.camel.json.simple</shadedPattern> - </relocation> - </relocations> - <filters> - <filter> - <artifact>*:*</artifact> - <excludes> - <exclude>META-INF/LICENSE</exclude> - <exclude>META-INF/NOTICE</exclude> - </excludes> - </filter> - </filters> - </configuration> - </execution> - </executions> - </plugin> - --> - <!-- generate the attached tests jar --> <plugin> <artifactId>maven-jar-plugin</artifactId> diff --git a/camel-management-api/pom.xml b/camel-management-api/pom.xml index 72c78de..0c2ac6a 100644 --- a/camel-management-api/pom.xml +++ b/camel-management-api/pom.xml @@ -69,7 +69,7 @@ <dependency> <groupId>org.apache.camel</groupId> - <artifactId>json-simple-ordered</artifactId> + <artifactId>camel-util-json</artifactId> <version>${project.version}</version> </dependency> diff --git a/camel-management-impl/pom.xml b/camel-management-impl/pom.xml index ccc9f46..5e458f3 100644 --- a/camel-management-impl/pom.xml +++ b/camel-management-impl/pom.xml @@ -75,7 +75,7 @@ <dependency> <groupId>org.apache.camel</groupId> - <artifactId>json-simple-ordered</artifactId> + <artifactId>camel-util-json</artifactId> <version>${project.version}</version> </dependency> diff --git a/camel-support/pom.xml b/camel-support/pom.xml index f0647ff..878a3d8 100644 --- a/camel-support/pom.xml +++ b/camel-support/pom.xml @@ -87,7 +87,7 @@ <!-- we shade our patched ordered json-simple parser --> <dependency> <groupId>org.apache.camel</groupId> - <artifactId>json-simple-ordered</artifactId> + <artifactId>camel-util-json</artifactId> <version>${project.version}</version> </dependency> diff --git a/tooling/pom.xml b/camel-util-json/pom.xml similarity index 54% copy from tooling/pom.xml copy to camel-util-json/pom.xml index f4d218e..d1c303a 100644 --- a/tooling/pom.xml +++ b/camel-util-json/pom.xml @@ -19,6 +19,7 @@ --> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> <parent> @@ -28,52 +29,17 @@ <relativePath>../parent</relativePath> </parent> - <artifactId>tooling</artifactId> - <name>Camel :: Tooling</name> - <description>Camel Tooling</description> - <packaging>pom</packaging> - - <properties> - <camel.osgi.export.pkg /> - </properties> + <artifactId>camel-util-json</artifactId> + <name>Camel :: Util :: JSon</name> + <description>A json simple parser that preserves the ordering in Map as read from JSon source</description> - <modules> - <module>parent</module> - <module>meta-annotations</module> - <module>spi-annotations</module> - <module>json-simple-ordered</module> - <module>apt</module> - <module>camel-route-parser</module> - <module>swagger-rest-dsl-generator</module> - <module>maven</module> - <module>camel-manual</module> - </modules> + <dependencies> - <!-- profiles> - <profile> - <id>build.manual</id> - <modules> - <module>camel-manual</module> - </modules> - </profile> - <profile> - <id>assembly</id> - <modules> - <module>camel-manual</module> - </modules> - </profile> - <profile> - <id>deploy</id> - <modules> - <module>camel-manual</module> - </modules> - </profile> - <profile> - <id>apache-release</id> - <modules> - <module>camel-manual</module> - </modules> - </profile> - </profiles --> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> </project> diff --git a/camel-util-json/src/main/java/org/apache/camel/util/json/DeserializationException.java b/camel-util-json/src/main/java/org/apache/camel/util/json/DeserializationException.java new file mode 100644 index 0000000..596b4dd --- /dev/null +++ b/camel-util-json/src/main/java/org/apache/camel/util/json/DeserializationException.java @@ -0,0 +1,79 @@ +/* Copyright 2016 Clifton Labs + * 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 org.apache.camel.util.json; + +/** DeserializationException explains how and where the problem occurs in the source JSON text during deserialization. + * @since 2.0.0 */ +public class DeserializationException extends Exception{ + /** The kinds of exceptions that can trigger a DeserializationException. */ + enum Problems{ + @SuppressWarnings("javadoc") + DISALLOWED_TOKEN, + @SuppressWarnings("javadoc") + UNEXPECTED_CHARACTER, + @SuppressWarnings("javadoc") + UNEXPECTED_EXCEPTION, + @SuppressWarnings("javadoc") + UNEXPECTED_TOKEN; + } + + private static final long serialVersionUID = -7880698968187728547L; + private final int position; + private final Problems problemType; + private final Object unexpectedObject; + + /** @param position where the exception occurred. + * @param problemType how the exception occurred. + * @param unexpectedObject what caused the exception. */ + public DeserializationException(final int position, final Problems problemType, final Object unexpectedObject){ + this.position = position; + this.problemType = problemType; + this.unexpectedObject = unexpectedObject; + } + + @Override + public String getMessage(){ + final StringBuilder sb = new StringBuilder(); + switch(this.problemType){ + case DISALLOWED_TOKEN: + sb.append("The disallowed token (").append(this.unexpectedObject).append(") was found at position ").append(this.position).append(". If this is in error, try again with a parse that allows the token instead. Otherwise, fix the parsable string and try again."); + break; + case UNEXPECTED_CHARACTER: + sb.append("The unexpected character (").append(this.unexpectedObject).append(") was found at position ").append(this.position).append(". Fix the parsable string and try again."); + break; + case UNEXPECTED_TOKEN: + sb.append("The unexpected token ").append(this.unexpectedObject).append(" was found at position ").append(this.position).append(". Fix the parsable string and try again."); + break; + case UNEXPECTED_EXCEPTION: + sb.append("Please report this to the library's maintainer. The unexpected exception that should be addressed before trying again occurred at position ").append(this.position).append(": ").append(this.unexpectedObject); + break; + default: + sb.append("Please report this to the library's maintainer. An error at position ").append(this.position).append(" occurred. There are no recovery recommendations available."); + break; + } + return sb.toString(); + } + + /** @return an index of the string character the error type occurred at. */ + public int getPosition(){ + return this.position; + } + + /** @return the enumeration for how the exception occurred. */ + public Problems getProblemType(){ + return this.problemType; + } + + /** @return a representation of what caused the exception. */ + public Object getUnexpectedObject(){ + return this.unexpectedObject; + } +} diff --git a/camel-util-json/src/main/java/org/apache/camel/util/json/JsonArray.java b/camel-util-json/src/main/java/org/apache/camel/util/json/JsonArray.java new file mode 100644 index 0000000..8252808 --- /dev/null +++ b/camel-util-json/src/main/java/org/apache/camel/util/json/JsonArray.java @@ -0,0 +1,346 @@ +/* Copyright 2016 Clifton Labs + * 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 org.apache.camel.util.json; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +/** JsonArray is a common non-thread safe data format for a collection of data. The contents of a JsonArray are only + * validated as JSON values on serialization. + * @see Jsoner + * @since 2.0.0 */ +public class JsonArray extends ArrayList<Object> implements Jsonable{ + /** The serialization version this class is compatible + * with. This value doesn't need to be incremented if and only if the only changes to occur were updating comments, + * updating javadocs, adding new + * fields to the class, changing the fields from static to non-static, or changing the fields from transient to non + * transient. All other changes require this number be incremented. */ + private static final long serialVersionUID = 1L; + + /** Instantiates an empty JsonArray. */ + public JsonArray(){ + super(); + } + + /** Instantiate a new JsonArray using ArrayList's constructor of the same type. + * @param collection represents the elements to produce the JsonArray with. */ + public JsonArray(final Collection<?> collection){ + super(collection); + } + + /** A convenience method that assumes every element of the JsonArray is castable to T before adding it to a + * collection of Ts. + * @param <T> represents the type that all of the elements of the JsonArray should be cast to and the type the + * collection will contain. + * @param destination represents where all of the elements of the JsonArray are added to after being cast to the + * generic type + * provided. + * @throws ClassCastException if the unchecked cast of an element to T fails. */ + @SuppressWarnings("unchecked") + public <T> void asCollection(final Collection<T> destination){ + for(final Object o : this){ + destination.add((T)o); + } + } + + /** A convenience method that assumes there is a BigDecimal, Number, or String at the given index. If a Number or + * String is there it is used to construct a new BigDecimal. + * @param index representing where the value is expected to be at. + * @return the value stored at the key or the default provided if the key doesn't exist. + * @throws ClassCastException if there was a value but didn't match the assumed return types. + * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal. + * @see BigDecimal + * @see Number#doubleValue() */ + public BigDecimal getBigDecimal(final int index){ + Object returnable = this.get(index); + if(returnable instanceof BigDecimal){ + /* Success there was a BigDecimal. */ + }else if(returnable instanceof Number){ + /* A number can be used to construct a BigDecimal. */ + returnable = new BigDecimal(returnable.toString()); + }else if(returnable instanceof String){ + /* A number can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return (BigDecimal)returnable; + } + + /** A convenience method that assumes there is a Boolean or String value at the given index. + * @param index represents where the value is expected to be at. + * @return the value at the index provided cast to a boolean. + * @throws ClassCastException if there was a value but didn't match the assumed return type. + * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. */ + public Boolean getBoolean(final int index){ + Object returnable = this.get(index); + if(returnable instanceof String){ + returnable = Boolean.valueOf((String)returnable); + } + return (Boolean)returnable; + } + + /** A convenience method that assumes there is a Number or String value at the given index. + * @param index represents where the value is expected to be at. + * @return the value at the index provided cast to a byte. + * @throws ClassCastException if there was a value but didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. + * @see Number */ + public Byte getByte(final int index){ + Object returnable = this.get(index); + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).byteValue(); + } + + /** A convenience method that assumes there is a Collection value at the given index. + * @param <T> the kind of collection to expect at the index. Note unless manually added, collection values will be a + * JsonArray. + * @param index represents where the value is expected to be at. + * @return the value at the index provided cast to a Collection. + * @throws ClassCastException if there was a value but didn't match the assumed return type. + * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. + * @see Collection */ + @SuppressWarnings("unchecked") + public <T extends Collection<?>> T getCollection(final int index){ + /* The unchecked warning is suppressed because there is no way of guaranteeing at compile time the cast will + * work. */ + return (T)this.get(index); + } + + /** A convenience method that assumes there is a Number or String value at the given index. + * @param index represents where the value is expected to be at. + * @return the value at the index provided cast to a double. + * @throws ClassCastException if there was a value but didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. + * @see Number */ + public Double getDouble(final int index){ + Object returnable = this.get(index); + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).doubleValue(); + } + + /** A convenience method that assumes there is a String value at the given index representing a fully qualified name + * in dot notation of an enum. + * @param index representing where the value is expected to be at. + * @param <T> the Enum type the value at the index is expected to belong to. + * @return the enum based on the string found at the index, or null if the value at the index was null. + * @throws ClassNotFoundException if the element was a String but the declaring enum type couldn't be determined + * with it. + * @throws ClassCastException if the element at the index was not a String or if the fully qualified enum name is of + * the wrong type. + * @throws IllegalArgumentException if an enum type was dynamically determined but it doesn't define an enum with + * the dynamically determined name. + * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. + * @see Enum#valueOf(Class, String) */ + @SuppressWarnings("unchecked") + public <T extends Enum<T>> T getEnum(final int index) throws ClassNotFoundException{ + /* Supressing the unchecked warning because the returnType is dynamically identified and could lead to a + * ClassCastException when returnType is cast to Class<T>, which is expected by the method's contract. */ + T returnable; + final String element; + final String[] splitValues; + final int numberOfValues; + final StringBuilder returnTypeName; + final StringBuilder enumName; + final Class<T> returnType; + /* Make sure the element at the index is a String. */ + element = this.getString(index); + if(element == null){ + return null; + } + /* Get the package, class, and enum names. */ + splitValues = element.split("\\."); + numberOfValues = splitValues.length; + returnTypeName = new StringBuilder(); + enumName = new StringBuilder(); + for(int i = 0; i < numberOfValues; i++){ + if(i == (numberOfValues - 1)){ + /* If it is the last split value then it should be the name of the Enum since dots are not allowed in + * enum names. */ + enumName.append(splitValues[i]); + }else if(i == (numberOfValues - 2)){ + /* If it is the penultimate split value then it should be the end of the package/enum type and not need + * a dot appended to it. */ + returnTypeName.append(splitValues[i]); + }else{ + /* Must be part of the package/enum type and will need a dot appended to it since they got removed in + * the split. */ + returnTypeName.append(splitValues[i]); + returnTypeName.append("."); + } + } + /* Use the package/class and enum names to get the Enum<T>. */ + returnType = (Class<T>)Class.forName(returnTypeName.toString()); + returnable = Enum.valueOf(returnType, enumName.toString()); + return returnable; + } + + /** A convenience method that assumes there is a Number or String value at the given index. + * @param index represents where the value is expected to be at. + * @return the value at the index provided cast to a float. + * @throws ClassCastException if there was a value but didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. + * @see Number */ + public Float getFloat(final int index){ + Object returnable = this.get(index); + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).floatValue(); + } + + /** A convenience method that assumes there is a Number or String value at the given index. + * @param index represents where the value is expected to be at. + * @return the value at the index provided cast to a int. + * @throws ClassCastException if there was a value but didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. + * @see Number */ + public Integer getInteger(final int index){ + Object returnable = this.get(index); + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).intValue(); + } + + /** A convenience method that assumes there is a Number or String value at the given index. + * @param index represents where the value is expected to be at. + * @return the value at the index provided cast to a long. + * @throws ClassCastException if there was a value but didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. + * @see Number */ + public Long getLong(final int index){ + Object returnable = this.get(index); + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).longValue(); + } + + /** A convenience method that assumes there is a Map value at the given index. + * @param <T> the kind of map to expect at the index. Note unless manually added, Map values will be a JsonObject. + * @param index represents where the value is expected to be at. + * @return the value at the index provided cast to a Map. + * @throws ClassCastException if there was a value but didn't match the assumed return type. + * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. + * @see Map */ + @SuppressWarnings("unchecked") + public <T extends Map<?, ?>> T getMap(final int index){ + /* The unchecked warning is suppressed because there is no way of guaranteeing at compile time the cast will + * work. */ + return (T)this.get(index); + } + + /** A convenience method that assumes there is a Number or String value at the given index. + * @param index represents where the value is expected to be at. + * @return the value at the index provided cast to a short. + * @throws ClassCastException if there was a value but didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. + * @see Number */ + public Short getShort(final int index){ + Object returnable = this.get(index); + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).shortValue(); + } + + /** A convenience method that assumes there is a Boolean, Number, or String value at the given index. + * @param index represents where the value is expected to be at. + * @return the value at the index provided cast to a String. + * @throws ClassCastException if there was a value but didn't match the assumed return type. + * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. */ + public String getString(final int index){ + Object returnable = this.get(index); + if(returnable instanceof Boolean){ + returnable = returnable.toString(); + }else if(returnable instanceof Number){ + returnable = returnable.toString(); + } + return (String)returnable; + } + + /* (non-Javadoc) + * @see org.apache.camel.util.json.Jsonable#asJsonString() */ + @Override + public String toJson(){ + final StringWriter writable = new StringWriter(); + try{ + this.toJson(writable); + }catch(final IOException caught){ + /* See java.io.StringWriter. */ + } + return writable.toString(); + } + + /* (non-Javadoc) + * @see org.apache.camel.util.json.Jsonable#toJsonString(java.io.Writer) */ + @Override + public void toJson(final Writer writable) throws IOException{ + boolean isFirstElement = true; + final Iterator<Object> elements = this.iterator(); + writable.write('['); + while(elements.hasNext()){ + if(isFirstElement){ + isFirstElement = false; + }else{ + writable.write(','); + } + writable.write(Jsoner.serialize(elements.next())); + } + writable.write(']'); + } +} diff --git a/camel-util-json/src/main/java/org/apache/camel/util/json/JsonObject.java b/camel-util-json/src/main/java/org/apache/camel/util/json/JsonObject.java new file mode 100644 index 0000000..c097f10 --- /dev/null +++ b/camel-util-json/src/main/java/org/apache/camel/util/json/JsonObject.java @@ -0,0 +1,655 @@ +/* Copyright 2016 Clifton Labs + * 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 org.apache.camel.util.json; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.math.BigDecimal; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Iterator; +import java.util.Map; + +/** JsonObject is a common non-thread safe data format for string to data mappings. The contents of a JsonObject are + * only validated as JSON values on serialization. + * @see Jsoner + * @since 2.0.0 */ +public class JsonObject extends LinkedHashMap<String, Object> implements Jsonable{ + /** The serialization version this class is compatible + * with. This value doesn't need to be incremented if and only if the only changes to occur were updating comments, + * updating javadocs, adding new + * fields to the class, changing the fields from static to non-static, or changing the fields from transient to non + * transient. All other changes require this number be incremented. */ + private static final long serialVersionUID = 1L; + + /** Instantiates an empty JsonObject. */ + public JsonObject(){ + super(); + } + + /** Instantiate a new JsonObject by accepting a map's entries, which could lead to de/serialization issues of the + * resulting JsonObject since the entry values aren't validated as JSON values. + * @param map represents the mappings to produce the JsonObject with. */ + public JsonObject(final Map<String, ?> map){ + super(map); + } + + /** A convenience method that assumes there is a BigDecimal, Number, or String at the given key. If a Number is + * there + * its Number#toString() is used to construct a new BigDecimal(String). If a String is there it is used to + * construct a new BigDecimal(String). + * @param key representing where the value ought to be stored at. + * @return the value stored at the key. + * @throws ClassCastException if the value didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @see BigDecimal + * @see Number#toString() */ + public BigDecimal getBigDecimal(final String key){ + Object returnable = this.get(key); + if(returnable instanceof BigDecimal){ + /* Success there was a BigDecimal or it defaulted. */ + }else if(returnable instanceof Number){ + /* A number can be used to construct a BigDecimal */ + returnable = new BigDecimal(returnable.toString()); + }else if(returnable instanceof String){ + /* A number can be used to construct a BigDecimal */ + returnable = new BigDecimal((String)returnable); + } + return (BigDecimal)returnable; + } + + /** A convenience method that assumes there is a BigDecimal, Number, or String at the given key. If a Number is + * there + * its Number#toString() is used to construct a new BigDecimal(String). If a String is there it is used to + * construct a new BigDecimal(String). + * @param key representing where the value ought to be stored at. + * @param defaultValue representing what is returned when the key isn't in the JsonObject. + * @return the value stored at the key or the default provided if the key doesn't exist. + * @throws ClassCastException if there was a value but didn't match the assumed return types. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @see BigDecimal + * @see Number#toString() */ + public BigDecimal getBigDecimalOrDefault(final String key, final BigDecimal defaultValue){ + Object returnable; + if(this.containsKey(key)){ + returnable = this.get(key); + }else{ + return defaultValue; + } + if(returnable instanceof BigDecimal){ + /* Success there was a BigDecimal or it defaulted. */ + }else if(returnable instanceof Number){ + /* A number can be used to construct a BigDecimal */ + returnable = new BigDecimal(returnable.toString()); + }else if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal */ + returnable = new BigDecimal((String)returnable); + } + return (BigDecimal)returnable; + } + + /** A convenience method that assumes there is a Boolean or String value at the given key. + * @param key representing where the value ought to be stored at. + * @return the value stored at the key. + * @throws ClassCastException if the value didn't match the assumed return type. */ + public Boolean getBoolean(final String key){ + Object returnable = this.get(key); + if(returnable instanceof String){ + returnable = Boolean.valueOf((String)returnable); + } + return (Boolean)returnable; + } + + /** A convenience method that assumes there is a Boolean or String value at the given key. + * @param key representing where the value ought to be stored at. + * @param defaultValue representing what is returned when the key isn't in the JsonObject. + * @return the value stored at the key or the default provided if the key doesn't exist. + * @throws ClassCastException if there was a value but didn't match the assumed return type. */ + public Boolean getBooleanOrDefault(final String key, final boolean defaultValue){ + Object returnable; + if(this.containsKey(key)){ + returnable = this.get(key); + }else{ + return defaultValue; + } + if(returnable instanceof String){ + returnable = Boolean.valueOf((String)returnable); + } + return (Boolean)returnable; + } + + /** A convenience method that assumes there is a Number or String value at the given key. + * @param key representing where the value ought to be stored at. + * @return the value stored at the key (which may involve rounding or truncation). + * @throws ClassCastException if the value didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @see Number#byteValue() */ + public Byte getByte(final String key){ + Object returnable = this.get(key); + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).byteValue(); + } + + /** A convenience method that assumes there is a Number or String value at the given key. + * @param key representing where the value ought to be stored at. + * @param defaultValue representing what is returned when the key isn't in the JsonObject. + * @return the value stored at the key (which may involve rounding or truncation) or the default provided if the key + * doesn't exist. + * @throws ClassCastException if there was a value but didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @see Number#byteValue() */ + public Byte getByteOrDefault(final String key, final byte defaultValue){ + Object returnable; + if(this.containsKey(key)){ + returnable = this.get(key); + }else{ + return defaultValue; + } + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).byteValue(); + } + + /** A convenience method that assumes there is a Collection at the given key. + * @param <T> the kind of collection to expect at the key. Note unless manually added, collection values will be a + * JsonArray. + * @param key representing where the value ought to be stored at. + * @return the value stored at the key. + * @throws ClassCastException if the value didn't match the assumed return type. */ + @SuppressWarnings("unchecked") + public <T extends Collection<?>> T getCollection(final String key){ + /* The unchecked warning is suppressed because there is no way of guaranteeing at compile time the cast will + * work. */ + return (T)this.get(key); + } + + /** A convenience method that assumes there is a Collection at the given key. + * @param <T> the kind of collection to expect at the key. Note unless manually added, collection values will be a + * JsonArray. + * @param key representing where the value ought to be stored at. + * @param defaultValue representing what is returned when the key isn't in the JsonObject. + * @return the value stored at the key or the default provided if the key doesn't exist. + * @throws ClassCastException if there was a value but didn't match the assumed return type. */ + @SuppressWarnings("unchecked") + public <T extends Collection<?>> T getCollectionOrDefault(final String key, final T defaultValue){ + /* The unchecked warning is suppressed because there is no way of guaranteeing at compile time the cast will + * work. */ + Object returnable; + if(this.containsKey(key)){ + returnable = this.get(key); + }else{ + return defaultValue; + } + return (T)returnable; + } + + /** A convenience method that assumes there is a Number or String value at the given key. + * @param key representing where the value ought to be stored at. + * @return the value stored at the key (which may involve rounding or truncation). + * @throws ClassCastException if the value didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @see Number#doubleValue() */ + public Double getDouble(final String key){ + Object returnable = this.get(key); + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).doubleValue(); + } + + /** A convenience method that assumes there is a Number or String value at the given key. + * @param key representing where the value ought to be stored at. + * @param defaultValue representing what is returned when the key isn't in the JsonObject. + * @return the value stored at the key (which may involve rounding or truncation) or the default provided if the key + * doesn't exist. + * @throws ClassCastException if there was a value but didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @see Number#doubleValue() */ + public Double getDoubleOrDefault(final String key, final double defaultValue){ + Object returnable; + if(this.containsKey(key)){ + returnable = this.get(key); + }else{ + return defaultValue; + } + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).doubleValue(); + } + + /** A convenience method that assumes there is a String value at the given key representing a fully qualified name + * in + * dot notation of an enum. + * @param key representing where the value ought to be stored at. + * @param <T> the Enum type the value at the key is expected to belong to. + * @return the enum based on the string found at the key, or null if the value paired with the provided key is null. + * @throws ClassNotFoundException if the value was a String but the declaring enum type couldn't be determined with + * it. + * @throws ClassCastException if the element at the index was not a String or if the fully qualified enum name is of + * the wrong type. + * @throws IllegalArgumentException if an enum type was determined but it doesn't define an enum with the determined + * name. + * @see Enum#valueOf(Class, String) */ + @SuppressWarnings("unchecked") + public <T extends Enum<T>> T getEnum(final String key) throws ClassNotFoundException{ + /* Supressing the unchecked warning because the returnType is dynamically identified and could lead to a + * ClassCastException when returnType is cast to Class<T>, which is expected by the method's contract. */ + T returnable; + final String value; + final String[] splitValues; + final int numberOfSplitValues; + final StringBuilder returnTypeName; + final StringBuilder enumName; + final Class<T> returnType; + /* Make sure the value at the key is a String. */ + value = this.getStringOrDefault(key, ""); + if(value == null){ + return null; + } + /* Get the package, class, and enum names. */ + splitValues = value.split("\\."); + numberOfSplitValues = splitValues.length; + returnTypeName = new StringBuilder(); + enumName = new StringBuilder(); + for(int i = 0; i < numberOfSplitValues; i++){ + if(i == (numberOfSplitValues - 1)){ + /* If it is the last split value then it should be the name of the Enum since dots are not allowed + * in enum names. */ + enumName.append(splitValues[i]); + }else if(i == (numberOfSplitValues - 2)){ + /* If it is the penultimate split value then it should be the end of the package/enum type and not + * need a dot appended to it. */ + returnTypeName.append(splitValues[i]); + }else{ + /* Must be part of the package/enum type and will need a dot appended to it since they got removed + * in the split. */ + returnTypeName.append(splitValues[i]); + returnTypeName.append("."); + } + } + /* Use the package/class and enum names to get the Enum<T>. */ + returnType = (Class<T>)Class.forName(returnTypeName.toString()); + returnable = Enum.valueOf(returnType, enumName.toString()); + return returnable; + } + + /** A convenience method that assumes there is a String value at the given key representing a fully qualified name + * in + * dot notation of an enum. + * @param key representing where the value ought to be stored at. + * @param defaultValue representing what is returned when the key isn't in the JsonObject. + * @param <T> the Enum type the value at the key is expected to belong to. + * @return the enum based on the string found at the key, or the defaultValue provided if the key doesn't exist, or + * null if the value paired with provided key is null. + * @throws ClassNotFoundException if the value was a String but the declaring enum type couldn't be determined with + * it. + * @throws ClassCastException if the element at the index was not a String or if the fully qualified enum name is of + * the wrong type. + * @throws IllegalArgumentException if an enum type was determined but it doesn't define an enum with the determined + * name. + * @see Enum#valueOf(Class, String) */ + @SuppressWarnings("unchecked") + public <T extends Enum<T>> T getEnumOrDefault(final String key, final T defaultValue) throws ClassNotFoundException{ + /* Supressing the unchecked warning because the returnType is dynamically identified and could lead to a + * ClassCastException when returnType is cast to Class<T>, which is expected by the method's contract. */ + T returnable; + final String value; + final String[] splitValues; + final int numberOfSplitValues; + final StringBuilder returnTypeName; + final StringBuilder enumName; + final Class<T> returnType; + /* Check to make sure the key wasn't actually there and wasn't coincidentally the defaulted String as its + * value. */ + if(this.containsKey(key)){ + /* Make sure the value at the key is a String. */ + value = this.getStringOrDefault(key, ""); + if(value == null){ + return null; + } + /* Get the package, class, and enum names. */ + splitValues = value.split("\\."); + numberOfSplitValues = splitValues.length; + returnTypeName = new StringBuilder(); + enumName = new StringBuilder(); + for(int i = 0; i < numberOfSplitValues; i++){ + if(i == (numberOfSplitValues - 1)){ + /* If it is the last split value then it should be the name of the Enum since dots are not allowed + * in enum names. */ + enumName.append(splitValues[i]); + }else if(i == (numberOfSplitValues - 2)){ + /* If it is the penultimate split value then it should be the end of the package/enum type and not + * need a dot appended to it. */ + returnTypeName.append(splitValues[i]); + }else{ + /* Must be part of the package/enum type and will need a dot appended to it since they got removed + * in the split. */ + returnTypeName.append(splitValues[i]); + returnTypeName.append("."); + } + } + /* Use the package/class and enum names to get the Enum<T>. */ + returnType = (Class<T>)Class.forName(returnTypeName.toString()); + returnable = Enum.valueOf(returnType, enumName.toString()); + }else{ + /* It wasn't there and according to the method's contract we return the default value. */ + return defaultValue; + } + return returnable; + } + + /** A convenience method that assumes there is a Number or String value at the given key. + * @param key representing where the value ought to be stored at. + * @return the value stored at the key (which may involve rounding or truncation). + * @throws ClassCastException if the value didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @see Number#floatValue() */ + public Float getFloat(final String key){ + Object returnable = this.get(key); + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).floatValue(); + } + + /** A convenience method that assumes there is a Number or String value at the given key. + * @param key representing where the value ought to be stored at. + * @param defaultValue representing what is returned when the key isn't in the JsonObject. + * @return the value stored at the key (which may involve rounding or truncation) or the default provided if the key + * doesn't exist. + * @throws ClassCastException if there was a value but didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @see Number#floatValue() */ + public Float getFloatOrDefault(final String key, final float defaultValue){ + Object returnable; + if(this.containsKey(key)){ + returnable = this.get(key); + }else{ + return defaultValue; + } + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).floatValue(); + } + + /** A convenience method that assumes there is a Number or String value at the given key. + * @param key representing where the value ought to be stored at. + * @return the value stored at the key (which may involve rounding or truncation). + * @throws ClassCastException if the value didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @see Number#intValue() */ + public Integer getInteger(final String key){ + Object returnable = this.get(key); + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).intValue(); + } + + /** A convenience method that assumes there is a Number or String value at the given key. + * @param key representing where the value ought to be stored at. + * @param defaultValue representing what is returned when the key isn't in the JsonObject. + * @return the value stored at the key (which may involve rounding or truncation) or the default provided if the key + * doesn't exist. + * @throws ClassCastException if there was a value but didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @see Number#intValue() */ + public Integer getIntegerOrDefault(final String key, final int defaultValue){ + Object returnable; + if(this.containsKey(key)){ + returnable = this.get(key); + }else{ + return defaultValue; + } + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).intValue(); + } + + /** A convenience method that assumes there is a Number or String value at the given key. + * @param key representing where the value ought to be stored at. + * @return the value stored at the key (which may involve rounding or truncation). + * @throws ClassCastException if the value didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @see Number#longValue() */ + public Long getLong(final String key){ + Object returnable = this.get(key); + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).longValue(); + } + + /** A convenience method that assumes there is a Number or String value at the given key. + * @param key representing where the value ought to be stored at. + * @param defaultValue representing what is returned when the key isn't in the JsonObject. + * @return the value stored at the key (which may involve rounding or truncation) or the default provided if the key + * doesn't exist. + * @throws ClassCastException if there was a value but didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @see Number#longValue() */ + public Long getLongOrDefault(final String key, final long defaultValue){ + Object returnable; + if(this.containsKey(key)){ + returnable = this.get(key); + }else{ + return defaultValue; + } + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).longValue(); + } + + /** A convenience method that assumes there is a Map at the given key. + * @param <T> the kind of map to expect at the key. Note unless manually added, Map values will be a JsonObject. + * @param key representing where the value ought to be stored at. + * @return the value stored at the key. + * @throws ClassCastException if the value didn't match the assumed return type. */ + @SuppressWarnings("unchecked") + public <T extends Map<?, ?>> T getMap(final String key){ + /* The unchecked warning is suppressed because there is no way of guaranteeing at compile time the cast will + * work. */ + return (T)this.get(key); + } + + /** A convenience method that assumes there is a Map at the given key. + * @param <T> the kind of map to expect at the key. Note unless manually added, Map values will be a JsonObject. + * @param key representing where the value ought to be stored at. + * @param defaultValue representing what is returned when the key isn't in the JsonObject. + * @return the value stored at the key or the default provided if the key doesn't exist. + * @throws ClassCastException if there was a value but didn't match the assumed return type. */ + @SuppressWarnings("unchecked") + public <T extends Map<?, ?>> T getMapOrDefault(final String key, final T defaultValue){ + /* The unchecked warning is suppressed because there is no way of guaranteeing at compile time the cast will + * work. */ + Object returnable; + if(this.containsKey(key)){ + returnable = this.get(key); + }else{ + returnable = defaultValue; + } + return (T)returnable; + } + + /** A convenience method that assumes there is a Number or String value at the given key. + * @param key representing where the value ought to be stored at. + * @return the value stored at the key (which may involve rounding or truncation). + * @throws ClassCastException if the value didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @see Number#shortValue() */ + public Short getShort(final String key){ + Object returnable = this.get(key); + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).shortValue(); + } + + /** A convenience method that assumes there is a Number or String value at the given key. + * @param key representing where the value ought to be stored at. + * @param defaultValue representing what is returned when the key isn't in the JsonObject. + * @return the value stored at the key (which may involve rounding or truncation) or the default provided if the key + * doesn't exist. + * @throws ClassCastException if there was a value but didn't match the assumed return type. + * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number + * represents the double or float Infinity or NaN. + * @see Number#shortValue() */ + public Short getShortOrDefault(final String key, final short defaultValue){ + Object returnable; + if(this.containsKey(key)){ + returnable = this.get(key); + }else{ + return defaultValue; + } + if(returnable == null){ + return null; + } + if(returnable instanceof String){ + /* A String can be used to construct a BigDecimal. */ + returnable = new BigDecimal((String)returnable); + } + return ((Number)returnable).shortValue(); + } + + /** A convenience method that assumes there is a Boolean, Number, or String value at the given key. + * @param key representing where the value ought to be stored at. + * @return the value stored at the key. + * @throws ClassCastException if the value didn't match the assumed return type. */ + public String getString(final String key){ + Object returnable = this.get(key); + if(returnable instanceof Boolean){ + returnable = returnable.toString(); + }else if(returnable instanceof Number){ + returnable = returnable.toString(); + } + return (String)returnable; + } + + /** A convenience method that assumes there is a Boolean, Number, or String value at the given key. + * @param key representing where the value ought to be stored at. + * @param defaultValue representing what is returned when the key isn't in the JsonObject. + * @return the value stored at the key or the default provided if the key doesn't exist. + * @throws ClassCastException if there was a value but didn't match the assumed return type. */ + public String getStringOrDefault(final String key, final String defaultValue){ + Object returnable; + if(this.containsKey(key)){ + returnable = this.get(key); + }else{ + return defaultValue; + } + if(returnable instanceof Boolean){ + returnable = returnable.toString(); + }else if(returnable instanceof Number){ + returnable = returnable.toString(); + } + return (String)returnable; + } + + /* (non-Javadoc) + * @see org.apache.camel.util.json.Jsonable#asJsonString() */ + @Override + public String toJson(){ + final StringWriter writable = new StringWriter(); + try{ + this.toJson(writable); + }catch(final IOException caught){ + /* See java.io.StringWriter. */ + } + return writable.toString(); + } + + /* (non-Javadoc) + * @see org.apache.camel.util.json.Jsonable#toJsonString(java.io.Writer) */ + @Override + public void toJson(final Writer writable) throws IOException{ + /* Writes the map in JSON object format. */ + boolean isFirstEntry = true; + final Iterator<Map.Entry<String, Object>> entries = this.entrySet().iterator(); + writable.write('{'); + while(entries.hasNext()){ + if(isFirstEntry){ + isFirstEntry = false; + }else{ + writable.write(','); + } + final Map.Entry<String, Object> entry = entries.next(); + writable.write(Jsoner.serialize(entry.getKey())); + writable.write(':'); + writable.write(Jsoner.serialize(entry.getValue())); + } + writable.write('}'); + } +} diff --git a/camel-util-json/src/main/java/org/apache/camel/util/json/Jsonable.java b/camel-util-json/src/main/java/org/apache/camel/util/json/Jsonable.java new file mode 100644 index 0000000..ff344d2 --- /dev/null +++ b/camel-util-json/src/main/java/org/apache/camel/util/json/Jsonable.java @@ -0,0 +1,27 @@ +/* Copyright 2016 Clifton Labs + * 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 org.apache.camel.util.json; + +import java.io.IOException; +import java.io.Writer; + +/** Jsonables can be serialized in java script object notation (JSON). + * @since 2.0.0 */ +public interface Jsonable{ + /** Serialize to a JSON formatted string. + * @return a string, formatted in JSON, that represents the Jsonable. */ + public String toJson(); + + /** Serialize to a JSON formatted stream. + * @param writable where the resulting JSON text should be sent. + * @throws IOException when the writable encounters an I/O error. */ + public void toJson(Writer writable) throws IOException; +} diff --git a/camel-util-json/src/main/java/org/apache/camel/util/json/Jsoner.java b/camel-util-json/src/main/java/org/apache/camel/util/json/Jsoner.java new file mode 100644 index 0000000..8c518b1 --- /dev/null +++ b/camel-util-json/src/main/java/org/apache/camel/util/json/Jsoner.java @@ -0,0 +1,860 @@ +/* Copyright 2016 Clifton Labs + * 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 org.apache.camel.util.json; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Collection; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; + +/** Jsoner provides JSON utilities for escaping strings to be JSON compatible, thread safe parsing (RFC 4627) JSON + * strings, and serializing data to strings in JSON format. + * @since 2.0.0 */ +public class Jsoner{ + /** Flags to tweak the behavior of the primary deserialization method. */ + private static enum DeserializationOptions{ + /** Whether a multiple JSON values can be deserialized as a root element. */ + ALLOW_CONCATENATED_JSON_VALUES, + /** Whether a JsonArray can be deserialized as a root element. */ + ALLOW_JSON_ARRAYS, + /** Whether a boolean, null, Number, or String can be deserialized as a root element. */ + ALLOW_JSON_DATA, + /** Whether a JsonObject can be deserialized as a root element. */ + ALLOW_JSON_OBJECTS; + } + + /** Flags to tweak the behavior of the primary serialization method. */ + private static enum SerializationOptions{ + /** Instead of aborting serialization on non-JSON values that are Enums it will continue serialization with the + * Enums' "${PACKAGE}.${DECLARING_CLASS}.${NAME}". + * @see Enum */ + ALLOW_FULLY_QUALIFIED_ENUMERATIONS, + /** Instead of aborting serialization on non-JSON values it will continue serialization by serializing the + * non-JSON value directly into the now invalid JSON. Be mindful that invalid JSON will not successfully + * deserialize. */ + ALLOW_INVALIDS, + /** Instead of aborting serialization on non-JSON values that implement Jsonable it will continue serialization + * by deferring serialization to the Jsonable. + * @see Jsonable */ + ALLOW_JSONABLES, + /** Instead of aborting serialization on non-JSON values it will continue serialization by using reflection to + * best describe the value as a JsonObject. */ + ALLOW_UNDEFINEDS; + } + + /** The possible States of a JSON deserializer. */ + private static enum States{ + /** Post-parsing state. */ + DONE, + /** Pre-parsing state. */ + INITIAL, + /** Parsing error, ParsingException should be thrown. */ + PARSED_ERROR, + PARSING_ARRAY, + /** Parsing a key-value pair inside of an object. */ + PARSING_ENTRY, + PARSING_OBJECT; + } + + private Jsoner(){ + /* Keeping it classy. */ + } + + /** Deserializes a readable stream according to the RFC 4627 JSON specification. + * @param readableDeserializable representing content to be deserialized as JSON. + * @return either a boolean, null, Number, String, JsonObject, or JsonArray that best represents the deserializable. + * @throws DeserializationException if an unexpected token is encountered in the deserializable. To recover from a + * DeserializationException: fix the deserializable + * to no longer have an unexpected token and try again. + * @throws IOException if the underlying reader encounters an I/O error. Ensure the reader is properly instantiated, + * isn't closed, or that it is ready before trying again. */ + public static Object deserialize(final Reader readableDeserializable) throws DeserializationException, IOException{ + return Jsoner.deserialize(readableDeserializable, EnumSet.of(DeserializationOptions.ALLOW_JSON_ARRAYS, DeserializationOptions.ALLOW_JSON_OBJECTS, DeserializationOptions.ALLOW_JSON_DATA)).get(0); + } + + /** Deserialize a stream with all deserialized JSON values are wrapped in a JsonArray. + * @param deserializable representing content to be deserialized as JSON. + * @param flags representing the allowances and restrictions on deserialization. + * @return the allowable object best represented by the deserializable. + * @throws DeserializationException if a disallowed or unexpected token is encountered in the deserializable. To + * recover from a DeserializationException: fix the + * deserializable to no longer have a disallowed or unexpected token and try again. + * @throws IOException if the underlying reader encounters an I/O error. Ensure the reader is properly instantiated, + * isn't closed, or that it is ready before trying again. */ + private static JsonArray deserialize(final Reader deserializable, final Set<DeserializationOptions> flags) throws DeserializationException, IOException{ + final Yylex lexer = new Yylex(deserializable); + Yytoken token; + States currentState; + int returnCount = 1; + final LinkedList<States> stateStack = new LinkedList<>(); + final LinkedList<Object> valueStack = new LinkedList<>(); + stateStack.addLast(States.INITIAL); + //System.out.println("//////////DESERIALIZING//////////"); + do{ + /* Parse through the parsable string's tokens. */ + currentState = Jsoner.popNextState(stateStack); + token = Jsoner.lexNextToken(lexer); + switch(currentState){ + case DONE: + /* The parse has finished a JSON value. */ + if(!flags.contains(DeserializationOptions.ALLOW_CONCATENATED_JSON_VALUES) || Yytoken.Types.END.equals(token.getType())){ + /* Break if concatenated values are not allowed or if an END token is read. */ + break; + } + /* Increment the amount of returned JSON values and treat the token as if it were a fresh parse. */ + returnCount += 1; + /* Fall through to the case for the initial state. */ + //$FALL-THROUGH$ + case INITIAL: + /* The parse has just started. */ + switch(token.getType()){ + case DATUM: + /* A boolean, null, Number, or String could be detected. */ + if(flags.contains(DeserializationOptions.ALLOW_JSON_DATA)){ + valueStack.addLast(token.getValue()); + stateStack.addLast(States.DONE); + }else{ + throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.DISALLOWED_TOKEN, token); + } + break; + case LEFT_BRACE: + /* An object is detected. */ + if(flags.contains(DeserializationOptions.ALLOW_JSON_OBJECTS)){ + valueStack.addLast(new JsonObject()); + stateStack.addLast(States.PARSING_OBJECT); + }else{ + throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.DISALLOWED_TOKEN, token); + } + break; + case LEFT_SQUARE: + /* An array is detected. */ + if(flags.contains(DeserializationOptions.ALLOW_JSON_ARRAYS)){ + valueStack.addLast(new JsonArray()); + stateStack.addLast(States.PARSING_ARRAY); + }else{ + throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.DISALLOWED_TOKEN, token); + } + break; + default: + /* Neither a JSON array or object was detected. */ + throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token); + } + break; + case PARSED_ERROR: + /* The parse could be in this state due to the state stack not having a state to pop off. */ + throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token); + case PARSING_ARRAY: + switch(token.getType()){ + case COMMA: + /* The parse could detect a comma while parsing an array since it separates each element. */ + stateStack.addLast(currentState); + break; + case DATUM: + /* The parse found an element of the array. */ + JsonArray val = (JsonArray)valueStack.getLast(); + val.add(token.getValue()); + stateStack.addLast(currentState); + break; + case LEFT_BRACE: + /* The parse found an object in the array. */ + val = (JsonArray)valueStack.getLast(); + final JsonObject object = new JsonObject(); + val.add(object); + valueStack.addLast(object); + stateStack.addLast(currentState); + stateStack.addLast(States.PARSING_OBJECT); + break; + case LEFT_SQUARE: + /* The parse found another array in the array. */ + val = (JsonArray)valueStack.getLast(); + final JsonArray array = new JsonArray(); + val.add(array); + valueStack.addLast(array); + stateStack.addLast(currentState); + stateStack.addLast(States.PARSING_ARRAY); + break; + case RIGHT_SQUARE: + /* The parse found the end of the array. */ + if(valueStack.size() > returnCount){ + valueStack.removeLast(); + }else{ + /* The parse has been fully resolved. */ + stateStack.addLast(States.DONE); + } + break; + default: + /* Any other token is invalid in an array. */ + throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token); + } + break; + case PARSING_OBJECT: + /* The parse has detected the start of an object. */ + switch(token.getType()){ + case COMMA: + /* The parse could detect a comma while parsing an object since it separates each key value + * pair. Continue parsing the object. */ + stateStack.addLast(currentState); + break; + case DATUM: + /* The token ought to be a key. */ + if(token.getValue() instanceof String){ + /* JSON keys are always strings, strings are not always JSON keys but it is going to be + * treated as one. Continue parsing the object. */ + final String key = (String)token.getValue(); + valueStack.addLast(key); + stateStack.addLast(currentState); + stateStack.addLast(States.PARSING_ENTRY); + }else{ + /* Abort! JSON keys are always strings and it wasn't a string. */ + throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token); + } + break; + case RIGHT_BRACE: + /* The parse has found the end of the object. */ + if(valueStack.size() > returnCount){ + /* There are unresolved values remaining. */ + valueStack.removeLast(); + }else{ + /* The parse has been fully resolved. */ + stateStack.addLast(States.DONE); + } + break; + default: + /* The parse didn't detect the end of an object or a key. */ + throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token); + } + break; + case PARSING_ENTRY: + switch(token.getType()){ + /* Parsed pair keys can only happen while parsing objects. */ + case COLON: + /* The parse could detect a colon while parsing a key value pair since it separates the key + * and value from each other. Continue parsing the entry. */ + stateStack.addLast(currentState); + break; + case DATUM: + /* The parse has found a value for the parsed pair key. */ + String key = (String)valueStack.removeLast(); + JsonObject parent = (JsonObject)valueStack.getLast(); + parent.put(key, token.getValue()); + break; + case LEFT_BRACE: + /* The parse has found an object for the parsed pair key. */ + key = (String)valueStack.removeLast(); + parent = (JsonObject)valueStack.getLast(); + final JsonObject object = new JsonObject(); + parent.put(key, object); + valueStack.addLast(object); + stateStack.addLast(States.PARSING_OBJECT); + break; + case LEFT_SQUARE: + /* The parse has found an array for the parsed pair key. */ + key = (String)valueStack.removeLast(); + parent = (JsonObject)valueStack.getLast(); + final JsonArray array = new JsonArray(); + parent.put(key, array); + valueStack.addLast(array); + stateStack.addLast(States.PARSING_ARRAY); + break; + default: + /* The parse didn't find anything for the parsed pair key. */ + throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token); + } + break; + default: + break; + } + //System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + //System.out.println(currentState); + //System.out.println(token); + //System.out.println(valueStack); + //System.out.println(stateStack); + /* If we're not at the END and DONE then do the above again. */ + }while(!(States.DONE.equals(currentState) && Yytoken.Types.END.equals(token.getType()))); + //System.out.println("!!!!!!!!!!DESERIALIZED!!!!!!!!!!"); + return new JsonArray(valueStack); + } + + /** A convenience method that assumes a StringReader to deserialize a string. + * @param deserializable representing content to be deserialized as JSON. + * @return either a boolean, null, Number, String, JsonObject, or JsonArray that best represents the deserializable. + * @throws DeserializationException if an unexpected token is encountered in the deserializable. To recover from a + * DeserializationException: fix the deserializable + * to no longer have an unexpected token and try again. + * @see Jsoner#deserialize(Reader) + * @see StringReader */ + public static Object deserialize(final String deserializable) throws DeserializationException{ + Object returnable; + StringReader readableDeserializable = null; + try{ + readableDeserializable = new StringReader(deserializable); + returnable = Jsoner.deserialize(readableDeserializable); + }catch(IOException | NullPointerException caught){ + /* They both have the same recovery scenario. + * See StringReader. + * If deserializable is null, it should be reasonable to expect null back. */ + returnable = null; + }finally{ + if(readableDeserializable != null){ + readableDeserializable.close(); + } + } + return returnable; + } + + /** A convenience method that assumes a JsonArray must be deserialized. + * @param deserializable representing content to be deserializable as a JsonArray. + * @param defaultValue representing what would be returned if deserializable isn't a JsonArray or an IOException, + * NullPointerException, or DeserializationException occurs during deserialization. + * @return a JsonArray that represents the deserializable, or the defaultValue if there isn't a JsonArray that + * represents deserializable. + * @see Jsoner#deserialize(Reader) */ + public static JsonArray deserialize(final String deserializable, final JsonArray defaultValue){ + StringReader readable = null; + JsonArray returnable; + try{ + readable = new StringReader(deserializable); + returnable = Jsoner.deserialize(readable, EnumSet.of(DeserializationOptions.ALLOW_JSON_ARRAYS)).<JsonArray> getCollection(0); + }catch(NullPointerException | IOException | DeserializationException caught){ + /* Don't care, just return the default value. */ + returnable = defaultValue; + }finally{ + if(readable != null){ + readable.close(); + } + } + return returnable; + } + + /** A convenience method that assumes a JsonObject must be deserialized. + * @param deserializable representing content to be deserializable as a JsonObject. + * @param defaultValue representing what would be returned if deserializable isn't a JsonObject or an IOException, + * NullPointerException, or DeserializationException occurs during deserialization. + * @return a JsonObject that represents the deserializable, or the defaultValue if there isn't a JsonObject that + * represents deserializable. + * @see Jsoner#deserialize(Reader) */ + public static JsonObject deserialize(final String deserializable, final JsonObject defaultValue){ + StringReader readable = null; + JsonObject returnable; + try{ + readable = new StringReader(deserializable); + returnable = Jsoner.deserialize(readable, EnumSet.of(DeserializationOptions.ALLOW_JSON_OBJECTS)).<JsonObject> getMap(0); + }catch(NullPointerException | IOException | DeserializationException caught){ + /* Don't care, just return the default value. */ + returnable = defaultValue; + }finally{ + if(readable != null){ + readable.close(); + } + } + return returnable; + } + + /** A convenience method that assumes multiple RFC 4627 JSON values (except numbers) have been concatenated together + * for deserilization which will be collectively returned in a JsonArray wrapper. + * There may be numbers included, they just must not be concatenated together as it is prone to + * NumberFormatExceptions (thus causing a DeserializationException) or the numbers no longer represent their + * respective values. + * Examples: + * "123null321" returns [123, null, 321] + * "nullnullnulltruefalse\"\"{}[]" returns [null, null, null, true, false, "", {}, []] + * "123" appended to "321" returns [123321] + * "12.3" appended to "3.21" throws DeserializationException(NumberFormatException) + * "123" appended to "-321" throws DeserializationException(NumberFormatException) + * "123e321" appended to "-1" throws DeserializationException(NumberFormatException) + * "null12.33.21null" throws DeserializationException(NumberFormatException) + * @param deserializable representing concatenated content to be deserialized as JSON in one reader. Its contents + * may + * not contain two numbers concatenated together. + * @return a JsonArray that contains each of the concatenated objects as its elements. Each concatenated element is + * either a boolean, null, Number, String, JsonArray, or JsonObject that best represents the concatenated + * content inside deserializable. + * @throws DeserializationException if an unexpected token is encountered in the deserializable. To recover from a + * DeserializationException: fix the deserializable to no longer have an unexpected token and try again. + * @throws IOException when the underlying reader encounters an I/O error. Ensure the reader is properly + * instantiated, isn't closed, or that it is ready before trying again. */ + public static JsonArray deserializeMany(final Reader deserializable) throws DeserializationException, IOException{ + return Jsoner.deserialize(deserializable, EnumSet.of(DeserializationOptions.ALLOW_JSON_ARRAYS, DeserializationOptions.ALLOW_JSON_OBJECTS, DeserializationOptions.ALLOW_JSON_DATA, DeserializationOptions.ALLOW_CONCATENATED_JSON_VALUES)); + } + + /** Escapes potentially confusing or important characters in the String provided. + * @param escapable an unescaped string. + * @return an escaped string for usage in JSON; An escaped string is one that has escaped all of the quotes ("), + * backslashes (\), return character (\r), new line character (\n), tab character (\t), + * backspace character (\b), form feed character (\f) and other control characters [u0000..u001F] or + * characters [u007F..u009F], [u2000..u20FF] with a + * backslash (\) which itself must be escaped by the backslash in a java string. */ + public static String escape(final String escapable){ + final StringBuilder builder = new StringBuilder(); + final int characters = escapable.length(); + for(int i = 0; i < characters; i++){ + final char character = escapable.charAt(i); + switch(character){ + case '"': + builder.append("\\\""); + break; + case '\\': + builder.append("\\\\"); + break; + case '\b': + builder.append("\\b"); + break; + case '\f': + builder.append("\\f"); + break; + case '\n': + builder.append("\\n"); + break; + case '\r': + builder.append("\\r"); + break; + case '\t': + builder.append("\\t"); + break; + case '/': + builder.append("\\/"); + break; + default: + /* The many characters that get replaced are benign to software but could be mistaken by people + * reading it for a JSON relevant character. */ + if(((character >= '\u0000') && (character <= '\u001F')) || ((character >= '\u007F') && (character <= '\u009F')) || ((character >= '\u2000') && (character <= '\u20FF'))){ + final String characterHexCode = Integer.toHexString(character); + builder.append("\\u"); + for(int k = 0; k < (4 - characterHexCode.length()); k++){ + builder.append("0"); + } + builder.append(characterHexCode.toUpperCase()); + }else{ + /* Character didn't need escaping. */ + builder.append(character); + } + } + } + return builder.toString(); + } + + /** Processes the lexer's reader for the next token. + * @param lexer represents a text processor being used in the deserialization process. + * @return a token representing a meaningful element encountered by the lexer. + * @throws DeserializationException if an unexpected character is encountered while processing the text. + * @throws IOException if the underlying reader inside the lexer encounters an I/O problem, like being prematurely + * closed. */ + private static Yytoken lexNextToken(final Yylex lexer) throws DeserializationException, IOException{ + Yytoken returnable; + /* Parse through the next token. */ + returnable = lexer.yylex(); + if(returnable == null){ + /* If there isn't another token, it must be the end. */ + returnable = new Yytoken(Yytoken.Types.END, null); + } + return returnable; + } + + /** Used for state transitions while deserializing. + * @param stateStack represents the deserialization states saved for future processing. + * @return a state for deserialization context so it knows how to consume the next token. */ + private static States popNextState(final LinkedList<States> stateStack){ + if(stateStack.size() > 0){ + return stateStack.removeLast(); + }else{ + return States.PARSED_ERROR; + } + } + + /** Formats the JSON string to be more easily human readable using tabs for indentation. + * @param printable representing a JSON formatted string with out extraneous characters, like one returned from + * Jsoner#serialize(Object). + * @return printable except it will have '\n' then '\t' characters inserted after '[', '{', ',' and before ']' '}' + * tokens in the JSON. It will return null if printable isn't a JSON string. */ + public static String prettyPrint(final String printable){ + return Jsoner.prettyPrint(printable, "\t"); + } + + /** Formats the JSON string to be more easily human readable using an arbitrary amount of spaces for indentation. + * @param printable representing a JSON formatted string with out extraneous characters, like one returned from + * Jsoner#serialize(Object). + * @param spaces representing the amount of spaces to use for indentation. Must be between 2 and 10. + * @return printable except it will have '\n' then space characters inserted after '[', '{', ',' and before ']' '}' + * tokens in the JSON. It will return null if printable isn't a JSON string. + * @throws IllegalArgumentException if spaces isn't between [2..10]. + * @see Jsoner#prettyPrint(String) + * @since 2.2.0 to allow pretty printing with spaces instead of tabs. */ + public static String prettyPrint(final String printable, final int spaces){ + if((spaces > 10) || (spaces < 2)){ + throw new IllegalArgumentException("Indentation with spaces must be between 2 and 10."); + } + final StringBuilder indentation = new StringBuilder(""); + for(int i = 0; i < spaces; i++){ + indentation.append(" "); + } + return Jsoner.prettyPrint(printable, indentation.toString()); + } + + /** Makes the JSON string more easily human readable using indentation of the caller's choice. + * @param printable representing a JSON formatted string with out extraneous characters, like one returned from + * Jsoner#serialize(Object). + * @param indentation representing the indentation used to format the JSON string. + * @return printable except it will have '\n' then indentation characters inserted after '[', '{', ',' and before + * ']' '}' + * tokens in the JSON. It will return null if printable isn't a JSON string. */ + private static String prettyPrint(final String printable, final String indentation){ + final Yylex lexer = new Yylex(new StringReader(printable)); + Yytoken lexed; + final StringBuilder returnable = new StringBuilder(); + int level = 0; + try{ + do{ + lexed = Jsoner.lexNextToken(lexer); + switch(lexed.getType()){ + case COLON: + returnable.append(":"); + break; + case COMMA: + returnable.append(lexed.getValue()); + returnable.append("\n"); + for(int i = 0; i < level; i++){ + returnable.append(indentation); + } + break; + case END: + break; + case LEFT_BRACE: + case LEFT_SQUARE: + returnable.append(lexed.getValue()); + returnable.append("\n"); + level++; + for(int i = 0; i < level; i++){ + returnable.append(indentation); + } + break; + case RIGHT_BRACE: + case RIGHT_SQUARE: + returnable.append("\n"); + level--; + for(int i = 0; i < level; i++){ + returnable.append(indentation); + } + returnable.append(lexed.getValue()); + break; + default: + if(lexed.getValue() instanceof String){ + returnable.append("\""); + returnable.append(Jsoner.escape((String)lexed.getValue())); + returnable.append("\""); + }else{ + returnable.append(lexed.getValue()); + } + break; + } + //System.out.println(lexed); + }while(!lexed.getType().equals(Yytoken.Types.END)); + }catch(final DeserializationException caught){ + /* This is according to the method's contract. */ + return null; + }catch(final IOException caught){ + /* See StringReader. */ + return null; + } + //System.out.println(printable); + //System.out.println(returnable); + //System.out.println(Jsoner.escape(returnable.toString())); + return returnable.toString(); + } + + /** A convenience method that assumes a StringWriter. + * @param jsonSerializable represents the object that should be serialized as a string in JSON format. + * @return a string, in JSON format, that represents the object provided. + * @throws IllegalArgumentException if the jsonSerializable isn't serializable in JSON. + * @see Jsoner#serialize(Object, Writer) + * @see StringWriter */ + public static String serialize(final Object jsonSerializable){ + final StringWriter writableDestination = new StringWriter(); + try{ + Jsoner.serialize(jsonSerializable, writableDestination); + }catch(final IOException caught){ + /* See StringWriter. */ + } + return writableDestination.toString(); + } + + /** Serializes values according to the RFC 4627 JSON specification. It will also trust the serialization provided by + * any Jsonables it serializes and serializes Enums that don't implement Jsonable as a string of their fully + * qualified name. + * @param jsonSerializable represents the object that should be serialized in JSON format. + * @param writableDestination represents where the resulting JSON text is written to. + * @throws IOException if the writableDestination encounters an I/O problem, like being closed while in use. + * @throws IllegalArgumentException if the jsonSerializable isn't serializable in JSON. */ + public static void serialize(final Object jsonSerializable, final Writer writableDestination) throws IOException{ + Jsoner.serialize(jsonSerializable, writableDestination, EnumSet.of(SerializationOptions.ALLOW_JSONABLES, SerializationOptions.ALLOW_FULLY_QUALIFIED_ENUMERATIONS)); + } + + /** Serialize values to JSON and write them to the provided writer based on behavior flags. + * @param jsonSerializable represents the object that should be serialized to a string in JSON format. + * @param writableDestination represents where the resulting JSON text is written to. + * @param replacement represents what is serialized instead of a non-JSON value when replacements are allowed. + * @param flags represents the allowances and restrictions on serialization. + * @throws IOException if the writableDestination encounters an I/O problem. + * @throws IllegalArgumentException if the jsonSerializable isn't serializable in JSON. + * @see SerializationOptions */ + private static void serialize(final Object jsonSerializable, final Writer writableDestination, final Set<SerializationOptions> flags) throws IOException{ + if(jsonSerializable == null){ + /* When a null is passed in the word null is supported in JSON. */ + writableDestination.write("null"); + }else if(((jsonSerializable instanceof Jsonable) && flags.contains(SerializationOptions.ALLOW_JSONABLES))){ + /* Writes the writable as defined by the writable. */ + writableDestination.write(((Jsonable)jsonSerializable).toJson()); + }else if((jsonSerializable instanceof Enum) && flags.contains(SerializationOptions.ALLOW_FULLY_QUALIFIED_ENUMERATIONS)){ + /* Writes the enum as a special case of string. All enums (unless they implement Jsonable) will be the + * string literal "${DECLARING_CLASS_NAME}.${ENUM_NAME}" as their value. */ + @SuppressWarnings("rawtypes") + final Enum e = (Enum)jsonSerializable; + writableDestination.write('"'); + writableDestination.write(e.getDeclaringClass().getName()); + writableDestination.write('.'); + writableDestination.write(e.name()); + writableDestination.write('"'); + }else if(jsonSerializable instanceof String){ + /* Make sure the string is properly escaped. */ + writableDestination.write('"'); + writableDestination.write(Jsoner.escape((String)jsonSerializable)); + writableDestination.write('"'); + }else if(jsonSerializable instanceof Double){ + if(((Double)jsonSerializable).isInfinite() || ((Double)jsonSerializable).isNaN()){ + /* Infinite and not a number are not supported by the JSON specification, so null is used instead. */ + writableDestination.write("null"); + }else{ + writableDestination.write(jsonSerializable.toString()); + } + }else if(jsonSerializable instanceof Float){ + if(((Float)jsonSerializable).isInfinite() || ((Float)jsonSerializable).isNaN()){ + /* Infinite and not a number are not supported by the JSON specification, so null is used instead. */ + writableDestination.write("null"); + }else{ + writableDestination.write(jsonSerializable.toString()); + } + }else if(jsonSerializable instanceof Number){ + writableDestination.write(jsonSerializable.toString()); + }else if(jsonSerializable instanceof Boolean){ + writableDestination.write(jsonSerializable.toString()); + }else if(jsonSerializable instanceof Map){ + /* Writes the map in JSON object format. */ + boolean isFirstEntry = true; + @SuppressWarnings("rawtypes") + final Iterator entries = ((Map)jsonSerializable).entrySet().iterator(); + writableDestination.write('{'); + while(entries.hasNext()){ + if(isFirstEntry){ + isFirstEntry = false; + }else{ + writableDestination.write(','); + } + @SuppressWarnings("rawtypes") + final Map.Entry entry = (Map.Entry)entries.next(); + Jsoner.serialize(entry.getKey(), writableDestination, flags); + writableDestination.write(':'); + Jsoner.serialize(entry.getValue(), writableDestination, flags); + } + writableDestination.write('}'); + }else if(jsonSerializable instanceof Collection){ + /* Writes the collection in JSON array format. */ + boolean isFirstElement = true; + @SuppressWarnings("rawtypes") + final Iterator elements = ((Collection)jsonSerializable).iterator(); + writableDestination.write('['); + while(elements.hasNext()){ + if(isFirstElement){ + isFirstElement = false; + }else{ + writableDestination.write(','); + } + Jsoner.serialize(elements.next(), writableDestination, flags); + } + writableDestination.write(']'); + }else if(jsonSerializable instanceof byte[]){ + /* Writes the array in JSON array format. */ + final byte[] writableArray = (byte[])jsonSerializable; + final int numberOfElements = writableArray.length; + writableDestination.write('['); + for(int i = 1; i <= numberOfElements; i++){ + if(i == numberOfElements){ + Jsoner.serialize(writableArray[i], writableDestination, flags); + }else{ + Jsoner.serialize(writableArray[i], writableDestination, flags); + writableDestination.write(','); + } + } + writableDestination.write(']'); + }else if(jsonSerializable instanceof short[]){ + /* Writes the array in JSON array format. */ + final short[] writableArray = (short[])jsonSerializable; + final int numberOfElements = writableArray.length; + writableDestination.write('['); + for(int i = 1; i <= numberOfElements; i++){ + if(i == numberOfElements){ + Jsoner.serialize(writableArray[i], writableDestination, flags); + }else{ + Jsoner.serialize(writableArray[i], writableDestination, flags); + writableDestination.write(','); + } + } + writableDestination.write(']'); + }else if(jsonSerializable instanceof int[]){ + /* Writes the array in JSON array format. */ + final int[] writableArray = (int[])jsonSerializable; + final int numberOfElements = writableArray.length; + writableDestination.write('['); + for(int i = 1; i <= numberOfElements; i++){ + if(i == numberOfElements){ + Jsoner.serialize(writableArray[i], writableDestination, flags); + }else{ + Jsoner.serialize(writableArray[i], writableDestination, flags); + writableDestination.write(','); + } + } + writableDestination.write(']'); + }else if(jsonSerializable instanceof long[]){ + /* Writes the array in JSON array format. */ + final long[] writableArray = (long[])jsonSerializable; + final int numberOfElements = writableArray.length; + writableDestination.write('['); + for(int i = 1; i <= numberOfElements; i++){ + if(i == numberOfElements){ + Jsoner.serialize(writableArray[i], writableDestination, flags); + }else{ + Jsoner.serialize(writableArray[i], writableDestination, flags); + writableDestination.write(','); + } + } + writableDestination.write(']'); + }else if(jsonSerializable instanceof float[]){ + /* Writes the array in JSON array format. */ + final float[] writableArray = (float[])jsonSerializable; + final int numberOfElements = writableArray.length; + writableDestination.write('['); + for(int i = 1; i <= numberOfElements; i++){ + if(i == numberOfElements){ + Jsoner.serialize(writableArray[i], writableDestination, flags); + }else{ + Jsoner.serialize(writableArray[i], writableDestination, flags); + writableDestination.write(','); + } + } + writableDestination.write(']'); + }else if(jsonSerializable instanceof double[]){ + /* Writes the array in JSON array format. */ + final double[] writableArray = (double[])jsonSerializable; + final int numberOfElements = writableArray.length; + writableDestination.write('['); + for(int i = 1; i <= numberOfElements; i++){ + if(i == numberOfElements){ + Jsoner.serialize(writableArray[i], writableDestination, flags); + }else{ + Jsoner.serialize(writableArray[i], writableDestination, flags); + writableDestination.write(','); + } + } + writableDestination.write(']'); + }else if(jsonSerializable instanceof boolean[]){ + /* Writes the array in JSON array format. */ + final boolean[] writableArray = (boolean[])jsonSerializable; + final int numberOfElements = writableArray.length; + writableDestination.write('['); + for(int i = 1; i <= numberOfElements; i++){ + if(i == numberOfElements){ + Jsoner.serialize(writableArray[i], writableDestination, flags); + }else{ + Jsoner.serialize(writableArray[i], writableDestination, flags); + writableDestination.write(','); + } + } + writableDestination.write(']'); + }else if(jsonSerializable instanceof char[]){ + /* Writes the array in JSON array format. */ + final char[] writableArray = (char[])jsonSerializable; + final int numberOfElements = writableArray.length; + writableDestination.write("[\""); + for(int i = 1; i <= numberOfElements; i++){ + if(i == numberOfElements){ + Jsoner.serialize(writableArray[i], writableDestination, flags); + }else{ + Jsoner.serialize(writableArray[i], writableDestination, flags); + writableDestination.write("\",\""); + } + } + writableDestination.write("\"]"); + }else if(jsonSerializable instanceof Object[]){ + /* Writes the array in JSON array format. */ + final Object[] writableArray = (Object[])jsonSerializable; + final int numberOfElements = writableArray.length; + writableDestination.write('['); + for(int i = 1; i <= numberOfElements; i++){ + if(i == numberOfElements){ + Jsoner.serialize(writableArray[i], writableDestination, flags); + }else{ + Jsoner.serialize(writableArray[i], writableDestination, flags); + writableDestination.write(","); + } + } + writableDestination.write(']'); + }else{ + /* TODO a potential feature for future release since POJOs are often represented as JsonObjects. It would be + * nice to have a flag that tries to reflectively figure out what a non-Jsonable POJO's fields are and use + * their names as keys and their respective values for the keys' values in the JsonObject? + * Naturally implementing Jsonable is safer and in many ways makes this feature a convenience for not + * needing + * to implement Jsonable for very simple POJOs. + * If it fails to produce a JsonObject to serialize it should defer to replacements if allowed. + * If replacement fails it should defer to invalids if allowed. + * This feature would require another serialize method exposed to allow this serialization. + * This feature (although perhaps useful on its own) would also include a method in the JsonObject where you + * pass it a class and it would do its best to instantiate a POJO of the class using the keys in the + * JsonObject. */ + /* It cannot by any measure be safely serialized according to specification. */ + if(flags.contains(SerializationOptions.ALLOW_INVALIDS)){ + /* Can be helpful for debugging how it isn't valid. */ + writableDestination.write(jsonSerializable.toString()); + }else{ + /* Notify the caller the cause of failure for the serialization. */ + throw new IllegalArgumentException("Encountered a: " + jsonSerializable.getClass().getName() + " as: " + jsonSerializable.toString() + " that isn't JSON serializable.\n Try:\n 1) Implementing the Jsonable interface for the object to return valid JSON. If it already does it probably has a bug.\n 2) If you cannot edit the source of the object or couple it with this library consider wrapping it in a class that does implement the Jsonable interface.\n 3) Otherwise convert it t [...] + } + } + //System.out.println(writableDestination.toString()); + } + + /** Serializes like the first version of this library. + * It has been adapted to use Jsonable for serializing custom objects, but otherwise works like the old JSON string + * serializer. It + * will allow non-JSON values in its output like the old one. It can be helpful for last resort log statements and + * debugging errors in self generated JSON. Anything serialized using this method isn't guaranteed to be + * deserializable. + * @param jsonSerializable represents the object that should be serialized in JSON format. + * @param writableDestination represents where the resulting JSON text is written to. + * @throws IOException if the writableDestination encounters an I/O problem, like being closed while in use. */ + public static void serializeCarelessly(final Object jsonSerializable, final Writer writableDestination) throws IOException{ + Jsoner.serialize(jsonSerializable, writableDestination, EnumSet.of(SerializationOptions.ALLOW_JSONABLES, SerializationOptions.ALLOW_INVALIDS)); + } + + /** Serializes JSON values and only JSON values according to the RFC 4627 JSON specification. + * @param jsonSerializable represents the object that should be serialized in JSON format. + * @param writableDestination represents where the resulting JSON text is written to. + * @throws IOException if the writableDestination encounters an I/O problem, like being closed while in use. + * @throws IllegalArgumentException if the jsonSerializable isn't serializable in JSON. */ + public static void serializeStrictly(final Object jsonSerializable, final Writer writableDestination) throws IOException{ + Jsoner.serialize(jsonSerializable, writableDestination, EnumSet.noneOf(SerializationOptions.class)); + } +} diff --git a/camel-util-json/src/main/java/org/apache/camel/util/json/Yylex.java b/camel-util-json/src/main/java/org/apache/camel/util/json/Yylex.java new file mode 100644 index 0000000..1b48f15 --- /dev/null +++ b/camel-util-json/src/main/java/org/apache/camel/util/json/Yylex.java @@ -0,0 +1,695 @@ +/* The following code was generated by JFlex 1.4.3 on 8/30/16 5:50 PM */ + +package org.apache.camel.util.json; + + +/** + * This class is a scanner generated by + * <a href="http://www.jflex.de/">JFlex</a> 1.4.3 + * on 8/30/16 5:50 PM from the specification file + * <tt>/home/davinloegering/cliftonlabs/workspace/json-simple/src/main/lex/jsonstrict.lex</tt> + */ +class Yylex { + + /** This character denotes the end of file */ + public static final int YYEOF = -1; + + /** initial size of the lookahead buffer */ + private static final int ZZ_BUFFERSIZE = 16384; + + /** lexical states */ + public static final int YYINITIAL = 0; + public static final int STRING_BEGIN = 2; + + /** + * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l + * ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l + * at the beginning of a line + * l is of the form l = 2*k, k a non negative integer + */ + private static final int ZZ_LEXSTATE[] = { + 0, 0, 1, 1 + }; + + /** + * Translates characters to character classes + */ + private static final String ZZ_CMAP_PACKED = + "\11\0\1\7\1\7\2\0\1\7\22\0\1\7\1\0\1\11\10\0"+ + "\1\6\1\31\1\2\1\4\1\12\12\3\1\32\6\0\4\1\1\5"+ + "\1\1\24\0\1\27\1\10\1\30\3\0\1\22\1\13\2\1\1\21"+ + "\1\14\5\0\1\23\1\0\1\15\3\0\1\16\1\24\1\17\1\20"+ + "\5\0\1\25\1\0\1\26\uff82\0"; + + /** + * Translates characters to character classes + */ + private static final char [] ZZ_CMAP = zzUnpackCMap(ZZ_CMAP_PACKED); + + /** + * Translates DFA states to action switch labels. + */ + private static final int [] ZZ_ACTION = zzUnpackAction(); + + private static final String ZZ_ACTION_PACKED_0 = + "\2\0\2\1\1\2\1\3\1\4\3\1\1\5\1\6"+ + "\1\7\1\10\1\11\1\12\1\13\1\14\1\15\5\0"+ + "\1\14\1\16\1\17\1\20\1\21\1\22\1\23\1\24"+ + "\1\0\1\2\1\0\1\2\4\0\1\25\1\26\2\0"+ + "\1\27"; + + private static int [] zzUnpackAction() { + int [] result = new int[45]; + int offset = 0; + offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAction(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do result[j++] = value; while (--count > 0); + } + return j; + } + + + /** + * Translates a state to a row index in the transition table + */ + private static final int [] ZZ_ROWMAP = zzUnpackRowMap(); + + private static final String ZZ_ROWMAP_PACKED_0 = + "\0\0\0\33\0\66\0\121\0\154\0\207\0\66\0\242"+ + "\0\275\0\330\0\66\0\66\0\66\0\66\0\66\0\66"+ + "\0\363\0\u010e\0\66\0\u0129\0\u0144\0\u015f\0\u017a\0\u0195"+ + "\0\66\0\66\0\66\0\66\0\66\0\66\0\66\0\66"+ + "\0\u01b0\0\u01cb\0\u01e6\0\u01e6\0\u0201\0\u021c\0\u0237\0\u0252"+ + "\0\66\0\66\0\u026d\0\u0288\0\66"; + + private static int [] zzUnpackRowMap() { + int [] result = new int[45]; + int offset = 0; + offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackRowMap(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int high = packed.charAt(i++) << 16; + result[j++] = high | packed.charAt(i++); + } + return j; + } + + /** + * The transition table of the DFA + */ + private static final int ZZ_TRANS [] = { + 2, 2, 3, 4, 2, 2, 2, 5, 2, 6, + 2, 2, 7, 8, 2, 9, 2, 2, 2, 2, + 2, 10, 11, 12, 13, 14, 15, 16, 16, 16, + 16, 16, 16, 16, 16, 17, 18, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 4, 19, 20, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 20, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 5, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 21, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 22, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 23, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 16, 16, 16, 16, 16, 16, 16, + 16, -1, -1, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + -1, -1, -1, -1, -1, -1, -1, -1, 24, 25, + 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 33, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 34, 35, -1, -1, + 34, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 36, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 37, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 38, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 39, -1, 39, -1, 39, -1, -1, + -1, -1, -1, 39, 39, -1, -1, -1, -1, 39, + 39, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 33, -1, 20, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 20, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 35, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 38, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 40, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 41, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 42, -1, 42, -1, 42, + -1, -1, -1, -1, -1, 42, 42, -1, -1, -1, + -1, 42, 42, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 43, -1, 43, -1, 43, -1, -1, -1, + -1, -1, 43, 43, -1, -1, -1, -1, 43, 43, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 44, + -1, 44, -1, 44, -1, -1, -1, -1, -1, 44, + 44, -1, -1, -1, -1, 44, 44, -1, -1, -1, + -1, -1, -1, -1, -1, + }; + + /* error codes */ + private static final int ZZ_UNKNOWN_ERROR = 0; + private static final int ZZ_NO_MATCH = 1; + private static final int ZZ_PUSHBACK_2BIG = 2; + + /* error messages for the codes above */ + private static final String ZZ_ERROR_MSG[] = { + "Unkown internal scanner error", + "Error: could not match input", + "Error: pushback value was too large" + }; + + /** + * ZZ_ATTRIBUTE[aState] contains the attributes of state <code>aState</code> + */ + private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute(); + + private static final String ZZ_ATTRIBUTE_PACKED_0 = + "\2\0\1\11\3\1\1\11\3\1\6\11\2\1\1\11"+ + "\5\0\10\11\1\0\1\1\1\0\1\1\4\0\2\11"+ + "\2\0\1\11"; + + private static int [] zzUnpackAttribute() { + int [] result = new int[45]; + int offset = 0; + offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAttribute(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do result[j++] = value; while (--count > 0); + } + return j; + } + + /** the input device */ + private java.io.Reader zzReader; + + /** the current state of the DFA */ + private int zzState; + + /** the current lexical state */ + private int zzLexicalState = YYINITIAL; + + /** this buffer contains the current text to be matched and is + the source of the yytext() string */ + private char zzBuffer[] = new char[ZZ_BUFFERSIZE]; + + /** the textposition at the last accepting state */ + private int zzMarkedPos; + + /** the current text position in the buffer */ + private int zzCurrentPos; + + /** startRead marks the beginning of the yytext() string in the buffer */ + private int zzStartRead; + + /** endRead marks the last character in the buffer, that has been read + from input */ + private int zzEndRead; + + /** number of newlines encountered up to the start of the matched text */ + private int yyline; + + /** the number of characters up to the start of the matched text */ + private int yychar; + + /** + * the number of characters from the last newline up to the start of the + * matched text + */ + private int yycolumn; + + /** + * zzAtBOL == true <=> the scanner is currently at the beginning of a line + */ + private boolean zzAtBOL = true; + + /** zzAtEOF == true <=> the scanner is at the EOF */ + private boolean zzAtEOF; + + /** denotes if the user-EOF-code has already been executed */ + private boolean zzEOFDone; + + /* user code: */ +private StringBuilder sb=new StringBuilder(); + +int getPosition(){ + return yychar; +} + + + + /** + * Creates a new scanner + * There is also a java.io.InputStream version of this constructor. + * + * @param in the java.io.Reader to read input from. + */ + Yylex(java.io.Reader in) { + this.zzReader = in; + } + + /** + * Creates a new scanner. + * There is also java.io.Reader version of this constructor. + * + * @param in the java.io.Inputstream to read input from. + */ + Yylex(java.io.InputStream in) { + this(new java.io.InputStreamReader(in)); + } + + /** + * Unpacks the compressed character translation table. + * + * @param packed the packed character translation table + * @return the unpacked character translation table + */ + private static char [] zzUnpackCMap(String packed) { + char [] map = new char[0x10000]; + int i = 0; /* index in packed string */ + int j = 0; /* index in unpacked array */ + while (i < 90) { + int count = packed.charAt(i++); + char value = packed.charAt(i++); + do map[j++] = value; while (--count > 0); + } + return map; + } + + + /** + * Refills the input buffer. + * + * @return <code>false</code>, iff there was new input. + * + * @exception java.io.IOException if any I/O-Error occurs + */ + private boolean zzRefill() throws java.io.IOException { + + /* first: make room (if you can) */ + if (zzStartRead > 0) { + System.arraycopy(zzBuffer, zzStartRead, + zzBuffer, 0, + zzEndRead-zzStartRead); + + /* translate stored positions */ + zzEndRead-= zzStartRead; + zzCurrentPos-= zzStartRead; + zzMarkedPos-= zzStartRead; + zzStartRead = 0; + } + + /* is the buffer big enough? */ + if (zzCurrentPos >= zzBuffer.length) { + /* if not: blow it up */ + char newBuffer[] = new char[zzCurrentPos*2]; + System.arraycopy(zzBuffer, 0, newBuffer, 0, zzBuffer.length); + zzBuffer = newBuffer; + } + + /* finally: fill the buffer with new input */ + int numRead = zzReader.read(zzBuffer, zzEndRead, + zzBuffer.length-zzEndRead); + + if (numRead > 0) { + zzEndRead+= numRead; + return false; + } + // unlikely but not impossible: read 0 characters, but not at end of stream + if (numRead == 0) { + int c = zzReader.read(); + if (c == -1) { + return true; + } else { + zzBuffer[zzEndRead++] = (char) c; + return false; + } + } + + // numRead < 0 + return true; + } + + + /** + * Closes the input stream. + */ + public final void yyclose() throws java.io.IOException { + zzAtEOF = true; /* indicate end of file */ + zzEndRead = zzStartRead; /* invalidate buffer */ + + if (zzReader != null) + zzReader.close(); + } + + + /** + * Resets the scanner to read from a new input stream. + * Does not close the old reader. + * + * All internal variables are reset, the old input stream + * <b>cannot</b> be reused (internal buffer is discarded and lost). + * Lexical state is set to <tt>ZZ_INITIAL</tt>. + * + * @param reader the new input stream + */ + public final void yyreset(java.io.Reader reader) { + zzReader = reader; + zzAtBOL = true; + zzAtEOF = false; + zzEOFDone = false; + zzEndRead = zzStartRead = 0; + zzCurrentPos = zzMarkedPos = 0; + yyline = yychar = yycolumn = 0; + zzLexicalState = YYINITIAL; + } + + + /** + * Returns the current lexical state. + */ + public final int yystate() { + return zzLexicalState; + } + + + /** + * Enters a new lexical state + * + * @param newState the new lexical state + */ + public final void yybegin(int newState) { + zzLexicalState = newState; + } + + + /** + * Returns the text matched by the current regular expression. + */ + public final String yytext() { + return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead ); + } + + + /** + * Returns the character at position <tt>pos</tt> from the + * matched text. + * + * It is equivalent to yytext().charAt(pos), but faster + * + * @param pos the position of the character to fetch. + * A value from 0 to yylength()-1. + * + * @return the character at position pos + */ + public final char yycharat(int pos) { + return zzBuffer[zzStartRead+pos]; + } + + + /** + * Returns the length of the matched text region. + */ + public final int yylength() { + return zzMarkedPos-zzStartRead; + } + + + /** + * Reports an error that occured while scanning. + * + * In a wellformed scanner (no or only correct usage of + * yypushback(int) and a match-all fallback rule) this method + * will only be called with things that "Can't Possibly Happen". + * If this method is called, something is seriously wrong + * (e.g. a JFlex bug producing a faulty scanner etc.). + * + * Usual syntax/scanner level error handling should be done + * in error fallback rules. + * + * @param errorCode the code of the errormessage to display + */ + private void zzScanError(int errorCode) { + String message; + try { + message = ZZ_ERROR_MSG[errorCode]; + } + catch (ArrayIndexOutOfBoundsException e) { + message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR]; + } + + throw new Error(message); + } + + + /** + * Pushes the specified amount of characters back into the input stream. + * + * They will be read again by then next call of the scanning method + * + * @param number the number of characters to be read again. + * This number must not be greater than yylength()! + */ + public void yypushback(int number) { + if ( number > yylength() ) + zzScanError(ZZ_PUSHBACK_2BIG); + + zzMarkedPos -= number; + } + + + /** + * Resumes scanning until the next regular expression is matched, + * the end of input is encountered or an I/O-Error occurs. + * + * @return the next token + * @exception java.io.IOException if any I/O-Error occurs + */ + public Yytoken yylex() throws java.io.IOException, DeserializationException { + int zzInput; + int zzAction; + + // cached fields: + int zzCurrentPosL; + int zzMarkedPosL; + int zzEndReadL = zzEndRead; + char [] zzBufferL = zzBuffer; + char [] zzCMapL = ZZ_CMAP; + + int [] zzTransL = ZZ_TRANS; + int [] zzRowMapL = ZZ_ROWMAP; + int [] zzAttrL = ZZ_ATTRIBUTE; + + while (true) { + zzMarkedPosL = zzMarkedPos; + + yychar+= zzMarkedPosL-zzStartRead; + + zzAction = -1; + + zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL; + + zzState = ZZ_LEXSTATE[zzLexicalState]; + + + zzForAction: { + while (true) { + + if (zzCurrentPosL < zzEndReadL) + zzInput = zzBufferL[zzCurrentPosL++]; + else if (zzAtEOF) { + zzInput = YYEOF; + break zzForAction; + } + else { + // store back cached positions + zzCurrentPos = zzCurrentPosL; + zzMarkedPos = zzMarkedPosL; + boolean eof = zzRefill(); + // get translated positions and possibly new buffer + zzCurrentPosL = zzCurrentPos; + zzMarkedPosL = zzMarkedPos; + zzBufferL = zzBuffer; + zzEndReadL = zzEndRead; + if (eof) { + zzInput = YYEOF; + break zzForAction; + } + else { + zzInput = zzBufferL[zzCurrentPosL++]; + } + } + int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput] ]; + if (zzNext == -1) break zzForAction; + zzState = zzNext; + + int zzAttributes = zzAttrL[zzState]; + if ( (zzAttributes & 1) == 1 ) { + zzAction = zzState; + zzMarkedPosL = zzCurrentPosL; + if ( (zzAttributes & 8) == 8 ) break zzForAction; + } + + } + } + + // store back cached position + zzMarkedPos = zzMarkedPosL; + + switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) { + case 4: + { sb = null; sb = new StringBuilder(); yybegin(STRING_BEGIN); + } + case 24: break; + case 11: + { sb.append(yytext()); + } + case 25: break; + case 5: + { return new Yytoken(Yytoken.Types.LEFT_BRACE, null); + } + case 26: break; + case 16: + { sb.append('\b'); + } + case 27: break; + case 23: + { try{ + int ch=Integer.parseInt(yytext().substring(2),16); + sb.append((char)ch); + }catch(Exception e){ + /* The lexer is broken if it can build a 4 byte character code and fail to append the character. */ + throw new DeserializationException(yychar, DeserializationException.Problems.UNEXPECTED_EXCEPTION, e); + } + } + case 28: break; + case 22: + { Boolean val=Boolean.valueOf(yytext()); return new Yytoken(Yytoken.Types.DATUM, val); + } + case 29: break; + case 12: + { sb.append('\\'); + } + case 30: break; + case 10: + { return new Yytoken(Yytoken.Types.COLON, null); + } + case 31: break; + case 9: + { return new Yytoken(Yytoken.Types.COMMA, null); + } + case 32: break; + case 21: + { return new Yytoken(Yytoken.Types.DATUM, null); + } + case 33: break; + case 19: + { sb.append('\r'); + } + case 34: break; + case 15: + { sb.append('/'); + } + case 35: break; + case 2: + { java.math.BigDecimal val= new java.math.BigDecimal(yytext()); return new Yytoken(Yytoken.Types.DATUM, val); + } + case 36: break; + case 14: + { sb.append('"'); + } + case 37: break; + case 8: + { return new Yytoken(Yytoken.Types.RIGHT_SQUARE, null); + } + case 38: break; + case 17: + { sb.append('\f'); + } + case 39: break; + case 1: + { throw new DeserializationException(yychar, DeserializationException.Problems.UNEXPECTED_CHARACTER, new Character(yycharat(0))); + } + case 40: break; + case 6: + { return new Yytoken(Yytoken.Types.RIGHT_BRACE, null); + } + case 41: break; + case 20: + { sb.append('\t'); + } + case 42: break; + case 7: + { return new Yytoken(Yytoken.Types.LEFT_SQUARE, null); + } + case 43: break; + case 18: + { sb.append('\n'); + } + case 44: break; + case 13: + { yybegin(YYINITIAL);return new Yytoken(Yytoken.Types.DATUM, sb.toString()); + } + case 45: break; + case 3: + { + } + case 46: break; + default: + if (zzInput == YYEOF && zzStartRead == zzCurrentPos) { + zzAtEOF = true; + return null; + } + else { + zzScanError(ZZ_NO_MATCH); + } + } + } + } + + +} diff --git a/camel-util-json/src/main/java/org/apache/camel/util/json/Yytoken.java b/camel-util-json/src/main/java/org/apache/camel/util/json/Yytoken.java new file mode 100644 index 0000000..bed0759 --- /dev/null +++ b/camel-util-json/src/main/java/org/apache/camel/util/json/Yytoken.java @@ -0,0 +1,92 @@ +/* Copyright 2016 Clifton Labs + * 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 org.apache.camel.util.json; + +/** Represents structural entities in JSON. + * @since 2.0.0 */ +class Yytoken{ + /** Represents the different kinds of tokens. */ + enum Types{ + /** Tokens of this type will always have a value of ":" */ + COLON, + /** Tokens of this type will always have a value of "," */ + COMMA, + /** Tokens of this type will always have a value that is a boolean, null, number, or string. */ + DATUM, + /** Tokens of this type will always have a value of "" */ + END, + /** Tokens of this type will always have a value of "{" */ + LEFT_BRACE, + /** Tokens of this type will always have a value of "[" */ + LEFT_SQUARE, + /** Tokens of this type will always have a value of "}" */ + RIGHT_BRACE, + /** Tokens of this type will always have a value of "]" */ + RIGHT_SQUARE; + } + + private final Types type; + private final Object value; + + /** @param type represents the kind of token the instantiated token will be. + * @param value represents the value the token is associated with, will be ignored unless type is equal to + * Types.DATUM. + * @see Types */ + Yytoken(final Types type, final Object value){ + /* Sanity check. Make sure the value is ignored for the proper value unless it is a datum token. */ + switch(type){ + case COLON: + this.value = ":"; + break; + case COMMA: + this.value = ","; + break; + case END: + this.value = ""; + break; + case LEFT_BRACE: + this.value = "{"; + break; + case LEFT_SQUARE: + this.value = "["; + break; + case RIGHT_BRACE: + this.value = "}"; + break; + case RIGHT_SQUARE: + this.value = "]"; + break; + default: + this.value = value; + break; + } + this.type = type; + } + + /** @return which of the Types the token is. + * @see Types */ + Types getType(){ + return this.type; + } + + /** @return what the token is. + * @see Types */ + Object getValue(){ + return this.value; + } + + @Override + public String toString(){ + final StringBuffer sb = new StringBuffer(); + sb.append(this.type.toString()).append("(").append(this.value).append(")"); + return sb.toString(); + } +} diff --git a/tooling/json-simple-ordered/src/main/resources/META-INF/LICENSE.txt b/camel-util-json/src/main/resources/META-INF/LICENSE.txt similarity index 100% rename from tooling/json-simple-ordered/src/main/resources/META-INF/LICENSE.txt rename to camel-util-json/src/main/resources/META-INF/LICENSE.txt diff --git a/tooling/json-simple-ordered/src/main/resources/META-INF/NOTICE.txt b/camel-util-json/src/main/resources/META-INF/NOTICE.txt similarity index 100% rename from tooling/json-simple-ordered/src/main/resources/META-INF/NOTICE.txt rename to camel-util-json/src/main/resources/META-INF/NOTICE.txt diff --git a/tooling/json-simple-ordered/src/test/java/org/apache/camel/json/simple/JsonSimpleOrderedTest.java b/camel-util-json/src/test/java/org/apache/camel/util/json/JsonSimpleOrderedTest.java similarity index 96% rename from tooling/json-simple-ordered/src/test/java/org/apache/camel/json/simple/JsonSimpleOrderedTest.java rename to camel-util-json/src/test/java/org/apache/camel/util/json/JsonSimpleOrderedTest.java index d246dba..a7c40d8 100644 --- a/tooling/json-simple-ordered/src/test/java/org/apache/camel/json/simple/JsonSimpleOrderedTest.java +++ b/camel-util-json/src/test/java/org/apache/camel/util/json/JsonSimpleOrderedTest.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.json.simple; +package org.apache.camel.util.json; import java.io.BufferedReader; import java.io.FileInputStream; @@ -25,8 +25,6 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; -import org.json.simple.JsonObject; -import org.json.simple.Jsoner; import org.junit.Assert; import org.junit.Test; diff --git a/tooling/json-simple-ordered/src/test/resources/bean.json b/camel-util-json/src/test/resources/bean.json similarity index 100% rename from tooling/json-simple-ordered/src/test/resources/bean.json rename to camel-util-json/src/test/resources/bean.json diff --git a/camel-util/pom.xml b/camel-util/pom.xml index b2e32b9..225f434 100644 --- a/camel-util/pom.xml +++ b/camel-util/pom.xml @@ -57,7 +57,7 @@ <!-- we shade our patched ordered json-simple parser --> <dependency> <groupId>org.apache.camel</groupId> - <artifactId>json-simple-ordered</artifactId> + <artifactId>camel-util-json</artifactId> <version>${project.version}</version> </dependency> diff --git a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackComponentVerifierExtension.java b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackComponentVerifierExtension.java index 3770449..001800e 100644 --- a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackComponentVerifierExtension.java +++ b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackComponentVerifierExtension.java @@ -34,8 +34,8 @@ import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; import org.json.simple.JSONObject; -import org.json.simple.JsonObject; -import org.json.simple.Jsoner; +import org.apache.camel.util.json.JsonObject; +import org.apache.camel.util.json.Jsoner; import static org.apache.camel.component.slack.utils.SlackUtils.readResponse; diff --git a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackConsumer.java b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackConsumer.java index 0295779..38cd447 100644 --- a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackConsumer.java +++ b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackConsumer.java @@ -29,18 +29,16 @@ import org.apache.camel.Processor; import org.apache.camel.support.ScheduledBatchPollingConsumer; import org.apache.camel.util.CastUtils; import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.json.DeserializationException; +import org.apache.camel.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; +import org.apache.camel.util.json.Jsoner; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; -import org.json.simple.DeserializationException; -import org.json.simple.JSONObject; -import org.json.simple.JsonArray; -import org.json.simple.JsonObject; -import org.json.simple.Jsoner; -import org.json.simple.parser.JSONParser; import static org.apache.camel.component.slack.utils.SlackUtils.readResponse; @@ -74,22 +72,21 @@ public class SlackConsumer extends ScheduledBatchPollingConsumer { HttpResponse response = client.execute(httpPost); String jsonString = readResponse(response); - JSONParser parser = new JSONParser(); - JSONObject c = (JSONObject)parser.parse(jsonString); - List list = (List)c.get("messages"); + JsonObject c = (JsonObject) Jsoner.deserialize(jsonString); + JsonArray list = c.getCollection("messages"); exchanges = createExchanges(list); return processBatch(CastUtils.cast(exchanges)); } - private Queue<Exchange> createExchanges(List list) { + private Queue<Exchange> createExchanges(List<Object> list) { Queue<Exchange> answer = new LinkedList<>(); if (ObjectHelper.isNotEmpty(list)) { Iterator it = list.iterator(); int i = 0; while (it.hasNext()) { - Object object = (Object)it.next(); - JSONObject singleMess = (JSONObject)object; + Object object = it.next(); + JsonObject singleMess = (JsonObject) object; if (i == 0) { timestamp = (String)singleMess.get("ts"); } diff --git a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackEndpoint.java b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackEndpoint.java index 0bd291f..4151cc7 100644 --- a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackEndpoint.java +++ b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackEndpoint.java @@ -30,7 +30,7 @@ import org.apache.camel.spi.UriParam; import org.apache.camel.spi.UriPath; import org.apache.camel.support.ScheduledPollEndpoint; import org.apache.camel.util.ObjectHelper; -import org.json.simple.JSONObject; +import org.apache.camel.util.json.JsonObject; /** * The slack component allows you to send messages to Slack. @@ -181,21 +181,21 @@ public class SlackEndpoint extends ScheduledPollEndpoint { this.serverUrl = serverUrl; } - public Exchange createExchange(JSONObject object) { + public Exchange createExchange(JsonObject object) { return createExchange(getExchangePattern(), object); } - public Exchange createExchange(ExchangePattern pattern, JSONObject object) { + public Exchange createExchange(ExchangePattern pattern, JsonObject object) { Exchange exchange = super.createExchange(pattern); SlackMessage slackMessage = new SlackMessage(); - String text = (String)object.get("text"); - String username = (String)object.get("username"); + String text = object.getString("text"); + String username = object.getString("username"); slackMessage.setText(text); slackMessage.setUsername(username); - if (ObjectHelper.isNotEmpty((JSONObject)object.get("icons"))) { - JSONObject icons = (JSONObject)object.get("icons"); - if (ObjectHelper.isNotEmpty((String)icons.get("emoji"))) { - slackMessage.setIconEmoji((String)icons.get("emoji")); + if (ObjectHelper.isNotEmpty(object.get("icons"))) { + JsonObject icons = object.getMap("icons"); + if (ObjectHelper.isNotEmpty(icons.get("emoji"))) { + slackMessage.setIconEmoji(icons.getString("emoji")); } } Message message = exchange.getIn(); diff --git a/platforms/camel-catalog/pom.xml b/platforms/camel-catalog/pom.xml index d1dd3b3..2a960c7 100644 --- a/platforms/camel-catalog/pom.xml +++ b/platforms/camel-catalog/pom.xml @@ -44,7 +44,7 @@ <!-- we shade our patched ordered json-simple parser --> <dependency> <groupId>org.apache.camel</groupId> - <artifactId>json-simple-ordered</artifactId> + <artifactId>camel-util-json</artifactId> <version>${project.version}</version> </dependency> @@ -213,16 +213,9 @@ <configuration> <artifactSet> <includes> - <include>org.apache.camel:json-simple-ordered</include> + <include>org.apache.camel:camel-util-json</include> </includes> </artifactSet> - <relocations> - <relocation> - <pattern>org.json.simple</pattern> - <!-- scala compiler do not like org.apache.camel.org so its just json.simple instead of org.json.simple --> - <shadedPattern>org.apache.camel.json.simple</shadedPattern> - </relocation> - </relocations> </configuration> </execution> </executions> diff --git a/platforms/karaf/features/src/main/resources/features.xml b/platforms/karaf/features/src/main/resources/features.xml index cae15fd..0a2f4e2 100644 --- a/platforms/karaf/features/src/main/resources/features.xml +++ b/platforms/karaf/features/src/main/resources/features.xml @@ -41,7 +41,7 @@ <feature name='camel-core' version='${project.version}' start-level='50'> <feature version='${servicemix-specs-version}'>xml-specs-api</feature> <bundle dependency='true'>mvn:com.github.ben-manes.caffeine/caffeine/${caffeine-version}</bundle> - <bundle dependency='true'>mvn:org.apache.camel/json-simple-ordered/${project.version}</bundle> + <bundle dependency='true'>mvn:org.apache.camel/camel-util-json/${project.version}</bundle> <bundle>mvn:org.apache.camel/camel-api/${project.version}</bundle> <bundle>mvn:org.apache.camel/camel-util/${project.version}</bundle> <bundle>mvn:org.apache.camel/camel-browse/${project.version}</bundle> @@ -1902,7 +1902,7 @@ </feature> <feature name='camel-slack' version='${project.version}' start-level='50'> <feature version='${project.version}'>camel-core</feature> - <bundle dependency='true'>mvn:com.googlecode.json-simple/json-simple/${json-simple-version}</bundle> + <bundle dependency='true'>mvn:org.apache.camel/camel-util-json/${project.version}</bundle> <bundle dependency='true'>mvn:org.apache.httpcomponents/httpcore-osgi/${httpcore4-version}</bundle> <bundle dependency='true'>mvn:org.apache.httpcomponents/httpclient-osgi/${httpclient4-version}</bundle> <bundle dependency='true'>mvn:javax.servlet/javax.servlet-api/${javax.servlet-api-version}</bundle> diff --git a/pom.xml b/pom.xml index 176dec4..246926e 100644 --- a/pom.xml +++ b/pom.xml @@ -143,6 +143,7 @@ <module>tooling-bundle</module> <module>etc</module> <module>bom</module> + <module>camel-util-json</module> <module>buildingtools</module> <module>camel-util</module> <module>camel-api</module> diff --git a/tooling/apt/pom.xml b/tooling/apt/pom.xml index 0ae491f..6f01ea9 100644 --- a/tooling/apt/pom.xml +++ b/tooling/apt/pom.xml @@ -50,7 +50,7 @@ <!-- use our patched ordered json-simple parser --> <dependency> <groupId>org.apache.camel</groupId> - <artifactId>json-simple-ordered</artifactId> + <artifactId>camel-util-json</artifactId> <version>${project.version}</version> </dependency> diff --git a/tooling/apt/src/main/java/org/apache/camel/tools/apt/EndpointAnnotationProcessor.java b/tooling/apt/src/main/java/org/apache/camel/tools/apt/EndpointAnnotationProcessor.java index 221fcac..d504299 100644 --- a/tooling/apt/src/main/java/org/apache/camel/tools/apt/EndpointAnnotationProcessor.java +++ b/tooling/apt/src/main/java/org/apache/camel/tools/apt/EndpointAnnotationProcessor.java @@ -56,8 +56,8 @@ import org.apache.camel.tools.apt.model.ComponentModel; import org.apache.camel.tools.apt.model.ComponentOption; import org.apache.camel.tools.apt.model.EndpointOption; import org.apache.camel.tools.apt.model.EndpointPath; -import org.json.simple.JsonObject; -import org.json.simple.Jsoner; +import org.apache.camel.util.json.JsonObject; +import org.apache.camel.util.json.Jsoner; import static org.apache.camel.tools.apt.AnnotationProcessorHelper.findFieldElement; import static org.apache.camel.tools.apt.AnnotationProcessorHelper.findJavaDoc; diff --git a/tooling/apt/src/main/java/org/apache/camel/tools/apt/helper/JsonSchemaHelper.java b/tooling/apt/src/main/java/org/apache/camel/tools/apt/helper/JsonSchemaHelper.java index f47ce4b..feec0cd 100644 --- a/tooling/apt/src/main/java/org/apache/camel/tools/apt/helper/JsonSchemaHelper.java +++ b/tooling/apt/src/main/java/org/apache/camel/tools/apt/helper/JsonSchemaHelper.java @@ -26,8 +26,8 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.json.simple.JsonObject; -import org.json.simple.Jsoner; +import org.apache.camel.util.json.JsonObject; +import org.apache.camel.util.json.Jsoner; /** * A helper class for <a href="http://json-schema.org/">JSON schema</a>. diff --git a/tooling/json-simple-ordered/pom.xml b/tooling/json-simple-ordered/pom.xml deleted file mode 100644 index 55480f1..0000000 --- a/tooling/json-simple-ordered/pom.xml +++ /dev/null @@ -1,120 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You 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. - ---> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> - - <modelVersion>4.0.0</modelVersion> - - <parent> - <groupId>org.apache.camel</groupId> - <artifactId>tooling</artifactId> - <version>3.0.0-SNAPSHOT</version> - </parent> - - <!-- should not be OSGi bundle as we shade this component into camel-core and camel-catalog --> - - <artifactId>json-simple-ordered</artifactId> - <name>Camel :: JSon Simple Ordered</name> - <description>A patched json-simple parser that preserves the ordering in Map as read from JSon source</description> - - <!-- TODO: this is only needed until https://github.com/cliftonlabs/json-simple/issues/17 is fixed and released --> - - <properties> - <camel.osgi.export.pkg> - org.json.simple - </camel.osgi.export.pkg> - </properties> - - <dependencies> - - <!-- json parser --> - <dependency> - <groupId>com.github.cliftonlabs</groupId> - <artifactId>json-simple</artifactId> - <version>${json-simple2-version}</version> - <scope>provided</scope> - </dependency> - - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <scope>test</scope> - </dependency> - </dependencies> - - <build> - <plugins> - - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-dependency-plugin</artifactId> - <version>3.0.2</version> - <executions> - <execution> - <id>unpack-source</id> - <phase>initialize</phase> - <goals> - <goal>unpack-dependencies</goal> - </goals> - <configuration> - <classifier>sources</classifier> - <includeGroupIds>com.github.cliftonlabs</includeGroupIds> - <outputDirectory>src/main/java</outputDirectory> - <excludes>META-INF/**,**/JSONArray.java,**/JSONObject.java</excludes> - </configuration> - </execution> - </executions> - </plugin> - - <!-- patch json-simple --> - <plugin> - <groupId>com.google.code.maven-replacer-plugin</groupId> - <artifactId>replacer</artifactId> - <version>1.5.3</version> - <executions> - <execution> - <phase>generate-sources</phase> - <goals> - <goal>replace</goal> - </goals> - </execution> - </executions> - <configuration> - <includes> - <include>${basedir}/src/main/java/org/json/simple/JsonObject.java</include> - </includes> - <replacements> - <replacement> - <token>import java.util.HashMap;</token> - <value>import java.util.LinkedHashMap;</value> - </replacement> - <replacement> - <token>public class JsonObject extends HashMap</token> - <value>public class JsonObject extends LinkedHashMap</value> - </replacement> - </replacements> - </configuration> - </plugin> - - </plugins> - - </build> - -</project> diff --git a/tooling/json-simple-ordered/src/main/java/.gitignore b/tooling/json-simple-ordered/src/main/java/.gitignore deleted file mode 100644 index 3bd67e5..0000000 --- a/tooling/json-simple-ordered/src/main/java/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# source file is copied from archive and rebuild each time -org \ No newline at end of file diff --git a/tooling/maven/camel-package-maven-plugin/pom.xml b/tooling/maven/camel-package-maven-plugin/pom.xml index 4c96f50..117ee38 100644 --- a/tooling/maven/camel-package-maven-plugin/pom.xml +++ b/tooling/maven/camel-package-maven-plugin/pom.xml @@ -49,7 +49,7 @@ <!-- use our patched ordered json-simple parser --> <dependency> <groupId>org.apache.camel</groupId> - <artifactId>json-simple-ordered</artifactId> + <artifactId>camel-util-json</artifactId> <version>${project.version}</version> </dependency> diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/JSonSchemaHelper.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/JSonSchemaHelper.java index ee30216..05939d4 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/JSonSchemaHelper.java +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/JSonSchemaHelper.java @@ -21,8 +21,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import org.json.simple.JsonObject; -import org.json.simple.Jsoner; +import org.apache.camel.util.json.JsonObject; +import org.apache.camel.util.json.Jsoner; public final class JSonSchemaHelper { diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/UpdateSpringBootAutoConfigurationReadmeMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/UpdateSpringBootAutoConfigurationReadmeMojo.java index 53716a2..85b55ef 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/UpdateSpringBootAutoConfigurationReadmeMojo.java +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/UpdateSpringBootAutoConfigurationReadmeMojo.java @@ -28,6 +28,10 @@ import java.util.Locale; import java.util.stream.Collectors; import org.apache.camel.maven.packaging.model.SpringBootAutoConfigureOptionModel; +import org.apache.camel.util.json.DeserializationException; +import org.apache.camel.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; +import org.apache.camel.util.json.Jsoner; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -35,10 +39,6 @@ import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; -import org.json.simple.DeserializationException; -import org.json.simple.JsonArray; -import org.json.simple.JsonObject; -import org.json.simple.Jsoner; import org.mvel2.templates.TemplateRuntime; import org.sonatype.plexus.build.incremental.BuildContext; diff --git a/tooling/pom.xml b/tooling/pom.xml index f4d218e..c910d0a 100644 --- a/tooling/pom.xml +++ b/tooling/pom.xml @@ -41,7 +41,6 @@ <module>parent</module> <module>meta-annotations</module> <module>spi-annotations</module> - <module>json-simple-ordered</module> <module>apt</module> <module>camel-route-parser</module> <module>swagger-rest-dsl-generator</module>