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