This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch sandbox/camel-3.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit 9a3de64451939b021e550a6e82fdd365c8988d68 Author: Guillaume Nodet <gno...@gmail.com> AuthorDate: Mon Oct 29 22:33:02 2018 +0100 Use statically generated type converter for core converters and optimize the converter registry Before ------ Benchmark Mode Cnt Score Error Units ConverterBenchmark.benchmarkConversionIntToLong avgt 5 17,861 ± 1,333 us/op ConverterBenchmark.benchmarkConversionListToStringArray avgt 5 90,854 ± 21,918 us/op ConverterBenchmark.benchmarkConversionStringToChar avgt 5 27,644 ± 1,563 us/op ConverterBenchmark.benchmarkConversionStringToURI avgt 5 157,240 ± 11,084 us/op ConverterBenchmark.benchmarkConversionTimeEnum avgt 5 73,541 ± 10,457 us/op ConverterBenchmark.benchmarkLoadTime avgt 5 586,724 ± 73,011 us/op After ----- Benchmark Mode Cnt Score Error Units ConverterBenchmark.benchmarkConversionIntToLong avgt 5 13,242 ± 0,041 us/op ConverterBenchmark.benchmarkConversionListToStringArray avgt 5 50,731 ± 2,220 us/op ConverterBenchmark.benchmarkConversionStringToChar avgt 5 24,443 ± 1,268 us/op ConverterBenchmark.benchmarkConversionStringToURI avgt 5 98,887 ± 6,203 us/op ConverterBenchmark.benchmarkConversionTimeEnum avgt 5 35,148 ± 1,820 us/op ConverterBenchmark.benchmarkLoadTime avgt 5 551,463 ± 12,174 us/op --- .../main/java/org/apache/camel/TypeConverter.java | 2 + camel-core/pom.xml | 12 + .../apache/camel/component/bean/BeanConverter.java | 4 +- .../camel/converter/IOConverterOptimised.java | 172 ------- .../camel/converter/NIOConverterOptimised.java | 82 ---- .../apache/camel/converter/ObjectConverter.java | 165 +++---- .../camel/converter/ObjectConverterOptimised.java | 84 ---- .../converter/TimePatternConverterOptimised.java | 42 -- .../impl/converter/BaseTypeConverterRegistry.java | 529 +++++++++++---------- .../converter/CorePackageScanClassResolver.java | 125 ----- .../impl/converter/CoreTypeConverterLoader.java | 39 -- .../camel/impl/converter/EnumTypeConverter.java | 6 +- .../camel/impl/converter/FutureTypeConverter.java | 4 +- .../impl/converter/OptimisedTypeConverter.java | 77 --- .../impl/converter/ToStringTypeConverter.java | 8 +- .../apache/camel/builder/SimpleBuilderTest.java | 2 +- .../apache/camel/builder/xml/XPathFeatureTest.java | 6 +- .../apache/camel/converter/ConverterBenchmark.java | 160 +++++++ .../org/apache/camel/converter/ConverterTest.java | 20 + .../camel/converter/DurationConverterTest.java | 2 +- .../camel/converter/ObjectConverterTest.java | 24 +- .../java/org/apache/camel/util/ObjectHelper.java | 10 +- .../component/cxf/converter/CxfConverter.java | 6 +- .../cxf/converter/CxfPayloadConverter.java | 12 +- .../camel/component/exec/ExecResultConverter.java | 4 +- .../camel/component/jetty/JettyConverter.java | 4 +- .../apache/camel/tools/apt/ConverterProcessor.java | 159 +++++-- 27 files changed, 712 insertions(+), 1048 deletions(-) diff --git a/camel-api/src/main/java/org/apache/camel/TypeConverter.java b/camel-api/src/main/java/org/apache/camel/TypeConverter.java index 1f1fe5b..1222d0e 100644 --- a/camel-api/src/main/java/org/apache/camel/TypeConverter.java +++ b/camel-api/src/main/java/org/apache/camel/TypeConverter.java @@ -25,6 +25,8 @@ package org.apache.camel; */ public interface TypeConverter { + Object MISS_VALUE = Void.TYPE; + /** * Whether the type converter allows returning null as a valid response. * <p/> diff --git a/camel-core/pom.xml b/camel-core/pom.xml index 4bda334..51c6e83 100644 --- a/camel-core/pom.xml +++ b/camel-core/pom.xml @@ -203,6 +203,18 @@ </exclusions> </dependency> <dependency> + <groupId>org.openjdk.jmh</groupId> + <artifactId>jmh-core</artifactId> + <version>1.21</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.openjdk.jmh</groupId> + <artifactId>jmh-generator-annprocess</artifactId> + <version>1.21</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.hamcrest</groupId> <artifactId>java-hamcrest</artifactId> <version>${hamcrest-version}</version> diff --git a/camel-core/src/main/java/org/apache/camel/component/bean/BeanConverter.java b/camel-core/src/main/java/org/apache/camel/component/bean/BeanConverter.java index 6ca7403..fc85841 100644 --- a/camel-core/src/main/java/org/apache/camel/component/bean/BeanConverter.java +++ b/camel-core/src/main/java/org/apache/camel/component/bean/BeanConverter.java @@ -22,6 +22,8 @@ import org.apache.camel.FallbackConverter; import org.apache.camel.TypeConverter; import org.apache.camel.spi.TypeConverterRegistry; +import static org.apache.camel.TypeConverter.MISS_VALUE; + /** * A set of converter methods for working with beans */ @@ -40,7 +42,7 @@ public final class BeanConverter { BeanInvocation bi = (BeanInvocation) value; if (bi.getArgs() == null || bi.getArgs().length != 1) { // not possible to convert at this time as we try to convert the data passed in at first argument - return Void.TYPE; + return MISS_VALUE; } Class<?> from = bi.getArgs()[0].getClass(); diff --git a/camel-core/src/main/java/org/apache/camel/converter/IOConverterOptimised.java b/camel-core/src/main/java/org/apache/camel/converter/IOConverterOptimised.java deleted file mode 100644 index cddbb03..0000000 --- a/camel-core/src/main/java/org/apache/camel/converter/IOConverterOptimised.java +++ /dev/null @@ -1,172 +0,0 @@ -/** - * 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. - */ -package org.apache.camel.converter; - -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.InputStream; -import java.io.ObjectInput; -import java.io.ObjectOutput; -import java.io.OutputStream; -import java.io.Reader; -import java.io.Writer; -import java.net.URL; -import java.util.Properties; - -import org.apache.camel.Exchange; -import org.apache.camel.StreamCache; - -/** - * Optimised {@link IOConverter} - */ -public final class IOConverterOptimised { - - private IOConverterOptimised() { - } - - // CHECKSTYLE:OFF - public static Object convertTo(final Class<?> type, final Exchange exchange, final Object value) throws Exception { - Class fromType = value.getClass(); - - // if the value is StreamCache then ensure its readable before doing conversions - // by resetting it (this is also what StreamCachingAdvice does) - if (value instanceof StreamCache) { - ((StreamCache) value).reset(); - } - - if (type == InputStream.class) { - if (fromType == String.class) { - return IOConverter.toInputStream((String) value, exchange); - } else if (fromType == URL.class) { - return IOConverter.toInputStream((URL) value); - } else if (fromType == File.class) { - return IOConverter.toInputStream((File) value); - } else if (fromType == byte[].class) { - return IOConverter.toInputStream((byte[]) value); - } else if (fromType == ByteArrayOutputStream.class) { - return IOConverter.toInputStream((ByteArrayOutputStream) value); - } else if (fromType == BufferedReader.class) { - return IOConverter.toInputStream((BufferedReader) value, exchange); - } else if (fromType == StringBuilder.class) { - return IOConverter.toInputStream((StringBuilder) value, exchange); - } - return null; - } - - if (type == Reader.class) { - if (fromType == File.class) { - return IOConverter.toReader((File) value, exchange); - } else if (fromType == String.class) { - return IOConverter.toReader((String) value); - } else if (InputStream.class.isAssignableFrom(fromType)) { - return IOConverter.toReader((InputStream) value, exchange); - } - return null; - } - - if (type == File.class) { - if (fromType == String.class) { - return IOConverter.toFile((String) value); - } - return null; - } - - if (type == OutputStream.class) { - if (fromType == File.class) { - return IOConverter.toOutputStream((File) value); - } - return null; - } - - if (type == Writer.class) { - if (fromType == File.class) { - return IOConverter.toWriter((File) value, exchange); - } else if (OutputStream.class.isAssignableFrom(fromType)) { - return IOConverter.toWriter((OutputStream) value, exchange); - } - return null; - } - - if (type == String.class) { - if (fromType == byte[].class) { - return IOConverter.toString((byte[]) value, exchange); - } else if (fromType == File.class) { - return IOConverter.toString((File) value, exchange); - } else if (fromType == URL.class) { - return IOConverter.toString((URL) value, exchange); - } else if (fromType == BufferedReader.class) { - return IOConverter.toString((BufferedReader) value); - } else if (Reader.class.isAssignableFrom(fromType)) { - return IOConverter.toString((Reader) value); - } else if (InputStream.class.isAssignableFrom(fromType)) { - return IOConverter.toString((InputStream) value, exchange); - } else if (fromType == ByteArrayOutputStream.class) { - return IOConverter.toString((ByteArrayOutputStream) value, exchange); - } - return null; - } - - if (type == byte[].class) { - if (fromType == BufferedReader.class) { - return IOConverter.toByteArray((BufferedReader) value, exchange); - } else if (Reader.class.isAssignableFrom(fromType)) { - return IOConverter.toByteArray((Reader) value, exchange); - } else if (fromType == File.class) { - return IOConverter.toByteArray((File) value); - } else if (fromType == String.class) { - return IOConverter.toByteArray((String) value, exchange); - } else if (fromType == ByteArrayOutputStream.class) { - return IOConverter.toByteArray((ByteArrayOutputStream) value); - } else if (InputStream.class.isAssignableFrom(fromType)) { - return IOConverter.toBytes((InputStream) value); - } - return null; - } - - if (type == ObjectInput.class) { - if (fromType == InputStream.class || fromType == BufferedInputStream.class) { - return IOConverter.toObjectInput((InputStream) value, exchange); - } - return null; - } - - if (type == ObjectOutput.class) { - if (fromType == OutputStream.class) { - return IOConverter.toObjectOutput((OutputStream) value); - } - return null; - } - - if (type == Properties.class) { - if (fromType == File.class) { - return IOConverter.toProperties((File) value); - } else if (fromType == InputStream.class) { - return IOConverter.toProperties((InputStream) value); - } else if (fromType == Reader.class) { - return IOConverter.toProperties((Reader) value); - } - return null; - } - - // no optimised type converter found - return null; - } - // CHECKSTYLE:ON - -} diff --git a/camel-core/src/main/java/org/apache/camel/converter/NIOConverterOptimised.java b/camel-core/src/main/java/org/apache/camel/converter/NIOConverterOptimised.java deleted file mode 100644 index cbef7a9..0000000 --- a/camel-core/src/main/java/org/apache/camel/converter/NIOConverterOptimised.java +++ /dev/null @@ -1,82 +0,0 @@ -/** - * 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. - */ -package org.apache.camel.converter; - -import java.io.File; -import java.io.InputStream; -import java.nio.ByteBuffer; - -import org.apache.camel.Exchange; - -/** - * Optimised {@link NIOConverter} - */ -public final class NIOConverterOptimised { - - private NIOConverterOptimised() { - } - - public static Object convertTo(final Class<?> type, final Exchange exchange, final Object value) throws Exception { - Class fromType = value.getClass(); - - if (type == String.class) { - if (fromType == ByteBuffer.class) { - return NIOConverter.toString((ByteBuffer) value, exchange); - } - return null; - } - - if (type == byte[].class) { - if (fromType == ByteBuffer.class) { - return NIOConverter.toByteArray((ByteBuffer) value); - } - return null; - } - - if (type == InputStream.class) { - if (fromType == ByteBuffer.class) { - return NIOConverter.toInputStream((ByteBuffer) value); - } - return null; - } - - if (type == ByteBuffer.class) { - if (fromType == byte[].class) { - return NIOConverter.toByteBuffer((byte[]) value); - } else if (fromType == File.class) { - return NIOConverter.toByteBuffer((File) value); - } else if (fromType == String.class) { - return NIOConverter.toByteBuffer((String) value, exchange); - } else if (fromType == short.class || fromType == Short.class) { - return NIOConverter.toByteBuffer((Short) value); - } else if (fromType == int.class || fromType == Integer.class) { - return NIOConverter.toByteBuffer((Integer) value); - } else if (fromType == long.class || fromType == Long.class) { - return NIOConverter.toByteBuffer((Long) value); - } else if (fromType == float.class || fromType == Float.class) { - return NIOConverter.toByteBuffer((Float) value); - } else if (fromType == double.class || fromType == Double.class) { - return NIOConverter.toByteBuffer((Double) value); - } - return null; - } - - // no optimised type converter found - return null; - } - -} diff --git a/camel-core/src/main/java/org/apache/camel/converter/ObjectConverter.java b/camel-core/src/main/java/org/apache/camel/converter/ObjectConverter.java index 75f0131..3e326d7 100644 --- a/camel-core/src/main/java/org/apache/camel/converter/ObjectConverter.java +++ b/camel-core/src/main/java/org/apache/camel/converter/ObjectConverter.java @@ -76,18 +76,17 @@ public final class ObjectConverter { /** * Returns the converted value, or null if the value is null */ - @Converter - public static Byte toByte(Object value) { - if (value instanceof Byte) { - return (Byte) value; - } else if (value instanceof Number) { - Number number = (Number) value; - return number.byteValue(); - } else if (value instanceof String) { - return Byte.valueOf((String) value); - } else { + @Converter(allowNull = true) + public static Byte toByte(Number value) { + if (org.apache.camel.util.ObjectHelper.isNaN(value)) { return null; } + return value.byteValue(); + } + + @Converter + public static Byte toByte(String value) { + return Byte.valueOf(value); } @Converter @@ -118,85 +117,77 @@ public final class ObjectConverter { * Returns the converted value, or null if the value is null */ @Converter - public static Class<?> toClass(Object value, Exchange exchange) { - if (value instanceof Class) { - return (Class<?>) value; - } else if (value instanceof String) { - // prefer to use class resolver API - if (exchange != null) { - return exchange.getContext().getClassResolver().resolveClass((String) value); - } else { - return org.apache.camel.util.ObjectHelper.loadClass((String) value); - } + public static Class<?> toClass(String value, Exchange exchange) { + // prefer to use class resolver API + if (exchange != null) { + return exchange.getContext().getClassResolver().resolveClass((String) value); } else { - return null; + return org.apache.camel.util.ObjectHelper.loadClass((String) value); } } /** * Returns the converted value, or null if the value is null */ - @Converter - public static Short toShort(Object value) { - if (value instanceof Short) { - return (Short) value; - } else if (value instanceof Number) { - Number number = (Number) value; - return number.shortValue(); - } else if (value instanceof String) { - return Short.valueOf((String) value); - } else { + @Converter(allowNull = true) + public static Short toShort(Number value) { + if (org.apache.camel.util.ObjectHelper.isNaN(value)) { return null; } + return value.shortValue(); + } + + @Converter + public static Short toShort(String value) { + return Short.valueOf(value); } /** * Returns the converted value, or null if the value is null */ - @Converter - public static Integer toInteger(Object value) { - if (value instanceof Integer) { - return (Integer) value; - } else if (value instanceof Number) { - Number number = (Number) value; - return number.intValue(); - } else if (value instanceof String) { - return Integer.valueOf((String) value); - } else { + @Converter(allowNull = true) + public static Integer toInteger(Number value) { + if (org.apache.camel.util.ObjectHelper.isNaN(value)) { return null; } + return value.intValue(); + } + + @Converter + public static Integer toInteger(String value) { + return Integer.valueOf(value); } /** * Returns the converted value, or null if the value is null */ - @Converter - public static Long toLong(Object value) { - if (value instanceof Long) { - return (Long) value; - } else if (value instanceof Number) { - Number number = (Number) value; - return number.longValue(); - } else if (value instanceof String) { - return Long.valueOf((String) value); - } else { + @Converter(allowNull = true) + public static Long toLong(Number value) { + if (org.apache.camel.util.ObjectHelper.isNaN(value)) { return null; } + return value.longValue(); + } + + @Converter + public static Long toLong(String value) { + return Long.valueOf(value); } /** * Returns the converted value, or null if the value is null */ - @Converter + @Converter(allowNull = true) public static BigInteger toBigInteger(Object value) { + if (org.apache.camel.util.ObjectHelper.isNaN(value)) { + return null; + } if (value instanceof String) { return new BigInteger((String) value); } Long num = null; - if (value instanceof Long) { - num = (Long) value; - } else if (value instanceof Number) { + if (value instanceof Number) { Number number = (Number) value; num = number.longValue(); } @@ -211,40 +202,32 @@ public final class ObjectConverter { * Returns the converted value, or null if the value is null */ @Converter - public static Float toFloat(Object value) { - if (value instanceof Float) { - return (Float) value; - } else if (value instanceof Number) { - if (org.apache.camel.util.ObjectHelper.isNaN(value)) { - return Float.NaN; - } - Number number = (Number) value; - return number.floatValue(); - } else if (value instanceof String) { - return Float.valueOf((String) value); - } else { - return null; + public static Float toFloat(Number value) { + if (org.apache.camel.util.ObjectHelper.isNaN(value)) { + return Float.NaN; } + return value.floatValue(); + } + + @Converter + public static Float toFloat(String value) { + return Float.valueOf(value); } /** * Returns the converted value, or null if the value is null */ @Converter - public static Double toDouble(Object value) { - if (value instanceof Double) { - return (Double) value; - } else if (value instanceof Number) { - if (org.apache.camel.util.ObjectHelper.isNaN(value)) { - return Double.NaN; - } - Number number = (Number) value; - return number.doubleValue(); - } else if (value instanceof String) { - return Double.valueOf((String) value); - } else { - return null; + public static Double toDouble(Number value) { + if (org.apache.camel.util.ObjectHelper.isNaN(value)) { + return Double.NaN; } + return value.doubleValue(); + } + + @Converter + public static Double toDouble(String value) { + return Double.valueOf(value); } // add fast type converters from most common used @@ -275,26 +258,6 @@ public final class ObjectConverter { } @Converter - public static Integer toInteger(String value) { - return Integer.valueOf(value); - } - - @Converter - public static Long toLong(String value) { - return Long.valueOf(value); - } - - @Converter - public static Float toFloat(String value) { - return Float.valueOf(value); - } - - @Converter - public static Double toDouble(String value) { - return Double.valueOf(value); - } - - @Converter public static Boolean toBoolean(String value) { return Boolean.parseBoolean(value); } diff --git a/camel-core/src/main/java/org/apache/camel/converter/ObjectConverterOptimised.java b/camel-core/src/main/java/org/apache/camel/converter/ObjectConverterOptimised.java deleted file mode 100644 index c6391ed..0000000 --- a/camel-core/src/main/java/org/apache/camel/converter/ObjectConverterOptimised.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * 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. - */ -package org.apache.camel.converter; - -import java.util.Iterator; - -import org.apache.camel.Exchange; -import org.apache.camel.support.ObjectHelper; - -/** - * Optimised {@link ObjectConverter} - */ -public final class ObjectConverterOptimised { - - private ObjectConverterOptimised() { - } - - public static Object convertTo(final Class<?> type, final Exchange exchange, final Object value) throws Exception { - // converting to a String is very common - if (type == String.class) { - Class fromType = value.getClass(); - if (fromType == boolean.class || fromType == Boolean.class) { - return value.toString(); - } else if (fromType == int.class || fromType == Integer.class) { - return value.toString(); - } else if (fromType == long.class || fromType == Long.class) { - return value.toString(); - } else if (fromType == char[].class) { - return ObjectConverter.fromCharArray((char[]) value); - } else if (fromType == StringBuffer.class || fromType == StringBuilder.class) { - return value.toString(); - } - return null; - } - - if (type == boolean.class || type == Boolean.class) { - return ObjectConverter.toBoolean(value); - } else if (type == int.class || type == Integer.class) { - return ObjectConverter.toInteger(value); - } else if (type == long.class || type == Long.class) { - return ObjectConverter.toLong(value); - } else if (type == byte.class || type == Byte.class) { - return ObjectConverter.toByte(value); - } else if (type == double.class || type == Double.class) { - return ObjectConverter.toDouble(value); - } else if (type == float.class || type == Float.class) { - return ObjectConverter.toFloat(value); - } else if (type == short.class || type == Short.class) { - return ObjectConverter.toShort(value); - } else if ((type == char.class || type == Character.class) && value.getClass() == String.class) { - return ObjectConverter.toCharacter((String) value); - } else if ((type == char[].class || type == Character[].class) && value.getClass() == String.class) { - return ObjectConverter.toCharArray((String) value); - } - - if (type == Iterator.class) { - return ObjectHelper.createIterator(value); - } else if (type == Iterable.class) { - return ObjectHelper.createIterable(value); - } - - if (type == Class.class) { - return ObjectConverter.toClass(value, exchange); - } - - // no optimised type converter found - return null; - } - -} diff --git a/camel-core/src/main/java/org/apache/camel/converter/TimePatternConverterOptimised.java b/camel-core/src/main/java/org/apache/camel/converter/TimePatternConverterOptimised.java deleted file mode 100644 index bb45a40..0000000 --- a/camel-core/src/main/java/org/apache/camel/converter/TimePatternConverterOptimised.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * 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. - */ -package org.apache.camel.converter; - -import org.apache.camel.Exchange; - -/** - * Optimised {@link TimePatternConverter} - */ -public final class TimePatternConverterOptimised { - - private TimePatternConverterOptimised() { - } - - public static Object convertTo(final Class<?> type, final Exchange exchange, final Object value) throws Exception { - // special for String -> long where we support time patterns - if (type == long.class || type == Long.class) { - Class fromType = value.getClass(); - if (fromType == String.class) { - return TimePatternConverter.toMilliSeconds(value.toString()); - } - } - - // no optimised type converter found - return null; - } - -} diff --git a/camel-core/src/main/java/org/apache/camel/impl/converter/BaseTypeConverterRegistry.java b/camel-core/src/main/java/org/apache/camel/impl/converter/BaseTypeConverterRegistry.java index 27d90dc..a728621 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/converter/BaseTypeConverterRegistry.java +++ b/camel-core/src/main/java/org/apache/camel/impl/converter/BaseTypeConverterRegistry.java @@ -18,16 +18,13 @@ package org.apache.camel.impl.converter; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.LongAdder; +import java.util.concurrent.locks.StampedLock; +import java.util.function.Predicate; import org.apache.camel.CamelContext; import org.apache.camel.CamelContextAware; @@ -43,31 +40,33 @@ import org.apache.camel.TypeConverterExists; import org.apache.camel.TypeConverterExistsException; import org.apache.camel.TypeConverterLoaderException; import org.apache.camel.TypeConverters; +import org.apache.camel.spi.CamelLogger; import org.apache.camel.spi.FactoryFinder; import org.apache.camel.spi.Injector; import org.apache.camel.spi.PackageScanClassResolver; import org.apache.camel.spi.TypeConverterAware; import org.apache.camel.spi.TypeConverterLoader; import org.apache.camel.spi.TypeConverterRegistry; -import org.apache.camel.spi.CamelLogger; -import org.apache.camel.support.LRUCacheFactory; import org.apache.camel.support.MessageHelper; import org.apache.camel.support.ServiceSupport; +import org.apache.camel.support.TypeConverterSupport; import org.apache.camel.util.ObjectHelper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.camel.util.function.TriConsumer; /** * Base implementation of a type converter registry used for * <a href="http://camel.apache.org/type-converter.html">type converters</a> in Camel. */ public abstract class BaseTypeConverterRegistry extends ServiceSupport implements TypeConverter, TypeConverterRegistry, CamelContextAware { - protected final Logger log = LoggerFactory.getLogger(getClass()); - protected final OptimisedTypeConverter optimisedTypeConverter = new OptimisedTypeConverter(); - protected final ConcurrentMap<TypeMapping, TypeConverter> typeMappings = new ConcurrentHashMap<>(); - // for misses use a soft reference cache map, as the classes may be un-deployed at runtime - @SuppressWarnings("unchecked") - protected final Map<TypeMapping, TypeMapping> misses = LRUCacheFactory.newLRUSoftCache(1000); + + protected static final TypeConverter MISS_CONVERTER = new TypeConverterSupport() { + @Override + public <T> T convertTo(Class<T> type, Exchange exchange, Object value) throws TypeConversionException { + return (T) MISS_VALUE; + } + }; + + protected final DoubleMap<Class<?>, Class<?>, TypeConverter> typeMappings = new DoubleMap<>(200); protected final List<TypeConverterLoader> typeConverterLoaders = new ArrayList<>(); protected final List<FallbackTypeConverter> fallbackConverters = new CopyOnWriteArrayList<>(); protected final PackageScanClassResolver resolver; @@ -130,44 +129,7 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement @SuppressWarnings("unchecked") @Override public <T> T convertTo(Class<T> type, Exchange exchange, Object value) { - if (!isRunAllowed()) { - throw new IllegalStateException(this + " is not started"); - } - - Object answer; - try { - answer = doConvertTo(type, exchange, value, false); - } catch (Exception e) { - if (statistics.isStatisticsEnabled()) { - failedCounter.increment(); - } - // if its a ExecutionException then we have rethrow it as its not due to failed conversion - // this is special for FutureTypeConverter - boolean execution = ObjectHelper.getException(ExecutionException.class, e) != null - || ObjectHelper.getException(CamelExecutionException.class, e) != null; - if (execution) { - throw CamelExecutionException.wrapCamelExecutionException(exchange, e); - } - - // error occurred during type conversion - if (e instanceof TypeConversionException) { - throw (TypeConversionException) e; - } else { - throw createTypeConversionException(exchange, type, value, e); - } - } - if (answer == Void.TYPE) { - if (statistics.isStatisticsEnabled()) { - missCounter.increment(); - } - // Could not find suitable conversion - return null; - } else { - if (statistics.isStatisticsEnabled()) { - hitCounter.increment(); - } - return (T) answer; - } + return (T) doConvertTo(type, exchange, value, false, false); } @Override @@ -178,36 +140,12 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement @SuppressWarnings("unchecked") @Override public <T> T mandatoryConvertTo(Class<T> type, Exchange exchange, Object value) throws NoTypeConversionAvailableException { - if (!isRunAllowed()) { - throw new IllegalStateException(this + " is not started"); - } - - Object answer; - try { - answer = doConvertTo(type, exchange, value, false); - } catch (Exception e) { - if (statistics.isStatisticsEnabled()) { - failedCounter.increment(); - } - // error occurred during type conversion - if (e instanceof TypeConversionException) { - throw (TypeConversionException) e; - } else { - throw createTypeConversionException(exchange, type, value, e); - } - } - if (answer == Void.TYPE || value == null) { - if (statistics.isStatisticsEnabled()) { - missCounter.increment(); - } + Object answer = doConvertTo(type, exchange, value, true, false); + if (answer == null) { // Could not find suitable conversion throw new NoTypeConversionAvailableException(value, type); - } else { - if (statistics.isStatisticsEnabled()) { - hitCounter.increment(); - } - return (T) answer; } + return (T) answer; } @Override @@ -218,20 +156,32 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement @SuppressWarnings("unchecked") @Override public <T> T tryConvertTo(Class<T> type, Exchange exchange, Object value) { - if (!isRunAllowed()) { - return null; - } + return (T) doConvertTo(type, exchange, value, false, true); + } + protected Object doConvertTo(final Class<?> type, final Exchange exchange, final Object value, final boolean mandatory, final boolean tryConvert) { Object answer; try { - answer = doConvertTo(type, exchange, value, true); + answer = doConvertTo(type, exchange, value, tryConvert); } catch (Exception e) { if (statistics.isStatisticsEnabled()) { failedCounter.increment(); } - return null; + if (tryConvert) { + return null; + } + + // if its a ExecutionException then we have rethrow it as its not due to failed conversion + // this is special for FutureTypeConverter + boolean execution = ObjectHelper.getException(ExecutionException.class, e) != null + || ObjectHelper.getException(CamelExecutionException.class, e) != null; + if (execution) { + throw CamelExecutionException.wrapCamelExecutionException(exchange, e); + } + // error occurred during type conversion + throw createTypeConversionException(exchange, type, value, e); } - if (answer == Void.TYPE) { + if (answer == MISS_VALUE) { // Could not find suitable conversion if (statistics.isStatisticsEnabled()) { missCounter.increment(); @@ -241,25 +191,52 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement if (statistics.isStatisticsEnabled()) { hitCounter.increment(); } - return (T) answer; + return answer; } } protected Object doConvertTo(final Class<?> type, final Exchange exchange, final Object value, final boolean tryConvert) throws Exception { - if (log.isTraceEnabled()) { + boolean trace = log.isTraceEnabled(); + boolean statisticsEnabled = statistics.isStatisticsEnabled(); + + if (trace) { log.trace("Finding type converter to convert {} -> {} with value: {}", - new Object[]{value == null ? "null" : value.getClass().getCanonicalName(), - type.getCanonicalName(), value}); + value == null ? "null" : value.getClass().getCanonicalName(), + type.getCanonicalName(), value); } + if (value == null) { // no type conversion was needed - if (statistics.isStatisticsEnabled()) { + if (statisticsEnabled) { noopCounter.increment(); } - // lets avoid NullPointerException when converting to boolean for null values - if (boolean.class == type) { - return Boolean.FALSE; + // lets avoid NullPointerException when converting to primitives for null values + if (type.isPrimitive()) { + if (boolean.class == type) { + return Boolean.FALSE; + } + if (int.class == type) { + return 0; + } + if (long.class == type) { + return 0L; + } + if (byte.class == type) { + return (byte) 0; + } + if (short.class == type) { + return (short) 0; + } + if (double.class == type) { + return 0.0; + } + if (float.class == type) { + return 0.0f; + } + if (char.class == type) { + return '\0'; + } } return null; } @@ -267,66 +244,33 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement // same instance type if (type.isInstance(value)) { // no type conversion was needed - if (statistics.isStatisticsEnabled()) { + if (statisticsEnabled) { noopCounter.increment(); } return value; } - // special for NaN numbers, which we can only convert for floating numbers - if ((value instanceof Float && value.equals(Float.NaN)) || (value instanceof Double && value.equals(Double.NaN))) { - // no type conversion was needed - if (statistics.isStatisticsEnabled()) { - noopCounter.increment(); - } - if (Float.class.isAssignableFrom(type)) { - return Float.NaN; - } else if (Double.class.isAssignableFrom(type)) { - return Double.NaN; - } else { - // we cannot convert the NaN - return Void.TYPE; - } - } - // okay we need to attempt to convert - if (statistics.isStatisticsEnabled()) { + if (statisticsEnabled) { attemptCounter.increment(); } - // use the optimised core converter first - Object result = optimisedTypeConverter.convertTo(type, exchange, value); - if (result != null) { - if (statistics.isStatisticsEnabled()) { - baseHitCounter.increment(); - } - if (log.isTraceEnabled()) { - log.trace("Using optimised core converter to convert: {} -> {}", type, value.getClass().getCanonicalName()); - } - return result; - } - - // check if we have tried it before and if its a miss - TypeMapping key = new TypeMapping(type, value.getClass()); - if (misses.containsKey(key)) { - // we have tried before but we cannot convert this one - return Void.TYPE; - } - // try to find a suitable type converter - TypeConverter converter = getOrFindTypeConverter(key); + TypeConverter converter = getOrFindTypeConverter(type, value.getClass()); if (converter != null) { - log.trace("Using converter: {} to convert {}", converter, key); + if (trace) { + log.trace("Using converter: {} to convert [{}=>{}]", converter, value.getClass(), type); + } Object rc; if (tryConvert) { rc = converter.tryConvertTo(type, exchange, value); } else { rc = converter.convertTo(type, exchange, value); } - if (rc == null && converter.allowNull()) { - return null; - } else if (rc != null) { + if (rc != null) { return rc; + } else if (converter.allowNull()) { + return null; } } @@ -335,7 +279,7 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement Class<?> primitiveType = ObjectHelper.convertPrimitiveTypeToWrapperType(type); if (primitiveType != type) { Class<?> fromType = value.getClass(); - TypeConverter tc = getOrFindTypeConverter(new TypeMapping(primitiveType, fromType)); + TypeConverter tc = getOrFindTypeConverter(primitiveType, fromType); if (tc != null) { // add the type as a known type converter as we can convert from primitive to object converter addTypeConverter(type, fromType, tc); @@ -367,9 +311,9 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement return null; } - if (Void.TYPE.equals(rc)) { + if (rc == MISS_VALUE) { // it cannot be converted so give up - return Void.TYPE; + return MISS_VALUE; } if (rc != null) { @@ -378,15 +322,14 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement // add it as a known type converter since we found a fallback that could do it if (log.isDebugEnabled()) { log.debug("Promoting fallback type converter as a known type converter to convert from: {} to: {} for the fallback converter: {}", - new Object[]{type.getCanonicalName(), value.getClass().getCanonicalName(), fallback.getFallbackTypeConverter()}); + type.getCanonicalName(), value.getClass().getCanonicalName(), fallback.getFallbackTypeConverter()); } addTypeConverter(type, value.getClass(), fallback.getFallbackTypeConverter()); } if (log.isTraceEnabled()) { log.trace("Fallback type converter {} converted type from: {} to: {}", - new Object[]{fallback.getFallbackTypeConverter(), - type.getCanonicalName(), value.getClass().getCanonicalName()}); + fallback.getFallbackTypeConverter(), type.getCanonicalName(), value.getClass().getCanonicalName()); } // return converted value @@ -397,18 +340,17 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement if (!tryConvert) { // Could not find suitable conversion, so remember it // do not register misses for try conversions - misses.put(key, key); + typeMappings.put(type, value.getClass(), MISS_CONVERTER); } // Could not find suitable conversion, so return Void to indicate not found - return Void.TYPE; + return MISS_VALUE; } @Override public void addTypeConverter(Class<?> toType, Class<?> fromType, TypeConverter typeConverter) { log.trace("Adding type converter: {}", typeConverter); - TypeMapping key = new TypeMapping(toType, fromType); - TypeConverter converter = typeMappings.get(key); + TypeConverter converter = typeMappings.get(toType, fromType); // only override it if its different // as race conditions can lead to many threads trying to promote the same fallback converter @@ -433,9 +375,7 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement } if (add) { - typeMappings.put(key, typeConverter); - // remove any previous misses, as we added the new type converter - misses.remove(key); + typeMappings.put(toType, fromType, typeConverter); } } } @@ -455,13 +395,7 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement @Override public boolean removeTypeConverter(Class<?> toType, Class<?> fromType) { log.trace("Removing type converter from: {} to: {}", fromType, toType); - TypeMapping key = new TypeMapping(toType, fromType); - TypeConverter converter = typeMappings.remove(key); - if (converter != null) { - typeMappings.remove(key); - misses.remove(key); - } - return converter != null; + return typeMappings.remove(toType, fromType); } @Override @@ -502,8 +436,7 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement } public TypeConverter getTypeConverter(Class<?> toType, Class<?> fromType) { - TypeMapping key = new TypeMapping(toType, fromType); - return typeMappings.get(key); + return typeMappings.get(toType, fromType); } @Override @@ -516,36 +449,13 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement this.injector = injector; } - public Set<Class<?>> getFromClassMappings() { - Set<Class<?>> answer = new HashSet<>(); - for (TypeMapping mapping : typeMappings.keySet()) { - answer.add(mapping.getFromType()); - } - return answer; - } - - public Map<Class<?>, TypeConverter> getToClassMappings(Class<?> fromClass) { - Map<Class<?>, TypeConverter> answer = new HashMap<>(); - for (Map.Entry<TypeMapping, TypeConverter> entry : typeMappings.entrySet()) { - TypeMapping mapping = entry.getKey(); - if (mapping.isApplicable(fromClass)) { - answer.put(mapping.getToType(), entry.getValue()); - } - } - return answer; - } - - public Map<TypeMapping, TypeConverter> getTypeMappings() { - return typeMappings; - } - - protected <T> TypeConverter getOrFindTypeConverter(TypeMapping key) { - TypeConverter converter = typeMappings.get(key); + protected <T> TypeConverter getOrFindTypeConverter(Class<?> toType, Class<?> fromType) { + TypeConverter converter = typeMappings.get(toType, fromType); if (converter == null) { // converter not found, try to lookup then - converter = lookup(key.getToType(), key.getFromType()); + converter = lookup(toType, fromType); if (converter != null) { - typeMappings.putIfAbsent(key, converter); + typeMappings.put(toType, fromType, converter); } } return converter; @@ -588,21 +498,16 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement if (fromType != null && !fromType.equals(Object.class)) { // lets try classes derived from this toType - Set<Map.Entry<TypeMapping, TypeConverter>> entries = typeMappings.entrySet(); - for (Map.Entry<TypeMapping, TypeConverter> entry : entries) { - TypeMapping key = entry.getKey(); - Class<?> aToType = key.getToType(); - if (toType.isAssignableFrom(aToType)) { - Class<?> aFromType = key.getFromType(); + TypeConverter converter = typeMappings.getFirst( + to -> toType.isAssignableFrom(to), // skip Object based we do them last - if (!aFromType.equals(Object.class) && aFromType.isAssignableFrom(fromType)) { - return entry.getValue(); - } - } + from -> !from.equals(Object.class) && from.isAssignableFrom(fromType)); + if (converter != null) { + return converter; } // lets test for Object based converters as last resort - TypeConverter converter = getTypeConverter(toType, Object.class); + converter = getTypeConverter(toType, Object.class); if (converter != null) { return converter; } @@ -614,10 +519,8 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement } public List<Class<?>[]> listAllTypeConvertersFromTo() { - List<Class<?>[]> answer = new ArrayList<>(typeMappings.size()); - for (TypeMapping mapping : typeMappings.keySet()) { - answer.add(new Class<?>[]{mapping.getFromType(), mapping.getToType()}); - } + List<Class<?>[]> answer = new ArrayList<>(); + typeMappings.forEach((k1, k2, v) -> answer.add(new Class<?>[]{k2, k1})); return answer; } @@ -626,7 +529,7 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement */ public void loadCoreTypeConverters() throws Exception { // load all the type converters from camel-core - CoreTypeConverterLoader core = new CoreTypeConverterLoader(); + CoreStaticTypeConverterLoader core = new CoreStaticTypeConverterLoader(); core.load(this); } @@ -654,6 +557,11 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement } protected TypeConversionException createTypeConversionException(Exchange exchange, Class<?> type, Object value, Throwable cause) { + if (cause instanceof TypeConversionException) { + if (((TypeConversionException) cause).getToType() == type) { + return (TypeConversionException) cause; + } + } Object body; // extract the body for logging which allows to limit the message body in the exception/stacktrace // and also can be used to turn off logging sensitive message data @@ -701,12 +609,17 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement // log utilization statistics when stopping, including mappings if (statistics.isStatisticsEnabled()) { String info = statistics.toString(); - info += String.format(" mappings[total=%s, misses=%s]", typeMappings.size(), misses.size()); + AtomicInteger misses = new AtomicInteger(); + typeMappings.forEach((k1, k2, v) -> { + if (v == MISS_CONVERTER) { + misses.incrementAndGet(); + } + }); + info += String.format(" mappings[total=%s, misses=%s]", typeMappings.size(), misses); log.info(info); } typeMappings.clear(); - misses.clear(); statistics.reset(); } @@ -775,75 +688,193 @@ public abstract class BaseTypeConverterRegistry extends ServiceSupport implement } /** - * Represents a mapping from one type (which can be null) to another + * Represents a fallback type converter */ - protected static final class TypeMapping { - private final Class<?> toType; - private final Class<?> fromType; - private final int hashCode; + protected static class FallbackTypeConverter { + private final boolean canPromote; + private final TypeConverter fallbackTypeConverter; - TypeMapping(Class<?> toType, Class<?> fromType) { - this.toType = toType; - this.fromType = fromType; + FallbackTypeConverter(TypeConverter fallbackTypeConverter, boolean canPromote) { + this.canPromote = canPromote; + this.fallbackTypeConverter = fallbackTypeConverter; + } - // pre calculate hashcode - int hash = toType.hashCode(); - if (fromType != null) { - hash *= 37 + fromType.hashCode(); - } - hashCode = hash; + public boolean isCanPromote() { + return canPromote; } - public Class<?> getFromType() { - return fromType; + public TypeConverter getFallbackTypeConverter() { + return fallbackTypeConverter; } + } - public Class<?> getToType() { - return toType; + @SuppressWarnings("unchecked") + protected static class DoubleMap<K1, K2, V> { + + static class Entry { + Object k1; + Object k2; + Object v; + Entry next; } - @Override - public boolean equals(Object object) { - if (object instanceof TypeMapping) { - TypeMapping that = (TypeMapping) object; - return this.fromType == that.fromType && this.toType == that.toType; + private Entry[] table; + private int mask; + + public DoubleMap(int size) { + table = new Entry[closedTableSize(size)]; + mask = table.length - 1; + } + + public V get(K1 k1, K2 k2) { + Entry[] table = this.table; + int mask = this.mask; + int index = smear(k1.hashCode() * 31 + k2.hashCode()) & mask; + for (Entry entry = table[index]; entry != null; entry = entry.next) { + if (k1 == entry.k1 && k2 == entry.k2) { + return (V) entry.v; + } + } + return null; + } + + public void forEach(TriConsumer<K1, K2, V> consumer) { + Entry[] table = this.table; + for (Entry entry : table) { + while (entry != null) { + consumer.accept((K1) entry.k1, (K2) entry.k2, (V) entry.v); + entry = entry.next; + } + } + } + + public boolean containsKey(K1 k1, K2 k2) { + Entry[] table = this.table; + int mask = this.mask; + int index = smear(k1.hashCode() * 31 + k2.hashCode()) & mask; + for (Entry entry = table[index]; entry != null; entry = entry.next) { + if (k1 == entry.k1 && k2 == entry.k2) { + return true; + } } return false; } - @Override - public int hashCode() { - return hashCode; + public synchronized void put(K1 k1, K2 k2, V v) { + Entry[] table = this.table; + int size = size() + 1; + int realSize = closedTableSize(size); + if (realSize <= table.length) { + realSize = table.length; + int index = smear(k1.hashCode() * 31 + k2.hashCode()) & (realSize - 1); + for (Entry oldEntry = table[index]; oldEntry != null; oldEntry = oldEntry.next) { + if (oldEntry.k1 == k1 && oldEntry.k2 == k2) { + oldEntry.v = v; + return; + } + } + Entry entry = new Entry(); + entry.k1 = k1; + entry.k2 = k2; + entry.v = v; + entry.next = table[index]; + table[index] = entry; + } else { + Entry[] newT = new Entry[realSize]; + int index = smear(k1.hashCode() * 31 + k2.hashCode()) & (realSize - 1); + Entry entry = new Entry(); + newT[index] = entry; + entry.k1 = k1; + entry.k2 = k2; + entry.v = v; + for (Entry oldEntry : table) { + while (oldEntry != null) { + if (k1 != oldEntry.k1 || k2 != oldEntry.k2) { + index = smear(oldEntry.k1.hashCode() * 31 + oldEntry.k2.hashCode()) & (realSize - 1); + Entry newEntry = new Entry(); + newEntry.k1 = oldEntry.k1; + newEntry.k2 = oldEntry.k2; + newEntry.v = oldEntry.v; + newEntry.next = newT[index]; + newT[index] = newEntry; + } + oldEntry = oldEntry.next; + } + } + this.table = newT; + this.mask = realSize - 1; + } } - @Override - public String toString() { - return "[" + fromType + "=>" + toType + "]"; + public synchronized boolean remove(K1 k1, K2 k2) { + Entry[] table = this.table; + int mask = this.mask; + int index = smear(k1.hashCode() * 31 + k2.hashCode()) & mask; + Entry prevEntry = null; + for (Entry oldEntry = table[index]; oldEntry != null; prevEntry = oldEntry, oldEntry = oldEntry.next) { + if (oldEntry.k1 == k1 && oldEntry.k2 == k2) { + if (prevEntry == null) { + table[index] = oldEntry.next; + } else { + prevEntry.next = oldEntry.next; + } + return true; + } + } + return false; } - public boolean isApplicable(Class<?> fromClass) { - return fromType.isAssignableFrom(fromClass); + public V getFirst(Predicate<K1> p1, Predicate<K2> p2) { + for (Entry entry : table) { + while (entry != null) { + if (p1.test((K1) entry.k1) && p2.test((K2) entry.k2)) { + return (V) entry.v; + } + entry = entry.next; + } + } + return null; } - } - /** - * Represents a fallback type converter - */ - protected static class FallbackTypeConverter { - private final boolean canPromote; - private final TypeConverter fallbackTypeConverter; + public int size() { + Entry[] table = this.table; + int n = 0; + if (table != null) { + for (Entry e : table) { + for (Entry c = e; c != null; c = c.next) { + n++; + } + } + } + return n; + } - FallbackTypeConverter(TypeConverter fallbackTypeConverter, boolean canPromote) { - this.canPromote = canPromote; - this.fallbackTypeConverter = fallbackTypeConverter; + public synchronized void clear() { + this.table = new Entry[table.length]; } - public boolean isCanPromote() { - return canPromote; + private static final double MAX_LOAD_FACTOR = 1.2; + private static final int MAX_TABLE_SIZE = 32768; + private static final int C1 = 0xcc9e2d51; + private static final int C2 = 0x1b873593; + + static int smear(int hashCode) { + return C2 * Integer.rotateLeft(hashCode * C1, 15); } - public TypeConverter getFallbackTypeConverter() { - return fallbackTypeConverter; + static int closedTableSize(int expectedEntries) { + // Get the recommended table size. + // Round down to the nearest power of 2. + expectedEntries = Math.max(expectedEntries, 2); + int tableSize = Integer.highestOneBit(expectedEntries); + // Check to make sure that we will not exceed the maximum load factor. + if (expectedEntries > (int) (MAX_LOAD_FACTOR * tableSize)) { + tableSize <<= 1; + return (tableSize > 0) ? tableSize : MAX_TABLE_SIZE; + } + return tableSize; } + } + } diff --git a/camel-core/src/main/java/org/apache/camel/impl/converter/CorePackageScanClassResolver.java b/camel-core/src/main/java/org/apache/camel/impl/converter/CorePackageScanClassResolver.java deleted file mode 100644 index 0ceb81d..0000000 --- a/camel-core/src/main/java/org/apache/camel/impl/converter/CorePackageScanClassResolver.java +++ /dev/null @@ -1,125 +0,0 @@ -/** - * 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. - */ -package org.apache.camel.impl.converter; - -import java.lang.annotation.Annotation; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; - -import org.apache.camel.component.bean.BeanConverter; -import org.apache.camel.component.file.GenericFileConverter; -import org.apache.camel.converter.AttachmentConverter; -import org.apache.camel.converter.CamelConverter; -import org.apache.camel.converter.CollectionConverter; -import org.apache.camel.converter.DateTimeConverter; -import org.apache.camel.converter.DurationConverter; -import org.apache.camel.converter.IOConverter; -import org.apache.camel.converter.NIOConverter; -import org.apache.camel.converter.ObjectConverter; -import org.apache.camel.converter.SQLConverter; -import org.apache.camel.converter.TimePatternConverter; -import org.apache.camel.converter.jaxp.DomConverter; -import org.apache.camel.converter.jaxp.StaxConverter; -import org.apache.camel.converter.jaxp.StreamSourceConverter; -import org.apache.camel.converter.jaxp.XmlConverter; -import org.apache.camel.converter.stream.StreamCacheConverter; -import org.apache.camel.spi.PackageScanClassResolver; -import org.apache.camel.spi.PackageScanFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A {@link org.apache.camel.spi.ClassResolver} which loads type converters - * from a hardcoded list of classes. - * <p/> - * <b>Important:</b> Whenever a new type converter class is added to camel-core - * then the class should be added to the list in this class. - * - * @see CoreTypeConverterLoader - */ -public class CorePackageScanClassResolver implements PackageScanClassResolver { - - protected final Logger log = LoggerFactory.getLogger(getClass()); - private final Set<ClassLoader> classLoaders = new LinkedHashSet<>(); - private final Set<Class<?>> converters = new LinkedHashSet<>(); - - public CorePackageScanClassResolver() { - converters.add(ObjectConverter.class); - converters.add(CollectionConverter.class); - converters.add(DateTimeConverter.class); - converters.add(SQLConverter.class); - converters.add(IOConverter.class); - converters.add(NIOConverter.class); - converters.add(StaxConverter.class); - converters.add(DomConverter.class); - converters.add(StreamSourceConverter.class); - converters.add(XmlConverter.class); - converters.add(CamelConverter.class); - converters.add(StreamCacheConverter.class); - converters.add(TimePatternConverter.class); - converters.add(FutureTypeConverter.class); - converters.add(BeanConverter.class); - converters.add(GenericFileConverter.class); - converters.add(DurationConverter.class); - converters.add(AttachmentConverter.class); - converters.add(UriTypeConverter.class); - } - - @Override - public Set<ClassLoader> getClassLoaders() { - // return a new set to avoid any concurrency issues in other runtimes such as OSGi - return Collections.unmodifiableSet(new LinkedHashSet<>(classLoaders)); - } - - @Override - public void addClassLoader(ClassLoader classLoader) { - classLoaders.add(classLoader); - } - - @Override - public Set<Class<?>> findAnnotated(Class<? extends Annotation> annotation, String... packageNames) { - return converters; - } - - @Override - public Set<Class<?>> findAnnotated(Set<Class<? extends Annotation>> annotations, String... packageNames) { - return converters; - } - - @Override - public Set<Class<?>> findImplementations(Class<?> parent, String... packageNames) { - // noop - return null; - } - - @Override - public Set<Class<?>> findByFilter(PackageScanFilter filter, String... packageNames) { - // noop - return null; - } - - @Override - public void addFilter(PackageScanFilter filter) { - // noop - } - - @Override - public void removeFilter(PackageScanFilter filter) { - // noop - } -} diff --git a/camel-core/src/main/java/org/apache/camel/impl/converter/CoreTypeConverterLoader.java b/camel-core/src/main/java/org/apache/camel/impl/converter/CoreTypeConverterLoader.java deleted file mode 100644 index 06b7353..0000000 --- a/camel-core/src/main/java/org/apache/camel/impl/converter/CoreTypeConverterLoader.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * 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. - */ -package org.apache.camel.impl.converter; - -import java.io.IOException; - -/** - * Will load all type converters from camel-core without classpath scanning, which makes - * it much faster. - * <p/> - * The {@link CorePackageScanClassResolver} contains a hardcoded list of the type converter classes to load. - */ -public class CoreTypeConverterLoader extends AnnotationTypeConverterLoader { - - public CoreTypeConverterLoader() { - super(new CorePackageScanClassResolver()); - } - - @Override - protected String[] findPackageNames() throws IOException { - // this method doesn't change the behavior of the CorePackageScanClassResolver - return new String[]{"org.apache.camel.converter", "org.apache.camel.component.bean", "org.apache.camel.component.file"}; - } - -} diff --git a/camel-core/src/main/java/org/apache/camel/impl/converter/EnumTypeConverter.java b/camel-core/src/main/java/org/apache/camel/impl/converter/EnumTypeConverter.java index 095bef8..bdcaaa6 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/converter/EnumTypeConverter.java +++ b/camel-core/src/main/java/org/apache/camel/impl/converter/EnumTypeConverter.java @@ -28,8 +28,12 @@ import org.apache.camel.support.TypeConverterSupport; */ public class EnumTypeConverter extends TypeConverterSupport { - @SuppressWarnings("unchecked") public <T> T convertTo(Class<T> type, Exchange exchange, Object value) { + return EnumTypeConverter.doConvertTo(type, exchange, value); + } + + @SuppressWarnings("unchecked") + public static <T> T doConvertTo(Class<T> type, Exchange exchange, Object value) { if (type.isEnum()) { String text = value.toString(); Class<Enum> enumClass = (Class<Enum>) type; diff --git a/camel-core/src/main/java/org/apache/camel/impl/converter/FutureTypeConverter.java b/camel-core/src/main/java/org/apache/camel/impl/converter/FutureTypeConverter.java index 38032c9..b2f2004 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/converter/FutureTypeConverter.java +++ b/camel-core/src/main/java/org/apache/camel/impl/converter/FutureTypeConverter.java @@ -64,7 +64,7 @@ public final class FutureTypeConverter extends TypeConverterSupport { if (future.isCancelled()) { // return void to indicate its not possible to convert at this time - return (T) Void.TYPE; + return (T) MISS_VALUE; } // do some trace logging as the get is blocking until the response is ready @@ -75,7 +75,7 @@ public final class FutureTypeConverter extends TypeConverterSupport { if (body == null) { // return void to indicate its not possible to convert at this time - return (T) Void.TYPE; + return (T) MISS_VALUE; } // maybe from is already the type we want diff --git a/camel-core/src/main/java/org/apache/camel/impl/converter/OptimisedTypeConverter.java b/camel-core/src/main/java/org/apache/camel/impl/converter/OptimisedTypeConverter.java deleted file mode 100644 index df774f8..0000000 --- a/camel-core/src/main/java/org/apache/camel/impl/converter/OptimisedTypeConverter.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * 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. - */ -package org.apache.camel.impl.converter; - -import java.util.Map; - -import org.apache.camel.Exchange; -import org.apache.camel.converter.IOConverterOptimised; -import org.apache.camel.converter.NIOConverterOptimised; -import org.apache.camel.converter.ObjectConverterOptimised; -import org.apache.camel.converter.TimePatternConverterOptimised; - -/** - * Optimised type converter for performing the most common conversions using the type converters - * from camel-core. - * <p/> - * The most commonly used type converters has been optimised to be invoked in a faster by - * using direct method calls instead of a calling via a reflection method call via - * {@link InstanceMethodTypeConverter} or {@link StaticMethodTypeConverter}. - * In addition the performance is faster because the type converter is not looked up - * via a key in the type converter {@link Map}; which requires creating a new object - * as they key and perform the map lookup. The caveat is that for any new type converter - * to be included it must be manually added by adding the nessasary source code to the - * optimised classes such as {@link ObjectConverterOptimised}. - */ -public class OptimisedTypeConverter { - - private final EnumTypeConverter enumTypeConverter = new EnumTypeConverter(); - - /** - * Attempts to convert the value to the given type - * - * @param type the type to convert to - * @param exchange the exchange, may be <tt>null</tt> - * @param value the value - * @return the converted value, or <tt>null</tt> if no optimised core type converter exists to convert - */ - public Object convertTo(final Class<?> type, final Exchange exchange, final Object value) throws Exception { - Object answer; - - // use the optimised type converters and use them in the most commonly used order - - // we need time pattern first as it can do a special String -> long conversion which should happen first - answer = TimePatternConverterOptimised.convertTo(type, exchange, value); - if (answer == null) { - answer = ObjectConverterOptimised.convertTo(type, exchange, value); - } - if (answer == null) { - answer = IOConverterOptimised.convertTo(type, exchange, value); - } - if (answer == null) { - answer = NIOConverterOptimised.convertTo(type, exchange, value); - } - - // specially optimised for enums - if (answer == null && type.isEnum()) { - answer = enumTypeConverter.convertTo(type, exchange, value); - } - - return answer; - } - -} diff --git a/camel-core/src/main/java/org/apache/camel/impl/converter/ToStringTypeConverter.java b/camel-core/src/main/java/org/apache/camel/impl/converter/ToStringTypeConverter.java index 2fa600d..c15f52f 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/converter/ToStringTypeConverter.java +++ b/camel-core/src/main/java/org/apache/camel/impl/converter/ToStringTypeConverter.java @@ -36,22 +36,22 @@ public class ToStringTypeConverter extends TypeConverterSupport { // should not try to convert Message if (Message.class.isAssignableFrom(value.getClass())) { - return (T) Void.TYPE; + return (T) MISS_VALUE; } // should not try to convert future if (Future.class.isAssignableFrom(value.getClass())) { - return (T) Void.TYPE; + return (T) MISS_VALUE; } // should not try to convert bean invocations if (BeanInvocation.class.isAssignableFrom(value.getClass())) { - return (T) Void.TYPE; + return (T) MISS_VALUE; } // should not try to convert files if (WrappedFile.class.isAssignableFrom(value.getClass())) { - return (T) Void.TYPE; + return (T) MISS_VALUE; } if (toType.equals(String.class)) { diff --git a/camel-core/src/test/java/org/apache/camel/builder/SimpleBuilderTest.java b/camel-core/src/test/java/org/apache/camel/builder/SimpleBuilderTest.java index 7e7d4de..7b29975 100644 --- a/camel-core/src/test/java/org/apache/camel/builder/SimpleBuilderTest.java +++ b/camel-core/src/test/java/org/apache/camel/builder/SimpleBuilderTest.java @@ -69,7 +69,7 @@ public class SimpleBuilderTest extends TestSupport { SimpleBuilder.simple("${body}", int.class).evaluate(exchange, Object.class); fail("Should have thrown exception"); } catch (TypeConversionException e) { - assertIsInstanceOf(NumberFormatException.class, e.getCause()); + assertIsInstanceOf(NumberFormatException.class, e.getCause().getCause()); } assertEquals(true, SimpleBuilder.simple("${header.cool}", boolean.class).evaluate(exchange, Object.class)); diff --git a/camel-core/src/test/java/org/apache/camel/builder/xml/XPathFeatureTest.java b/camel-core/src/test/java/org/apache/camel/builder/xml/XPathFeatureTest.java index d745d8e..50ff16b 100644 --- a/camel-core/src/test/java/org/apache/camel/builder/xml/XPathFeatureTest.java +++ b/camel-core/src/test/java/org/apache/camel/builder/xml/XPathFeatureTest.java @@ -58,8 +58,7 @@ public class XPathFeatureTest extends ContextTestSupport { xpath("/").stringResult().evaluate(createExchange(XML_DATA)); fail("Expect an Exception here"); } catch (TypeConversionException ex) { - assertTrue("Get a wrong exception cause.", ex.getCause() instanceof RuntimeCamelException); - assertTrue("Get a wrong exception cause.", ex.getCause().getCause() instanceof FileNotFoundException); + assertTrue("Get a wrong exception cause.", ex.getCause() instanceof FileNotFoundException); } finally { System.clearProperty(DOM_BUILDER_FACTORY_FEATURE + ":" + "http://xml.org/sax/features/external-general-entities"); @@ -83,8 +82,7 @@ public class XPathFeatureTest extends ContextTestSupport { xpath("/").stringResult().evaluate(createExchange(XML_DATA_INVALID)); fail("Expect an Exception here"); } catch (TypeConversionException ex) { - assertTrue("Get a wrong exception cause.", ex.getCause() instanceof RuntimeCamelException); - assertTrue("Get a wrong exception cause.", ex.getCause().getCause() instanceof SAXParseException); + assertTrue("Get a wrong exception cause.", ex.getCause() instanceof SAXParseException); } } diff --git a/camel-core/src/test/java/org/apache/camel/converter/ConverterBenchmark.java b/camel-core/src/test/java/org/apache/camel/converter/ConverterBenchmark.java new file mode 100644 index 0000000..3967a9c --- /dev/null +++ b/camel-core/src/test/java/org/apache/camel/converter/ConverterBenchmark.java @@ -0,0 +1,160 @@ +/** + * 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. + */ +package org.apache.camel.converter; + +import java.net.URI; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.apache.camel.LoggingLevel; +import org.apache.camel.impl.DefaultClassResolver; +import org.apache.camel.impl.DefaultFactoryFinderResolver; +import org.apache.camel.impl.DefaultPackageScanClassResolver; +import org.apache.camel.impl.converter.DefaultTypeConverter; +import org.apache.camel.spi.FactoryFinder; +import org.apache.camel.spi.Injector; +import org.apache.camel.util.ReflectionInjector; +import org.junit.Ignore; +import org.junit.Test; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.openjdk.jmh.runner.options.TimeValue; + +public class ConverterBenchmark { + + @Ignore + @Test + public void launchBenchmark() throws Exception { + + Options opt = new OptionsBuilder() + // Specify which benchmarks to run. + // You can be more specific if you'd like to run only one benchmark per test. + .include(this.getClass().getName() + ".*") + // Set the following options as needed + .mode (Mode.AverageTime) + .timeUnit(TimeUnit.MICROSECONDS) + .warmupTime(TimeValue.seconds(2)) + .warmupIterations(5) + .measurementTime(TimeValue.seconds(1)) + .measurementIterations(5) + .threads(2) + .forks(1) + .shouldFailOnError(true) + .shouldDoGC(true) + //.jvmArgs("-XX:+UnlockDiagnosticVMOptions", "-XX:+PrintInlining") + //.addProfiler(WinPerfAsmProfiler.class) + .build(); + + new Runner(opt).run(); + } + + // The JMH samples are the best documentation for how to use it + // http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/ + @State (Scope.Thread) + public static class BenchmarkState + { + DefaultPackageScanClassResolver packageScanClassResolver; + Injector injector; + FactoryFinder factoryFinder; + DefaultTypeConverter converter; + + @Setup (Level.Trial) + public void initialize() throws Exception { + packageScanClassResolver = new DefaultPackageScanClassResolver(); + injector = new ReflectionInjector(); + factoryFinder = new DefaultFactoryFinderResolver().resolveDefaultFactoryFinder(new DefaultClassResolver()); + converter = new DefaultTypeConverter(packageScanClassResolver, injector, factoryFinder, true); + converter.start(); + } + } + + @Benchmark + public void benchmarkLoadTime(BenchmarkState state, Blackhole bh) throws Exception { + + DefaultPackageScanClassResolver packageScanClassResolver = state.packageScanClassResolver; + Injector injector = state.injector; + FactoryFinder factoryFinder = state.factoryFinder; + + DefaultTypeConverter converter = new DefaultTypeConverter(packageScanClassResolver, injector, factoryFinder, true); + converter.start(); + bh.consume(converter); + } + + @Benchmark + public void benchmarkConversionTimeEnum(BenchmarkState state, Blackhole bh) { + DefaultTypeConverter converter = state.converter; + + for (int i = 0; i < 1000; i++) + bh.consume(converter.convertTo(LoggingLevel.class, "DEBUG")); + } + + @Benchmark + public void benchmarkConversionIntToLong(BenchmarkState state, Blackhole bh) { + DefaultTypeConverter converter = state.converter; + + for (int i = 0; i < 1000; i++) + bh.consume(converter.convertTo(Long.class, 3)); + } + + @Benchmark + public void benchmarkConversionStringToChar(BenchmarkState state, Blackhole bh) { + DefaultTypeConverter converter = state.converter; + + for (int i = 0; i < 1000; i++) + bh.consume(converter.convertTo(char[].class, "Hello world")); + } + + @Benchmark + public void benchmarkConversionStringToURI(BenchmarkState state, Blackhole bh) { + DefaultTypeConverter converter = state.converter; + + for (int i = 0; i < 1000; i++) + bh.consume(converter.convertTo(URI.class, "uri:foo")); + } + + @Benchmark + public void benchmarkConversionListToStringArray(BenchmarkState state, Blackhole bh) { + DefaultTypeConverter converter = state.converter; + + for (int i = 0; i < 1000; i++) + bh.consume(converter.convertTo(String[].class, Arrays.asList("DEBUG"))); + } + + @Ignore + @Test + public void testConvertEnumPerfs() throws Exception { + Blackhole bh = new Blackhole("Today's password is swordfish. I understand instantiating Blackholes directly is dangerous."); + BenchmarkState state = new BenchmarkState(); + state.initialize(); + doTest(bh, state); + } + + private void doTest(Blackhole bh, BenchmarkState state) { + DefaultTypeConverter converter = state.converter; + for (int i = 0; i < 1000000; i++) { + bh.consume(converter.convertTo(Long.class, 3)); + } + } +} diff --git a/camel-core/src/test/java/org/apache/camel/converter/ConverterTest.java b/camel-core/src/test/java/org/apache/camel/converter/ConverterTest.java index 18f48e8..d43b545 100644 --- a/camel-core/src/test/java/org/apache/camel/converter/ConverterTest.java +++ b/camel-core/src/test/java/org/apache/camel/converter/ConverterTest.java @@ -43,11 +43,14 @@ import org.apache.camel.support.ServiceHelper; import org.apache.camel.util.ReflectionInjector; import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ConverterTest extends Assert { + private static final Logger LOG = LoggerFactory.getLogger(ConverterTest.class); protected TypeConverter converter = new DefaultTypeConverter(new DefaultPackageScanClassResolver(), @@ -262,4 +265,21 @@ public class ConverterTest extends Assert { } } + @Test + public void testNullToBoolean() throws Exception { + boolean b = converter.convertTo(boolean.class, null); + assertFalse(b); + } + + @Test + public void testNullToInt() throws Exception { + int i = converter.convertTo(int.class, null); + assertEquals(0, i); + } + + @Test + public void testToInt() throws Exception { + int i = converter.convertTo(int.class, "0"); + assertEquals(0, i); + } } diff --git a/camel-core/src/test/java/org/apache/camel/converter/DurationConverterTest.java b/camel-core/src/test/java/org/apache/camel/converter/DurationConverterTest.java index 86ad5f0..193a512 100644 --- a/camel-core/src/test/java/org/apache/camel/converter/DurationConverterTest.java +++ b/camel-core/src/test/java/org/apache/camel/converter/DurationConverterTest.java @@ -42,7 +42,7 @@ public class DurationConverterTest extends ContextTestSupport { context.getTypeConverter().convertTo(long.class, duration); fail("Should throw exception"); } catch (TypeConversionException e) { - assertIsInstanceOf(ArithmeticException.class, e.getCause().getCause()); + assertIsInstanceOf(ArithmeticException.class, e.getCause()); } } diff --git a/camel-core/src/test/java/org/apache/camel/converter/ObjectConverterTest.java b/camel-core/src/test/java/org/apache/camel/converter/ObjectConverterTest.java index be5e00f..f7c3275 100644 --- a/camel-core/src/test/java/org/apache/camel/converter/ObjectConverterTest.java +++ b/camel-core/src/test/java/org/apache/camel/converter/ObjectConverterTest.java @@ -53,14 +53,11 @@ public class ObjectConverterTest extends Assert { assertEquals(Byte.valueOf("4"), ObjectConverter.toByte(Byte.valueOf("4"))); assertEquals(Byte.valueOf("4"), ObjectConverter.toByte(Integer.valueOf("4"))); assertEquals(Byte.valueOf("4"), ObjectConverter.toByte("4")); - assertEquals(null, ObjectConverter.toByte(new Date())); } @Test public void testToClass() { - assertEquals(String.class, ObjectConverter.toClass(String.class, null)); assertEquals(String.class, ObjectConverter.toClass("java.lang.String", null)); - assertEquals(null, ObjectConverter.toClass(new Integer(4), null)); assertEquals(null, ObjectConverter.toClass("foo.Bar", null)); } @@ -69,9 +66,8 @@ public class ObjectConverterTest extends Assert { assertEquals(Short.valueOf("4"), ObjectConverter.toShort(Short.valueOf("4"))); assertEquals(Short.valueOf("4"), ObjectConverter.toShort(Integer.valueOf("4"))); assertEquals(Short.valueOf("4"), ObjectConverter.toShort("4")); - assertEquals(null, ObjectConverter.toShort(new Date())); - assertEquals(Short.valueOf("0"), ObjectConverter.toShort(Double.NaN)); - assertEquals(Short.valueOf("0"), ObjectConverter.toShort(Float.NaN)); + assertEquals(null, ObjectConverter.toShort(Double.NaN)); + assertEquals(null, ObjectConverter.toShort(Float.NaN)); assertEquals(Short.valueOf("4"), ObjectConverter.toShort(Short.valueOf("4"))); } @@ -80,9 +76,8 @@ public class ObjectConverterTest extends Assert { assertEquals(Integer.valueOf("4"), ObjectConverter.toInteger(Integer.valueOf("4"))); assertEquals(Integer.valueOf("4"), ObjectConverter.toInteger(Long.valueOf("4"))); assertEquals(Integer.valueOf("4"), ObjectConverter.toInteger("4")); - assertEquals(null, ObjectConverter.toInteger(new Date())); - assertEquals(Integer.valueOf("0"), ObjectConverter.toInteger(Double.NaN)); - assertEquals(Integer.valueOf("0"), ObjectConverter.toInteger(Float.NaN)); + assertEquals(null, ObjectConverter.toInteger(Double.NaN)); + assertEquals(null, ObjectConverter.toInteger(Float.NaN)); assertEquals(Integer.valueOf("4"), ObjectConverter.toInteger(Integer.valueOf("4"))); } @@ -91,9 +86,8 @@ public class ObjectConverterTest extends Assert { assertEquals(Long.valueOf("4"), ObjectConverter.toLong(Long.valueOf("4"))); assertEquals(Long.valueOf("4"), ObjectConverter.toLong(Integer.valueOf("4"))); assertEquals(Long.valueOf("4"), ObjectConverter.toLong("4")); - assertEquals(null, ObjectConverter.toLong(new Date())); - assertEquals(Long.valueOf("0"), ObjectConverter.toLong(Double.NaN)); - assertEquals(Long.valueOf("0"), ObjectConverter.toLong(Float.NaN)); + assertEquals(null, ObjectConverter.toLong(Double.NaN)); + assertEquals(null, ObjectConverter.toLong(Float.NaN)); assertEquals(Long.valueOf("4"), ObjectConverter.toLong(Long.valueOf("4"))); } @@ -102,7 +96,6 @@ public class ObjectConverterTest extends Assert { assertEquals(Float.valueOf("4"), ObjectConverter.toFloat(Float.valueOf("4"))); assertEquals(Float.valueOf("4"), ObjectConverter.toFloat(Integer.valueOf("4"))); assertEquals(Float.valueOf("4"), ObjectConverter.toFloat("4")); - assertEquals(null, ObjectConverter.toFloat(new Date())); assertEquals((Float) Float.NaN, ObjectConverter.toFloat(Double.NaN)); assertEquals((Float) Float.NaN, ObjectConverter.toFloat(Float.NaN)); assertEquals(Float.valueOf("4"), ObjectConverter.toFloat(Float.valueOf("4"))); @@ -113,7 +106,6 @@ public class ObjectConverterTest extends Assert { assertEquals(Double.valueOf("4"), ObjectConverter.toDouble(Double.valueOf("4"))); assertEquals(Double.valueOf("4"), ObjectConverter.toDouble(Integer.valueOf("4"))); assertEquals(Double.valueOf("4"), ObjectConverter.toDouble("4")); - assertEquals(null, ObjectConverter.toDouble(new Date())); assertEquals((Double) Double.NaN, ObjectConverter.toDouble(Double.NaN)); assertEquals((Double) Double.NaN, ObjectConverter.toDouble(Float.NaN)); assertEquals(Double.valueOf("4"), ObjectConverter.toDouble(Double.valueOf("4"))); @@ -126,8 +118,8 @@ public class ObjectConverterTest extends Assert { assertEquals(BigInteger.valueOf(4), ObjectConverter.toBigInteger("4")); assertEquals(BigInteger.valueOf(123456789L), ObjectConverter.toBigInteger("123456789")); assertEquals(null, ObjectConverter.toBigInteger(new Date())); - assertEquals(BigInteger.valueOf(0), ObjectConverter.toBigInteger(Double.NaN)); - assertEquals(BigInteger.valueOf(0), ObjectConverter.toBigInteger(Float.NaN)); + assertEquals(null, ObjectConverter.toBigInteger(Double.NaN)); + assertEquals(null, ObjectConverter.toBigInteger(Float.NaN)); assertEquals(BigInteger.valueOf(4), ObjectConverter.toBigInteger(Long.valueOf("4"))); assertEquals(new BigInteger("14350442579497085228"), ObjectConverter.toBigInteger("14350442579497085228")); } diff --git a/camel-util/src/main/java/org/apache/camel/util/ObjectHelper.java b/camel-util/src/main/java/org/apache/camel/util/ObjectHelper.java index c4ee55b..f9acd94 100644 --- a/camel-util/src/main/java/org/apache/camel/util/ObjectHelper.java +++ b/camel-util/src/main/java/org/apache/camel/util/ObjectHelper.java @@ -1080,6 +1080,9 @@ public final class ObjectHelper { return null; } + private static final Float FLOAT_NAN = Float.NaN; + private static final Double DOUBLE_NAN = Double.NaN; + /** * Is the given value a numeric NaN type * @@ -1087,11 +1090,8 @@ public final class ObjectHelper { * @return <tt>true</tt> if its a {@link Float#NaN} or {@link Double#NaN}. */ public static boolean isNaN(Object value) { - if (value == null || !(value instanceof Number)) { - return false; - } - // value must be a number - return value.equals(Float.NaN) || value.equals(Double.NaN); + return (value instanceof Number) + && (FLOAT_NAN.equals(value) || DOUBLE_NAN.equals(value)); } } diff --git a/components/camel-cxf/src/main/java/org/apache/camel/component/cxf/converter/CxfConverter.java b/components/camel-cxf/src/main/java/org/apache/camel/component/cxf/converter/CxfConverter.java index 4b33df0..323e028 100644 --- a/components/camel-cxf/src/main/java/org/apache/camel/component/cxf/converter/CxfConverter.java +++ b/components/camel-cxf/src/main/java/org/apache/camel/component/cxf/converter/CxfConverter.java @@ -36,6 +36,8 @@ import org.apache.camel.spi.TypeConverterRegistry; import org.apache.camel.support.ExchangeHelper; import org.apache.cxf.message.MessageContentsList; +import static org.apache.camel.TypeConverter.MISS_VALUE; + /** * The <a href="http://camel.apache.org/type-converter.html">Type Converters</a> * for CXF related types' converting . @@ -160,7 +162,7 @@ public final class CxfConverter { } } // return void to indicate its not possible to convert at this time - return (T) Void.TYPE; + return (T) MISS_VALUE; } // CXF-RS Response class @@ -174,7 +176,7 @@ public final class CxfConverter { } // return void to indicate its not possible to convert at this time - return (T) Void.TYPE; + return (T) MISS_VALUE; } return null; diff --git a/components/camel-cxf/src/main/java/org/apache/camel/component/cxf/converter/CxfPayloadConverter.java b/components/camel-cxf/src/main/java/org/apache/camel/component/cxf/converter/CxfPayloadConverter.java index 2e4da69..9bc96d7 100644 --- a/components/camel-cxf/src/main/java/org/apache/camel/component/cxf/converter/CxfPayloadConverter.java +++ b/components/camel-cxf/src/main/java/org/apache/camel/component/cxf/converter/CxfPayloadConverter.java @@ -46,6 +46,8 @@ import org.apache.camel.spi.TypeConverterRegistry; import org.apache.cxf.staxutils.StaxSource; import org.apache.cxf.staxutils.StaxUtils; +import static org.apache.camel.TypeConverter.MISS_VALUE; + @Converter public final class CxfPayloadConverter { private static XmlConverter xml = new XmlConverter(); @@ -179,10 +181,10 @@ public final class CxfPayloadConverter { } } catch (RuntimeCamelException e) { // the internal conversion to XML can throw an exception if the content is not XML - // ignore this and return Void.TYPE to indicate that we cannot convert this + // ignore this and return MISS_VALUE to indicate that we cannot convert this } // no we could not do it currently - return (T) Void.TYPE; + return (T) MISS_VALUE; } // Convert a CxfPayload into something else if (CxfPayload.class.isAssignableFrom(value.getClass())) { @@ -234,7 +236,7 @@ public final class CxfPayloadConverter { Object result = tc.convertTo(type, exchange, cxfPayloadToNodeList((CxfPayload<?>) value, exchange)); if (result == null) { // no we could not do it currently, and we just abort the convert here - return (T) Void.TYPE; + return (T) MISS_VALUE; } else { return (T) result; } @@ -249,12 +251,12 @@ public final class CxfPayloadConverter { return tc.convertTo(type, exchange, nodeList.item(0)); } else { // no we could not do it currently - return (T) Void.TYPE; + return (T) MISS_VALUE; } } else { if (size == 0) { // empty size so we cannot convert - return (T) Void.TYPE; + return (T) MISS_VALUE; } } } diff --git a/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecResultConverter.java b/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecResultConverter.java index c49dceb..ca9723f 100644 --- a/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecResultConverter.java +++ b/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecResultConverter.java @@ -30,6 +30,8 @@ import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.camel.TypeConverter.MISS_VALUE; + /** * Default converters for {@link ExecResult}. For details how to extend the * converters check out <a @@ -96,7 +98,7 @@ public final class ExecResultConverter { } else { // use Void to indicate we cannot convert it // (prevents Camel from using a fallback converter which may convert a String from the instance name) - return (T) Void.TYPE; + return (T) MISS_VALUE; } } diff --git a/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyConverter.java b/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyConverter.java index 112db0a..e382ab8 100644 --- a/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyConverter.java +++ b/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyConverter.java @@ -23,6 +23,8 @@ import org.apache.camel.spi.TypeConverterRegistry; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; +import static org.apache.camel.TypeConverter.MISS_VALUE; + @Converter public final class JettyConverter { @@ -41,7 +43,7 @@ public final class JettyConverter { if (value != null) { // should not try to convert Request as its not possible if (Request.class.isAssignableFrom(value.getClass())) { - return (T) Void.TYPE; + return (T) MISS_VALUE; } } diff --git a/tooling/apt/src/main/java/org/apache/camel/tools/apt/ConverterProcessor.java b/tooling/apt/src/main/java/org/apache/camel/tools/apt/ConverterProcessor.java index 43232a0..1757f21 100644 --- a/tooling/apt/src/main/java/org/apache/camel/tools/apt/ConverterProcessor.java +++ b/tooling/apt/src/main/java/org/apache/camel/tools/apt/ConverterProcessor.java @@ -21,9 +21,12 @@ import java.io.FileOutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; +import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; @@ -33,6 +36,8 @@ import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -50,20 +55,25 @@ public class ConverterProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { try { - if (this.processingEnv.getElementUtils().getTypeElement("org.apache.camel.impl.converter.CoreFallbackConverter") != null) { + if (roundEnv.processingOver()) { return false; } - if (roundEnv.processingOver()) { + if (this.processingEnv.getElementUtils().getTypeElement("org.apache.camel.impl.converter.CoreStaticTypeConverterLoader") != null) { + return false; + } + + // We're in tests, do not generate anything + if (this.processingEnv.getElementUtils().getTypeElement("org.apache.camel.converter.ObjectConverter") == null) { return false; } Comparator<TypeMirror> comparator = (o1, o2) -> processingEnv.getTypeUtils().isAssignable(o1, o2) ? -1 : processingEnv.getTypeUtils().isAssignable(o2, o1) ? +1 : o1.toString().compareTo(o2.toString()); - Map<String, Map<TypeMirror, ExecutableElement>> converters = new HashMap<>(); - TypeElement annotationType = this.processingEnv.getElementUtils().getTypeElement("org.apache.camel.Converter"); - for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) { + Map<String, Map<TypeMirror, ExecutableElement>> converters = new TreeMap<>(); + TypeElement converterAnnotationType = this.processingEnv.getElementUtils().getTypeElement("org.apache.camel.Converter"); + for (Element element : roundEnv.getElementsAnnotatedWith(converterAnnotationType)) { if (element.getKind() == ElementKind.METHOD) { ExecutableElement ee = (ExecutableElement) element; TypeMirror to = ee.getReturnType(); @@ -81,57 +91,127 @@ public class ConverterProcessor extends AbstractProcessor { converters.computeIfAbsent(toString(to), c -> new TreeMap<>(comparator)).put(from, ee); } } - - // We're in tests, do not generate anything - if (this.processingEnv.getElementUtils().getTypeElement("org.apache.camel.converter.ObjectConverter") == null) { - return false; + TypeElement fallbackAnnotationType = this.processingEnv.getElementUtils().getTypeElement("org.apache.camel.FallbackConverter"); + List<ExecutableElement> fallbackConverters = new ArrayList<>(); + for (Element element : roundEnv.getElementsAnnotatedWith(fallbackAnnotationType)) { + if (element.getKind() == ElementKind.METHOD) { + ExecutableElement ee = (ExecutableElement) element; + fallbackConverters.add(ee); + } } String p = "org.apache.camel.impl.converter"; - String c = "CoreFallbackConverter"; + String c = "CoreStaticTypeConverterLoader"; JavaFileObject jfo = processingEnv.getFiler().createSourceFile(p + "." + c); Set<String> converterClasses = new LinkedHashSet<>(); try (Writer writer = jfo.openWriter()) { writer.append("package ").append(p).append(";\n"); writer.append("\n"); - writer.append("import org.apache.camel.support.TypeConverterSupport;\n"); writer.append("import org.apache.camel.Exchange;\n"); writer.append("import org.apache.camel.TypeConversionException;\n"); + writer.append("import org.apache.camel.TypeConverterLoaderException;\n"); + writer.append("import org.apache.camel.spi.TypeConverterLoader;\n"); + writer.append("import org.apache.camel.spi.TypeConverterRegistry;\n"); + writer.append("import org.apache.camel.support.TypeConverterSupport;\n"); writer.append("\n"); writer.append("@SuppressWarnings(\"unchecked\")\n"); - writer.append("public class ").append(c).append(" extends TypeConverterSupport {\n"); + writer.append("public class ").append(c).append(" implements TypeConverterLoader {\n"); + writer.append("\n"); + writer.append(" static abstract class SimpleTypeConverter extends TypeConverterSupport {\n"); + writer.append(" private final boolean allowNull;\n"); writer.append("\n"); - writer.append(" public <T> T convertTo(Class<T> type, Exchange exchange, Object value) throws TypeConversionException {\n"); - writer.append(" try {\n"); - writer.append(" return (T) doConvert(type, exchange, value);\n"); - writer.append(" } catch (TypeConversionException e) {\n"); - writer.append(" throw e;\n"); - writer.append(" } catch (Exception e) {\n"); - writer.append(" throw new TypeConversionException(value, type, e);\n"); + writer.append(" public SimpleTypeConverter(boolean allowNull) {\n"); + writer.append(" this.allowNull = allowNull;\n"); writer.append(" }\n"); - writer.append(" }\n"); writer.append("\n"); - writer.append(" private Object doConvert(Class<?> type, Exchange exchange, Object value) throws Exception {\n"); - writer.append(" switch (type.getName()) {\n"); + writer.append(" @Override\n"); + writer.append(" public boolean allowNull() {\n"); + writer.append(" return allowNull;\n"); + writer.append(" }\n"); + writer.append("\n"); + writer.append(" @Override\n"); + writer.append(" public <T> T convertTo(Class<T> type, Exchange exchange, Object value) throws TypeConversionException {\n"); + writer.append(" try {\n"); + writer.append(" return (T) doConvert(exchange, value);\n"); + writer.append(" } catch (TypeConversionException e) {\n"); + writer.append(" throw e;\n"); + writer.append(" } catch (Exception e) {\n"); + writer.append(" throw new TypeConversionException(value, type, e);\n"); + writer.append(" }\n"); + writer.append(" }\n"); + writer.append(" protected abstract Object doConvert(Exchange exchange, Object value) throws Exception;\n"); + writer.append(" };\n"); + writer.append("\n"); + writer.append(" @Override\n"); + writer.append(" public void load(TypeConverterRegistry registry) throws TypeConverterLoaderException {\n"); + for (Map.Entry<String, Map<TypeMirror, ExecutableElement>> to : converters.entrySet()) { - writer.append(" case \"").append(to.getKey()).append("\": {\n"); for (Map.Entry<TypeMirror, ExecutableElement> from : to.getValue().entrySet()) { - String name = toString(from.getKey()); - if ("java.lang.Object".equals(name)) { - writer.append(" if (value != null) {\n"); - } else { - writer.append(" if (value instanceof ").append(name).append(") {\n"); + boolean allowNull = false; + for (AnnotationMirror ann : from.getValue().getAnnotationMirrors()) { + if (ann.getAnnotationType().asElement() == converterAnnotationType) { + for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : ann.getElementValues().entrySet()) { + switch (entry.getKey().getSimpleName().toString()) { + case "allowNull": + allowNull = (Boolean) entry.getValue().getValue(); + break; + default: + throw new IllegalStateException(); + } + } + } + } + writer.append(" registry.addTypeConverter(").append(to.getKey()).append(".class").append(", ") + .append(toString(from.getKey())).append(".class, new SimpleTypeConverter(") + .append(Boolean.toString(allowNull)).append(") {\n"); + writer.append(" @Override\n"); + writer.append(" public Object doConvert(Exchange exchange, Object value) throws Exception {\n"); + writer.append(" return ").append(toJava(from.getValue(), converterClasses)).append(";\n"); + writer.append(" }\n"); + writer.append(" });\n"); + } + } + + for (ExecutableElement ee : fallbackConverters) { + boolean allowNull = false; + boolean canPromote = false; + for (AnnotationMirror ann : ee.getAnnotationMirrors()) { + if (ann.getAnnotationType().asElement() == fallbackAnnotationType) { + for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : ann.getElementValues().entrySet()) { + switch (entry.getKey().getSimpleName().toString()) { + case "allowNull": + allowNull = (Boolean) entry.getValue().getValue(); + break; + case "canPromote": + canPromote = (Boolean) entry.getValue().getValue(); + break; + default: + throw new IllegalStateException(); + } + } } - writer.append(" return ").append(toJava(from.getValue(), converterClasses)).append(";\n"); - writer.append(" }\n"); } - writer.append(" break;\n"); + writer.append(" registry.addFallbackTypeConverter(new TypeConverterSupport() {\n"); + writer.append(" @Override\n"); + writer.append(" public boolean allowNull() {\n"); + writer.append(" return ").append(Boolean.toString(allowNull)).append(";\n"); writer.append(" }\n"); + writer.append(" @Override\n"); + writer.append(" public <T> T convertTo(Class<T> type, Exchange exchange, Object value) throws TypeConversionException {\n"); + writer.append(" try {\n"); + writer.append(" return (T) ").append(toJavaFallback(ee, converterClasses)).append(";\n"); + writer.append(" } catch (TypeConversionException e) {\n"); + writer.append(" throw e;\n"); + writer.append(" } catch (Exception e) {\n"); + writer.append(" throw new TypeConversionException(value, type, e);\n"); + writer.append(" }\n"); + writer.append(" }\n"); + writer.append(" }, ").append(Boolean.toString(canPromote)).append(");\n"); } - writer.append(" }\n"); - writer.append(" return null;\n"); + writer.append("\n"); writer.append(" }\n"); + writer.append("\n"); for (String f : converterClasses) { String s = f.substring(f.lastIndexOf('.') + 1); @@ -177,6 +257,19 @@ public class ConverterProcessor extends AbstractProcessor { return pfx + "(" + cast + "value" + (converter.getParameters().size() == 2 ? ", exchange" : "") + ")"; } + private String toJavaFallback(ExecutableElement converter, Set<String> converterClasses) { + String pfx; + if (converter.getModifiers().contains(Modifier.STATIC)) { + pfx = converter.getEnclosingElement().toString() + "." + converter.getSimpleName(); + } else { + converterClasses.add(converter.getEnclosingElement().toString()); + pfx = "get" + converter.getEnclosingElement().getSimpleName() + "()." + converter.getSimpleName(); + } + String type = toString(converter.getParameters().get(converter.getParameters().size() - 2).asType()); + String cast = type.equals("java.lang.Object") ? "" : "(" + type + ") "; + return pfx + "(type, " + (converter.getParameters().size() == 4 ? "exchange, " : "") + cast + "value" + ", registry)"; + } + public static void dumpExceptionToErrorFile(String fileName, String message, Throwable e) { File file = new File(fileName); try {