Direction estimators are simple classes which estimate a string's direction using various heuristics.
This is useful for automatically setting the direction of an element that contains text.

Review at http://gwt-code-reviews.appspot.com/338801


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7973 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/i18n/shared/AnyRtlDirectionEstimator.java b/user/src/com/google/gwt/i18n/shared/AnyRtlDirectionEstimator.java
new file mode 100644
index 0000000..e3855d4
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/shared/AnyRtlDirectionEstimator.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010 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.gwt.i18n.shared;
+
+import com.google.gwt.i18n.client.HasDirection.Direction;
+
+/**
+ * Direction estimator that uses the "any RTL" heuristic.
+ */
+public class AnyRtlDirectionEstimator extends DirectionEstimator {
+
+  /**
+   * An instance of AnyRtlDirectionEstimator, to be returned by {@link #get}.
+   */
+  private static final AnyRtlDirectionEstimator instance =
+      new AnyRtlDirectionEstimator();
+
+  /**
+   * Get an instance of AnyRtlDirectionEstimator.
+   * 
+   * @return An instance of AnyRtlDirectionEstimator.
+   */
+  public static AnyRtlDirectionEstimator get() {
+    return instance;
+  }
+
+  /**
+   * Estimates the direction of a given string using the "any RTL" heuristic:
+   * the return value is RTL if the string contains at least one RTL character.
+   * Otherwise, it is LTR.
+   * 
+   * @param str Input string.
+   * @return Direction The estimated direction of {@code str}.
+   */
+  @Override
+  public Direction estimateDirection(String str) {
+    return BidiUtils.get().hasAnyRtl(str) ? Direction.RTL : Direction.LTR;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/shared/DirectionEstimator.java b/user/src/com/google/gwt/i18n/shared/DirectionEstimator.java
new file mode 100644
index 0000000..9488e54
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/shared/DirectionEstimator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2010 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.gwt.i18n.shared;
+
+import com.google.gwt.i18n.client.HasDirection.Direction;
+
+/**
+ * Interface for direction estimators.
+ */
+public abstract class DirectionEstimator {
+  
+  /**
+   * Estimates the direction of a plain-text string.
+   * 
+   * @param str The string to check.
+   * @return {@code str}'s estimated direction.
+   */
+  public abstract Direction estimateDirection(String str);
+
+  /**
+   * Estimates the direction of a string.
+   * 
+   * @param str The string to check.
+   * @param isHtml Whether {@code str} is HTML / HTML-escaped. {@code false}
+   *        means that {@code str} is plain-text.
+   * @return {@code str}'s estimated direction.
+   */
+  public Direction estimateDirection(String str, boolean isHtml) {
+    return estimateDirection(BidiUtils.get().stripHtmlIfNeeded(str, isHtml));
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/shared/FirstStrongDirectionEstimator.java b/user/src/com/google/gwt/i18n/shared/FirstStrongDirectionEstimator.java
new file mode 100644
index 0000000..ca596a3
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/shared/FirstStrongDirectionEstimator.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2010 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.gwt.i18n.shared;
+
+import com.google.gwt.i18n.client.HasDirection.Direction;
+
+/**
+ * Direction estimator that uses the "first strong" heuristic.
+ */
+public class FirstStrongDirectionEstimator extends DirectionEstimator {
+
+  /**
+   * An instance of FirstStrongDirectionEstimator, to be returned by
+   * {@link #get}.
+   */
+  private static final FirstStrongDirectionEstimator instance =
+      new FirstStrongDirectionEstimator();
+  
+  /**
+   * Get an instance of FirstStrongDirectionEstimator.
+   * 
+   * @return An instance of FirstStrongDirectionEstimator.
+   */
+  public static FirstStrongDirectionEstimator get() {
+    return instance;
+  }
+
+  /**
+   * Estimates the direction of a given string using the "first strong"
+   * heuristic: The return value is determined by the first character in the
+   * string with strong directionality. If there is no such character, the
+   * return value is DEFAULT.
+   *
+   * @param str Input string.
+   * @return Direction The estimated direction of {@code str}.
+   */
+  @Override
+  public Direction estimateDirection(String str) {
+    return BidiUtils.get().startsWithRtl(str) ? Direction.RTL :
+      BidiUtils.get().startsWithLtr(str) ? Direction.LTR : Direction.DEFAULT;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/shared/HasDirectionEstimator.java b/user/src/com/google/gwt/i18n/shared/HasDirectionEstimator.java
new file mode 100644
index 0000000..df86681
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/shared/HasDirectionEstimator.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010 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.gwt.i18n.shared;
+
+/**
+ * Interface for objects that have a direction estimator.
+ */
+public interface HasDirectionEstimator {
+
+  /**
+   * @return the {@code DirectionEstimator} object.
+   */
+  DirectionEstimator getDirectionEstimator();
+
+  /**
+   * Toggles on / off direction estimation.
+   * 
+   * @param enabled Whether to enable direction estimation. If {@true}, sets the
+   *        DirectionEstimator object to a default {@DirectionEstimator}.
+   */
+  void setDirectionEstimator(boolean enabled);
+  
+  /**
+   * Sets the DirectionEstimator object.
+   * 
+   * @param directionEstimator The {@code directionEstimator} to be set. {@code
+   *        null} means turning off direction estimation.
+   */
+  void setDirectionEstimator(DirectionEstimator directionEstimator);
+}
diff --git a/user/src/com/google/gwt/i18n/shared/WordCountDirectionEstimator.java b/user/src/com/google/gwt/i18n/shared/WordCountDirectionEstimator.java
new file mode 100644
index 0000000..0333609
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/shared/WordCountDirectionEstimator.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010 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.gwt.i18n.shared;
+
+import com.google.gwt.i18n.client.HasDirection.Direction;
+
+/**
+ * Direction estimator that uses the "word count" heuristic.
+ * 
+ * <p> Note: this is probably the recommended estimator for most use cases.
+ */
+public class WordCountDirectionEstimator extends DirectionEstimator {
+
+  /**
+   * An instance of WordCountDirectionEstimator, to be returned by {@link #get}.
+   */
+  private static final WordCountDirectionEstimator instance =
+      new WordCountDirectionEstimator();
+  
+  /**
+   * Get an instance of WordCountDirectionEstimator.
+   * 
+   * @return An instance of WordCountDirectionEstimator.
+   */
+  public static WordCountDirectionEstimator get() {
+    return instance;
+  }
+
+  /**
+   * Estimates the direction of a given string using the "word count" heuristic,
+   * as defined at {@link BidiUtils#estimateDirection}.
+   *
+   * @param str Input string.
+   * @return Direction The estimated direction of {@code str}.
+   */
+  @Override
+  public Direction estimateDirection(String str) {
+    return BidiUtils.get().estimateDirection(str);
+  }
+}
diff --git a/user/test/com/google/gwt/i18n/I18NSuite.java b/user/test/com/google/gwt/i18n/I18NSuite.java
index 0088aad..a1357fc 100644
--- a/user/test/com/google/gwt/i18n/I18NSuite.java
+++ b/user/test/com/google/gwt/i18n/I18NSuite.java
@@ -44,9 +44,12 @@
 import com.google.gwt.i18n.rebind.MessageFormatParserTest;
 import com.google.gwt.i18n.server.GwtLocaleTest;
 import com.google.gwt.i18n.server.RegionInheritanceTest;
+import com.google.gwt.i18n.shared.AnyRtlDirectionEstimatorTest;
 import com.google.gwt.i18n.shared.BidiFormatterTest;
 import com.google.gwt.i18n.shared.BidiUtilsTest;
+import com.google.gwt.i18n.shared.FirstStrongDirectionEstimatorTest;
 import com.google.gwt.i18n.shared.GwtBidiUtilsTest;
+import com.google.gwt.i18n.shared.WordCountDirectionEstimatorTest;
 import com.google.gwt.junit.tools.GWTTestSuite;
 
 import junit.framework.Test;
@@ -61,6 +64,7 @@
     // $JUnit-BEGIN$
     suite.addTestSuite(ArabicPluralsTest.class);
     suite.addTestSuite(AnnotationsTest.class);
+    suite.addTestSuite(AnyRtlDirectionEstimatorTest.class);
     suite.addTestSuite(BidiFormatterTest.class);
     suite.addTestSuite(BidiUtilsTest.class);
     suite.addTestSuite(ConstantMapTest.class);
@@ -72,6 +76,7 @@
     suite.addTestSuite(DateTimeFormat_pl_Test.class);
     suite.addTestSuite(DateTimeParse_en_Test.class);
     suite.addTestSuite(DateTimeParse_zh_CN_Test.class);
+    suite.addTestSuite(FirstStrongDirectionEstimatorTest.class);
     suite.addTestSuite(GwtBidiUtilsTest.class);
     suite.addTestSuite(GwtLocaleTest.class);
     suite.addTestSuite(I18NTest.class);
@@ -92,6 +97,7 @@
     suite.addTestSuite(RuntimeLocalesTest.class);
     suite.addTestSuite(TimeZoneInfoTest.class);
     suite.addTestSuite(TimeZoneTest.class);
+    suite.addTestSuite(WordCountDirectionEstimatorTest.class);
     // $JUnit-END$
 
     return suite;
diff --git a/user/test/com/google/gwt/i18n/shared/AnyRtlDirectionEstimatorTest.java b/user/test/com/google/gwt/i18n/shared/AnyRtlDirectionEstimatorTest.java
new file mode 100644
index 0000000..66d7687
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/shared/AnyRtlDirectionEstimatorTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2010 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.gwt.i18n.shared;
+
+import com.google.gwt.i18n.client.HasDirection.Direction;
+
+/**
+ * Unit tests for {@link AnyRtlDirectionEstimator}.
+ */
+public class AnyRtlDirectionEstimatorTest extends DirectionEstimatorTestBase {
+ 
+  @Override
+  protected void assertDirectionEstimation(Direction expectedDirection,
+      String str, boolean isHtml) {
+    assertDirectionEstimation(expectedDirection, AnyRtlDirectionEstimator.get(),
+        str, isHtml);
+  }
+  
+  private final String containsRtlChar = EN_WORD + DIGITS_WORD +
+      WORD_WITH_ONE_RTL_CHAR;
+  private final String noRtlChars = EN_WORD + DIGITS_WORD + NEUTRAL_WORD;
+  private final String noRtlCharsHtml = LONG_MIXED_TAG + EN_WORD + DIGITS_WORD;
+
+  public void testEstimateDirection() {
+    assertDirectionEstimation(Direction.RTL, containsRtlChar);
+    assertDirectionEstimation(Direction.LTR, noRtlChars);
+    
+    assertDirectionEstimation(Direction.RTL, noRtlCharsHtml);
+    assertDirectionEstimationHtml(Direction.LTR, noRtlCharsHtml);
+  }
+}
diff --git a/user/test/com/google/gwt/i18n/shared/DirectionEstimatorTestBase.java b/user/test/com/google/gwt/i18n/shared/DirectionEstimatorTestBase.java
new file mode 100644
index 0000000..7960906
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/shared/DirectionEstimatorTestBase.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2010 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.gwt.i18n.shared;
+
+import com.google.gwt.i18n.client.HasDirection.Direction;
+
+import junit.framework.TestCase;
+
+/**
+ * TestCase extension that includes some useful variables for estimation tests.
+ */
+public abstract class DirectionEstimatorTestBase extends TestCase {
+ 
+  protected final String DIGITS_WORD = " 012";
+  protected final String EN_WORD = " abc";
+  protected final String IW_WORD = " \u05e0\u05e1\u05e2";
+  protected final String LONG_LTR_TAG = "<some nasty tag>";
+  
+  protected final String LONG_MIXED_TAG = "<some nasty tag" + IW_WORD + ">";
+  protected final String NEUTRAL_WORD = " ___";
+  protected final String WORD_WITH_ONE_RTL_CHAR = " ab\u05e0cd";
+  
+  /**
+   * Asserts that the estimated direction of a given String matches the expected
+   * direction.
+   * 
+   * @param expectedDirection The expected direction.
+   * @param directionEstimator A direction estimator object. 
+   * @param str A String to estimate its direction.
+   * @param isHtml Whether {@code str} is HTML / HTML-escaped.
+   */
+  protected void assertDirectionEstimation(Direction expectedDirection,
+      DirectionEstimator directionEstimator, String str, boolean isHtml) {
+    assertEquals(expectedDirection,
+        directionEstimator.estimateDirection(str, isHtml));
+  }
+  
+  /**
+   * Asserts that the estimated direction of a given String matches the expected
+   * direction.
+   * The implementation will usually call {@link #assertDirectionEstimation(
+   * Direction, DirectionEstimator, String, boolean)} with the {@code
+   * DirectionEstimator} object to be tested.
+   * 
+   * @param expectedDirection The expected direction.
+   * @param str A String whose direction is estimated.
+   * @param isHtml Whether {@code str} is HTML / HTML-escaped.
+   */
+  protected abstract void assertDirectionEstimation(Direction expectedDirection,
+      String str, boolean isHtml);
+  
+  /**
+   * Operates like {@link #assertDirectionEstimation(Direction, String,
+   * boolean)}, but assuming  {@code str} is not HTML / HTML-escaped.
+   * 
+   * @param expectedDirection The expected direction.
+   * @param str A String whose direction is estimated.
+   */
+  protected void assertDirectionEstimation(Direction expectedDirection,
+      String str) {
+    assertDirectionEstimation(expectedDirection, str, false);
+  }
+
+  /**
+   * Operates like {@link #assertDirectionEstimation(Direction, String,
+   * boolean)}, but assuming  {@code str} is HTML / HTML-escaped.
+   * 
+   * @param expectedDirection The expected direction.
+   * @param str A String whose direction is estimated.
+   */
+  protected void assertDirectionEstimationHtml(Direction expectedDirection,
+      String str) {
+    assertDirectionEstimation(expectedDirection, str, true);
+  }
+}
diff --git a/user/test/com/google/gwt/i18n/shared/FirstStrongDirectionEstimatorTest.java b/user/test/com/google/gwt/i18n/shared/FirstStrongDirectionEstimatorTest.java
new file mode 100644
index 0000000..efb0d2d
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/shared/FirstStrongDirectionEstimatorTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010 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.gwt.i18n.shared;
+
+import com.google.gwt.i18n.client.HasDirection.Direction;
+
+/**
+ * Unit tests for {@link FirstStrongDirectionEstimator}.
+ */
+public class FirstStrongDirectionEstimatorTest extends DirectionEstimatorTestBase {
+
+  @Override
+  protected void assertDirectionEstimation(Direction expectedDirection,
+      String str, boolean isHtml) {
+    assertDirectionEstimation(expectedDirection,
+        FirstStrongDirectionEstimator.get(), str, isHtml);
+  }
+  
+  private String firstStrongLtr = DIGITS_WORD + EN_WORD + IW_WORD + IW_WORD;
+  private String firstStrongRtl = DIGITS_WORD + IW_WORD + EN_WORD + EN_WORD;
+  private String firstStrongRtlHtml = DIGITS_WORD + LONG_LTR_TAG + IW_WORD +
+      EN_WORD;
+  private String noStrongChars = NEUTRAL_WORD + DIGITS_WORD;
+ 
+  public void testEstimateDirection() {
+    assertDirectionEstimation(Direction.LTR, firstStrongLtr);
+    assertDirectionEstimation(Direction.RTL, firstStrongRtl);
+    assertDirectionEstimation(Direction.DEFAULT, noStrongChars);
+    
+    assertDirectionEstimation(Direction.LTR, firstStrongRtlHtml);
+    assertDirectionEstimationHtml(Direction.RTL, firstStrongRtlHtml);
+  }
+}
diff --git a/user/test/com/google/gwt/i18n/shared/WordCountDirectionEstimatorTest.java b/user/test/com/google/gwt/i18n/shared/WordCountDirectionEstimatorTest.java
new file mode 100644
index 0000000..dfb6561
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/shared/WordCountDirectionEstimatorTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2010 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.gwt.i18n.shared;
+
+import com.google.gwt.i18n.client.HasDirection.Direction;
+
+/**
+ * Unit tests for {@link WordCountDirectionEstimator}.
+ */
+public class WordCountDirectionEstimatorTest extends DirectionEstimatorTestBase {
+  
+  @Override
+  protected void assertDirectionEstimation(Direction expectedDirection,
+      String str, boolean isHtml) {
+    assertDirectionEstimation(expectedDirection,
+        WordCountDirectionEstimator.get(), str, isHtml);
+  }
+  
+  private String pureNeutral = NEUTRAL_WORD;
+  private String rtlAboveThreshold = EN_WORD + IW_WORD;
+  private String rtlBelowThreshold = IW_WORD + EN_WORD + EN_WORD;
+  private String rtlHtml = LONG_LTR_TAG + IW_WORD;
+  private String weaklyLtr = NEUTRAL_WORD + DIGITS_WORD;
+ 
+  public void testEstimateDirection() {
+    assertDirectionEstimation(Direction.RTL, rtlAboveThreshold);
+    assertDirectionEstimation(Direction.LTR, rtlBelowThreshold);
+    assertDirectionEstimation(Direction.LTR, weaklyLtr);
+    assertDirectionEstimation(Direction.DEFAULT, pureNeutral);
+    
+    assertDirectionEstimation(Direction.LTR, rtlHtml);
+    assertDirectionEstimationHtml(Direction.RTL, rtlHtml);
+  }
+}