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 */
017 package org.apache.commons.jxpath.util;
018
019 import java.lang.reflect.Array;
020 import java.lang.reflect.Modifier;
021 import java.math.BigDecimal;
022 import java.math.BigInteger;
023 import java.util.ArrayList;
024 import java.util.Collection;
025 import java.util.Collections;
026 import java.util.HashSet;
027 import java.util.Iterator;
028 import java.util.List;
029 import java.util.Set;
030 import java.util.SortedSet;
031
032 import org.apache.commons.beanutils.ConvertUtils;
033 import org.apache.commons.beanutils.Converter;
034 import org.apache.commons.jxpath.JXPathInvalidAccessException;
035 import org.apache.commons.jxpath.JXPathTypeConversionException;
036 import org.apache.commons.jxpath.NodeSet;
037 import org.apache.commons.jxpath.Pointer;
038
039 /**
040 * The default implementation of TypeConverter.
041 *
042 * @author Dmitri Plotnikov
043 * @version $Revision: 670727 $ $Date: 2008-06-23 15:10:38 -0500 (Mon, 23 Jun 2008) $
044 */
045 public class BasicTypeConverter implements TypeConverter {
046
047 /**
048 * Returns true if it can convert the supplied
049 * object to the specified class.
050 * @param object to check
051 * @param toType prospective destination class
052 * @return boolean
053 */
054 public boolean canConvert(Object object, final Class toType) {
055 if (object == null) {
056 return true;
057 }
058 final Class useType = TypeUtils.wrapPrimitive(toType);
059 Class fromType = object.getClass();
060
061 if (useType.isAssignableFrom(fromType)) {
062 return true;
063 }
064
065 if (useType == String.class) {
066 return true;
067 }
068
069 if (object instanceof Boolean && (Number.class.isAssignableFrom(useType)
070 || "java.util.concurrent.atomic.AtomicBoolean"
071 .equals(useType.getName()))) {
072 return true;
073 }
074 if (object instanceof Number
075 && (Number.class.isAssignableFrom(useType) || useType == Boolean.class)) {
076 return true;
077 }
078 if (object instanceof String
079 && (useType == Boolean.class
080 || useType == Character.class
081 || useType == Byte.class
082 || useType == Short.class
083 || useType == Integer.class
084 || useType == Long.class
085 || useType == Float.class
086 || useType == Double.class)) {
087 return true;
088 }
089 if (fromType.isArray()) {
090 // Collection -> array
091 if (useType.isArray()) {
092 Class cType = useType.getComponentType();
093 int length = Array.getLength(object);
094 for (int i = 0; i < length; i++) {
095 Object value = Array.get(object, i);
096 if (!canConvert(value, cType)) {
097 return false;
098 }
099 }
100 return true;
101 }
102 if (Collection.class.isAssignableFrom(useType)) {
103 return canCreateCollection(useType);
104 }
105 if (Array.getLength(object) > 0) {
106 Object value = Array.get(object, 0);
107 return canConvert(value, useType);
108 }
109 return canConvert("", useType);
110 }
111 if (object instanceof Collection) {
112 // Collection -> array
113 if (useType.isArray()) {
114 Class cType = useType.getComponentType();
115 Iterator it = ((Collection) object).iterator();
116 while (it.hasNext()) {
117 Object value = it.next();
118 if (!canConvert(value, cType)) {
119 return false;
120 }
121 }
122 return true;
123 }
124 if (Collection.class.isAssignableFrom(useType)) {
125 return canCreateCollection(useType);
126 }
127 if (((Collection) object).size() > 0) {
128 Object value;
129 if (object instanceof List) {
130 value = ((List) object).get(0);
131 }
132 else {
133 Iterator it = ((Collection) object).iterator();
134 value = it.next();
135 }
136 return canConvert(value, useType);
137 }
138 return canConvert("", useType);
139 }
140 if (object instanceof NodeSet) {
141 return canConvert(((NodeSet) object).getValues(), useType);
142 }
143 if (object instanceof Pointer) {
144 return canConvert(((Pointer) object).getValue(), useType);
145 }
146 return ConvertUtils.lookup(useType) != null;
147 }
148
149 /**
150 * Converts the supplied object to the specified
151 * type. Throws a runtime exception if the conversion is
152 * not possible.
153 * @param object to convert
154 * @param toType destination class
155 * @return converted object
156 */
157 public Object convert(Object object, final Class toType) {
158 if (object == null) {
159 return toType.isPrimitive() ? convertNullToPrimitive(toType) : null;
160 }
161
162 if (toType == Object.class) {
163 if (object instanceof NodeSet) {
164 return convert(((NodeSet) object).getValues(), toType);
165 }
166 if (object instanceof Pointer) {
167 return convert(((Pointer) object).getValue(), toType);
168 }
169 return object;
170 }
171 final Class useType = TypeUtils.wrapPrimitive(toType);
172 Class fromType = object.getClass();
173
174 if (useType.isAssignableFrom(fromType)) {
175 return object;
176 }
177
178 if (fromType.isArray()) {
179 int length = Array.getLength(object);
180 if (useType.isArray()) {
181 Class cType = useType.getComponentType();
182
183 Object array = Array.newInstance(cType, length);
184 for (int i = 0; i < length; i++) {
185 Object value = Array.get(object, i);
186 Array.set(array, i, convert(value, cType));
187 }
188 return array;
189 }
190 if (Collection.class.isAssignableFrom(useType)) {
191 Collection collection = allocateCollection(useType);
192 for (int i = 0; i < length; i++) {
193 collection.add(Array.get(object, i));
194 }
195 return unmodifiableCollection(collection);
196 }
197 if (length > 0) {
198 Object value = Array.get(object, 0);
199 return convert(value, useType);
200 }
201 return convert("", useType);
202 }
203 if (object instanceof Collection) {
204 int length = ((Collection) object).size();
205 if (useType.isArray()) {
206 Class cType = useType.getComponentType();
207 Object array = Array.newInstance(cType, length);
208 Iterator it = ((Collection) object).iterator();
209 for (int i = 0; i < length; i++) {
210 Object value = it.next();
211 Array.set(array, i, convert(value, cType));
212 }
213 return array;
214 }
215 if (Collection.class.isAssignableFrom(useType)) {
216 Collection collection = allocateCollection(useType);
217 collection.addAll((Collection) object);
218 return unmodifiableCollection(collection);
219 }
220 if (length > 0) {
221 Object value;
222 if (object instanceof List) {
223 value = ((List) object).get(0);
224 }
225 else {
226 Iterator it = ((Collection) object).iterator();
227 value = it.next();
228 }
229 return convert(value, useType);
230 }
231 return convert("", useType);
232 }
233 if (object instanceof NodeSet) {
234 return convert(((NodeSet) object).getValues(), useType);
235 }
236 if (object instanceof Pointer) {
237 return convert(((Pointer) object).getValue(), useType);
238 }
239 if (useType == String.class) {
240 return object.toString();
241 }
242 if (object instanceof Boolean) {
243 if (Number.class.isAssignableFrom(useType)) {
244 return allocateNumber(useType, ((Boolean) object).booleanValue() ? 1 : 0);
245 }
246 if ("java.util.concurrent.atomic.AtomicBoolean".equals(useType.getName())) {
247 try {
248 return useType.getConstructor(new Class[] { boolean.class })
249 .newInstance(new Object[] { object });
250 }
251 catch (Exception e) {
252 throw new JXPathTypeConversionException(useType.getName(), e);
253 }
254 }
255 }
256 if (object instanceof Number) {
257 double value = ((Number) object).doubleValue();
258 if (useType == Boolean.class) {
259 return value == 0.0 ? Boolean.FALSE : Boolean.TRUE;
260 }
261 if (Number.class.isAssignableFrom(useType)) {
262 return allocateNumber(useType, value);
263 }
264 }
265 if (object instanceof String) {
266 Object value = convertStringToPrimitive(object, useType);
267 if (value != null) {
268 return value;
269 }
270 }
271
272 Converter converter = ConvertUtils.lookup(useType);
273 if (converter != null) {
274 return converter.convert(useType, object);
275 }
276
277 throw new JXPathTypeConversionException("Cannot convert "
278 + object.getClass() + " to " + useType);
279 }
280
281 /**
282 * Convert null to a primitive type.
283 * @param toType destination class
284 * @return a wrapper
285 */
286 protected Object convertNullToPrimitive(Class toType) {
287 if (toType == boolean.class) {
288 return Boolean.FALSE;
289 }
290 if (toType == char.class) {
291 return new Character('\0');
292 }
293 if (toType == byte.class) {
294 return new Byte((byte) 0);
295 }
296 if (toType == short.class) {
297 return new Short((short) 0);
298 }
299 if (toType == int.class) {
300 return new Integer(0);
301 }
302 if (toType == long.class) {
303 return new Long(0L);
304 }
305 if (toType == float.class) {
306 return new Float(0.0f);
307 }
308 if (toType == double.class) {
309 return new Double(0.0);
310 }
311 return null;
312 }
313
314 /**
315 * Convert a string to a primitive type.
316 * @param object String
317 * @param toType destination class
318 * @return wrapper
319 */
320 protected Object convertStringToPrimitive(Object object, Class toType) {
321 toType = TypeUtils.wrapPrimitive(toType);
322 if (toType == Boolean.class) {
323 return Boolean.valueOf((String) object);
324 }
325 if (toType == Character.class) {
326 return new Character(((String) object).charAt(0));
327 }
328 if (toType == Byte.class) {
329 return new Byte((String) object);
330 }
331 if (toType == Short.class) {
332 return new Short((String) object);
333 }
334 if (toType == Integer.class) {
335 return new Integer((String) object);
336 }
337 if (toType == Long.class) {
338 return new Long((String) object);
339 }
340 if (toType == Float.class) {
341 return new Float((String) object);
342 }
343 if (toType == Double.class) {
344 return new Double((String) object);
345 }
346 return null;
347 }
348
349 /**
350 * Allocate a number of a given type and value.
351 * @param type destination class
352 * @param value double
353 * @return Number
354 */
355 protected Number allocateNumber(Class type, double value) {
356 type = TypeUtils.wrapPrimitive(type);
357 if (type == Byte.class) {
358 return new Byte((byte) value);
359 }
360 if (type == Short.class) {
361 return new Short((short) value);
362 }
363 if (type == Integer.class) {
364 return new Integer((int) value);
365 }
366 if (type == Long.class) {
367 return new Long((long) value);
368 }
369 if (type == Float.class) {
370 return new Float((float) value);
371 }
372 if (type == Double.class) {
373 return new Double(value);
374 }
375 if (type == BigInteger.class) {
376 return BigInteger.valueOf((long) value);
377 }
378 if (type == BigDecimal.class) {
379 return new BigDecimal(value);
380 }
381 String classname = type.getName();
382 Class initialValueType = null;
383 if ("java.util.concurrent.atomic.AtomicInteger".equals(classname)) {
384 initialValueType = int.class;
385 }
386 if ("java.util.concurrent.atomic.AtomicLong".equals(classname)) {
387 initialValueType = long.class;
388 }
389 if (initialValueType != null) {
390 try {
391 return (Number) type.getConstructor(
392 new Class[] { initialValueType })
393 .newInstance(
394 new Object[] { allocateNumber(initialValueType,
395 value) });
396 }
397 catch (Exception e) {
398 throw new JXPathTypeConversionException(classname, e);
399 }
400 }
401 return null;
402 }
403
404 /**
405 * Learn whether this BasicTypeConverter can create a collection of the specified type.
406 * @param type prospective destination class
407 * @return boolean
408 */
409 protected boolean canCreateCollection(Class type) {
410 if (!type.isInterface()
411 && ((type.getModifiers() & Modifier.ABSTRACT) == 0)) {
412 try {
413 type.getConstructor(new Class[0]);
414 return true;
415 }
416 catch (Exception e) {
417 return false;
418 }
419 }
420 return type == List.class || type == Collection.class || type == Set.class;
421 }
422
423 /**
424 * Create a collection of a given type.
425 * @param type destination class
426 * @return Collection
427 */
428 protected Collection allocateCollection(Class type) {
429 if (!type.isInterface()
430 && ((type.getModifiers() & Modifier.ABSTRACT) == 0)) {
431 try {
432 return (Collection) type.newInstance();
433 }
434 catch (Exception ex) {
435 throw new JXPathInvalidAccessException(
436 "Cannot create collection of type: " + type, ex);
437 }
438 }
439
440 if (type == List.class || type == Collection.class) {
441 return new ArrayList();
442 }
443 if (type == Set.class) {
444 return new HashSet();
445 }
446 throw new JXPathInvalidAccessException(
447 "Cannot create collection of type: " + type);
448 }
449
450 /**
451 * Get an unmodifiable version of a collection.
452 * @param collection to wrap
453 * @return Collection
454 */
455 protected Collection unmodifiableCollection(Collection collection) {
456 if (collection instanceof List) {
457 return Collections.unmodifiableList((List) collection);
458 }
459 if (collection instanceof SortedSet) {
460 return Collections.unmodifiableSortedSet((SortedSet) collection);
461 }
462 if (collection instanceof Set) {
463 return Collections.unmodifiableSet((Set) collection);
464 }
465 return Collections.unmodifiableCollection(collection);
466 }
467
468 /**
469 * NodeSet implementation
470 */
471 static final class ValueNodeSet implements NodeSet {
472 private List values;
473 private List pointers;
474
475 /**
476 * Create a new ValueNodeSet.
477 * @param values to return
478 */
479 public ValueNodeSet(List values) {
480 this.values = values;
481 }
482
483 public List getValues() {
484 return Collections.unmodifiableList(values);
485 }
486
487 public List getNodes() {
488 return Collections.unmodifiableList(values);
489 }
490
491 public List getPointers() {
492 if (pointers == null) {
493 pointers = new ArrayList();
494 for (int i = 0; i < values.size(); i++) {
495 pointers.add(new ValuePointer(values.get(i)));
496 }
497 pointers = Collections.unmodifiableList(pointers);
498 }
499 return pointers;
500 }
501 }
502
503 /**
504 * Value pointer
505 */
506 static final class ValuePointer implements Pointer {
507 private static final long serialVersionUID = -4817239482392206188L;
508
509 private Object bean;
510
511 /**
512 * Create a new ValuePointer.
513 * @param object value
514 */
515 public ValuePointer(Object object) {
516 this.bean = object;
517 }
518
519 public Object getValue() {
520 return bean;
521 }
522
523 public Object getNode() {
524 return bean;
525 }
526
527 public Object getRootNode() {
528 return bean;
529 }
530
531 public void setValue(Object value) {
532 throw new UnsupportedOperationException();
533 }
534
535 public Object clone() {
536 return this;
537 }
538
539 public int compareTo(Object object) {
540 return 0;
541 }
542
543 public String asPath() {
544 if (bean == null) {
545 return "null()";
546 }
547 if (bean instanceof Number) {
548 String string = bean.toString();
549 if (string.endsWith(".0")) {
550 string = string.substring(0, string.length() - 2);
551 }
552 return string;
553 }
554 if (bean instanceof Boolean) {
555 return ((Boolean) bean).booleanValue() ? "true()" : "false()";
556 }
557 if (bean instanceof String) {
558 return "'" + bean + "'";
559 }
560 return "{object of type " + bean.getClass().getName() + "}";
561 }
562 }
563 }