Skip to content

Commit d106caf

Browse files
committed
Added Java classes field accessing support.
1 parent 5e1ad87 commit d106caf

File tree

9 files changed

+249
-6
lines changed

9 files changed

+249
-6
lines changed

java/src/main/java/org/astonbitecode/j4rs/api/NativeInvocation.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ public interface NativeInvocation<T> extends ObjectValue, JsonValue {
6666
*/
6767
void initializeCallbackChannel(long channelAddress);
6868

69+
/**
70+
* Retrieves the instance held under the Field fieldName
71+
* @param fieldName
72+
* @return A {@link NativeInvocation} instance containing the defined field.
73+
*/
74+
NativeInvocation field(String fieldName);
75+
6976
/**
7077
* Casts a the object that is contained in a NativeInvocation to an object of class clazz.
7178
*

java/src/main/java/org/astonbitecode/j4rs/api/invocation/EagerJsonInvocationImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ public void initializeCallbackChannel(long channelAddress) {
5656
throw new RuntimeException("Not implemented yet. Please use the JsonInvocationImpl instead");
5757
}
5858

59+
@Override
60+
public NativeInvocation field(String methodName) {
61+
throw new RuntimeException("Not implemented yet. Please use the JsonInvocationImpl instead");
62+
}
63+
5964
@Override
6065
public T getObject() {
6166
throw new RuntimeException("Not implemented yet. Please use the JsonInvocationImpl instead");

java/src/main/java/org/astonbitecode/j4rs/api/invocation/JsonInvocationImpl.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.astonbitecode.j4rs.errors.InvocationException;
2424
import org.astonbitecode.j4rs.rust.RustPointer;
2525

26+
import java.lang.reflect.Field;
2627
import java.lang.reflect.Method;
2728
import java.util.Arrays;
2829

@@ -44,10 +45,6 @@ public JsonInvocationImpl(T instance, Class<T> clazz) {
4445

4546
@Override
4647
public NativeInvocation invoke(String methodName, InvocationArg... args) {
47-
// Check the existence of the instance to invoke
48-
if (this.object == null) {
49-
throw new InvocationException("Cannot invoke the class " + this.clazz.getName() + ". It is not instantiated yet.");
50-
}
5148
// Invoke the instance
5249
try {
5350
CreatedInstance createdInstance = invokeMethod(methodName, gen.generateArgObjects(args));
@@ -97,6 +94,16 @@ public void initializeCallbackChannel(long channelAddress) {
9794
}
9895
}
9996

97+
@Override
98+
public NativeInvocation field(String fieldName) {
99+
try {
100+
CreatedInstance createdInstance = getField(fieldName);
101+
return new JsonInvocationImpl(createdInstance.object, createdInstance.clazz);
102+
} catch (Exception error) {
103+
throw new InvocationException("Error while accessing field " + fieldName + " of Class " + this.clazz.getName(), error);
104+
}
105+
}
106+
100107
@Override
101108
public T getObject() {
102109
return object;
@@ -113,6 +120,12 @@ public String getJson() {
113120
return jsonValue.getJson();
114121
}
115122

123+
CreatedInstance getField(String fieldName) throws Exception {
124+
Field field = this.clazz.getField(fieldName);
125+
Object fieldObject = field.get(this.object);
126+
return new CreatedInstance(field.getType(), fieldObject);
127+
}
128+
116129
CreatedInstance invokeMethod(String methodName, GeneratedArg[] generatedArgs) throws Exception {
117130
Class[] argTypes = Arrays.stream(generatedArgs)
118131
.map(invGeneratedArg -> {

java/src/test/java/org/astonbitecode/j4rs/api/invocation/JsonInvocationImplTest.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,70 @@ public void invokeMethodNotFoundInHierarchy() {
104104
NativeInvocation ni = new JsonInvocationImpl(new GrandchildDummy(), GrandchildDummy.class);
105105
ni.invoke("nonExisting");
106106
}
107+
108+
@Test
109+
public void getFieldInstance() {
110+
NativeInvocation ni = new JsonInvocationImpl(new DummyWithFields(), DummyWithFields.class);
111+
112+
NativeInvocation res1 = ni.field("pubInt");
113+
Integer i1 = (Integer) res1.getObject();
114+
assert (i1.equals(11));
115+
116+
try {
117+
NativeInvocation res2 = ni.field("packageInt");
118+
res2.getObject();
119+
assert (false);
120+
} catch (InvocationException ie) {
121+
assert (true);
122+
}
123+
124+
try {
125+
NativeInvocation res3 = ni.field("protectedInt");
126+
res3.getObject();
127+
assert (false);
128+
} catch (InvocationException ie) {
129+
assert (true);
130+
}
131+
132+
try {
133+
NativeInvocation res4 = ni.field("privateInt");
134+
res4.getObject();
135+
assert (false);
136+
} catch (InvocationException ie) {
137+
assert (true);
138+
}
139+
}
140+
141+
@Test
142+
public void getFieldInstanceInHierarchy() {
143+
NativeInvocation ni = new JsonInvocationImpl(new ChildOfDummyWithFields(), ChildOfDummyWithFields.class);
144+
145+
NativeInvocation res1 = ni.field("pubInt");
146+
Integer i1 = (Integer) res1.getObject();
147+
assert (i1.equals(11));
148+
149+
try {
150+
NativeInvocation res2 = ni.field("packageInt");
151+
res2.getObject();
152+
assert (false);
153+
} catch (InvocationException ie) {
154+
assert (true);
155+
}
156+
157+
try {
158+
NativeInvocation res3 = ni.field("protectedInt");
159+
res3.getObject();
160+
assert (false);
161+
} catch (InvocationException ie) {
162+
assert (true);
163+
}
164+
165+
try {
166+
NativeInvocation res4 = ni.field("privateInt");
167+
res4.getObject();
168+
assert (false);
169+
} catch (InvocationException ie) {
170+
assert (true);
171+
}
172+
}
107173
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright 2019 astonbitecode
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
package org.astonbitecode.j4rs.utils;
16+
17+
public class ChildOfDummyWithFields extends DummyWithFields {
18+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 2019 astonbitecode
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
package org.astonbitecode.j4rs.utils;
16+
17+
public class DummyWithFields {
18+
public Integer pubInt = 11;
19+
Integer packageInt = 111;
20+
protected Integer protectedInt = 1111;
21+
private Integer privateInt = 1111;
22+
}
Binary file not shown.

rust/src/api.rs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,33 @@ impl Jvm {
459459
}
460460
}
461461

462+
/// Retrieves the static class `class_name`.
463+
pub fn static_class(&self, class_name: &str) -> errors::Result<Instance> {
464+
debug(&format!("Retrieving static class {}", class_name));
465+
unsafe {
466+
// Factory invocation - first argument: create a jstring to pass as argument for the class_name
467+
let class_name_jstring: jstring = (self.jni_new_string_utf)(
468+
self.jni_env,
469+
utils::to_java_string(class_name),
470+
);
471+
// Call the method of the factory that creates a NativeInvocation for static calls to methods of class `class_name`.
472+
// This returns a NativeInvocation that acts like a proxy to the Java world.
473+
let native_invocation_instance = (self.jni_call_static_object_method)(
474+
self.jni_env,
475+
self.factory_class,
476+
self.factory_create_for_static_method,
477+
class_name_jstring,
478+
);
479+
480+
delete_java_local_ref(self.jni_env, class_name_jstring);
481+
482+
let native_invocation_global_instance = create_global_ref_from_local_ref(native_invocation_instance, self.jni_env)?;
483+
484+
// Create and return the Instance
485+
self.do_return(Instance::from(native_invocation_global_instance)?)
486+
}
487+
}
488+
462489
/// Creates a new Java Array with elements of the class `class_name`.
463490
/// The array will have the `InvocationArg`s populated.
464491
/// The `InvocationArg`s __must__ be of type _class_name_.
@@ -576,6 +603,47 @@ impl Jvm {
576603
}
577604
}
578605

606+
/// Retrieves the field `field_name` of a created `Instance`.
607+
pub fn field(&self, instance: &Instance, field_name: &str) -> errors::Result<Instance> {
608+
debug(&format!("Retrieving field {} of class {}", field_name, instance.class_name));
609+
unsafe {
610+
let invoke_method_signature = format!(
611+
"(Ljava/lang/String;)L{};",
612+
INVO_IFACE_NAME);
613+
// Get the method ID for the `NativeInvocation.field`
614+
let field_method = (self.jni_get_method_id)(
615+
self.jni_env,
616+
self.native_invocation_class,
617+
utils::to_java_string("field"),
618+
utils::to_java_string(invoke_method_signature.as_ref()),
619+
);
620+
621+
// First argument: create a jstring to pass as argument for the field_name
622+
let field_name_jstring: jstring = (self.jni_new_string_utf)(
623+
self.jni_env,
624+
utils::to_java_string(field_name),
625+
);
626+
627+
// Call the method of the instance
628+
let native_invocation_instance = (self.jni_call_object_method)(
629+
self.jni_env,
630+
instance.jinstance,
631+
field_method,
632+
field_name_jstring,
633+
);
634+
635+
let native_invocation_global_instance = create_global_ref_from_local_ref(native_invocation_instance, self.jni_env)?;
636+
// Prevent memory leaks from the created local references
637+
delete_java_local_ref(self.jni_env, field_name_jstring);
638+
639+
// Create and return the Instance
640+
self.do_return(Instance {
641+
jinstance: native_invocation_global_instance,
642+
class_name: UNKNOWN_FOR_RUST.to_string(),
643+
})
644+
}
645+
}
646+
579647
/// Invokes the method `method_name` of a created `Instance`, passing an array of `InvocationArg`s.
580648
/// It returns a Result of `InstanceReceiver` that may be used to get an underlying `Receiver<Instance>`. The result of the invocation will come via this Receiver.
581649
pub fn invoke_to_channel(&self, instance: &Instance, method_name: &str, inv_args: &[InvocationArg]) -> errors::Result<InstanceReceiver> {
@@ -1677,7 +1745,7 @@ impl<'a> ChainableInstance<'a> {
16771745
Ok(ChainableInstance::new(instance, self.jvm))
16781746
}
16791747

1680-
/// Creates a clone of the provided Instance
1748+
/// Creates a clone of the Instance
16811749
pub fn clone_instance(&self) -> errors::Result<ChainableInstance> {
16821750
let instance = self.jvm.clone_instance(&self.instance)?;
16831751
Ok(ChainableInstance::new(instance, self.jvm))
@@ -1689,10 +1757,17 @@ impl<'a> ChainableInstance<'a> {
16891757
Ok(ChainableInstance::new(instance, self.jvm))
16901758
}
16911759

1760+
/// Retrieves the field `field_name` of the `Instance`.
1761+
pub fn field(&self, field_name: &str) -> errors::Result<ChainableInstance> {
1762+
let instance = self.jvm.field(&self.instance, field_name)?;
1763+
Ok(ChainableInstance::new(instance, self.jvm))
1764+
}
1765+
16921766
/// Returns the Rust representation of the provided instance
16931767
pub fn to_rust<T>(self) -> errors::Result<T> where T: DeserializeOwned {
16941768
self.jvm.to_rust(self.instance)
16951769
}
1770+
16961771
}
16971772

16981773
#[derive(Debug)]

rust/src/lib.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ mod lib_unit_tests {
278278
thread::sleep(thousand_millis);
279279
}
280280

281-
// #[test]
281+
// #[test]
282282
// #[ignore]
283283
fn _memory_leaks_create_instances_in_different_threads() {
284284
for i in 0..100000000 {
@@ -496,4 +496,41 @@ mod lib_unit_tests {
496496

497497
assert!(product == 18);
498498
}
499+
500+
#[test]
501+
fn static_invocation_chain_and_to_rust() {
502+
let jvm: Jvm = JvmBuilder::new()
503+
.build()
504+
.unwrap();
505+
506+
let static_invocation = jvm.static_class("java.lang.System").unwrap();
507+
508+
let _: isize = jvm.chain(static_invocation)
509+
.invoke("currentTimeMillis", &[]).unwrap()
510+
.to_rust().unwrap();
511+
}
512+
513+
#[test]
514+
fn access_class_field() {
515+
let jvm: Jvm = JvmBuilder::new()
516+
.build()
517+
.unwrap();
518+
519+
let static_invocation = jvm.static_class("java.lang.System").unwrap();
520+
let field_instance_res = jvm.field(&static_invocation, "out");
521+
assert!(field_instance_res.is_ok());
522+
}
523+
524+
#[test]
525+
fn java_hello_world() {
526+
let jvm: Jvm = JvmBuilder::new()
527+
.build()
528+
.unwrap();
529+
530+
let system = jvm.static_class("java.lang.System").unwrap();
531+
let _ = jvm.chain(system)
532+
.field("out").unwrap()
533+
.invoke("println", &vec![InvocationArg::from("Hello World")]).unwrap()
534+
.collect();
535+
}
499536
}

0 commit comments

Comments
 (0)