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.commons.jxpath.util; 018 019import java.lang.reflect.Array; 020import java.lang.reflect.Modifier; 021import java.math.BigDecimal; 022import java.math.BigInteger; 023import java.util.ArrayList; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.HashSet; 027import java.util.Iterator; 028import java.util.List; 029import java.util.Set; 030import java.util.SortedSet; 031 032import org.apache.commons.beanutils.ConvertUtils; 033import org.apache.commons.beanutils.Converter; 034import org.apache.commons.jxpath.JXPathInvalidAccessException; 035import org.apache.commons.jxpath.JXPathTypeConversionException; 036import org.apache.commons.jxpath.NodeSet; 037import 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 22:10:38 +0200 (Mo, 23 Jun 2008) $ 044 */ 045public 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}