PERFORMANCE: Bring Javafier design in line with Rubyfier and Valuefier

Fixes #8158
This commit is contained in:
Armin 2017-09-07 11:21:32 +02:00 committed by Armin Braun
parent 0467f0dbda
commit 029be86c6d
2 changed files with 60 additions and 64 deletions

View file

@ -1,5 +1,7 @@
package org.logstash;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jruby.RubyBoolean;
import org.jruby.RubyFixnum;
import org.jruby.RubyFloat;
@ -8,63 +10,67 @@ import org.logstash.bivalues.BiValue;
import org.logstash.bivalues.BiValues;
import org.logstash.ext.JrubyTimestampExtLibrary;
public class Javafier {
private static final String ERR_TEMPLATE = "Missing Ruby class handling for full class name=%s, simple name=%s";
/*
Javafier.deep() is called by getField.
When any value is added to the Event it should pass through Valuefier.convert.
deep(Object o) is the mechanism to pluck the Java value from a BiValue or convert a
ConvertedList and ConvertedMap back to ArrayList or HashMap.
*/
private Javafier(){}
public final class Javafier {
public static Object deep(Object o) {
if (o instanceof RubyString) {
return o.toString();
}
if (o instanceof String || o instanceof Float || o instanceof Double ||
o instanceof Long || o instanceof Integer || o instanceof Boolean ||
o instanceof Timestamp) {
return o;
}
if (o instanceof RubyFloat) {
return ((RubyFloat) o).getDoubleValue();
}
if (o instanceof JrubyTimestampExtLibrary.RubyTimestamp) {
return ((JrubyTimestampExtLibrary.RubyTimestamp) o).getTimestamp();
}
if (o instanceof RubyBoolean) {
return ((RubyBoolean) o).isTrue();
}
if (o instanceof RubyFixnum) {
return ((RubyFixnum) o).getLongValue();
}
if (o instanceof BiValue) {
return ((BiValue)o).javaValue();
} else if(o instanceof ConvertedMap) {
return ((ConvertedMap) o).unconvert();
} else if(o instanceof ConvertedList) {
return ((ConvertedList) o).unconvert();
} else {
return fallback(o);
}
}
private static final Map<Class<?>, Valuefier.Converter> CONVERTER_MAP = initConverters();
private static final Valuefier.Converter BIVALUES_CONVERTER =
value -> BiValues.newBiValue(value).javaValue();
/**
* Cold path of {@link Javafier#deep(Object)}.
* We assume that we never get an input that is neither {@link ConvertedMap}, {@link ConvertedList}
* nor {@link BiValue}, but fallback to attempting to create a {@link BiValue} from the input
* before converting to a Java type.
* @param o Know to not be an expected type in {@link Javafier#deep(Object)}.
* @return Input converted to Java type
* Javafier.deep() is called by getField.
* When any value is added to the Event it should pass through Valuefier.convert.
* deep(Object o) is the mechanism to pluck the Java value from a BiValue or convert a
* ConvertedList and ConvertedMap back to ArrayList or HashMap.
*/
private static Object fallback(final Object o) {
try {
return BiValues.newBiValue(o).javaValue();
} catch (IllegalArgumentException e) {
final Class<?> cls = o.getClass();
throw new IllegalArgumentException(String.format(ERR_TEMPLATE, cls.getName(), cls.getSimpleName()));
private Javafier() {
}
public static Object deep(Object o) {
final Class<?> cls = o.getClass();
final Valuefier.Converter converter = CONVERTER_MAP.get(cls);
if (converter != null) {
return converter.convert(o);
}
return fallbackConvert(o, cls);
}
private static Object fallbackConvert(final Object o, final Class<?> cls) {
for (final Map.Entry<Class<?>, Valuefier.Converter> entry : CONVERTER_MAP.entrySet()) {
if (entry.getKey().isAssignableFrom(cls)) {
final Valuefier.Converter found = entry.getValue();
CONVERTER_MAP.put(cls, found);
return found.convert(o);
}
}
CONVERTER_MAP.put(cls, BIVALUES_CONVERTER);
return BIVALUES_CONVERTER.convert(o);
}
private static Map<Class<?>, Valuefier.Converter> initConverters() {
final Map<Class<?>, Valuefier.Converter> converters =
new ConcurrentHashMap<>(50, 0.2F, 1);
converters.put(String.class, Valuefier.IDENTITY);
converters.put(Float.class, Valuefier.IDENTITY);
converters.put(Double.class, Valuefier.IDENTITY);
converters.put(Long.class, Valuefier.IDENTITY);
converters.put(Integer.class, Valuefier.IDENTITY);
converters.put(Boolean.class, Valuefier.IDENTITY);
converters.put(Timestamp.class, Valuefier.IDENTITY);
// Explicitly casting to RubyString when we know it's a RubyString for sure is faster
// than having the JVM look up the type.
converters.put(RubyString.class, value -> ((RubyString) value).toString());
converters.put(RubyBoolean.class, value -> ((RubyBoolean) value).isTrue());
converters.put(BiValue.class, value -> ((BiValue<?, ?>) value).javaValue());
converters.put(RubyFixnum.class, value -> ((RubyFixnum) value).getLongValue());
converters.put(RubyFloat.class, value -> ((RubyFloat) value).getDoubleValue());
converters.put(ConvertedMap.class, value -> ((ConvertedMap) value).unconvert());
converters.put(ConvertedList.class, value -> ((ConvertedList) value).unconvert());
converters.put(
JrubyTimestampExtLibrary.RubyTimestamp.class,
value -> ((JrubyTimestampExtLibrary.RubyTimestamp) value).getTimestamp()
);
return converters;
}
}

View file

@ -24,7 +24,7 @@ import org.logstash.ext.JrubyTimestampExtLibrary;
public final class Valuefier {
private static final Valuefier.Converter IDENTITY = input -> input;
public static final Valuefier.Converter IDENTITY = input -> input;
private static final Valuefier.Converter FLOAT_CONVERTER =
input -> RubyUtil.RUBY.newFloat(((Number) input).doubleValue());
@ -145,18 +145,8 @@ public final class Valuefier {
return converters;
}
/**
* Converter from either a Java or a Ruby type to a type that both {@link Javafier} and
* {@link Rubyfier} are able to convert back their respective types efficiently.
*/
private interface Converter {
public interface Converter {
/**
* Converts a Java or a Ruby typed object to an object that can be efficiently converted
* back to Java as well as Ruby.
* @param input Either a Java or Ruby type object
* @return Object that can be converted back to Java as well as Ruby efficiently
*/
Object convert(Object input);
}
}