/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.capedwarf.datastore.query;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.Date;
import java.util.Set;
import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.Category;
import com.google.appengine.api.datastore.Email;
import com.google.appengine.api.datastore.GeoPt;
import com.google.appengine.api.datastore.IMHandle;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Link;
import com.google.appengine.api.datastore.PhoneNumber;
import com.google.appengine.api.datastore.PostalAddress;
import com.google.appengine.api.datastore.Rating;
import com.google.appengine.api.datastore.ShortBlob;
import com.google.appengine.api.datastore.Text;
import com.google.appengine.api.users.User;
import com.google.common.collect.Sets;
import org.hibernate.search.bridge.TwoWayStringBridge;
/**
* @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a>
* @author <a href="mailto:marko.luksa@gmail.com">Marko Luksa</a>
*/
public enum Bridge implements TwoWayStringBridge {
// WARNING: DO NOT CHANGE NAMES (they are stored in the datastore - see Projections)
NULL("000", new NullBridge()),
LONG("010", new LongBridge()),
RATING("010", new RatingBridge()),
DATE("010", new DateBridge()),
BOOLEAN("020", new BooleanBridge()),
// SHORT_BLOB("030", new ShortBlobBridge()),
SHORT_BLOB("040", new ShortBlobBridge()),
STRING("040", new StringBridge()),
PHONE_NUMBER("040", new PhoneNumberBridge()),
POSTAL_ADDRESS("040", new PostalAddressBridge()),
EMAIL("040", new EmailBridge()),
IM_HANDLE("040", new IMHandleBridge()),
LINK("040", new LinkBridge()),
CATEGORY("040", new CategoryBridge()),
BLOB_KEY("040", new BlobKeyBridge()),
DOUBLE("050", new DoubleBridge()),
GEO_PT("060", new GeoPtBridge()),
USER("070", new UserBridge()),
KEY("080", new KeyBridge()),
COLLECTION("999", new CollectionBridge()),
TEXT("999", new TextBridge()),
BLOB("999", new BlobBridge()),
EMBEDDED_ENTITY("999", new EmbeddedEntityBridge());
private OrderingPrefixer orderingPrefixer;
BridgeSpi bridge;
private Bridge(String orderPrefix, BridgeSpi bridge) {
this.orderingPrefixer = new OrderingPrefixer(orderPrefix);
this.bridge = bridge;
}
public String objectToString(Object object) {
String str = bridge.objectToString(object);
return orderingPrefixer.addOrderingPrefix(str);
}
public Object stringToObject(String stringValue) {
stringValue = orderingPrefixer.removeOrderingPrefix(stringValue);
return bridge.stringToObject(stringValue);
}
public boolean isAssignableTo(Class<?> type) {
Set<Class<?>> types = bridge.types();
if (types == null) {
return true;
}
for (Class<?> bt : types) {
if (type.isAssignableFrom(bt)) {
return true;
}
}
return false;
}
public Object convertValue(Object value) {
return bridge.convertValue(value);
}
public Object getValue(String value) {
value = orderingPrefixer.removeOrderingPrefix(value);
return bridge.getValue(value);
}
static void checkType(Object value, Class<?> clazz) {
if (clazz.isInstance(value) == false) {
throw new IllegalArgumentException("Type mismatch");
}
}
public static class NullBridge implements BridgeSpi {
public static final String NULL_TOKEN = "__capedwarf___NULL___";
public Set<Class<?>> types() {
return null;
}
public Object stringToObject(String stringValue) {
return null;
}
public String objectToString(Object object) {
return NULL_TOKEN;
}
@Override
public Object getValue(String value) {
return null;
}
@Override
public Object convertValue(Object value) {
return null;
}
}
protected abstract static class BuiltInBridge implements BridgeSpi {
private TwoWayStringBridge bridge;
protected BuiltInBridge(TwoWayStringBridge bridge) {
this.bridge = bridge;
}
public Object stringToObject(String stringValue) {
return bridge.stringToObject(stringValue);
}
public String objectToString(Object object) {
return bridge.objectToString(object);
}
}
public static class BooleanBridge extends BuiltInBridge {
public BooleanBridge() {
super(new org.hibernate.search.bridge.builtin.BooleanBridge());
}
public Set<Class<?>> types() {
return Collections.<Class<?>>singleton(Boolean.class);
}
@Override
public Object getValue(String value) {
return stringToObject(value);
}
@Override
public Object convertValue(Object value) {
checkType(value, Boolean.class);
return value;
}
}
public static class StringBridge extends BuiltInBridge {
public StringBridge() {
super(org.hibernate.search.bridge.builtin.StringBridge.INSTANCE);
}
public Set<Class<?>> types() {
return Collections.<Class<?>>singleton(String.class);
}
public Object getValue(String value) {
return toUTF8ByteArray(value);
}
public Object convertValue(Object value) {
checkType(value, byte[].class);
return stringToObject(fromUTF8ByteArray((byte[]) value));
}
public static byte[] toUTF8ByteArray(String string) {
try {
return string.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new InternalError("UTF-8 not supported on this platform");
}
}
public static String fromUTF8ByteArray(byte[] array) {
try {
return new String(array, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new InternalError("UTF-8 not supported on this platform");
}
}
}
private static class CollectionBridge implements BridgeSpi {
public Set<Class<?>> types() {
return null; // return null, as this is checked against elements
}
public Object stringToObject(String stringValue) {
return null; // TODO
}
public String objectToString(Object object) {
return object.toString();
}
@Override
public Object getValue(String value) {
return null;
}
@Override
public Object convertValue(Object value) {
return null;
}
}
private static class DoubleBridge implements BridgeSpi {
public Set<Class<?>> types() {
return Sets.<Class<?>>newHashSet(Float.class, Double.class);
}
public String objectToString(Object object) {
return double2sortableStr(((Number) object).doubleValue());
}
public Object stringToObject(String stringValue) {
long f = sortableStr2long(stringValue);
if (f < 0) f ^= 0x7fffffffffffffffL;
return Double.longBitsToDouble(f);
}
public static String double2sortableStr(double val) {
long f = Double.doubleToRawLongBits(val);
if (f < 0) f ^= 0x7fffffffffffffffL;
return long2sortableStr(f);
}
// uses binary representation of an int to build a string of
// chars that will sort correctly. Only char ranges
// less than 0xd800 will be used to avoid UCS-16 surrogates.
// we can use the lowest 15 bits of a char, (or a mask of 0x7fff)
private static String long2sortableStr(long val) {
char[] out = new char[5];
int offset = 0;
val += Long.MIN_VALUE;
out[offset++] = (char) (val >>> 60);
out[offset++] = (char) (val >>> 45 & 0x7fff);
out[offset++] = (char) (val >>> 30 & 0x7fff);
out[offset++] = (char) (val >>> 15 & 0x7fff);
out[offset] = (char) (val & 0x7fff);
return new String(out);
}
private static long sortableStr2long(String sval) {
char[] out = sval.toCharArray();
int offset = 0;
long val = (long) (out[offset++]) << 60;
val |= ((long) out[offset++]) << 45;
val |= ((long) out[offset++]) << 30;
val |= out[offset++] << 15;
val |= out[offset];
val -= Long.MIN_VALUE;
return val;
}
@Override
public Object getValue(String value) {
return stringToObject(value);
}
@Override
public Object convertValue(Object value) {
checkType(value, Double.class);
return value;
}
}
private static class LongBridge implements BridgeSpi {
private static final int RADIX = 36;
private static final char NEGATIVE_PREFIX = '-';
// NB: NEGATIVE_PREFIX must be < POSITIVE_PREFIX
private static final char POSITIVE_PREFIX = '0';
//NB: this must be less than
/**
* Equivalent to longToString(Long.MIN_VALUE)
*/
public static final String MIN_STRING_VALUE = NEGATIVE_PREFIX + "0000000000000";
/**
* Equivalent to longToString(Long.MAX_VALUE)
*/
public static final String MAX_STRING_VALUE = POSITIVE_PREFIX + "1y2p0ij32e8e7";
/**
* the length of (all) strings returned by [EMAIL PROTECTED] #longToString}
*/
public static final int STR_SIZE = MIN_STRING_VALUE.length();
public Set<Class<?>> types() {
return Sets.<Class<?>>newHashSet(Long.class, Integer.class, Short.class, Byte.class);
}
public String objectToString(Object object) {
long num = ((Number) object).longValue();
return longToString(num);
}
public Object stringToObject(String stringValue) {
if (MIN_STRING_VALUE.equals(stringValue))
return Long.MIN_VALUE;
char prefix = stringValue.charAt(0);
long num = Long.parseLong(stringValue.substring(1), RADIX);
return (prefix == NEGATIVE_PREFIX) ? num - Long.MAX_VALUE - 1 : num;
}
/**
* Converts a long to a String suitable for indexing.
*/
public static String longToString(long num) {
if (num == Long.MIN_VALUE) {
// special case, because long is not symetric around zero
return MIN_STRING_VALUE;
}
StringBuilder buf = new StringBuilder(STR_SIZE);
if (num < 0) {
buf.append(NEGATIVE_PREFIX);
num = Long.MAX_VALUE + num + 1;
} else {
buf.append(POSITIVE_PREFIX);
}
String numStr = Long.toString(num, RADIX);
int padLen = STR_SIZE - numStr.length() - buf.length();
while (padLen-- > 0) {
buf.append('0');
}
buf.append(numStr);
return buf.toString();
}
@Override
public Object getValue(String value) {
return stringToObject(value);
}
@Override
public Object convertValue(Object value) {
checkType(value, Long.class);
return value;
}
}
private static class TextBridge extends StringTypeBasedBridge {
public Class<?> type() {
return Text.class;
}
public String objectToString(Object object) {
return ((Text) object).getValue();
}
public Object stringToObject(String stringValue) {
return new Text(stringValue);
}
}
private static abstract class StringTypeBasedBridge extends AbstractBridgeSpi {
public Object getValue(String value) {
return Bridge.StringBridge.toUTF8ByteArray(value);
}
public Object convertValue(Object value) {
checkType(value, byte[].class);
return stringToObject(Bridge.StringBridge.fromUTF8ByteArray((byte[]) value));
}
}
private static class PhoneNumberBridge extends StringTypeBasedBridge {
public Class<?> type() {
return PhoneNumber.class;
}
public String objectToString(Object object) {
return ((PhoneNumber) object).getNumber();
}
public Object stringToObject(String stringValue) {
return new PhoneNumber(stringValue);
}
}
private static class PostalAddressBridge extends StringTypeBasedBridge {
public Class<?> type() {
return PostalAddress.class;
}
public String objectToString(Object object) {
return ((PostalAddress) object).getAddress();
}
public Object stringToObject(String stringValue) {
return new PostalAddress(stringValue);
}
}
private static class EmailBridge extends StringTypeBasedBridge {
public Class<?> type() {
return Email.class;
}
public String objectToString(Object object) {
return ((Email) object).getEmail();
}
public Object stringToObject(String stringValue) {
return new Email(stringValue);
}
}
private static class UserBridge extends AbstractBridgeSpi {
public Class<?> type() {
return User.class;
}
public String objectToString(Object object) {
return ((User) object).getEmail(); // TODO: add other properties
}
public Object stringToObject(String stringValue) {
return new User(stringValue, "gmail.com");
}
}
private static class LinkBridge extends StringTypeBasedBridge {
public Class<?> type() {
return Link.class;
}
public String objectToString(Object object) {
return ((Link) object).getValue();
}
public Object stringToObject(String stringValue) {
return new Link(stringValue);
}
}
private static class KeyBridge extends AbstractBridgeSpi {
public Class<?> type() {
return Key.class;
}
public String objectToString(Object object) {
return GAEKeyTransformer.to(object);
}
public Object stringToObject(String stringValue) {
return GAEKeyTransformer.from(stringValue);
}
}
private static class RatingBridge extends LongTypeBasedBridge<Rating> {
public Class<Rating> type() {
return Rating.class;
}
@Override
protected long toLong(Rating value) {
return value.getRating();
}
@Override
protected Rating fromLong(long value) {
return new Rating((int)value);
}
}
private static class GeoPtBridge extends AbstractBridgeSpi {
private DoubleBridge doubleBridge = new DoubleBridge();
public Class<?> type() {
return GeoPt.class;
}
public String objectToString(Object object) {
return doubleBridge.objectToString(((GeoPt) object).getLatitude()) + ";" + doubleBridge.objectToString(((GeoPt) object).getLongitude());
}
public Object stringToObject(String stringValue) {
String[] pair = stringValue.split(";");
float latitude = ((Double) doubleBridge.stringToObject(pair[0])).floatValue();
float longitude = ((Double) doubleBridge.stringToObject(pair[1])).floatValue();
return new GeoPt(latitude, longitude);
}
}
private static class CategoryBridge extends StringTypeBasedBridge {
public Class<?> type() {
return Category.class;
}
public String objectToString(Object object) {
return ((Category) object).getCategory();
}
public Object stringToObject(String stringValue) {
return new Category(stringValue);
}
}
private static class IMHandleBridge extends StringTypeBasedBridge {
public Class<?> type() {
return IMHandle.class;
}
public String objectToString(Object object) {
return ((IMHandle) object).getProtocol() + " " + ((IMHandle) object).getAddress();
}
public Object stringToObject(String stringValue) {
int spaceIndex = stringValue.indexOf(' ');
return new IMHandle(IMHandle.Scheme.valueOf(stringValue.substring(0, spaceIndex)), stringValue.substring(spaceIndex+1));
}
}
private static class BlobKeyBridge extends StringTypeBasedBridge {
public Class<?> type() {
return BlobKey.class;
}
public String objectToString(Object object) {
return ((BlobKey) object).getKeyString();
}
public Object stringToObject(String stringValue) {
return new BlobKey(stringValue);
}
}
private static class BlobBridge extends StringTypeBasedBridge {
public Class<?> type() {
return Blob.class;
}
public String objectToString(Object object) {
byte[] bytes = ((Blob) object).getBytes();
return bytesToString(bytes);
}
public Object stringToObject(String stringValue) {
return new Blob(stringToBytes(stringValue));
}
private String bytesToString(byte bytes[]) {
// TODO: This impl is temporary. Find better one.
StringBuilder sbuf = new StringBuilder();
for (byte aByte : bytes) {
String hex = Integer.toString(aByte, 16);
String twoCharHex = (hex.length() == 1 ? "0" : "") + hex;
sbuf.append(twoCharHex);
}
return sbuf.toString();
}
private byte[] stringToBytes(String string) {
byte[] bytes = new byte[string.length() / 2];
for (int i = 0; i < bytes.length; i++) {
int j = i * 2;
String hex = string.substring(j, j + 2);
bytes[i] = (byte) Integer.parseInt(hex, 16);
}
return bytes;
}
}
private static class ShortBlobBridge extends StringTypeBasedBridge {
public Class<?> type() {
return ShortBlob.class;
}
public String objectToString(Object object) {
byte[] bytes = ((ShortBlob) object).getBytes();
return new String(bytes);
}
public Object stringToObject(String stringValue) {
return new ShortBlob(stringValue.getBytes());
}
}
// TODO -- check this
private static class EmbeddedEntityBridge extends AbstractBridgeSpi {
public static final String EMBEDDED_TOKEN = "__capedwarf___EMBEDDED___";
public Class<?> type() {
return Object.class;
}
public Object stringToObject(String stringValue) {
return null;
}
public String objectToString(Object object) {
return EMBEDDED_TOKEN;
}
}
private static abstract class LongTypeBasedBridge<T> extends AbstractBridgeSpi {
private LongBridge longBridge = new LongBridge();
@Override
protected abstract Class<T> type();
@Override
public Object getValue(String value) {
return longBridge.stringToObject(value);
}
@Override
public Object convertValue(Object value) {
checkType(value, Long.class);
return fromLong(Long.class.cast(value));
}
@SuppressWarnings("unchecked")
@Override
public String objectToString(Object object) {
return longBridge.objectToString(toLong((T) object));
}
@Override
public Object stringToObject(String stringValue) {
return convertValue(getValue(stringValue));
}
protected abstract long toLong(T value);
protected abstract T fromLong(long value);
}
private static class DateBridge extends LongTypeBasedBridge<Date> {
public Class<Date> type() {
return Date.class;
}
protected long toLong(Date date) {
return date.getTime() * 1000L;
}
@Override
protected Date fromLong(long usec) {
return new Date(usec / 1000L);
}
}
private class OrderingPrefixer {
public static final int ORDER_PREFIX_LENGTH = 3;
private final String orderPrefix;
public OrderingPrefixer(String orderPrefix) {
if (orderPrefix.length() != ORDER_PREFIX_LENGTH) {
throw new IllegalArgumentException("invalid length, orderPrefix=" + orderPrefix);
}
this.orderPrefix = orderPrefix;
}
private String addOrderingPrefix(String str) {
return orderPrefix + ":" + str;
}
private String removeOrderingPrefix(String stringValue) {
return stringValue.substring(ORDER_PREFIX_LENGTH + 1);
}
}
}