package er.neo4jadaptor.ersatz.lucene; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.apache.lucene.util.NumericUtils; import com.webobjects.eoaccess.EOAttribute; import com.webobjects.foundation.NSTimestamp; import er.neo4jadaptor.ersatz.lucene.LuceneTranslator.Coder; /** * Encodes and decodes java types as {@link String} values with the intention to be used for Lucene. * <p> * Numbers are padded with zeros so that textual representation of a lower number would be alphanumerically * lower then textual representation of a higher number: * <pre> * STRING.encode(10).compareTo(STRING.encode(2)) < 0 * * // which translates to... * "00000000000000000010".compareTo("00000000000000000002") < 0 * </pre> * <p> * <b>TODO: Floating point types are not correctly represented</b> * * @author Jedrzej Sobanski */ public enum StorableTypes implements Coder { STRING(String.class, 1) { public Object decode(String encoded) { return encoded; } public String encode(Object value) { if (value == null) { return ""; } return value.toString(); } }, INTEGER(Integer.class, 2) { public Object decode(String encoded) { return Integer.parseInt(encoded); } public String encode(Object value) { return LONG.encode(value); } }, LONG(Long.class, 2) { // XXX: the same typeId as for INTEGER public Object decode(String encoded) { return Long.parseLong(encoded); } public String encode(Object value) { return padded(NUMBER_LENGTH, '0', Long.toString(((Number) value).longValue())); } }, FLOAT(Float.class, 4) { public Object decode(String encoded) { return Float.parseFloat(encoded); } public String encode(Object value) { int l = NumericUtils.floatToSortableInt(((Number) value).floatValue()); return Integer.toString(l); } }, DOUBLE(Double.class, 5) { public Object decode(String encoded) { return Double.parseDouble(encoded); } public String encode(Object value) { long l = NumericUtils.doubleToSortableLong(((Number) value).doubleValue()); return Long.toString(l); } }, BOOL(Boolean.class, 6) { public Object decode(String encoded) { return Boolean.parseBoolean(encoded); } public String encode(Object value) { return Boolean.toString((Boolean) value); } }, TIMESTAMP(NSTimestamp.class, 7) { public Object decode(String encoded) { try { Date date = DATE_FORMAT.parse(encoded); return new NSTimestamp(date); } catch (ParseException e) { throw new RuntimeException(e); } } public String encode(Object value) { return DATE_FORMAT.format(value); } } ; /** * How many digits (including padding zeros) should be used to store a number */ private static final int NUMBER_LENGTH = (int) Math.ceil(Math.log10(Long.MAX_VALUE)); private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMddHH:mm:ss:SSS"); private static final String padded(int length, char prefixChar, String s) { StringBuilder b = new StringBuilder(length); for (int i=0; i<length - s.length(); i++) { b.append(prefixChar); } b.append(s); return b.toString(); } /* default */ final int typeId; private final Class<?> type; private StorableTypes(Class<?> type, int typeId) { this.type = type; this.typeId = typeId; } private static Map<String, StorableTypes> map = new HashMap<>(); static { for (StorableTypes type : values()) { map.put(type.type.getCanonicalName(), type); } } public static StorableTypes getForAttribute(EOAttribute att) { return map.get(att.valueTypeClassName()); } }