Fix JodaCompatibleZonedDateTime casts in Painless (#44874)

This is a temporary fix during the Joda to Java datetime transition. This will 
implicitly cast a JodaCompatibleZonedDateTime to a ZonedDateTime for 
both def and static types. This is necessary to insulate users from needing 
to know about JodaCompatibleZonedDateTime explicitly.
This commit is contained in:
Jack Conradson 2019-07-29 11:56:26 -07:00
parent aef419c0b0
commit 1a21682ed0
10 changed files with 155 additions and 44 deletions

View File

@ -22,7 +22,9 @@ package org.elasticsearch.painless;
import org.elasticsearch.painless.lookup.PainlessCast;
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
import org.elasticsearch.painless.lookup.def;
import org.elasticsearch.script.JodaCompatibleZonedDateTime;
import java.time.ZonedDateTime;
import java.util.Objects;
/**
@ -72,11 +74,19 @@ public final class AnalyzerCaster {
return PainlessCast.originalTypetoTargetType(def.class, Float.class, explicit);
} else if (expected == Double.class) {
return PainlessCast.originalTypetoTargetType(def.class, Double.class, explicit);
// TODO: remove this when the transition from Joda to Java datetimes is completed
} else if (expected == ZonedDateTime.class) {
return PainlessCast.originalTypetoTargetType(def.class, ZonedDateTime.class, explicit);
}
} else if (actual == String.class) {
if (expected == char.class && explicit) {
return PainlessCast.originalTypetoTargetType(String.class, char.class, true);
}
// TODO: remove this when the transition from Joda to Java datetimes is completed
} else if (actual == JodaCompatibleZonedDateTime.class) {
if (expected == ZonedDateTime.class) {
return PainlessCast.originalTypetoTargetType(JodaCompatibleZonedDateTime.class, ZonedDateTime.class, explicit);
}
} else if (actual == boolean.class) {
if (expected == def.class) {
return PainlessCast.boxOriginalType(Boolean.class, def.class, explicit, boolean.class);

View File

@ -23,11 +23,13 @@ import org.elasticsearch.painless.Locals.LocalMethod;
import org.elasticsearch.painless.lookup.PainlessLookup;
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
import org.elasticsearch.painless.lookup.PainlessMethod;
import org.elasticsearch.script.JodaCompatibleZonedDateTime;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.time.ZonedDateTime;
import java.util.BitSet;
import java.util.Collections;
import java.util.Iterator;
@ -1185,6 +1187,15 @@ public final class Def {
}
}
// TODO: remove this when the transition from Joda to Java datetimes is completed
public static ZonedDateTime defToZonedDateTime(final Object value) {
if (value instanceof JodaCompatibleZonedDateTime) {
return ((JodaCompatibleZonedDateTime)value).getZonedDateTime();
}
return (ZonedDateTime)value;
}
/**
* "Normalizes" the index into a {@code Map} by making no change to the index.
*/

View File

@ -22,6 +22,7 @@ package org.elasticsearch.painless;
import org.elasticsearch.painless.lookup.PainlessCast;
import org.elasticsearch.painless.lookup.PainlessMethod;
import org.elasticsearch.painless.lookup.def;
import org.elasticsearch.script.JodaCompatibleZonedDateTime;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
@ -30,6 +31,7 @@ import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;
import java.lang.reflect.Modifier;
import java.time.ZonedDateTime;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
@ -71,8 +73,10 @@ import static org.elasticsearch.painless.WriterConstants.DEF_TO_P_SHORT_EXPLICIT
import static org.elasticsearch.painless.WriterConstants.DEF_TO_P_SHORT_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_STRING_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_STRING_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_ZONEDDATETIME;
import static org.elasticsearch.painless.WriterConstants.DEF_UTIL_TYPE;
import static org.elasticsearch.painless.WriterConstants.INDY_STRING_CONCAT_BOOTSTRAP_HANDLE;
import static org.elasticsearch.painless.WriterConstants.JCZDT_TO_ZONEDDATETIME;
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
import static org.elasticsearch.painless.WriterConstants.MAX_INDY_STRING_CONCAT_ARGS;
import static org.elasticsearch.painless.WriterConstants.PAINLESS_ERROR_TYPE;
@ -156,6 +160,9 @@ public final class MethodWriter extends GeneratorAdapter {
invokeStatic(UTILITY_TYPE, CHAR_TO_STRING);
} else if (cast.originalType == String.class && cast.targetType == char.class) {
invokeStatic(UTILITY_TYPE, STRING_TO_CHAR);
// TODO: remove this when the transition from Joda to Java datetimes is completed
} else if (cast.originalType == JodaCompatibleZonedDateTime.class && cast.targetType == ZonedDateTime.class) {
invokeStatic(UTILITY_TYPE, JCZDT_TO_ZONEDDATETIME);
} else if (cast.unboxOriginalType != null && cast.boxTargetType != null) {
unbox(getType(cast.unboxOriginalType));
writeCast(cast.unboxOriginalType, cast.boxTargetType);
@ -191,6 +198,8 @@ public final class MethodWriter extends GeneratorAdapter {
else if (cast.targetType == Float.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_FLOAT_EXPLICIT);
else if (cast.targetType == Double.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_DOUBLE_EXPLICIT);
else if (cast.targetType == String.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_STRING_EXPLICIT);
// TODO: remove this when the transition from Joda to Java datetimes is completed
else if (cast.targetType == ZonedDateTime.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_ZONEDDATETIME);
else {
writeCast(cast.originalType, cast.targetType);
}
@ -212,6 +221,8 @@ public final class MethodWriter extends GeneratorAdapter {
else if (cast.targetType == Float.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_FLOAT_IMPLICIT);
else if (cast.targetType == Double.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_DOUBLE_IMPLICIT);
else if (cast.targetType == String.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_STRING_IMPLICIT);
// TODO: remove this when the transition from Joda to Java datetimes is completed
else if (cast.targetType == ZonedDateTime.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_ZONEDDATETIME);
else {
writeCast(cast.originalType, cast.targetType);
}

View File

@ -19,6 +19,10 @@
package org.elasticsearch.painless;
import org.elasticsearch.script.JodaCompatibleZonedDateTime;
import java.time.ZonedDateTime;
/**
* A set of methods for non-native boxing and non-native
* exact math operations used at both compile-time and runtime.
@ -43,5 +47,10 @@ public class Utility {
return value.charAt(0);
}
// TODO: remove this when the transition from Joda to Java datetimes is completed
public static ZonedDateTime JCZDTToZonedDateTime(final JodaCompatibleZonedDateTime jczdt) {
return jczdt.getZonedDateTime();
}
private Utility() {}
}

View File

@ -21,6 +21,7 @@ package org.elasticsearch.painless;
import org.elasticsearch.painless.api.Augmentation;
import org.elasticsearch.painless.lookup.PainlessLookup;
import org.elasticsearch.script.JodaCompatibleZonedDateTime;
import org.elasticsearch.script.ScriptException;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
@ -31,6 +32,7 @@ import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.time.ZonedDateTime;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
@ -98,13 +100,16 @@ public final class WriterConstants {
public static final Method STRING_TO_CHAR = getAsmMethod(char.class, "StringTochar", String.class);
public static final Method CHAR_TO_STRING = getAsmMethod(String.class, "charToString", char.class);
// TODO: remove this when the transition from Joda to Java datetimes is completed
public static final Method JCZDT_TO_ZONEDDATETIME =
getAsmMethod(ZonedDateTime.class, "JCZDTToZonedDateTime", JodaCompatibleZonedDateTime.class);
public static final Type METHOD_HANDLE_TYPE = Type.getType(MethodHandle.class);
public static final Type AUGMENTATION_TYPE = Type.getType(Augmentation.class);
/**
* A Method instance for {@linkplain Pattern#compile}. This isn't available from PainlessLookup because we intentionally don't add it
* A Method instance for {@linkplain Pattern}. This isn't available from PainlessLookup because we intentionally don't add it
* there so that the script can't create regexes without this syntax. Essentially, our static regex syntax has a monopoly on building
* regexes because it can do it statically. This is both faster and prevents the script from doing something super slow like building a
* regex per time it is run.
@ -161,6 +166,9 @@ public final class WriterConstants {
public static final Method DEF_TO_STRING_IMPLICIT = getAsmMethod(String.class, "defToStringImplicit", Object.class);
public static final Method DEF_TO_STRING_EXPLICIT = getAsmMethod(String.class, "defToStringExplicit", Object.class);
// TODO: remove this when the transition from Joda to Java datetimes is completed
public static final Method DEF_TO_ZONEDDATETIME = getAsmMethod(ZonedDateTime.class, "defToZonedDateTime", Object.class);
public static final Type DEF_ARRAY_LENGTH_METHOD_TYPE = Type.getMethodType(Type.INT_TYPE, Type.getType(Object.class));
/** invokedynamic bootstrap for lambda expression/method references */

View File

@ -87,7 +87,6 @@ class org.elasticsearch.script.JodaCompatibleZonedDateTime {
int getNano()
int getSecond()
int getYear()
ZoneId getZone()
ZonedDateTime minus(TemporalAmount)
ZonedDateTime minus(long,TemporalUnit)
ZonedDateTime minusYears(long)
@ -108,7 +107,6 @@ class org.elasticsearch.script.JodaCompatibleZonedDateTime {
ZonedDateTime plusSeconds(long)
ZonedDateTime plusWeeks(long)
ZonedDateTime plusYears(long)
Instant toInstant()
OffsetDateTime toOffsetDateTime()
ZonedDateTime truncatedTo(TemporalUnit)
ZonedDateTime with(TemporalAdjuster)
@ -127,25 +125,6 @@ class org.elasticsearch.script.JodaCompatibleZonedDateTime {
ZonedDateTime withZoneSameLocal(ZoneId)
ZonedDateTime withZoneSameInstant(ZoneId)
#### ChronoZonedDateTime
int compareTo(JodaCompatibleZonedDateTime)
Chronology getChronology()
String format(DateTimeFormatter)
int get(TemporalField)
long getLong(TemporalField)
ZoneOffset getOffset()
boolean isSupported(TemporalField)
long toEpochSecond()
LocalTime toLocalTime()
#### Joda methods that exist in java time
boolean equals(Object)
int hashCode()
boolean isAfter(JodaCompatibleZonedDateTime)
boolean isBefore(JodaCompatibleZonedDateTime)
boolean isEqual(JodaCompatibleZonedDateTime)
String toString()
#### Joda time methods
long getMillis()
int getCenturyOfEra()

View File

@ -139,4 +139,14 @@ public class BasicAPITests extends ScriptTestCase {
assertEquals(10, exec("staticAddIntsTest(7, 3)"));
assertEquals(15.5f, exec("staticAddFloatsTest(6.5f, 9.0f)"));
}
// TODO: remove this when the transition from Joda to Java datetimes is completed
public void testJCZDTToZonedDateTime() {
assertEquals(0L, exec(
"Instant instant = Instant.ofEpochMilli(434931330000L);" +
"JodaCompatibleZonedDateTime d = new JodaCompatibleZonedDateTime(instant, ZoneId.of('Z'));" +
"ZonedDateTime t = d;" +
"return ChronoUnit.MILLIS.between(d, t);"
));
}
}

View File

@ -683,4 +683,14 @@ public class DefCastTests extends ScriptTestCase {
public void testdefToStringExplicit() {
assertEquals("s", exec("def d = (char)'s'; String b = (String)d; b"));
}
// TODO: remove this when the transition from Joda to Java datetimes is completed
public void testdefToZonedDateTime() {
assertEquals(0L, exec(
"Instant instant = Instant.ofEpochMilli(434931330000L);" +
"def d = new JodaCompatibleZonedDateTime(instant, ZoneId.of('Z'));" +
"ZonedDateTime t = d;" +
"return ChronoUnit.MILLIS.between(d, t);"
));
}
}

View File

@ -1,4 +1,10 @@
# whitelist for tests
# TODO: remove this when the transition from Joda to Java datetimes is completed
class org.elasticsearch.script.JodaCompatibleZonedDateTime {
(Instant, ZoneId)
}
class org.elasticsearch.painless.BindingsTests$BindingsTestScript {
}

View File

@ -38,13 +38,18 @@ import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.chrono.ChronoZonedDateTime;
import java.time.chrono.Chronology;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAmount;
import java.time.temporal.TemporalField;
import java.time.temporal.TemporalQuery;
import java.time.temporal.TemporalUnit;
import java.time.temporal.ValueRange;
import java.time.temporal.WeekFields;
import java.util.Locale;
import java.util.Objects;
@ -52,7 +57,9 @@ import java.util.Objects;
/**
* A wrapper around ZonedDateTime that exposes joda methods for backcompat.
*/
public class JodaCompatibleZonedDateTime {
public class JodaCompatibleZonedDateTime
implements Comparable<ChronoZonedDateTime<?>>, ChronoZonedDateTime<LocalDate>, Temporal, TemporalAccessor {
private static final DateFormatter DATE_FORMATTER = DateFormatter.forPattern("strict_date_time");
private static final DeprecationLogger deprecationLogger =
new DeprecationLogger(LogManager.getLogger(JodaCompatibleZonedDateTime.class));
@ -83,9 +90,15 @@ public class JodaCompatibleZonedDateTime {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JodaCompatibleZonedDateTime that = (JodaCompatibleZonedDateTime) o;
return Objects.equals(dt, that.dt);
if (o == null)return false;
if (o.getClass() == JodaCompatibleZonedDateTime.class) {
JodaCompatibleZonedDateTime that = (JodaCompatibleZonedDateTime) o;
return Objects.equals(dt, that.dt);
} else if (o.getClass() == ZonedDateTime.class) {
ZonedDateTime that = (ZonedDateTime) o;
return Objects.equals(dt, that);
}
return false;
}
@Override
@ -98,56 +111,76 @@ public class JodaCompatibleZonedDateTime {
return DATE_FORMATTER.format(dt);
}
@Override
public String format(DateTimeFormatter formatter) {
return dt.format(formatter);
}
@Override
public ValueRange range(TemporalField field) {
return dt.range(field);
}
@Override
public int get(TemporalField field) {
return dt.get(field);
}
@Override
public long getLong(TemporalField field) {
return dt.getLong(field);
}
@Override
public Chronology getChronology() {
return dt.getChronology();
}
public int compareTo(JodaCompatibleZonedDateTime o) {
return dt.compareTo(o.dt);
}
@Override
public ZoneOffset getOffset() {
return dt.getOffset();
}
@Override
public boolean isSupported(TemporalField field) {
return dt.isSupported(field);
}
@Override
public boolean isSupported(TemporalUnit unit) {
return dt.isSupported(unit);
}
@Override
public long toEpochSecond() {
return dt.toEpochSecond();
}
@Override
public int compareTo(ChronoZonedDateTime<?> other) {
return dt.compareTo(other);
}
@Override
public boolean isBefore(ChronoZonedDateTime<?> other) {
return dt.isBefore(other);
}
@Override
public boolean isAfter(ChronoZonedDateTime<?> other) {
return dt.isAfter(other);
}
@Override
public boolean isEqual(ChronoZonedDateTime<?> other) {
return dt.isEqual(other);
}
@Override
public LocalTime toLocalTime() {
return dt.toLocalTime();
}
public boolean isAfter(JodaCompatibleZonedDateTime o) {
return dt.isAfter(o.getZonedDateTime());
}
public boolean isBefore(JodaCompatibleZonedDateTime o) {
return dt.isBefore(o.getZonedDateTime());
}
public boolean isEqual(JodaCompatibleZonedDateTime o) {
return dt.isEqual(o.getZonedDateTime());
}
public int getDayOfMonth() {
return dt.getDayOfMonth();
}
@ -160,10 +193,12 @@ public class JodaCompatibleZonedDateTime {
return dt.getHour();
}
@Override
public LocalDate toLocalDate() {
return dt.toLocalDate();
}
@Override
public LocalDateTime toLocalDateTime() {
return dt.toLocalDateTime();
}
@ -192,18 +227,31 @@ public class JodaCompatibleZonedDateTime {
return dt.getYear();
}
@Override
public ZoneId getZone() {
return dt.getZone();
}
@Override
public ZonedDateTime minus(TemporalAmount delta) {
return dt.minus(delta);
}
@Override
public ZonedDateTime minus(long amount, TemporalUnit unit) {
return dt.minus(amount, unit);
}
@Override
public <R> R query(TemporalQuery<R> query) {
return dt.query(query);
}
@Override
public long until(Temporal temporal, TemporalUnit temporalUnit) {
return dt.until(temporal, temporalUnit);
}
public ZonedDateTime minusYears(long amount) {
return dt.minusYears(amount);
}
@ -236,10 +284,12 @@ public class JodaCompatibleZonedDateTime {
return dt.minusNanos(amount);
}
@Override
public ZonedDateTime plus(TemporalAmount amount) {
return dt.plus(amount);
}
@Override
public ZonedDateTime plus(long amount,TemporalUnit unit) {
return dt.plus(amount, unit);
}
@ -276,6 +326,7 @@ public class JodaCompatibleZonedDateTime {
return dt.plusYears(amount);
}
@Override
public Instant toInstant() {
return dt.toInstant();
}
@ -289,10 +340,12 @@ public class JodaCompatibleZonedDateTime {
return dt.truncatedTo(unit);
}
@Override
public ZonedDateTime with(TemporalAdjuster adjuster) {
return dt.with(adjuster);
}
@Override
public ZonedDateTime with(TemporalField field, long newValue) {
return dt.with(field, newValue);
}
@ -305,6 +358,7 @@ public class JodaCompatibleZonedDateTime {
return dt.withDayOfYear(value);
}
@Override
public ZonedDateTime withEarlierOffsetAtOverlap() {
return dt.withEarlierOffsetAtOverlap();
}
@ -317,6 +371,7 @@ public class JodaCompatibleZonedDateTime {
return dt.withHour(value);
}
@Override
public ZonedDateTime withLaterOffsetAtOverlap() {
return dt.withLaterOffsetAtOverlap();
}
@ -341,10 +396,12 @@ public class JodaCompatibleZonedDateTime {
return dt.withYear(value);
}
@Override
public ZonedDateTime withZoneSameLocal(ZoneId zone) {
return dt.withZoneSameLocal(zone);
}
@Override
public ZonedDateTime withZoneSameInstant(ZoneId zone) {
return dt.withZoneSameInstant(zone);
}