55from typing import Any , Callable , Sequence , Tuple , Union
66
77import jpype
8+ from functools import partial
89
910from scyjava ._jvm import jimport , jvm_started , start_jvm
1011from scyjava .config import Mode , mode
@@ -324,46 +325,43 @@ def numeric_bounds(
324325 return None , None
325326
326327
327- def find_java_methods (data ) -> list [dict [str , Any ]]:
328+ def find_java (data , aspect : str ) -> list [dict [str , Any ]]:
328329 """
329330 Use Java reflection to introspect the given Java object,
330331 returning a table of its available methods.
331332
332- :param data: The object or class to inspect.
333- :return: List of table rows with columns "name", "static", "arguments", and "returns".
333+ :param data: The object or class or fully qualified class name to inspect.
334+ :param aspect: Either 'methods' or 'fields'
335+ :return: List of dicts with keys: "name", "static", "arguments", and "returns".
334336 """
335337
336- if not isjava (data ):
337- raise ValueError ("Not a Java object" )
338+ if not isjava (data ) and isinstance (data , str ):
339+ try :
340+ data = jimport (data )
341+ except :
342+ raise ValueError ("Not a Java object" )
338343
344+ Modifier = jimport ("java.lang.reflect.Modifier" )
339345 jcls = data if jinstance (data , "java.lang.Class" ) else jclass (data )
340346
341- methods = jcls .getMethods ()
342-
343- # NB: Methods are returned in inconsistent order.
344- # Arrays.sort(methods, (m1, m2) -> {
345- # final int nameComp = m1.getName().compareTo(m2.getName())
346- # if (nameComp != 0) return nameComp
347- # final int pCount1 = m1.getParameterCount()
348- # final int pCount2 = m2.getParameterCount()
349- # if (pCount1 != pCount2) return pCount1 - pCount2
350- # final Class<?>[] pTypes1 = m1.getParameterTypes()
351- # final Class<?>[] pTypes2 = m2.getParameterTypes()
352- # for (int i = 0; i < pTypes1.length; i++) {
353- # final int typeComp = ClassUtils.compare(pTypes1[i], pTypes2[i])
354- # if (typeComp != 0) return typeComp
355- # }
356- # return ClassUtils.compare(m1.getReturnType(), m2.getReturnType())
357- # })
347+ if aspect == "methods" :
348+ cls_aspects = jcls .getMethods ()
349+ elif aspect == "fields" :
350+ cls_aspects = jcls .getFields ()
351+ else :
352+ return "`aspect` must be either 'fields' or 'methods'"
358353
359354 table = []
360- Modifier = jimport ("java.lang.reflect.Modifier" )
361355
362- for m in methods :
356+ for m in cls_aspects :
363357 name = m .getName ()
364- args = [c .getName () for c in m .getParameterTypes ()]
358+ if aspect == "methods" :
359+ args = [c .getName () for c in m .getParameterTypes ()]
360+ returns = m .getReturnType ().getName ()
361+ elif aspect == "fields" :
362+ args = None
363+ returns = m .getType ().getName ()
365364 mods = Modifier .isStatic (m .getModifiers ())
366- returns = m .getReturnType ().getName ()
367365 table .append (
368366 {
369367 "name" : name ,
@@ -377,32 +375,6 @@ def find_java_methods(data) -> list[dict[str, Any]]:
377375 return sorted_table
378376
379377
380- # TODO
381- def find_java_fields (data ) -> list [dict [str , Any ]]:
382- """
383- Use Java reflection to introspect the given Java object,
384- returning a table of its available fields.
385-
386- :param data: The object or class to inspect.
387- :return: List of table rows with columns "name", "arguments", and "returns".
388- """
389- if not isjava (data ):
390- raise ValueError ("Not a Java object" )
391-
392- jcls = data if jinstance (data , "java.lang.Class" ) else jclass (data )
393-
394- fields = jcls .getFields ()
395- table = []
396-
397- for f in fields :
398- name = f .getName ()
399- ftype = f .getType ().getName ()
400- table .append ({"name" : name , "type" : ftype })
401- sorted_table = sorted (table , key = lambda d : d ["name" ])
402-
403- return sorted_table
404-
405-
406378def _map_syntax (base_type ):
407379 """
408380 Maps a java BaseType annotation (see link below) in an Java array
@@ -445,7 +417,7 @@ def _make_pretty_string(entry, offset):
445417
446418 # Handle fields
447419 if entry ["arguments" ] is None :
448- return f"{ return_val } = { obj_name } \n "
420+ return f"{ return_val } { modifier } = { obj_name } \n "
449421
450422 # Handle methods with no arguments
451423 if len (entry ["arguments" ]) == 0 :
@@ -455,82 +427,65 @@ def _make_pretty_string(entry, offset):
455427 return f"{ return_val } { modifier } = { obj_name } ({ arg_string } )\n "
456428
457429
458- def get_source_code (data ):
430+ def java_source (data ):
459431 """
460432 Tries to find the source code using Scijava's SourceFinder'
461- :param data: The object or class to check for source code.
433+ :param data: The object or class or fully qualified class name to check for source code.
434+ :return: The URL of the java class
462435 """
463436 types = jimport ("org.scijava.util.Types" )
464437 sf = jimport ("org.scijava.search.SourceFinder" )
465438 jstring = jimport ("java.lang.String" )
466439 try :
440+ if not isjava (data ) and isinstance (data , str ):
441+ try :
442+ data = jimport (data ) # check if data can be imported
443+ except :
444+ raise ValueError ("Not a Java object" )
467445 jcls = data if jinstance (data , "java.lang.Class" ) else jclass (data )
468446 if types .location (jcls ).toString ().startsWith (jstring ("jrt" )):
469447 # Handles Java RunTime (jrt) exceptions.
470- return " GitHub source code not available"
448+ raise ValueError ( "Java Builtin: GitHub source code not available")
471449 url = sf .sourceLocation (jcls , None )
472450 urlstring = url .toString ()
473451 return urlstring
474452 except jimport ("java.lang.IllegalArgumentException" ) as err :
475453 return f"Illegal argument provided { err = } , { type (err )= } "
454+ except ValueError as err :
455+ return f"{ err } "
456+ except TypeError :
457+ return f"Not a Java class { str (type (data ))} "
476458 except Exception as err :
477459 return f"Unexpected { err = } , { type (err )= } "
478460
479461
480- def fields (data ) -> str :
481- """
482- Writes data to a printed field names with the field value.
483- :param data: The object or class to inspect.
484- """
485- table = find_java_fields (data )
486- if len (table ) == 0 :
487- print ("No fields found" )
488- return
489-
490- all_fields = ""
491- offset = max (list (map (lambda entry : len (entry ["type" ]), table )))
492- for entry in table :
493- entry ["returns" ] = _map_syntax (entry ["type" ])
494- entry ["static" ] = False
495- entry ["arguments" ] = None
496- entry_string = _make_pretty_string (entry , offset )
497- all_fields += entry_string
498-
499- print (all_fields )
500-
501-
502- def attrs (data ):
503- """
504- Writes data to a printed field names with the field value. Alias for `fields(data)`.
505- :param data: The object or class to inspect.
506- """
507- fields (data )
508-
509-
510- def methods (data , static : bool | None = None , source : bool = True ) -> str :
462+ def _print_data (data , aspect , static : bool | None = None , source : bool = True ):
511463 """
512464 Writes data to a printed string of class methods with inputs, static modifier, arguments, and return values.
513465
514466 :param data: The object or class to inspect.
515- :param static: Which methods to print. Can be set as boolean to filter the class methods based on
516- static vs. instance methods. Optional, default is None (prints all methods).
467+ :param aspect: Whether to print class fields or methods.
468+ :param static: Filter on Static/Instance. Can be set as boolean to filter the class methods based on
469+ static vs. instance methods. Optional, default is None (prints all).
517470 :param source: Whether to print any available source code. Default True.
518471 """
519- table = find_java_methods (data )
472+ table = find_java (data , aspect )
473+ if len (table ) == 0 :
474+ print (f"No { aspect } found" )
475+ return
520476
521477 # Print source code
522478 offset = max (list (map (lambda entry : len (entry ["returns" ]), table )))
523479 all_methods = ""
524480 if source :
525- urlstring = get_source_code (data )
526- print (f"URL: { urlstring } " )
527- else :
528- pass
481+ urlstring = java_source (data )
482+ print (f"Source code URL: { urlstring } " )
529483
530484 # Print methods
531485 for entry in table :
532486 entry ["returns" ] = _map_syntax (entry ["returns" ])
533- entry ["arguments" ] = [_map_syntax (e ) for e in entry ["arguments" ]]
487+ if entry ["arguments" ]:
488+ entry ["arguments" ] = [_map_syntax (e ) for e in entry ["arguments" ]]
534489 if static is None :
535490 entry_string = _make_pretty_string (entry , offset )
536491 all_methods += entry_string
@@ -545,10 +500,25 @@ def methods(data, static: bool | None = None, source: bool = True) -> str:
545500 continue
546501
547502 # 4 added to align the asterisk with output.
548- print (f"{ '' :<{offset + 4 }} * indicates a static method " )
503+ print (f"{ '' :<{offset + 4 }} * indicates static modifier " )
549504 print (all_methods )
550505
551506
507+ methods = partial (_print_data , aspect = "methods" )
508+ fields = partial (_print_data , aspect = "fields" )
509+ attrs = partial (_print_data , aspect = "fields" )
510+
511+
512+ def src (data ):
513+ """
514+ Prints the source code URL for a Java class, object, or class name.
515+
516+ :param data: The Java class, object, or fully qualified class name as string
517+ """
518+ source_url = java_source (data )
519+ print (f"Source code URL: { source_url } " )
520+
521+
552522def _is_jtype (the_type : type , class_name : str ) -> bool :
553523 """
554524 Test if the given type object is *exactly* the specified Java type.
0 commit comments