Skip to content

Commit

Permalink
Make PlanningSolution, Entity and Problem Facts extend translated cla…
Browse files Browse the repository at this point in the history
…sses

PythonWrapperGenerator now uses PythonBytecodeToJavaBytecodeTranslator's
ClassLoader, which allows PythonClassTranslator to extend
Planning Entities and Problem Facts.

Because JPype automatically maps Java fields to Python fields, the
generated field and method names were changed to illegal identifiers
in Python (but legal in Java). This is becausing having the
fields/methods "hides" the Python Object fields
(which are accessed via __getattr__, which is only called if the field
is undefined), thus passing a PythonLikeObject to CPython, which
can cause type confusion when the user access it.

Has parent checks need to be changed, since every planning entity,
problem fact and planning solution now extend a class
(the translated Python class). For fields and method, the check
basically changed to trying to get the field/method from the
parent class, and creating the field/method if it does not
exist.

Added a new constructor <init>(PythonLikeType) to
entities/problem facts/planning solutions, which allow
PythonClassTranslator to extend them. Because the
extended class will NOT have the old expected constructor
(only a no-args and one that takes a PythonLikeType), added
a new "init" method for domain objects that serves the
old expected constructor purpose.

On construction, we set all the parent object fields from
the CPython Object. Additionally, we call both the OptaPy
and translated setter when setting domain fields.

Because class translation will happen for all domain object,
class translation will create a proxy to the CPython version
if it was unable to translate the method for any reason.

Bug fixes:

- Update getPythonLikeObjectForAttribute so that it unwraps
  OptaPyObjectReference instead of Long

- extract_joiners did not unpack the type tuple, thus sending a
  tuple containing a tuple containing the stream types to the
  translation, causing the code to not be translated.

- Added missing compareTo method for PythonString
  • Loading branch information
Christopher-Chianelli committed Jun 8, 2022
1 parent b5543e2 commit e87a9df
Show file tree
Hide file tree
Showing 12 changed files with 417 additions and 217 deletions.

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions optapy-core/src/main/python/constraint_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,22 +290,22 @@ def extract_joiners(joiner_tuple, *stream_types):
property_function = function_cast(joiner_info.join_function, stream_types[0])
created_joiner = joiner_info.joiner_creator(property_function)
elif isinstance(joiner_info, PropertyJoiner):
left_property_function = function_cast(joiner_info.left_join_function, stream_types[:-1])
left_property_function = function_cast(joiner_info.left_join_function, *stream_types[:-1])
right_property_function = function_cast(joiner_info.right_join_function, stream_types[-1])
created_joiner = joiner_info.joiner_creator(left_property_function, right_property_function)
elif isinstance(joiner_info, SameOverlappingPropertyUniJoiner):
start_function = function_cast(joiner_info.start_function, stream_types[0])
end_function = function_cast(joiner_info.end_function, stream_types[0])
created_joiner = joiner_info.joiner_creator(start_function, end_function)
elif isinstance(joiner_info, OverlappingPropertyJoiner):
left_start_function = function_cast(joiner_info.left_start_function, stream_types[:-1])
left_end_function = function_cast(joiner_info.left_end_function, stream_types[:-1])
left_start_function = function_cast(joiner_info.left_start_function, *stream_types[:-1])
left_end_function = function_cast(joiner_info.left_end_function, *stream_types[:-1])
right_start_function = function_cast(joiner_info.right_start_function, stream_types[-1])
right_end_function = function_cast(joiner_info.right_end_function, stream_types[-1])
created_joiner = joiner_info.joiner_creator(left_start_function, left_end_function,
right_start_function, right_end_function)
elif isinstance(joiner_info, FilteringJoiner):
filter_function = predicate_cast(joiner_info.filter_function, stream_types)
filter_function = predicate_cast(joiner_info.filter_function, *stream_types)
created_joiner = joiner_info.joiner_creator(filter_function)
else:
raise ValueError(f'Invalid Joiner: {joiner_info}. Create Joiners via optapy.constraint.Joiners.')
Expand Down
18 changes: 10 additions & 8 deletions optapy-core/src/main/python/optaplanner_java_interop.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,9 +412,7 @@ def ensure_init():
def set_class_output_directory(path: pathlib.Path):
ensure_init()

from org.optaplanner.optapy import PythonWrapperGenerator # noqa
from org.optaplanner.python.translator import PythonBytecodeToJavaBytecodeTranslator # noqa
PythonWrapperGenerator.classOutputRootPath = path
PythonBytecodeToJavaBytecodeTranslator.classOutputRootPath = path


Expand Down Expand Up @@ -1030,49 +1028,53 @@ def _does_class_define_eq_or_hashcode(python_class):
def _generate_problem_fact_class(python_class):
ensure_init()
from org.optaplanner.optapy import PythonWrapperGenerator # noqa
from javapython import translate_python_class_to_java_class, force_update_type
class_identifier = _get_class_identifier_for_object(python_class)
optaplanner_annotations = _get_optaplanner_annotations(python_class)
parent_class = None
parent_class = translate_python_class_to_java_class(python_class).getJavaClass()
has_eq_and_hashcode = _does_class_define_eq_or_hashcode(python_class)
if len(python_class.__bases__) == 1 and hasattr(python_class.__bases__[0], '__optapy_java_class'):
parent_class = get_class(python_class.__bases__[0])

out = PythonWrapperGenerator.defineProblemFactClass(_compose_unique_class_name(class_identifier),
parent_class,
has_eq_and_hashcode,
optaplanner_annotations)
class_identifier_to_java_class_map[class_identifier] = out
force_update_type(python_class, out.getField('$TYPE').get(None))
return out


def _generate_planning_entity_class(python_class: Type, annotation_data: Dict[str, Any]):
ensure_init()
from org.optaplanner.optapy import PythonWrapperGenerator # noqa
from javapython import translate_python_class_to_java_class, force_update_type
class_identifier = _get_class_identifier_for_object(python_class)
optaplanner_annotations = _get_optaplanner_annotations(python_class)
parent_class = None
parent_class = translate_python_class_to_java_class(python_class).getJavaClass()
has_eq_and_hashcode = _does_class_define_eq_or_hashcode(python_class)
if len(python_class.__bases__) == 1 and hasattr(python_class.__bases__[0], '__optapy_java_class'):
parent_class = get_class(python_class.__bases__[0])
out = PythonWrapperGenerator.definePlanningEntityClass(_compose_unique_class_name(class_identifier),
parent_class,
has_eq_and_hashcode,
optaplanner_annotations,
_to_java_map(annotation_data))
class_identifier_to_java_class_map[class_identifier] = out
force_update_type(python_class, out.getField('$TYPE').get(None))
return out


def _generate_planning_solution_class(python_class: Type) -> JClass:
ensure_init()
from org.optaplanner.optapy import PythonWrapperGenerator # noqa
from javapython import translate_python_class_to_java_class, force_update_type
class_identifier = _get_class_identifier_for_object(python_class)
optaplanner_annotations = _get_optaplanner_annotations(python_class)
parent_class = translate_python_class_to_java_class(python_class).getJavaClass()
has_eq_and_hashcode = _does_class_define_eq_or_hashcode(python_class)
out = PythonWrapperGenerator.definePlanningSolutionClass(_compose_unique_class_name(class_identifier),
parent_class,
has_eq_and_hashcode,
optaplanner_annotations)
class_identifier_to_java_class_map[class_identifier] = out
force_update_type(python_class, out.getField('$TYPE').get(None))
return out


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,30 @@

public class FieldDescriptor {

final String fieldName;
final String pythonFieldName;

final String javaFieldName;

final String declaringClassInternalName;
final String javaFieldTypeDescriptor;
final PythonLikeType fieldPythonLikeType;

public FieldDescriptor(String fieldName, String declaringClassInternalName, String javaFieldTypeDescriptor,
public FieldDescriptor(String pythonFieldName, String javaFieldName,
String declaringClassInternalName, String javaFieldTypeDescriptor,
PythonLikeType fieldPythonLikeType) {
this.fieldName = fieldName;
this.pythonFieldName = pythonFieldName;
this.javaFieldName = javaFieldName;
this.declaringClassInternalName = declaringClassInternalName;
this.javaFieldTypeDescriptor = javaFieldTypeDescriptor;
this.fieldPythonLikeType = fieldPythonLikeType;
}

public String getFieldName() {
return fieldName;
public String getPythonFieldName() {
return pythonFieldName;
}

public String getJavaFieldName() {
return javaFieldName;
}

public String getDeclaringClassInternalName() {
Expand All @@ -45,20 +53,23 @@ public boolean equals(Object o) {
return false;
}
FieldDescriptor that = (FieldDescriptor) o;
return fieldName.equals(that.fieldName) && declaringClassInternalName.equals(that.declaringClassInternalName)
return pythonFieldName.equals(that.pythonFieldName) && javaFieldName.equals(that.javaFieldName)
&& declaringClassInternalName.equals(that.declaringClassInternalName)
&& javaFieldTypeDescriptor.equals(that.javaFieldTypeDescriptor)
&& fieldPythonLikeType.equals(that.fieldPythonLikeType);
}

@Override
public int hashCode() {
return Objects.hash(fieldName, declaringClassInternalName, javaFieldTypeDescriptor, fieldPythonLikeType);
return Objects.hash(pythonFieldName, javaFieldName, declaringClassInternalName, javaFieldTypeDescriptor,
fieldPythonLikeType);
}

@Override
public String toString() {
return "FieldDescriptor{" +
"fieldName='" + fieldName + '\'' +
"pythonFieldName='" + pythonFieldName + '\'' +
", javaFieldName='" + javaFieldName + '\'' +
", declaringClassInternalName='" + declaringClassInternalName + '\'' +
", javaFieldTypeDescriptor='" + javaFieldTypeDescriptor + '\'' +
", fieldPythonLikeType=" + fieldPythonLikeType +
Expand Down
Loading

0 comments on commit e87a9df

Please sign in to comment.