Skip to content

Commit aa3996c

Browse files
ian-coccimiglioctrueden
authored andcommitted
Add partials, refactor, add java_source function
1 parent 640d57d commit aa3996c

File tree

2 files changed

+70
-98
lines changed

2 files changed

+70
-98
lines changed

src/scyjava/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,13 @@
124124
jclass,
125125
jinstance,
126126
jstacktrace,
127-
find_java_methods,
128-
find_java_fields,
127+
find_java,
128+
java_source,
129129
methods,
130130
fields,
131131
attrs,
132+
src,
133+
java_source,
132134
numeric_bounds,
133135
)
134136
from ._versions import compare_version, get_version, is_version_at_least

src/scyjava/_types.py

Lines changed: 66 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import Any, Callable, Sequence, Tuple, Union
66

77
import jpype
8+
from functools import partial
89

910
from scyjava._jvm import jimport, jvm_started, start_jvm
1011
from 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-
406378
def _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+
552522
def _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

Comments
 (0)