Skip to content

Commit b3adca3

Browse files
committed
MagicBean is implemented
1 parent 0df65af commit b3adca3

12 files changed

Lines changed: 857 additions & 519 deletions

File tree

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
language: java
2-
2+
script: mvn -X clean install
33
jdk:
44
- oraclejdk9
55

pom.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,15 @@
5252
<powermock.version>1.4.12</powermock.version>
5353
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
5454
</properties>
55-
<build>
55+
<reporting>
5656
<plugins>
5757
<plugin>
5858
<groupId>org.apache.maven.plugins</groupId>
5959
<artifactId>maven-site-plugin</artifactId>
6060
<version>3.6</version>
6161
<configuration>
6262
<reportPlugins>
63+
<!--
6364
<plugin>
6465
<groupId>org.apache.maven.plugins</groupId>
6566
<artifactId>maven-project-info-reports-plugin</artifactId>
@@ -76,9 +77,14 @@
7677
<artifactId>maven-javadoc-plugin</artifactId>
7778
<version>2.7</version>
7879
</plugin>
80+
-->
7981
</reportPlugins>
8082
</configuration>
8183
</plugin>
84+
</plugins>
85+
</reporting>
86+
<build>
87+
<plugins>
8288
<plugin>
8389
<artifactId>maven-assembly-plugin</artifactId>
8490
<configuration>
Lines changed: 175 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,163 +1,195 @@
11
package com.scriptbasic.executors.operators;
22

3-
import java.lang.reflect.Method;
4-
import java.util.ArrayList;
5-
import java.util.List;
6-
73
import com.scriptbasic.executors.AbstractIdentifieredExpression;
84
import com.scriptbasic.executors.rightvalues.AbstractPrimitiveRightValue;
95
import com.scriptbasic.executors.rightvalues.ArrayElementAccess;
106
import com.scriptbasic.executors.rightvalues.FunctionCall;
117
import com.scriptbasic.executors.rightvalues.VariableAccess;
12-
import com.scriptbasic.interfaces.BasicRuntimeException;
13-
import com.scriptbasic.interfaces.ExecutionException;
14-
import com.scriptbasic.interfaces.Expression;
15-
import com.scriptbasic.interfaces.ExpressionList;
16-
import com.scriptbasic.interfaces.ExtendedInterpreter;
17-
import com.scriptbasic.interfaces.RightValue;
8+
import com.scriptbasic.interfaces.*;
189
import com.scriptbasic.utility.ExpressionUtility;
1910
import com.scriptbasic.utility.KlassUtility;
2011
import com.scriptbasic.utility.ReflectionUtility;
2112
import com.scriptbasic.utility.RightValueUtility;
2213

14+
import java.lang.reflect.Method;
15+
import java.util.ArrayList;
16+
import java.util.Arrays;
17+
import java.util.List;
18+
import java.util.stream.Collectors;
19+
import java.util.stream.IntStream;
20+
2321
/**
2422
* This is the highest priority operator (priority 1) that is used to access a
2523
* field of an object. This operator is the (.) dot operator.
26-
*
24+
*
2725
* @author Peter Verhas
28-
*
2926
*/
3027
public class JavaObjectFieldAccessOperator extends AbstractBinaryOperator {
3128

32-
private Object fetchFieldObject(ExtendedInterpreter extendedInterpreter)
33-
throws ExecutionException {
34-
Object object = getLeftOperandObject(extendedInterpreter);
35-
AbstractIdentifieredExpression rightOp = (AbstractIdentifieredExpression) getRightOperand();
36-
String fieldName = rightOp.getVariableName();
37-
return KlassUtility.getField(object, fieldName);
38-
}
39-
40-
private RightValue fetchField(ExtendedInterpreter extendedInterpreter)
41-
throws ExecutionException {
42-
Object fieldObject = fetchFieldObject(extendedInterpreter);
43-
return RightValueUtility.createRightValue(fieldObject);
44-
}
45-
46-
private static Class<?>[] getClassArray(List<RightValue> args) {
47-
ArrayList<Class<?>> result = null;
48-
if (args != null) {
49-
result = new ArrayList<Class<?>>();
50-
for (RightValue arg : args) {
51-
result.add(RightValueUtility.getValueObject(arg).getClass());
52-
}
53-
}
54-
return result == null ? null : result.toArray(new Class<?>[0]);
55-
}
56-
57-
private RightValue callMethod(final ExtendedInterpreter interpreter,
58-
final Object object, final Class<?> klass)
59-
throws ExecutionException {
60-
RightValue result = null;
61-
FunctionCall rightOp = (FunctionCall) getRightOperand();
62-
String methodName = rightOp.getVariableName();
63-
ExpressionList expressionList = rightOp.getExpressionList();
64-
List<RightValue> args = ExpressionUtility.evaluateExpressionList(
65-
interpreter, expressionList);
66-
Method method = null;
67-
final Class<?> calculatedKlass = klass == null ? object.getClass()
68-
: klass;
69-
method = interpreter.getJavaMethod(calculatedKlass, methodName);
70-
if (method == null) {
71-
try {
72-
method = calculatedKlass.getMethod(methodName,
73-
getClassArray(args));
74-
} catch (Exception e) {
75-
throw new BasicRuntimeException("Method '" + methodName
76-
+ "' from class '" + klass + "' can not be accessed", e);
77-
}
78-
}
79-
Object methodResultObject = null;
80-
methodResultObject = ReflectionUtility.invoke(methodName, interpreter,
81-
method, object, args);
82-
83-
result = RightValueUtility.createRightValue(methodResultObject);
84-
return result;
85-
}
86-
87-
@SuppressWarnings("unchecked")
88-
private Object getLeftOperandObject(ExtendedInterpreter extendedInterpreter)
89-
throws ExecutionException {
90-
RightValue leftOp = getLeftOperand().evaluate(extendedInterpreter);
91-
if (!(leftOp instanceof AbstractPrimitiveRightValue<?>)) {
92-
throw new BasicRuntimeException("Can not get field access from "
93-
+ (leftOp == null ? "null" : leftOp.getClass())
94-
+ " from variable "
95-
+ ((VariableAccess) getLeftOperand()).getVariableName());
96-
}
97-
return ((AbstractPrimitiveRightValue<Object>) leftOp).getValue();
98-
}
99-
100-
private Class<?> getStaticClass(ExtendedInterpreter interpreter) {
101-
Class<?> result = null;
102-
if (getLeftOperand() instanceof VariableAccess) {
103-
String classAsName = ((VariableAccess) getLeftOperand())
104-
.getVariableName();
105-
if (interpreter.getUseMap().containsKey(classAsName)) {
106-
result = interpreter.getUseMap().get(classAsName);
107-
}
108-
}
109-
return result;
110-
}
111-
112-
private static Object getArrayElement(Object[] array, Integer index)
113-
throws ExecutionException {
114-
if (index < 0) {
115-
throw new BasicRuntimeException("Can not use " + index
116-
+ " < 0 as array index");
117-
}
118-
if (index >= array.length) {
119-
throw new BasicRuntimeException("Cann not use index" + index
120-
+ " > max index" + (array.length - 1) + " ");
121-
}
122-
return array[index];
123-
}
124-
125-
@Override
126-
public RightValue evaluate(ExtendedInterpreter interpreter)
127-
throws ExecutionException {
128-
RightValue result = null;
129-
Expression rightOp = getRightOperand();
130-
131-
if (rightOp instanceof VariableAccess) {
132-
133-
result = fetchField(interpreter);
134-
135-
} else if (rightOp instanceof FunctionCall) {
136-
Class<?> klass = getStaticClass(interpreter);
137-
Object object = null;
138-
if (klass == null) {
139-
object = getLeftOperandObject(interpreter);
140-
}
141-
result = callMethod(interpreter, object, klass);
142-
143-
} else if (rightOp instanceof ArrayElementAccess) {
144-
Object variable = fetchFieldObject(interpreter);
145-
for (Expression expression : ((ArrayElementAccess) rightOp)
146-
.getExpressionList()) {
147-
if (variable instanceof Object[]) {
148-
Integer index = RightValueUtility
149-
.convert2Integer(expression.evaluate(interpreter));
150-
variable = getArrayElement((Object[]) variable, index);
151-
} else {
152-
throw new BasicRuntimeException(
153-
"Java object field is not array, can not access it that way.");
154-
}
155-
}
156-
result = RightValueUtility.createRightValue(variable);
157-
} else {
158-
throw new BasicRuntimeException(
159-
"Field access operator is not implemented to handle variable field.");
160-
}
161-
return result;
162-
}
29+
private static Class<?>[] getClassArray(List<RightValue> args) {
30+
final ArrayList<Class<?>> result = new ArrayList<>();
31+
if (args != null) {
32+
for (RightValue arg : args) {
33+
result.add(RightValueUtility.getValueObject(arg).getClass());
34+
}
35+
}
36+
return result.isEmpty() ? new Class<?>[0] : result.toArray(new Class<?>[0]);
37+
}
38+
39+
private static Object getArrayElement(Object[] array, Integer index)
40+
throws ExecutionException {
41+
if (index < 0) {
42+
throw new BasicRuntimeException("Can not use " + index
43+
+ " < 0 as array index");
44+
}
45+
if (index >= array.length) {
46+
throw new BasicRuntimeException("Cann not use index" + index
47+
+ " > max index" + (array.length - 1) + " ");
48+
}
49+
return array[index];
50+
}
51+
52+
private Object fetchFieldObject(ExtendedInterpreter extendedInterpreter)
53+
throws ExecutionException {
54+
Object object = getLeftOperandObject(extendedInterpreter);
55+
AbstractIdentifieredExpression rightOp = (AbstractIdentifieredExpression) getRightOperand();
56+
String fieldName = rightOp.getVariableName();
57+
return KlassUtility.getField(object, fieldName);
58+
}
59+
60+
private RightValue fetchField(ExtendedInterpreter extendedInterpreter)
61+
throws ExecutionException {
62+
Object fieldObject = fetchFieldObject(extendedInterpreter);
63+
return RightValueUtility.createRightValue(fieldObject);
64+
}
65+
66+
private RightValue callMethod(final ExtendedInterpreter interpreter,
67+
final Object object, final Class<?> klass)
68+
throws ExecutionException {
69+
RightValue result = null;
70+
FunctionCall rightOp = (FunctionCall) getRightOperand();
71+
String methodName = rightOp.getVariableName();
72+
ExpressionList expressionList = rightOp.getExpressionList();
73+
List<RightValue> args = ExpressionUtility.evaluateExpressionList(
74+
interpreter, expressionList);
75+
final Class<?> calculatedKlass = klass == null ? object.getClass()
76+
: klass;
77+
Method method = interpreter.getJavaMethod(calculatedKlass, methodName);
78+
if (method == null) {
79+
try {
80+
method = findAppropriateMethod(calculatedKlass, methodName, args);
81+
} catch (NoSuchMethodException e) {
82+
throw new BasicRuntimeException("Method '" + methodName
83+
+ "' from class '" + klass + "' can not be accessed", e);
84+
}
85+
}
86+
Object methodResultObject = null;
87+
methodResultObject = ReflectionUtility.invoke(methodName, interpreter,
88+
method, object, args);
89+
90+
result = RightValueUtility.createRightValue(methodResultObject);
91+
return result;
92+
}
93+
94+
/**
95+
* Find a method that can be called from the BASIC program with the name as specified in the BASIC program
96+
* and with the arguments specified in the BASIC program.
97+
* <p>
98+
* The code tries to find all methods that have the given name, the same or more number of arguments and the
99+
* arguments can be assigned from the values of the BASIC call. If there are more arguments then they will be
100+
* null during the call, so it is checked that these arguments are not of primitive type.
101+
* <p>
102+
* If there are more than one such method then the one given by the reflection call
103+
* {@link Class#getMethod(String, Class[])}
104+
* returns. This requires exact match of the arguments.
105+
*
106+
* @param klass the class in which we look for the method
107+
* @param methodName the name of the method
108+
* @param args the arguments we want to pass to the method
109+
* @return the method that matches the name and the argument types
110+
* @throws BasicRuntimeException if there is no method with that name
111+
* @throws NoSuchMethodException if there is more than one method with name and matching argument
112+
* but none of them is exact match.
113+
*/
114+
private Method findAppropriateMethod(Class<?> klass, String methodName, List<RightValue> args) throws BasicRuntimeException, NoSuchMethodException {
115+
Class<?>[] argClasses = getClassArray(args);
116+
List<Method> methods = Arrays.stream(klass.getMethods()).filter(method -> method.getName().equals(methodName))
117+
.filter(method -> method.getParameterTypes().length >= argClasses.length)
118+
.filter(method ->
119+
!IntStream.range(0, method.getParameterTypes().length - 1)
120+
.anyMatch(i -> i < argClasses.length ? !method.getParameterTypes()[i].isAssignableFrom(argClasses[i])
121+
: method.getParameterTypes()[i].isPrimitive())
122+
).collect(Collectors.toList());
123+
if (methods.size() > 1) {
124+
return klass.getMethod(methodName, argClasses);
125+
}
126+
if (methods.size() == 0) {
127+
throw new BasicRuntimeException("There is no matching method for '" + methodName + "' in class '" + klass.getName() + "'");
128+
}
129+
return methods.get(0);
130+
}
131+
132+
@SuppressWarnings("unchecked")
133+
private Object getLeftOperandObject(ExtendedInterpreter extendedInterpreter)
134+
throws ExecutionException {
135+
RightValue leftOp = getLeftOperand().evaluate(extendedInterpreter);
136+
if (!(leftOp instanceof AbstractPrimitiveRightValue<?>)) {
137+
throw new BasicRuntimeException("Can not get field access from "
138+
+ (leftOp == null ? "null" : leftOp.getClass())
139+
+ " from variable "
140+
+ ((VariableAccess) getLeftOperand()).getVariableName());
141+
}
142+
return ((AbstractPrimitiveRightValue<Object>) leftOp).getValue();
143+
}
144+
145+
private Class<?> getStaticClass(ExtendedInterpreter interpreter) {
146+
Class<?> result = null;
147+
if (getLeftOperand() instanceof VariableAccess) {
148+
String classAsName = ((VariableAccess) getLeftOperand())
149+
.getVariableName();
150+
if (interpreter.getUseMap().containsKey(classAsName)) {
151+
result = interpreter.getUseMap().get(classAsName);
152+
}
153+
}
154+
return result;
155+
}
156+
157+
@Override
158+
public RightValue evaluate(ExtendedInterpreter interpreter)
159+
throws ExecutionException {
160+
RightValue result = null;
161+
Expression rightOp = getRightOperand();
162+
163+
if (rightOp instanceof VariableAccess) {
164+
165+
result = fetchField(interpreter);
166+
167+
} else if (rightOp instanceof FunctionCall) {
168+
Class<?> klass = getStaticClass(interpreter);
169+
Object object = null;
170+
if (klass == null) {
171+
object = getLeftOperandObject(interpreter);
172+
}
173+
result = callMethod(interpreter, object, klass);
174+
175+
} else if (rightOp instanceof ArrayElementAccess) {
176+
Object variable = fetchFieldObject(interpreter);
177+
for (Expression expression : ((ArrayElementAccess) rightOp)
178+
.getExpressionList()) {
179+
if (variable instanceof Object[]) {
180+
Integer index = RightValueUtility
181+
.convert2Integer(expression.evaluate(interpreter));
182+
variable = getArrayElement((Object[]) variable, index);
183+
} else {
184+
throw new BasicRuntimeException(
185+
"Java object field is not array, can not access it that way.");
186+
}
187+
}
188+
result = RightValueUtility.createRightValue(variable);
189+
} else {
190+
throw new BasicRuntimeException(
191+
"Field access operator is not implemented to handle variable field.");
192+
}
193+
return result;
194+
}
163195
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.scriptbasic.interfaces;
2+
3+
/**
4+
* When a BASIC program has a reference to an object that is an instance of a class that implements Magic.Bean
5+
* setting a field and getting the value of a field is done through the "magic" methods defined in this interface
6+
* instead of setting the field or getting the value of the field through the getters/setters or accessing the
7+
* fields directly.
8+
*/
9+
public interface Magic {
10+
11+
interface Getter {
12+
Object get(String fieldName) throws BasicRuntimeException;
13+
}
14+
15+
interface Setter {
16+
void set(String fieldName, Object value) throws BasicRuntimeException;
17+
}
18+
19+
interface Bean extends Getter, Setter {
20+
}
21+
}

0 commit comments

Comments
 (0)