Skip to content

Commit ea4ac6e

Browse files
committed
Implement Python-to-Java type deconversion for additional types (time, datetime, list, etc.), enhance utility class structure, and add support for various java.time formats.
1 parent b1b209c commit ea4ac6e

25 files changed

+464
-134
lines changed

.idea/misc.xml

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bind/python-executor-bind-core/src/main/java/io/maksymuimanov/python/deconverter/AbstractPythonTypeDeconverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
public abstract class AbstractPythonTypeDeconverter<R extends PythonRepresentation, P> implements PythonTypeDeconverter<R> {
1515
private final Map<CharSequence, P> cache;
1616

17-
public AbstractPythonTypeDeconverter() {
17+
protected AbstractPythonTypeDeconverter() {
1818
this(new ConcurrentHashMap<>());
1919
}
2020

bind/python-executor-bind-core/src/main/java/io/maksymuimanov/python/deconverter/PythonBigDecimalDeconverter.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
import java.math.BigDecimal;
1010

1111
public class PythonBigDecimalDeconverter extends AbstractPythonTypeDeconverter<PythonBigDecimal, BigDecimal> {
12+
@Override
13+
public @Nullable Object deconvert(PythonBigDecimal pythonRepresentation, PythonDeserializer pythonDeserializer) {
14+
return this.deconvert(pythonRepresentation, BigDecimal.class, pythonDeserializer);
15+
}
16+
1217
@Override
1318
@Nullable
1419
@SuppressWarnings("unchecked")
@@ -23,7 +28,7 @@ public <T> T deconvert(PythonBigDecimal pythonRepresentation, Class<T> clazz, Py
2328

2429
@Override
2530
public PythonBigDecimal resolve(CharSequence value, PythonDeserializer pythonDeserializer) {
26-
BigDecimal result = this.getValue(value, (key) -> new BigDecimal(key.toString()));
31+
BigDecimal result = this.getValue(value, key -> new BigDecimal(key.toString()));
2732
return new PythonBigDecimal(result);
2833
}
2934

bind/python-executor-bind-core/src/main/java/io/maksymuimanov/python/deconverter/PythonBigIntegerDeconverter.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
import java.math.BigInteger;
1010

1111
public class PythonBigIntegerDeconverter extends AbstractPythonTypeDeconverter<PythonBigInteger, BigInteger> {
12+
@Override
13+
public @Nullable Object deconvert(PythonBigInteger pythonRepresentation, PythonDeserializer pythonDeserializer) {
14+
return this.deconvert(pythonRepresentation, BigInteger.class, pythonDeserializer);
15+
}
16+
1217
@Override
1318
@Nullable
1419
@SuppressWarnings("unchecked")
@@ -23,7 +28,7 @@ public <T> T deconvert(PythonBigInteger pythonRepresentation, Class<T> clazz, Py
2328

2429
@Override
2530
public PythonBigInteger resolve(CharSequence value, PythonDeserializer pythonDeserializer) {
26-
BigInteger result = this.getValue(value, (key) -> new BigInteger(key.toString()));
31+
BigInteger result = this.getValue(value, key -> new BigInteger(key.toString()));
2732
return new PythonBigInteger(result);
2833
}
2934

bind/python-executor-bind-core/src/main/java/io/maksymuimanov/python/deconverter/PythonBooleanDeconverter.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
import org.jspecify.annotations.Nullable;
88

99
public class PythonBooleanDeconverter extends AbstractPythonTypeDeconverter<PythonBoolean, Boolean> {
10+
@Override
11+
public @Nullable Object deconvert(PythonBoolean pythonRepresentation, PythonDeserializer pythonDeserializer) {
12+
return this.deconvert(pythonRepresentation, Boolean.class, pythonDeserializer);
13+
}
14+
1015
@Override
1116
@Nullable
1217
@SuppressWarnings("unchecked")
@@ -20,7 +25,7 @@ public <T> T deconvert(PythonBoolean pythonRepresentation, Class<T> clazz, Pytho
2025

2126
@Override
2227
public PythonBoolean resolve(CharSequence value, PythonDeserializer pythonDeserializer) {
23-
Boolean result = this.getValue(value, (key) -> Boolean.parseBoolean(key.toString()));
28+
Boolean result = this.getValue(value, key -> Boolean.parseBoolean(key.toString()));
2429
return new PythonBoolean(result);
2530
}
2631

bind/python-executor-bind-core/src/main/java/io/maksymuimanov/python/deconverter/PythonDateDeconverter.java

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,25 @@
88

99
import java.time.LocalDate;
1010
import java.time.Month;
11+
import java.time.ZoneId;
1112
import java.util.Arrays;
1213
import java.util.Map;
1314
import java.util.regex.Matcher;
1415
import java.util.regex.Pattern;
1516
import java.util.stream.Collectors;
1617

1718
public class PythonDateDeconverter extends AbstractPythonTypeDeconverter<PythonDate, LocalDate> {
18-
public static final Map<String, Integer> MONTHS = Arrays.stream(Month.values())
19+
protected static final Map<String, Integer> MONTHS = Arrays.stream(Month.values())
1920
.collect(Collectors.toUnmodifiableMap(month -> month.name().substring(0, 3), Month::getValue));
20-
public static final Pattern YEAR_PATTERN = Pattern.compile("[0-9]{4}");
21-
public static final Pattern MONTH_PATTERN = Pattern.compile("[A-Za-z]{3}");
22-
public static final Pattern DAY_PATTERN = Pattern.compile("[0-9]{2}");
23-
public static final int TOTAL_MONTHS = 12;
21+
protected static final Pattern YEAR_PATTERN = Pattern.compile("[0-9]{4}");
22+
protected static final Pattern MONTH_PATTERN = Pattern.compile("[A-Za-z]{3}");
23+
protected static final Pattern DAY_PATTERN = Pattern.compile("[0-9]{2}");
24+
protected static final int TOTAL_MONTHS = 12;
25+
26+
@Override
27+
public @Nullable Object deconvert(PythonDate pythonRepresentation, PythonDeserializer pythonDeserializer) {
28+
return this.deconvert(pythonRepresentation, LocalDate.class, pythonDeserializer);
29+
}
2430

2531
@Override
2632
@Nullable
@@ -29,14 +35,18 @@ public <T> T deconvert(PythonDate pythonRepresentation, Class<T> clazz, PythonDe
2935
LocalDate date = pythonRepresentation.getValue();
3036
if (LocalDate.class.equals(clazz)) {
3137
return (T) date;
38+
} else if (java.util.Date.class.equals(clazz)) {
39+
return (T) java.util.Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant());
40+
} else if (java.sql.Date.class.equals(clazz)) {
41+
return (T) java.sql.Date.valueOf(date);
3242
} else {
3343
throw new PythonUnsupportedTypeDeconversionException(clazz + " is not supported");
3444
}
3545
}
3646

3747
@Override
3848
public PythonDate resolve(CharSequence value, PythonDeserializer pythonDeserializer) {
39-
LocalDate result = this.getValue(value, (key) -> {
49+
LocalDate result = this.getValue(value, key -> {
4050
StringBuilder dateBuilder = new StringBuilder(key);
4151
int year = -1, month = -1, day = -1;
4252

@@ -55,22 +65,33 @@ public PythonDate resolve(CharSequence value, PythonDeserializer pythonDeseriali
5565
dateBuilder.delete(monthMatcher.start(), monthMatcher.end());
5666
}
5767

68+
int first = -1, second = -1;
5869
Matcher dayMatcher = DAY_PATTERN.matcher(dateBuilder);
5970
while (dayMatcher.find()) {
6071
int datePart = Integer.parseInt(dayMatcher.group());
6172
if (month != -1) {
6273
day = datePart;
63-
} else if (day == -1) {
64-
if (datePart > TOTAL_MONTHS) {
65-
day = datePart;
74+
} else if (second == -1) {
75+
if (first != -1) {
76+
second = datePart;
6677
} else {
67-
month = datePart;
78+
first = datePart;
6879
}
6980
} else {
7081
break;
7182
}
7283
}
73-
84+
85+
if (month == -1) {
86+
if (first > TOTAL_MONTHS || second <= TOTAL_MONTHS) {
87+
day = first;
88+
month = second;
89+
} else {
90+
month = first;
91+
day = second;
92+
}
93+
}
94+
7495
return LocalDate.of(year, month, day);
7596
});
7697
return new PythonDate(result);
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package io.maksymuimanov.python.deconverter;
2+
3+
import io.maksymuimanov.python.bind.PythonDateTime;
4+
import io.maksymuimanov.python.deserializer.PythonDeserializer;
5+
import io.maksymuimanov.python.exception.PythonUnsupportedTypeDeconversionException;
6+
import io.maksymuimanov.python.util.StringUtils;
7+
import org.jspecify.annotations.Nullable;
8+
9+
import java.sql.Timestamp;
10+
import java.time.*;
11+
import java.util.Arrays;
12+
import java.util.regex.Matcher;
13+
import java.util.regex.Pattern;
14+
15+
public class PythonDateTimeDeconverter extends AbstractPythonTypeDeconverter<PythonDateTime, LocalDateTime> {
16+
public static final Pattern NUMBER_PATTERN = Pattern.compile("\\d+");
17+
18+
@Override
19+
public @Nullable Object deconvert(PythonDateTime pythonRepresentation, PythonDeserializer pythonDeserializer) {
20+
return this.deconvert(pythonRepresentation, LocalDateTime.class, pythonDeserializer);
21+
}
22+
23+
@Override
24+
@Nullable
25+
@SuppressWarnings("unchecked")
26+
public <T> T deconvert(PythonDateTime pythonRepresentation, Class<T> clazz, PythonDeserializer pythonDeserializer) {
27+
LocalDateTime dateTime = pythonRepresentation.getValue();
28+
if (LocalDateTime.class.equals(clazz)) {
29+
return (T) dateTime;
30+
} else if (Timestamp.class.equals(clazz)) {
31+
return (T) Timestamp.valueOf(dateTime);
32+
} else if (ZonedDateTime.class.equals(clazz)) {
33+
return (T) ZonedDateTime.of(dateTime, ZoneId.systemDefault());
34+
} else if (OffsetDateTime.class.equals(clazz)) {
35+
return (T) OffsetDateTime.of(dateTime, (ZoneOffset) ZoneOffset.systemDefault());
36+
} else {
37+
throw new PythonUnsupportedTypeDeconversionException(clazz + " is not supported");
38+
}
39+
}
40+
41+
@Override
42+
public PythonDateTime resolve(CharSequence value, PythonDeserializer pythonDeserializer) {
43+
LocalDateTime result = this.getValue(value, key -> {
44+
int[] dateTimeParts = new int[7];
45+
Arrays.fill(dateTimeParts, 0);
46+
Matcher matcher = NUMBER_PATTERN.matcher(key);
47+
int partIndex = 0;
48+
while (matcher.find()) {
49+
String group = matcher.group();
50+
dateTimeParts[partIndex++] = Integer.parseInt(group);
51+
}
52+
53+
return LocalDateTime.of(dateTimeParts[0], dateTimeParts[1], dateTimeParts[2], dateTimeParts[3], dateTimeParts[4], dateTimeParts[5], dateTimeParts[6] * 1000);
54+
});
55+
56+
return new PythonDateTime(result);
57+
}
58+
59+
@Override
60+
public boolean matches(CharSequence value) {
61+
return StringUtils.isDateTime(value);
62+
}
63+
}

bind/python-executor-bind-core/src/main/java/io/maksymuimanov/python/deconverter/PythonFloatDeconverter.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
import org.jspecify.annotations.Nullable;
88

99
public class PythonFloatDeconverter extends AbstractPythonTypeDeconverter<PythonFloat, Number> {
10+
@Override
11+
public @Nullable Object deconvert(PythonFloat pythonRepresentation, PythonDeserializer pythonDeserializer) {
12+
return this.deconvert(pythonRepresentation, Number.class, pythonDeserializer);
13+
}
14+
1015
@Override
1116
@Nullable
1217
@SuppressWarnings("unchecked")
@@ -23,13 +28,13 @@ public <T> T deconvert(PythonFloat pythonRepresentation, Class<T> clazz, PythonD
2328

2429
@Override
2530
public PythonFloat resolve(CharSequence value, PythonDeserializer pythonDeserializer) {
26-
Number result = this.getValue(value, (key) -> Double.parseDouble(key.toString()));
31+
Number result = this.getValue(value, key -> Double.parseDouble(key.toString()));
2732
return new PythonFloat(result);
2833
}
2934

3035
@Override
3136
public boolean matches(CharSequence value) {
32-
return this.matches(value, StringUtils::isFloat, (cs) -> {
37+
return this.matches(value, StringUtils::isFloat, cs -> {
3338
try {
3439
Number doubleValue = Double.parseDouble(value.toString());
3540
this.saveValue(value, doubleValue);

bind/python-executor-bind-core/src/main/java/io/maksymuimanov/python/deconverter/PythonIntDeconverter.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
import org.jspecify.annotations.Nullable;
88

99
public class PythonIntDeconverter extends AbstractPythonTypeDeconverter<PythonInt, Number> {
10+
@Override
11+
public @Nullable Object deconvert(PythonInt pythonRepresentation, PythonDeserializer pythonDeserializer) {
12+
return this.deconvert(pythonRepresentation, Number.class, pythonDeserializer);
13+
}
14+
1015
@Override
1116
@Nullable
1217
@SuppressWarnings("unchecked")
@@ -27,13 +32,13 @@ public <T> T deconvert(PythonInt pythonRepresentation, Class<T> clazz, PythonDes
2732

2833
@Override
2934
public PythonInt resolve(CharSequence value, PythonDeserializer pythonDeserializer) {
30-
Number result = this.getValue(value, (key) -> Long.parseLong(key.toString()));
35+
Number result = this.getValue(value, key -> Long.parseLong(key.toString()));
3136
return new PythonInt(result);
3237
}
3338

3439
@Override
3540
public boolean matches(CharSequence value) {
36-
return this.matches(value, StringUtils::isInteger, (cs) -> {
41+
return this.matches(value, StringUtils::isInteger, cs -> {
3742
try {
3843
Number longValue = Long.parseLong(value.toString());
3944
this.saveValue(value, longValue);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package io.maksymuimanov.python.deconverter;
2+
3+
import io.maksymuimanov.python.bind.PythonList;
4+
import io.maksymuimanov.python.deserializer.PythonDeserializer;
5+
import io.maksymuimanov.python.exception.PythonUnsupportedTypeDeconversionException;
6+
import io.maksymuimanov.python.script.PythonRepresentation;
7+
import io.maksymuimanov.python.util.StringUtils;
8+
import org.jspecify.annotations.Nullable;
9+
10+
import java.util.ArrayList;
11+
import java.util.LinkedList;
12+
import java.util.List;
13+
import java.util.stream.Collectors;
14+
import java.util.stream.Stream;
15+
16+
public class PythonListDeconverter extends AbstractPythonTypeDeconverter<PythonList, List<PythonRepresentation>> {
17+
@Override
18+
public @Nullable Object deconvert(PythonList pythonRepresentation, PythonDeserializer pythonDeserializer) {
19+
return this.deconvert(pythonRepresentation, List.class, pythonDeserializer);
20+
}
21+
22+
@Override
23+
@Nullable
24+
@SuppressWarnings("unchecked")
25+
public <T> T deconvert(PythonList pythonRepresentation, Class<T> clazz, PythonDeserializer pythonDeserializer) {
26+
Stream<?> stream = pythonRepresentation.getValue()
27+
.stream()
28+
.map(pr -> pythonDeserializer.deserialize(pythonRepresentation));
29+
if (List.class.equals(clazz)) {
30+
return (T) stream.toList();
31+
} else if (ArrayList.class.equals(clazz)) {
32+
return (T) stream.collect(Collectors.toCollection(ArrayList::new));
33+
} else if (LinkedList.class.equals(clazz)) {
34+
return (T) stream.collect(Collectors.toCollection(LinkedList::new));
35+
} else {
36+
throw new PythonUnsupportedTypeDeconversionException(clazz + " is not supported");
37+
}
38+
}
39+
40+
@Override
41+
public PythonList resolve(CharSequence value, PythonDeserializer pythonDeserializer) {
42+
List<PythonRepresentation> result = this.getValue(value, key -> {
43+
return List.of();
44+
});
45+
return new PythonList(result);
46+
}
47+
48+
@Override
49+
public boolean matches(CharSequence value) {
50+
return StringUtils.isList(value);
51+
}
52+
}

0 commit comments

Comments
 (0)