1 35 package org.jruby.javasupport; 36 37 import java.beans.BeanInfo ; 38 import java.beans.IntrospectionException ; 39 import java.beans.Introspector ; 40 import java.beans.PropertyDescriptor ; 41 import java.lang.reflect.Array ; 42 import java.lang.reflect.Constructor ; 43 import java.lang.reflect.Field ; 44 import java.lang.reflect.Method ; 45 import java.lang.reflect.Modifier ; 46 import java.util.ArrayList ; 47 import java.util.HashMap ; 48 import java.util.Iterator ; 49 import java.util.List ; 50 import java.util.Map ; 51 import java.util.regex.Matcher ; 52 import java.util.regex.Pattern ; 53 54 import org.jruby.Ruby; 55 import org.jruby.RubyArray; 56 import org.jruby.RubyBoolean; 57 import org.jruby.RubyClass; 58 import org.jruby.RubyFixnum; 59 import org.jruby.RubyInteger; 60 import org.jruby.RubyModule; 61 import org.jruby.RubyNumeric; 62 import org.jruby.RubyString; 63 import org.jruby.RubySymbol; 64 import org.jruby.exceptions.RaiseException; 65 import org.jruby.runtime.Arity; 66 import org.jruby.runtime.Block; 67 import org.jruby.runtime.CallType; 68 import org.jruby.runtime.CallbackFactory; 69 import org.jruby.runtime.ObjectAllocator; 70 import org.jruby.runtime.ThreadContext; 71 import org.jruby.runtime.builtin.IRubyObject; 72 import org.jruby.runtime.callback.Callback; 73 74 public class JavaClass extends JavaObject { 75 76 private JavaClass(Ruby runtime, Class javaClass) { 77 super(runtime, (RubyClass) runtime.getModule("Java").getClass("JavaClass"), javaClass); 78 } 79 80 public static synchronized JavaClass get(Ruby runtime, Class klass) { 81 JavaClass javaClass = runtime.getJavaSupport().getJavaClassFromCache(klass); 82 if (javaClass == null) { 83 javaClass = new JavaClass(runtime, klass); 84 runtime.getJavaSupport().putJavaClassIntoCache(javaClass); 85 } 86 return javaClass; 87 } 88 89 public static RubyClass createJavaClassClass(Ruby runtime, RubyModule javaModule) { 90 RubyClass result = javaModule.defineClassUnder("JavaClass", javaModule.getClass("JavaObject"), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR); 96 97 CallbackFactory callbackFactory = runtime.callbackFactory(JavaClass.class); 98 99 result.includeModule(runtime.getModule("Comparable")); 100 101 JavaObject.registerRubyMethods(runtime, result); 102 103 result.getMetaClass().defineFastMethod("for_name", 104 callbackFactory.getFastSingletonMethod("for_name", IRubyObject.class)); 105 result.defineFastMethod("public?", 106 callbackFactory.getFastMethod("public_p")); 107 result.defineFastMethod("protected?", 108 callbackFactory.getFastMethod("protected_p")); 109 result.defineFastMethod("private?", 110 callbackFactory.getFastMethod("private_p")); 111 result.defineFastMethod("final?", 112 callbackFactory.getFastMethod("final_p")); 113 result.defineFastMethod("interface?", 114 callbackFactory.getFastMethod("interface_p")); 115 result.defineFastMethod("array?", 116 callbackFactory.getFastMethod("array_p")); 117 result.defineFastMethod("name", 118 callbackFactory.getFastMethod("name")); 119 result.defineFastMethod("simple_name", 120 callbackFactory.getFastMethod("simple_name")); 121 result.defineFastMethod("to_s", 122 callbackFactory.getFastMethod("name")); 123 result.defineFastMethod("superclass", 124 callbackFactory.getFastMethod("superclass")); 125 result.defineFastMethod("<=>", 126 callbackFactory.getFastMethod("op_cmp", IRubyObject.class)); 127 result.defineFastMethod("java_instance_methods", 128 callbackFactory.getFastMethod("java_instance_methods")); 129 result.defineFastMethod("java_class_methods", 130 callbackFactory.getFastMethod("java_class_methods")); 131 result.defineFastMethod("java_method", 132 callbackFactory.getFastOptMethod("java_method")); 133 result.defineFastMethod("constructors", 134 callbackFactory.getFastMethod("constructors")); 135 result.defineFastMethod("constructor", 136 callbackFactory.getFastOptMethod("constructor")); 137 result.defineFastMethod("array_class", 138 callbackFactory.getFastMethod("array_class")); 139 result.defineFastMethod("new_array", 140 callbackFactory.getFastMethod("new_array", IRubyObject.class)); 141 result.defineFastMethod("fields", 142 callbackFactory.getFastMethod("fields")); 143 result.defineFastMethod("field", 144 callbackFactory.getFastMethod("field", IRubyObject.class)); 145 result.defineFastMethod("interfaces", 146 callbackFactory.getFastMethod("interfaces")); 147 result.defineFastMethod("primitive?", 148 callbackFactory.getFastMethod("primitive_p")); 149 result.defineFastMethod("assignable_from?", 150 callbackFactory.getFastMethod("assignable_from_p", IRubyObject.class)); 151 result.defineFastMethod("component_type", 152 callbackFactory.getFastMethod("component_type")); 153 result.defineFastMethod("declared_instance_methods", 154 callbackFactory.getFastMethod("declared_instance_methods")); 155 result.defineFastMethod("declared_class_methods", 156 callbackFactory.getFastMethod("declared_class_methods")); 157 result.defineFastMethod("declared_fields", 158 callbackFactory.getFastMethod("declared_fields")); 159 result.defineFastMethod("declared_field", 160 callbackFactory.getFastMethod("declared_field", IRubyObject.class)); 161 result.defineFastMethod("declared_constructors", 162 callbackFactory.getFastMethod("declared_constructors")); 163 result.defineFastMethod("declared_constructor", 164 callbackFactory.getFastOptMethod("declared_constructor")); 165 result.defineFastMethod("declared_classes", 166 callbackFactory.getFastMethod("declared_classes")); 167 result.defineFastMethod("declared_method", 168 callbackFactory.getFastOptMethod("declared_method")); 169 result.defineFastMethod("define_instance_methods_for_proxy", 170 callbackFactory.getFastMethod("define_instance_methods_for_proxy", IRubyObject.class)); 171 172 result.getMetaClass().undefineMethod("new"); 173 result.getMetaClass().undefineMethod("allocate"); 174 175 return result; 176 } 177 178 public static synchronized JavaClass for_name(IRubyObject recv, IRubyObject name) { 179 String className = name.asSymbol(); 180 Class klass = recv.getRuntime().getJavaSupport().loadJavaClass(className); 181 return JavaClass.get(recv.getRuntime(), klass); 182 } 183 184 188 private Map getMethodsClumped(boolean isStatic) { 189 Map map = new HashMap (); 190 if(((Class )getValue()).isInterface()) { 191 return map; 192 } 193 194 Method methods[] = javaClass().getMethods(); 195 196 for (int i = 0; i < methods.length; i++) { 197 if (isStatic != Modifier.isStatic(methods[i].getModifiers())) { 198 continue; 199 } 200 201 String key = methods[i].getName(); 202 RubyArray methodsWithName = (RubyArray) map.get(key); 203 204 if (methodsWithName == null) { 205 methodsWithName = RubyArray.newArrayLight(getRuntime()); 206 map.put(key, methodsWithName); 207 } 208 209 methodsWithName.append(JavaMethod.create(getRuntime(), methods[i])); 210 } 211 212 return map; 213 } 214 215 private Map getPropertysClumped() { 216 Map map = new HashMap (); 217 BeanInfo info; 218 219 try { 220 info = Introspector.getBeanInfo(javaClass()); 221 } catch (IntrospectionException e) { 222 return map; 223 } 224 225 PropertyDescriptor [] descriptors = info.getPropertyDescriptors(); 226 227 for (int i = 0; i < descriptors.length; i++) { 228 Method readMethod = descriptors[i].getReadMethod(); 229 230 if (readMethod != null) { 231 String key = readMethod.getName(); 232 List aliases = (List ) map.get(key); 233 234 if (aliases == null) { 235 aliases = new ArrayList (); 236 237 map.put(key, aliases); 238 } 239 240 if (readMethod.getReturnType() == Boolean .class || 241 readMethod.getReturnType() == boolean.class) { 242 aliases.add(descriptors[i].getName() + "?"); 243 } 244 aliases.add(descriptors[i].getName()); 245 } 246 247 Method writeMethod = descriptors[i].getWriteMethod(); 248 249 if (writeMethod != null) { 250 String key = writeMethod.getName(); 251 List aliases = (List ) map.get(key); 252 253 if (aliases == null) { 254 aliases = new ArrayList (); 255 map.put(key, aliases); 256 } 257 258 aliases.add(descriptors[i].getName() + "="); 259 } 260 } 261 262 return map; 263 } 264 265 private void define_instance_method_for_proxy(final RubyClass proxy, List names, 266 final RubyArray methods) { 267 final RubyModule javaUtilities = getRuntime().getModule("JavaUtilities"); 268 Callback method = new Callback() { 269 private Map matchingMethods; 270 public IRubyObject execute(IRubyObject self, IRubyObject[] args, Block block) { 271 IRubyObject[] argsArray = new IRubyObject[args.length + 1]; 272 ThreadContext context = self.getRuntime().getCurrentContext(); 273 274 argsArray[0] = self.getInstanceVariable("@java_object"); 275 RubyArray argsAsArray = RubyArray.newArrayLight(getRuntime()); 276 int argsTypeHash = 0; 277 for (int j = 0; j < args.length; j++) { 278 argsArray[j+1] = Java.ruby_to_java(proxy, args[j], Block.NULL_BLOCK); 279 argsAsArray.append(argsArray[j+1]); 280 argsTypeHash += System.identityHashCode(args[j].getMetaClass()); 282 } 283 Integer argsKey = new Integer (argsTypeHash); 284 285 if (matchingMethods == null) { 286 matchingMethods = new HashMap (); 287 } 288 289 IRubyObject match = (IRubyObject)matchingMethods.get(argsKey); 290 if (match == null) { 291 IRubyObject[] mmArgs = new IRubyObject[] {methods, argsAsArray}; 292 match = javaUtilities.callMethod(context, "matching_method", mmArgs); 293 matchingMethods.put(argsKey, match); 294 } 295 296 return Java.java_to_ruby(self, match.callMethod(context, "invoke", argsArray), Block.NULL_BLOCK); 297 } 298 299 public Arity getArity() { 300 return Arity.optional(); 301 } 302 }; 303 304 for(Iterator iter = names.iterator(); iter.hasNext(); ) { 305 String methodName = (String ) iter.next(); 306 307 if (!methodName.equals("class")) { 310 proxy.defineFastMethod(methodName, method); 311 312 String rubyCasedName = getRubyCasedName(methodName); 313 if (rubyCasedName != null) { 314 proxy.defineAlias(rubyCasedName, methodName); 315 } 316 } 317 } 318 } 319 320 private static final Pattern CAMEL_CASE_SPLITTER = Pattern.compile("([a-z])([A-Z])"); 321 322 public static String getRubyCasedName(String javaCasedName) { 323 Matcher m = CAMEL_CASE_SPLITTER.matcher(javaCasedName); 324 325 String rubyCasedName = m.replaceAll("$1_$2").toLowerCase(); 326 327 if (rubyCasedName.equals(javaCasedName)) { 328 return null; 329 } 330 331 return rubyCasedName; 332 } 333 334 private static final Callback __jsend_method = new Callback() { 335 public IRubyObject execute(IRubyObject self, IRubyObject[] args, Block block) { 336 int v = RubyNumeric.fix2int(((org.jruby.RubyMethod)self.method(((RubySymbol)args[0].callMethod(self.getRuntime().getCurrentContext(),"to_sym")))).arity()); 337 String name = args[0].asSymbol(); 338 339 IRubyObject[] newArgs = new IRubyObject[args.length - 1]; 340 System.arraycopy(args, 1, newArgs, 0, newArgs.length); 341 342 if(v < 0 || v == (newArgs.length)) { 343 return self.callMethod(self.getRuntime().getCurrentContext(), name, newArgs, CallType.FUNCTIONAL, block); 344 } else { 345 return self.callMethod(self.getRuntime().getCurrentContext(),self.getMetaClass().getSuperClass(), name, newArgs, CallType.SUPER, block); 346 } 347 } 348 349 public Arity getArity() { 350 return Arity.optional(); 351 } 352 }; 353 354 public IRubyObject define_instance_methods_for_proxy(IRubyObject arg) { 355 assert arg instanceof RubyClass; 356 357 Map aliasesClump = getPropertysClumped(); 358 Map methodsClump = getMethodsClumped(false); 359 RubyClass proxy = (RubyClass) arg; 360 361 proxy.defineFastMethod("__jsend!", __jsend_method); 362 363 for (Iterator iter = methodsClump.keySet().iterator(); iter.hasNext(); ) { 364 String name = (String ) iter.next(); 365 RubyArray methods = (RubyArray) methodsClump.get(name); 366 List aliases = (List ) aliasesClump.get(name); 367 368 if (aliases == null) { 369 aliases = new ArrayList (); 370 } 371 372 aliases.add(name); 373 374 define_instance_method_for_proxy(proxy, aliases, methods); 375 } 376 377 return getRuntime().getNil(); 378 } 379 380 public RubyBoolean public_p() { 381 return getRuntime().newBoolean(Modifier.isPublic(javaClass().getModifiers())); 382 } 383 384 public RubyBoolean protected_p() { 385 return getRuntime().newBoolean(Modifier.isProtected(javaClass().getModifiers())); 386 } 387 388 public RubyBoolean private_p() { 389 return getRuntime().newBoolean(Modifier.isPrivate(javaClass().getModifiers())); 390 } 391 392 Class javaClass() { 393 return (Class ) getValue(); 394 } 395 396 public RubyBoolean final_p() { 397 return getRuntime().newBoolean(Modifier.isFinal(javaClass().getModifiers())); 398 } 399 400 public RubyBoolean interface_p() { 401 return getRuntime().newBoolean(javaClass().isInterface()); 402 } 403 404 public RubyBoolean array_p() { 405 return getRuntime().newBoolean(javaClass().isArray()); 406 } 407 408 public RubyString name() { 409 return getRuntime().newString(javaClass().getName()); 410 } 411 412 413 private static String getSimpleName(Class class_) { 414 if (class_.isArray()) { 415 return getSimpleName(class_.getComponentType()) + "[]"; 416 } 417 418 String className = class_.getName(); 419 420 int i = className.lastIndexOf('$'); 421 if (i != -1) { 422 do { 423 i++; 424 } while (i < className.length() && Character.isDigit(className.charAt(i))); 425 return className.substring(i); 426 } 427 428 return className.substring(className.lastIndexOf('.') + 1); 429 } 430 431 public RubyString simple_name() { 432 return getRuntime().newString(getSimpleName(javaClass())); 433 } 434 435 public IRubyObject superclass() { 436 Class superclass = javaClass().getSuperclass(); 437 if (superclass == null) { 438 return getRuntime().getNil(); 439 } 440 return JavaClass.get(getRuntime(), superclass); 441 } 442 443 public RubyFixnum op_cmp(IRubyObject other) { 444 if (! (other instanceof JavaClass)) { 445 throw getRuntime().newTypeError("<=> requires JavaClass (" + other.getType() + " given)"); 446 } 447 JavaClass otherClass = (JavaClass) other; 448 if (this.javaClass() == otherClass.javaClass()) { 449 return getRuntime().newFixnum(0); 450 } 451 if (otherClass.javaClass().isAssignableFrom(this.javaClass())) { 452 return getRuntime().newFixnum(-1); 453 } 454 return getRuntime().newFixnum(1); 455 } 456 457 public RubyArray java_instance_methods() { 458 return java_methods(javaClass().getMethods(), false); 459 } 460 461 public RubyArray declared_instance_methods() { 462 return java_methods(javaClass().getDeclaredMethods(), false); 463 } 464 465 private RubyArray java_methods(Method [] methods, boolean isStatic) { 466 RubyArray result = getRuntime().newArray(methods.length); 467 for (int i = 0; i < methods.length; i++) { 468 Method method = methods[i]; 469 if (isStatic == Modifier.isStatic(method.getModifiers())) { 470 result.append(JavaMethod.create(getRuntime(), method)); 471 } 472 } 473 return result; 474 } 475 476 public RubyArray java_class_methods() { 477 return java_methods(javaClass().getMethods(), true); 478 } 479 480 public RubyArray declared_class_methods() { 481 return java_methods(javaClass().getDeclaredMethods(), true); 482 } 483 484 public JavaMethod java_method(IRubyObject[] args) throws ClassNotFoundException { 485 String methodName = args[0].asSymbol(); 486 Class [] argumentTypes = buildArgumentTypes(args); 487 return JavaMethod.create(getRuntime(), javaClass(), methodName, argumentTypes); 488 } 489 490 public JavaMethod declared_method(IRubyObject[] args) throws ClassNotFoundException { 491 String methodName = args[0].asSymbol(); 492 Class [] argumentTypes = buildArgumentTypes(args); 493 return JavaMethod.createDeclared(getRuntime(), javaClass(), methodName, argumentTypes); 494 } 495 496 private Class [] buildArgumentTypes(IRubyObject[] args) throws ClassNotFoundException { 497 if (args.length < 1) { 498 throw getRuntime().newArgumentError(args.length, 1); 499 } 500 Class [] argumentTypes = new Class [args.length - 1]; 501 for (int i = 1; i < args.length; i++) { 502 JavaClass type = for_name(this, args[i]); 503 argumentTypes[i - 1] = type.javaClass(); 504 } 505 return argumentTypes; 506 } 507 508 public RubyArray constructors() { 509 return buildConstructors(javaClass().getConstructors()); 510 } 511 512 public RubyArray declared_classes() { 513 return buildClasses(javaClass().getDeclaredClasses()); 514 } 515 516 private RubyArray buildClasses(Class [] classes) { 517 RubyArray result = getRuntime().newArray(classes.length); 518 for (int i = 0; i < classes.length; i++) { 519 result.append(new JavaClass(getRuntime(), classes[i])); 520 } 521 return result; 522 } 523 524 525 public RubyArray declared_constructors() { 526 return buildConstructors(javaClass().getDeclaredConstructors()); 527 } 528 529 private RubyArray buildConstructors(Constructor [] constructors) { 530 RubyArray result = getRuntime().newArray(constructors.length); 531 for (int i = 0; i < constructors.length; i++) { 532 result.append(new JavaConstructor(getRuntime(), constructors[i])); 533 } 534 return result; 535 } 536 537 public JavaConstructor constructor(IRubyObject[] args) { 538 try { 539 Class [] parameterTypes = buildClassArgs(args); 540 Constructor constructor; 541 constructor = javaClass().getConstructor(parameterTypes); 542 return new JavaConstructor(getRuntime(), constructor); 543 } catch (NoSuchMethodException nsme) { 544 throw getRuntime().newNameError("no matching java constructor", null); 545 } 546 } 547 548 public JavaConstructor declared_constructor(IRubyObject[] args) { 549 try { 550 Class [] parameterTypes = buildClassArgs(args); 551 Constructor constructor; 552 constructor = javaClass().getDeclaredConstructor (parameterTypes); 553 return new JavaConstructor(getRuntime(), constructor); 554 } catch (NoSuchMethodException nsme) { 555 throw getRuntime().newNameError("no matching java constructor", null); 556 } 557 } 558 559 private Class [] buildClassArgs(IRubyObject[] args) { 560 Class [] parameterTypes = new Class [args.length]; 561 for (int i = 0; i < args.length; i++) { 562 String name = args[i].asSymbol(); 563 parameterTypes[i] = getRuntime().getJavaSupport().loadJavaClass(name); 564 } 565 return parameterTypes; 566 } 567 568 public JavaClass array_class() { 569 return JavaClass.get(getRuntime(), Array.newInstance(javaClass(), 0).getClass()); 570 } 571 572 public JavaObject new_array(IRubyObject lengthArgument) { 573 if (lengthArgument instanceof RubyInteger) { 574 int length = (int) ((RubyInteger) lengthArgument).getLongValue(); 576 return new JavaArray(getRuntime(), Array.newInstance(javaClass(), length)); 577 } else if (lengthArgument instanceof RubyArray) { 578 List list = ((RubyArray)lengthArgument).getList(); 580 int length = list.size(); 581 if (length == 0) { 582 throw getRuntime().newArgumentError("empty dimensions specifier for java array"); 583 } 584 int[] dimensions = new int[length]; 585 for (int i = length; --i >= 0; ) { 586 IRubyObject dimensionLength = (IRubyObject)list.get(i); 587 if ( !(dimensionLength instanceof RubyInteger) ) { 588 throw getRuntime() 589 .newTypeError(dimensionLength, getRuntime().getClass("Integer")); 590 } 591 dimensions[i] = (int) ((RubyInteger) dimensionLength).getLongValue(); 592 } 593 return new JavaArray(getRuntime(), Array.newInstance(javaClass(), dimensions)); 594 } else { 595 throw getRuntime().newArgumentError( 596 "invalid length or dimensions specifier for java array" + 597 " - must be Integer or Array of Integer"); 598 } 599 } 600 601 public RubyArray fields() { 602 return buildFieldResults(javaClass().getFields()); 603 } 604 605 public RubyArray declared_fields() { 606 return buildFieldResults(javaClass().getDeclaredFields()); 607 } 608 609 private RubyArray buildFieldResults(Field [] fields) { 610 RubyArray result = getRuntime().newArray(fields.length); 611 for (int i = 0; i < fields.length; i++) { 612 result.append(new JavaField(getRuntime(), fields[i])); 613 } 614 return result; 615 } 616 617 public JavaField field(IRubyObject name) { 618 String stringName = name.asSymbol(); 619 try { 620 Field field = javaClass().getField(stringName); 621 return new JavaField(getRuntime(),field); 622 } catch (NoSuchFieldException nsfe) { 623 throw undefinedFieldError(stringName); 624 } 625 } 626 627 public JavaField declared_field(IRubyObject name) { 628 String stringName = name.asSymbol(); 629 try { 630 Field field = javaClass().getDeclaredField(stringName); 631 return new JavaField(getRuntime(),field); 632 } catch (NoSuchFieldException nsfe) { 633 throw undefinedFieldError(stringName); 634 } 635 } 636 637 private RaiseException undefinedFieldError(String name) { 638 return getRuntime().newNameError("undefined field '" + name + "' for class '" + javaClass().getName() + "'", name); 639 } 640 641 public RubyArray interfaces() { 642 Class [] interfaces = javaClass().getInterfaces(); 643 RubyArray result = getRuntime().newArray(interfaces.length); 644 for (int i = 0; i < interfaces.length; i++) { 645 result.append(JavaClass.get(getRuntime(), interfaces[i])); 646 } 647 return result; 648 } 649 650 public RubyBoolean primitive_p() { 651 return getRuntime().newBoolean(isPrimitive()); 652 } 653 654 public RubyBoolean assignable_from_p(IRubyObject other) { 655 if (! (other instanceof JavaClass)) { 656 throw getRuntime().newTypeError("assignable_from requires JavaClass (" + other.getType() + " given)"); 657 } 658 659 Class otherClass = ((JavaClass) other).javaClass(); 660 661 if (!javaClass().isPrimitive() && otherClass == Void.TYPE || 662 javaClass().isAssignableFrom(otherClass)) { 663 return getRuntime().getTrue(); 664 } 665 otherClass = JavaUtil.primitiveToWrapper(otherClass); 666 Class thisJavaClass = JavaUtil.primitiveToWrapper(javaClass()); 667 if (thisJavaClass.isAssignableFrom(otherClass)) { 668 return getRuntime().getTrue(); 669 } 670 if (Number .class.isAssignableFrom(thisJavaClass)) { 671 if (Number .class.isAssignableFrom(otherClass)) { 672 return getRuntime().getTrue(); 673 } 674 if (otherClass.equals(Character .class)) { 675 return getRuntime().getTrue(); 676 } 677 } 678 if (thisJavaClass.equals(Character .class)) { 679 if (Number .class.isAssignableFrom(otherClass)) { 680 return getRuntime().getTrue(); 681 } 682 } 683 return getRuntime().getFalse(); 684 } 685 686 private boolean isPrimitive() { 687 return javaClass().isPrimitive(); 688 } 689 690 public JavaClass component_type() { 691 if (! javaClass().isArray()) { 692 throw getRuntime().newTypeError("not a java array-class"); 693 } 694 return JavaClass.get(getRuntime(), javaClass().getComponentType()); 695 } 696 } 697 | Popular Tags |