Change Key.toString() to be useful for URL encoding
This makes the key string format shorter, and more suitable for use
within a URL.
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/src/main/java/com/google/gwtorm/client/CompoundKey.java b/src/main/java/com/google/gwtorm/client/CompoundKey.java
index d1e5341..214c96d 100644
--- a/src/main/java/com/google/gwtorm/client/CompoundKey.java
+++ b/src/main/java/com/google/gwtorm/client/CompoundKey.java
@@ -76,21 +76,18 @@
@Override
public String toString() {
final StringBuffer r = new StringBuffer();
- r.append(getClass().getName());
- r.append('[');
boolean first = true;
if (getParentKey() != null) {
- r.append(getParentKey().toString());
+ r.append(KeyUtil.encode(getParentKey().toString()));
first = false;
}
- for (final Key<?> k : members()){
+ for (final Key<?> k : members()) {
if (!first) {
- r.append(", ");
+ r.append(',');
}
- r.append(k.toString());
+ r.append(KeyUtil.encode(k.toString()));
first = false;
}
- r.append(']');
return r.toString();
}
diff --git a/src/main/java/com/google/gwtorm/client/IntKey.java b/src/main/java/com/google/gwtorm/client/IntKey.java
index cba56dd..2d70972 100644
--- a/src/main/java/com/google/gwtorm/client/IntKey.java
+++ b/src/main/java/com/google/gwtorm/client/IntKey.java
@@ -60,14 +60,11 @@
@Override
public String toString() {
final StringBuffer r = new StringBuffer();
- r.append(getClass().getName());
- r.append('[');
if (getParentKey() != null) {
r.append(getParentKey().toString());
- r.append(", ");
+ r.append(',');
}
r.append(get());
- r.append(']');
return r.toString();
}
diff --git a/src/main/java/com/google/gwtorm/client/KeyUtil.java b/src/main/java/com/google/gwtorm/client/KeyUtil.java
index acecb10..4f64f84 100644
--- a/src/main/java/com/google/gwtorm/client/KeyUtil.java
+++ b/src/main/java/com/google/gwtorm/client/KeyUtil.java
@@ -14,8 +14,42 @@
package com.google.gwtorm.client;
-class KeyUtil {
- static <T extends Key<?>> boolean eq(final T a, final T b) {
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.http.client.URL;
+import com.google.gwtorm.server.StandardKeyEncoder;
+
+
+/** Common utility functions for {@link Key} implementors. */
+public class KeyUtil {
+ private static Encoder ENCODER_IMPL;
+
+ static {
+ if (GWT.isClient()) {
+ ENCODER_IMPL = new GwtEncoder();
+ }
+ }
+
+ /**
+ * Set the encoder implementation to a valid implementation.
+ * <p>
+ * Server-side code needs to set the encoder to a {@link StandardKeyEncoder}
+ * instance prior to invoking any methods in this class. Typically this is
+ * done by the {@link SchemaFactory} implementation's static initializer.
+ */
+ public static void setEncoderImpl(final Encoder e) {
+ ENCODER_IMPL = e;
+ }
+
+ /**
+ * Determine if two keys are equal, supporting null references.
+ *
+ * @param <T> type of the key entity.
+ * @param a first key to test; may be null.
+ * @param b second key to test; may be null.
+ * @return true if both <code>a</code> and <code>b</code> are null, or if both
+ * are not-null and <code>a.equals(b)</code> is true. Otherwise false.
+ */
+ public static <T extends Key<?>> boolean eq(final T a, final T b) {
if (a == b) {
return true;
}
@@ -25,6 +59,51 @@
return a.equals(b);
}
+ /**
+ * Encode a string to be safe for use within a URL like string.
+ * <p>
+ * The returned encoded string has URL component characters escaped with hex
+ * escapes (e.g. ' ' is '+' and '%' is '%25'). The special character '/' is
+ * left literal. The comma character (',') is always encoded, permitting
+ * multiple encoded string values to be joined together safely.
+ *
+ * @param e the string to encode, must not be null.
+ * @return the encoded string.
+ */
+ public static String encode(final String e) {
+ return ENCODER_IMPL.encode(e);
+ }
+
+ /**
+ * Decode a string previously encoded by {@link #encode(String)}.
+ *
+ * @param e the string to decode, must not be null.
+ * @return the decoded string.
+ */
+ public static String decode(final String e) {
+ return ENCODER_IMPL.decode(e);
+ }
+
+ public static abstract class Encoder {
+ public abstract String encode(String e);
+
+ public abstract String decode(String e);
+ }
+
+ private static class GwtEncoder extends Encoder {
+ @Override
+ public String encode(final String e) {
+ return fixPathImpl(URL.encodeComponent(e));
+ }
+
+ @Override
+ public String decode(final String e) {
+ return URL.decodeComponent(e);
+ }
+
+ private static native String fixPathImpl(String path) /*-{ return path.replace(/%2F/g, "/"); }-*/;
+ }
+
private KeyUtil() {
}
}
diff --git a/src/main/java/com/google/gwtorm/client/LongKey.java b/src/main/java/com/google/gwtorm/client/LongKey.java
index 3741e2c..4bd5bae 100644
--- a/src/main/java/com/google/gwtorm/client/LongKey.java
+++ b/src/main/java/com/google/gwtorm/client/LongKey.java
@@ -60,14 +60,11 @@
@Override
public String toString() {
final StringBuffer r = new StringBuffer();
- r.append(getClass().getName());
- r.append('[');
if (getParentKey() != null) {
r.append(getParentKey().toString());
- r.append(", ");
+ r.append(',');
}
r.append(get());
- r.append(']');
return r.toString();
}
diff --git a/src/main/java/com/google/gwtorm/client/ShortKey.java b/src/main/java/com/google/gwtorm/client/ShortKey.java
index 937c3f3..30daf88 100644
--- a/src/main/java/com/google/gwtorm/client/ShortKey.java
+++ b/src/main/java/com/google/gwtorm/client/ShortKey.java
@@ -61,14 +61,11 @@
@Override
public String toString() {
final StringBuffer r = new StringBuffer();
- r.append(getClass().getName());
- r.append('[');
if (getParentKey() != null) {
r.append(getParentKey().toString());
- r.append(", ");
+ r.append(',');
}
r.append(get());
- r.append(']');
return r.toString();
}
diff --git a/src/main/java/com/google/gwtorm/client/StringKey.java b/src/main/java/com/google/gwtorm/client/StringKey.java
index 20172c4..a906142 100644
--- a/src/main/java/com/google/gwtorm/client/StringKey.java
+++ b/src/main/java/com/google/gwtorm/client/StringKey.java
@@ -62,14 +62,11 @@
@Override
public String toString() {
final StringBuffer r = new StringBuffer();
- r.append(getClass().getName());
- r.append('[');
if (getParentKey() != null) {
r.append(getParentKey().toString());
- r.append(", ");
+ r.append(',');
}
- r.append(get());
- r.append(']');
+ r.append(KeyUtil.encode(get()));
return r.toString();
}
diff --git a/src/main/java/com/google/gwtorm/jdbc/Database.java b/src/main/java/com/google/gwtorm/jdbc/Database.java
index 7f575f6..0b9a39f 100644
--- a/src/main/java/com/google/gwtorm/jdbc/Database.java
+++ b/src/main/java/com/google/gwtorm/jdbc/Database.java
@@ -14,6 +14,7 @@
package com.google.gwtorm.jdbc;
+import com.google.gwtorm.client.KeyUtil;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Schema;
import com.google.gwtorm.client.SchemaFactory;
@@ -25,6 +26,7 @@
import com.google.gwtorm.schema.sql.DialectH2;
import com.google.gwtorm.schema.sql.DialectPostgreSQL;
import com.google.gwtorm.schema.sql.SqlDialect;
+import com.google.gwtorm.server.StandardKeyEncoder;
import java.sql.Connection;
import java.sql.SQLException;
@@ -53,6 +55,10 @@
private static final Map<Class<?>, String> schemaFactoryNames =
Collections.synchronizedMap(new WeakHashMap<Class<?>, String>());
+ static {
+ KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+ }
+
private final DataSource dataSource;
private final JavaSchemaModel schemaModel;
private final AbstractSchemaFactory<T> implFactory;
diff --git a/src/main/java/com/google/gwtorm/server/StandardKeyEncoder.java b/src/main/java/com/google/gwtorm/server/StandardKeyEncoder.java
new file mode 100644
index 0000000..c9076e8
--- /dev/null
+++ b/src/main/java/com/google/gwtorm/server/StandardKeyEncoder.java
@@ -0,0 +1,118 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.server;
+
+import com.google.gwtorm.client.KeyUtil.Encoder;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+
+public class StandardKeyEncoder extends Encoder {
+ private static final char[] hexc =
+ {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
+ 'E', 'F'};
+ private static final char safe[];
+ private static final byte hexb[];
+
+ static {
+ safe = new char[256];
+ safe['-'] = '-';
+ safe['_'] = '_';
+ safe['.'] = '.';
+ safe['!'] = '!';
+ safe['~'] = '~';
+ safe['*'] = '*';
+ safe['\''] = '\'';
+ safe['('] = '(';
+ safe[')'] = ')';
+ safe['/'] = '/';
+ safe[' '] = '+';
+ for (char c = '0'; c <= '9'; c++)
+ safe[c] = c;
+ for (char c = 'A'; c <= 'Z'; c++)
+ safe[c] = c;
+ for (char c = 'a'; c <= 'z'; c++)
+ safe[c] = c;
+
+ hexb = new byte['f' + 1];
+ Arrays.fill(hexb, (byte) -1);
+ for (char i = '0'; i <= '9'; i++)
+ hexb[i] = (byte) (i - '0');
+ for (char i = 'A'; i <= 'F'; i++)
+ hexb[i] = (byte) ((i - 'A') + 10);
+ for (char i = 'a'; i <= 'f'; i++)
+ hexb[i] = (byte) ((i - 'a') + 10);
+ }
+
+ @Override
+ public String encode(final String e) {
+ final byte[] b;
+ try {
+ b = e.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e1) {
+ throw new RuntimeException("No UTF-8 support", e1);
+ }
+
+ final StringBuilder r = new StringBuilder(b.length);
+ for (int i = 0; i < b.length; i++) {
+ final int c = b[i] & 0xff;
+ final char s = safe[c];
+ if (s == 0) {
+ r.append('%');
+ r.append(hexc[c >> 4]);
+ r.append(hexc[c & 15]);
+ } else {
+ r.append(s);
+ }
+ }
+ return r.toString();
+ }
+
+ @Override
+ public String decode(final String e) {
+ if (e.indexOf('%') < 0) {
+ return e.replace('+', ' ');
+ }
+
+ final byte[] b = new byte[e.length()];
+ int bPtr = 0;
+ try {
+ for (int i = 0; i < e.length();) {
+ final char c = e.charAt(i);
+ if (c == '%' && i + 2 < e.length()) {
+ final int v = (hexb[e.charAt(i + 1)] << 4) | hexb[e.charAt(i + 2)];
+ if (v < 0) {
+ throw new IllegalArgumentException(e.substring(i, i + 3));
+ }
+ b[bPtr++] = (byte) v;
+ i += 3;
+ } else if (c == '+') {
+ b[bPtr++] = ' ';
+ i++;
+ } else {
+ b[bPtr++] = (byte) c;
+ i++;
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException err) {
+ throw new IllegalArgumentException("Bad encoding: " + e);
+ }
+ try {
+ return new String(b, 0, bPtr, "UTF-8");
+ } catch (UnsupportedEncodingException e1) {
+ throw new RuntimeException("No UTF-8 support", e1);
+ }
+ }
+}
\ No newline at end of file