001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.util;
018
019import java.math.BigInteger;
020import java.net.URI;
021import java.net.URISyntaxException;
022import java.util.Date;
023import java.util.HashMap;
024import java.util.Map;
025
026import org.apache.activemq.command.ActiveMQDestination;
027import org.fusesource.hawtbuf.UTF8Buffer;
028
029/**
030 * Type conversion support for ActiveMQ.
031 */
032public final class TypeConversionSupport {
033
034    private static final Converter IDENTITY_CONVERTER = new Converter() {
035        @Override
036        public Object convert(Object value) {
037            return value;
038        }
039    };
040
041    private static class ConversionKey {
042        final Class<?> from;
043        final Class<?> to;
044        final int hashCode;
045
046        public ConversionKey(Class<?> from, Class<?> to) {
047            this.from = from;
048            this.to = to;
049            this.hashCode = from.hashCode() ^ (to.hashCode() << 1);
050        }
051
052        @Override
053        public boolean equals(Object obj) {
054            if (this == obj)
055                return true;
056            if (obj == null)
057                return false;
058            if (getClass() != obj.getClass())
059                return false;
060            ConversionKey other = (ConversionKey) obj;
061            if (from == null) {
062                if (other.from != null)
063                    return false;
064            } else if (!from.equals(other.from))
065                return false;
066            if (to == null) {
067                if (other.to != null)
068                    return false;
069            } else if (!to.equals(other.to))
070                return false;
071            return true;
072        }
073
074        @Override
075        public int hashCode() {
076            return hashCode;
077        }
078    }
079
080    public interface Converter {
081        Object convert(Object value);
082    }
083
084    private static final Map<ConversionKey, Converter> CONVERSION_MAP = new HashMap<ConversionKey, Converter>();
085    static {
086        Converter toStringConverter = new Converter() {
087            @Override
088            public Object convert(Object value) {
089                return value.toString();
090            }
091        };
092        CONVERSION_MAP.put(new ConversionKey(Boolean.class, String.class), toStringConverter);
093        CONVERSION_MAP.put(new ConversionKey(Byte.class, String.class), toStringConverter);
094        CONVERSION_MAP.put(new ConversionKey(Short.class, String.class), toStringConverter);
095        CONVERSION_MAP.put(new ConversionKey(Integer.class, String.class), toStringConverter);
096        CONVERSION_MAP.put(new ConversionKey(Long.class, String.class), toStringConverter);
097        CONVERSION_MAP.put(new ConversionKey(Float.class, String.class), toStringConverter);
098        CONVERSION_MAP.put(new ConversionKey(Double.class, String.class), toStringConverter);
099        CONVERSION_MAP.put(new ConversionKey(UTF8Buffer.class, String.class), toStringConverter);
100        CONVERSION_MAP.put(new ConversionKey(URI.class, String.class), toStringConverter);
101        CONVERSION_MAP.put(new ConversionKey(BigInteger.class, String.class), toStringConverter);
102
103        CONVERSION_MAP.put(new ConversionKey(String.class, Boolean.class), new Converter() {
104            @Override
105            public Object convert(Object value) {
106                return Boolean.valueOf((String)value);
107            }
108        });
109        CONVERSION_MAP.put(new ConversionKey(String.class, Byte.class), new Converter() {
110            @Override
111            public Object convert(Object value) {
112                return Byte.valueOf((String)value);
113            }
114        });
115        CONVERSION_MAP.put(new ConversionKey(String.class, Short.class), new Converter() {
116            @Override
117            public Object convert(Object value) {
118                return Short.valueOf((String)value);
119            }
120        });
121        CONVERSION_MAP.put(new ConversionKey(String.class, Integer.class), new Converter() {
122            @Override
123            public Object convert(Object value) {
124                return Integer.valueOf((String)value);
125            }
126        });
127        CONVERSION_MAP.put(new ConversionKey(String.class, Long.class), new Converter() {
128            @Override
129            public Object convert(Object value) {
130                return Long.valueOf((String)value);
131            }
132        });
133        CONVERSION_MAP.put(new ConversionKey(String.class, Float.class), new Converter() {
134            @Override
135            public Object convert(Object value) {
136                return Float.valueOf((String)value);
137            }
138        });
139        CONVERSION_MAP.put(new ConversionKey(String.class, Double.class), new Converter() {
140            @Override
141            public Object convert(Object value) {
142                return Double.valueOf((String)value);
143            }
144        });
145
146        Converter longConverter = new Converter() {
147            @Override
148            public Object convert(Object value) {
149                return Long.valueOf(((Number)value).longValue());
150            }
151        };
152        CONVERSION_MAP.put(new ConversionKey(Byte.class, Long.class), longConverter);
153        CONVERSION_MAP.put(new ConversionKey(Short.class, Long.class), longConverter);
154        CONVERSION_MAP.put(new ConversionKey(Integer.class, Long.class), longConverter);
155        CONVERSION_MAP.put(new ConversionKey(Date.class, Long.class), new Converter() {
156            @Override
157            public Object convert(Object value) {
158                return Long.valueOf(((Date)value).getTime());
159            }
160        });
161
162        Converter intConverter = new Converter() {
163            @Override
164            public Object convert(Object value) {
165                return Integer.valueOf(((Number)value).intValue());
166            }
167        };
168        CONVERSION_MAP.put(new ConversionKey(Byte.class, Integer.class), intConverter);
169        CONVERSION_MAP.put(new ConversionKey(Short.class, Integer.class), intConverter);
170
171        CONVERSION_MAP.put(new ConversionKey(Byte.class, Short.class), new Converter() {
172            @Override
173            public Object convert(Object value) {
174                return Short.valueOf(((Number)value).shortValue());
175            }
176        });
177
178        CONVERSION_MAP.put(new ConversionKey(Float.class, Double.class), new Converter() {
179            @Override
180            public Object convert(Object value) {
181                return new Double(((Number)value).doubleValue());
182            }
183        });
184        CONVERSION_MAP.put(new ConversionKey(String.class, ActiveMQDestination.class), new Converter() {
185            @Override
186            public Object convert(Object value) {
187                return ActiveMQDestination.createDestination((String)value, ActiveMQDestination.QUEUE_TYPE);
188            }
189        });
190        CONVERSION_MAP.put(new ConversionKey(String.class, URI.class), new Converter() {
191            @Override
192            public Object convert(Object value) {
193                String text = value.toString();
194                try {
195                    return new URI(text);
196                } catch (URISyntaxException e) {
197                    throw new RuntimeException(e);
198                }
199            }
200        });
201    }
202
203    private TypeConversionSupport() {
204    }
205
206    public static Object convert(Object value, Class<?> to) {
207        if (value == null) {
208            // lets avoid NullPointerException when converting to boolean for null values
209            if (boolean.class.isAssignableFrom(to)) {
210                return Boolean.FALSE;
211            }
212            return null;
213        }
214
215        // eager same instance type test to avoid the overhead of invoking the type converter
216        // if already same type
217        if (to.isInstance(value)) {
218            return to.cast(value);
219        }
220
221        // lookup converter
222        Converter c = lookupConverter(value.getClass(), to);
223        if (c != null) {
224            return c.convert(value);
225        } else {
226            return null;
227        }
228    }
229
230    public static Converter lookupConverter(Class<?> from, Class<?> to) {
231        // use wrapped type for primitives
232        if (from.isPrimitive()) {
233            from = convertPrimitiveTypeToWrapperType(from);
234        }
235        if (to.isPrimitive()) {
236            to = convertPrimitiveTypeToWrapperType(to);
237        }
238
239        if (from.equals(to)) {
240            return IDENTITY_CONVERTER;
241        }
242
243        return CONVERSION_MAP.get(new ConversionKey(from, to));
244    }
245
246    /**
247     * Converts primitive types such as int to its wrapper type like
248     * {@link Integer}
249     */
250    private static Class<?> convertPrimitiveTypeToWrapperType(Class<?> type) {
251        Class<?> rc = type;
252        if (type.isPrimitive()) {
253            if (type == int.class) {
254                rc = Integer.class;
255            } else if (type == long.class) {
256                rc = Long.class;
257            } else if (type == double.class) {
258                rc = Double.class;
259            } else if (type == float.class) {
260                rc = Float.class;
261            } else if (type == short.class) {
262                rc = Short.class;
263            } else if (type == byte.class) {
264                rc = Byte.class;
265            } else if (type == boolean.class) {
266                rc = Boolean.class;
267            }
268        }
269        return rc;
270    }
271}