If you don't hear anything back in a few days from Daniel or others, I'll make the changes.

--
James Mitchell
Software Engineer / Open Source Evangelist
Consulting / Mentoring / Freelance
EdgeTech, Inc.
http://www.edgetechservices.net/
678.910.8017
AIM:   jmitchtx
Yahoo: jmitchtx
MSN:   [EMAIL PROTECTED]
Skype: jmitchtx



On Jul 14, 2005, at 1:41 AM, Mattias Jiderhamn wrote:

As you may have noticed, I chose to create a Bugzilla entry (35726, http://issues.apache.org/bugzilla/show_bug.cgi?id=35726), where I intend to post the patches for the remaining issues, so that they are readily available to anyone.

At 2005-07-13 22:35, Mattias Jiderhamn wrote:

I haven't heard from Daniel Florey for a while now, so my guess is he is on vacation. Therefore I wonder if there is somebody else with sandbox commit access that could commit my patch below, adding a JDBC/database MessageProvider to the i18n component.

I intend to continue working on the remaining issues (as of e-mail on 2005-06-18) and I would prefer to separate the different issues into different patches/commits, rather than one big "my contributions".

If needed for correct encoding/line breaks, I could e-mail a zipped diff to somebody privately, or create a Bugzilla entry.

Thanks in advance.
  Mattias Jiderhamn

Here is the patch:
--------------------------------------------------------

Index: project.properties
===================================================================
--- project.properties  (revision 201952)
+++ project.properties  (working copy)
@@ -28,3 +28,8 @@
 # M A V E N  J A R  O V E R R I D E
# --------------------------------------------------------------------- ---
 maven.jar.override = on
+
+
+# Set target to Java 1.4, since JCoverage does not work with Java 1.5
+maven.compile.source=1.4
+maven.compile.target=1.4
\ No newline at end of file
Index: project.xml
===================================================================
--- project.xml (revision 201952)
+++ project.xml (working copy)
@@ -74,6 +74,23 @@
     </contributors>

     <dependencies>
+ <!-- Please note, these depenencies are needed for unit tests only! -->
+        <dependency>
+            <id>hsqldb</id>
+            <version>1.7.3.3</version>
+        </dependency>
+        <dependency>
+            <id>commons-dbcp</id>
+            <version>1.2.1</version>
+        </dependency>
+        <dependency>
+            <id>commons-pool</id>
+            <version>1.2</version>
+        </dependency>
+        <dependency>
+            <id>commons-collections</id>
+            <version>2.1.1</version>
+        </dependency>
     </dependencies>

     <build>
Index: src/test/org/apache/commons/i18n/I18nUtilsTest.java
===================================================================
--- src/test/org/apache/commons/i18n/I18nUtilsTest.java (revision 0)
+++ src/test/org/apache/commons/i18n/I18nUtilsTest.java (revision 0)
@@ -0,0 +1,45 @@
+/*
+*
+* ====================================================================
+*
+* Copyright 2004 The Apache Software Foundation
+*
+* 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 org.apache.commons.i18n;
+
+import junit.framework.TestCase;
+
+import java.util.Locale;
+
+/**
+ * @author Mattias Jiderhamn
+ */
+public class I18nUtilsTest extends TestCase {
+    public void testGetParentLocale() {
+        assertEquals("Language, country and variant",
+                new Locale("en", "GB"),
+ I18nUtils.getParentLocale(new Locale("en", "GB", "scottish")));
+
+        assertEquals("Language and country",
+                Locale.ENGLISH,
+                I18nUtils.getParentLocale(new Locale("en", "GB")));
+
+        assertEquals("Language and variant",
+                Locale.ENGLISH,
+ I18nUtils.getParentLocale(new Locale("en", "", "scottish")));
+
+ assertNull("Language only", I18nUtils.getParentLocale (Locale.ENGLISH));
+    }
+}
Index: src/test/org/apache/commons/i18n/ ResourceBundleMessageProviderTest.java
===================================================================
--- src/test/org/apache/commons/i18n/ ResourceBundleMessageProviderTest.java (revision 201952) +++ src/test/org/apache/commons/i18n/ ResourceBundleMessageProviderTest.java (working copy)
@@ -137,7 +137,7 @@
Map germanEntries = new ResourceBundleMessageProvider ("messageBundle").getEntries("helloWorld", Locale.GERMAN);
         assertEquals("No of entries", 3, germanEntries.size());
         assertEquals("Hallo Welt", germanEntries.get("title"));
- assertEquals("Ich w�nsche Dir alles Gute und ein frohes Fest!", germanEntries.get("text")); + assertEquals("Ich wünsche Dir alles Gute und ein frohes Fest!", germanEntries.get("text")); assertEquals("This entry is not translated to any other languages", germanEntries.get("notTranslated"));

Map frenchEntries = new ResourceBundleMessageProvider ("messageBundle").getEntries("helloWorld", Locale.FRENCH);
Index: src/test/org/apache/commons/i18n/JdbcMessageProviderTest.java
===================================================================
--- src/test/org/apache/commons/i18n/JdbcMessageProviderTest.java (revision 0) +++ src/test/org/apache/commons/i18n/JdbcMessageProviderTest.java (revision 0)
@@ -0,0 +1,169 @@
+package org.apache.commons.i18n;
+
+import junit.framework.TestCase;
+
+import java.sql.DriverManager;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.dbcp.BasicDataSource;
+
+/**
+ * @author Mattias Jiderhamn
+ */
+public class JdbcMessageProviderTest extends TestCase {
+
+ private static Connection getNewConnection() throws SQLException { + return DriverManager.getConnection("jdbc:hsqldb:.", "sa", ""); // Connect to in-memory database
+    }
+
+    public void setUp() throws Exception {
+        /* Make sure en_US is the default Locale for tests */
+        Locale.setDefault(Locale.US);
+
+ Class.forName("org.hsqldb.jdbcDriver"); // Load HSQLDB database driver
+        Connection conn = getNewConnection();
+        Statement stmt = conn.createStatement();
+        stmt.execute(
+                "CREATE TABLE messages ( " +
+                "  'id' VARCHAR(30), " +
+                "  'language' VARCHAR(2), " +
+                "  'title' VARCHAR(100), " +
+                "  'text' VARCHAR(100)" +
+                ")");
+        stmt.execute(
+                "INSERT INTO messages VALUES (" +
+                "  'helloWorld', 'en', " +
+                "  'Hello World', 'I wish you a merry christmas!'" +
+                ")"
+        );
+        stmt.execute(
+                "INSERT INTO messages VALUES (" +
+                "  'helloWorld', 'de', " +
+ " 'Hallo Welt', 'Ich wünsche Dir alles Gute und ein frohes Fest!'" +
+                ")"
+        );
+        stmt.close();
+        conn.close();
+    }
+
+    public void tearDown() throws Exception {
+        Connection conn = getNewConnection();
+        conn.createStatement().execute(
+                "DROP TABLE messages"
+        );
+        conn.close();
+    }
+
+    public void testConstructors() throws Exception {
+        // Connection constructor
+ Connection conn = DriverManager.getConnection ("jdbc:hsqldb:.", "sa", ""); // Connect to in-memory database + JdbcMessageProvider jdbcMessageProvider = new JdbcMessageProvider(conn, "messages", "id", "language");
+        conn.close();
+ assertEquals("Hello World", jdbcMessageProvider.getText ("helloWorld", "title", Locale.ENGLISH));
+
+        // DataSource constructor
+        BasicDataSource dataSource = new BasicDataSource();
+        dataSource.setUrl("jdbc:hsqldb:.");
+        dataSource.setUsername("sa");
+        dataSource.setPassword("");
+ jdbcMessageProvider = new JdbcMessageProvider(dataSource, "messages", "id", "language"); + assertEquals("Hello World", jdbcMessageProvider.getText ("helloWorld", "title", Locale.ENGLISH));
+
+        // Map/Properties constructor
+        Properties props = new Properties();
+ props.setProperty("jdbc.connect.driver", "org.hsqldb.jdbcDriver");
+        props.setProperty("jdbc.connect.url", "jdbc:hsqldb:.");
+        props.setProperty("jdbc.connect.login", "sa");
+        props.setProperty("jdbc.connect.password", "");
+
+        props.setProperty("jdbc.sql.table", "messages");
+        props.setProperty("jdbc.sql.key.column", "id");
+        props.setProperty("jdbc.sql.locale.column", "language");
+        jdbcMessageProvider = new JdbcMessageProvider(props);
+ assertEquals("Hello World", jdbcMessageProvider.getText ("helloWorld", "title", Locale.ENGLISH));
+
+        // Test install
+ MessageManager.addMessageProvider("messages", jdbcMessageProvider); + assertEquals("Hello World", MessageManager.getText ("helloWorld", "title", null, Locale.ENGLISH));
+    }
+
+    public void testGetText() throws Exception {
+ Connection conn = DriverManager.getConnection ("jdbc:hsqldb:.", "sa", ""); // Connect to in-memory database + JdbcMessageProvider jdbcMessageProvider = new JdbcMessageProvider(conn, "messages", "id", "language");
+        conn.close();
+
+        // Explicit default locale
+ assertEquals("Hello World", jdbcMessageProvider.getText ("helloWorld", "title", Locale.ENGLISH)); + assertEquals("I wish you a merry christmas!", jdbcMessageProvider.getText("helloWorld", "text", Locale.ENGLISH));
+
+        // Default locale with country
+ assertEquals("Hello World", jdbcMessageProvider.getText ("helloWorld", "title", Locale.US)); + assertEquals("I wish you a merry christmas!", jdbcMessageProvider.getText("helloWorld", "text", Locale.US));
+
+        // Default locale with country and variant
+        Locale scottish = new Locale("en", "", "scottish");
+ assertEquals("Hello World", jdbcMessageProvider.getText ("helloWorld", "title", scottish)); + assertEquals("I wish you a merry christmas!", jdbcMessageProvider.getText("helloWorld", "text", scottish));
+
+ assertEquals("Hallo Welt", jdbcMessageProvider.getText ("helloWorld", "title", Locale.GERMAN)); + assertEquals("Ich wünsche Dir alles Gute und ein frohes Fest!", jdbcMessageProvider.getText("helloWorld", "text", Locale.GERMAN));
+
+        // Default locale with country
+ assertEquals("Hallo Welt", jdbcMessageProvider.getText ("helloWorld", "title", Locale.GERMANY)); + assertEquals("Ich wünsche Dir alles Gute und ein frohes Fest!", jdbcMessageProvider.getText("helloWorld", "text", Locale.GERMANY));
+
+        // Test use of defaule
+ assertEquals("Hello World", jdbcMessageProvider.getText ("helloWorld", "title", Locale.JAPANESE)); + assertEquals("I wish you a merry christmas!", jdbcMessageProvider.getText("helloWorld", "text", Locale.JAPANESE));
+
+        // Test non-existent
+ assertNull(jdbcMessageProvider.getText("foo", "bar", Locale.ENGLISH));
+    }
+
+    public void testGetEntries() throws Exception {
+ Connection conn = DriverManager.getConnection ("jdbc:hsqldb:.", "sa", ""); // Connect to in-memory database + JdbcMessageProvider jdbcMessageProvider = new JdbcMessageProvider(conn, "messages", "id", "language");
+        conn.close();
+
+        // Explicit default locale
+ Map entries = jdbcMessageProvider.getEntries ("helloWorld", Locale.ENGLISH);
+        assertEquals("No of entries", 2, entries.size());
+        assertEquals("Hello World", (String)entries.get("title"));
+ assertEquals("I wish you a merry christmas!", (String) entries.get("text"));
+
+        // Default locale with country
+ entries = jdbcMessageProvider.getEntries("helloWorld", Locale.US);
+        assertEquals("No of entries", 2, entries.size());
+        assertEquals("Hello World", (String)entries.get("title"));
+ assertEquals("I wish you a merry christmas!", (String) entries.get("text"));
+
+        // Default locale with country and variant
+        Locale scottish = new Locale("en", "", "scottish");
+ entries = jdbcMessageProvider.getEntries("helloWorld", scottish);
+        assertEquals("No of entries", 2, entries.size());
+        assertEquals("Hello World", (String)entries.get("title"));
+ assertEquals("I wish you a merry christmas!", (String) entries.get("text"));
+
+ entries = jdbcMessageProvider.getEntries("helloWorld", Locale.GERMAN);
+        assertEquals("No of entries", 2, entries.size());
+        assertEquals("Hallo Welt", (String)entries.get("title"));
+ assertEquals("Ich wünsche Dir alles Gute und ein frohes Fest!", (String)entries.get("text"));
+
+        // Default locale with country
+ entries = jdbcMessageProvider.getEntries("helloWorld", Locale.GERMANY);
+        assertEquals("No of entries", 2, entries.size());
+        assertEquals("Hallo Welt", (String)entries.get("title"));
+ assertEquals("Ich wünsche Dir alles Gute und ein frohes Fest!", (String)entries.get("text"));
+
+        // Test use of defaule
+ entries = jdbcMessageProvider.getEntries("helloWorld", Locale.JAPANESE);
+        assertEquals("No of entries", 2, entries.size());
+        assertEquals("Hello World", (String)entries.get("title"));
+ assertEquals("I wish you a merry christmas!", (String) entries.get("text"));
+    }
+}
Index: src/java/org/apache/commons/i18n/XMLMessageProvider.java
===================================================================
--- src/java/org/apache/commons/i18n/XMLMessageProvider.java (revision 201952) +++ src/java/org/apache/commons/i18n/XMLMessageProvider.java (working copy)
@@ -123,9 +123,11 @@
     private Message lookupMessage(String id, Locale locale) {
         String key = id + '_' + locale.toString();
if (messages.containsKey(key)) return (Message) messages.get(key);
-        while (key.lastIndexOf('_') > 0) {
-            key = key.substring(0, key.lastIndexOf('_'));
+        locale = I18nUtils.getParentLocale(locale);
+        while (locale != null) {
+            key = id + '_' + locale.toString();
if (messages.containsKey(key)) return (Message) messages.get(key);
+            locale = I18nUtils.getParentLocale(locale);
         }
         return null;
     }
Index: src/java/org/apache/commons/i18n/I18nUtils.java
===================================================================
--- src/java/org/apache/commons/i18n/I18nUtils.java     (revision 0)
+++ src/java/org/apache/commons/i18n/I18nUtils.java     (revision 0)
@@ -0,0 +1,40 @@
+/*
+*
+* ====================================================================
+*
+* Copyright 2004 The Apache Software Foundation
+*
+* 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 org.apache.commons.i18n;
+
+import java.util.Locale;
+
+/**
+ * This class holds utility methods useful when working with i18n.
+ * @author Mattias Jiderhamn
+ */
+public class I18nUtils {
+    private I18nUtils() {
+    }
+
+    public static Locale getParentLocale (Locale locale) {
+        if(locale.getVariant().length() != 0)
+ return new Locale(locale.getLanguage(), locale.getCountry());
+        else if(locale.getCountry().length() != 0)
+            return new Locale(locale.getLanguage());
+        else // Locale with only language have no parent
+            return null;
+    }
+}
\ No newline at end of file
Index: src/java/org/apache/commons/i18n/JdbcMessageProvider.java
===================================================================
--- src/java/org/apache/commons/i18n/JdbcMessageProvider.java (revision 0) +++ src/java/org/apache/commons/i18n/JdbcMessageProvider.java (revision 0)
@@ -0,0 +1,183 @@
+package org.apache.commons.i18n;
+
+import javax.sql.DataSource;
+import java.util.*;
+import java.sql.*;
+
+/**
+ * @author Mattias Jiderhamn
+ */
+public class JdbcMessageProvider implements MessageProvider {
+    /**
+ * This Map has locale or language as key, and a Map with the different
+     * messages as value.
+     */
+    private final Map locales = new HashMap();
+
+    private String idColumn;
+
+    private String languageColumn;
+
+ public JdbcMessageProvider(Connection conn, String table, String idColumn, String languageColumn)
+            throws SQLException {
+        this.idColumn = idColumn;
+        this.languageColumn = languageColumn;
+        init(conn, table);
+    }
+
+ public JdbcMessageProvider(DataSource ds, String table, String idColumn, String languageColumn)
+            throws SQLException {
+        this.idColumn = idColumn;
+        this.languageColumn = languageColumn;
+        Connection conn = null;
+        try {
+            conn = ds.getConnection();
+            init(conn, table);
+        }
+        finally {
+            if(conn != null)
+                conn.close();
+        }
+    }
+
+    /**
+     * Create JDBC MessageProvider from properties in a Map, such
+ * as a java.util.Properties object. The following are the properties in use, which + * are the same as for JDBCResources of Jakarta Commons Resources
+     * jdbc.connect.driver               = org.gjt.mm.mysql.Driver
+ * jdbc.connect.url = jdbc:mysql://localhost/ resources
+     * jdbc.connect.login                = resourcesTest
+     * jdbc.connect.password             = resourcesTest
+     *
+     * jdbc.sql.table                    = resources
+     * jdbc.sql.locale.column            = locale
+     * jdbc.sql.key.column               = msgKey
+     */
+ public JdbcMessageProvider(Map properties) throws ClassNotFoundException, SQLException { + String driver = (String)properties.get ("jdbc.connect.driver");
+        String url    = (String)properties.get("jdbc.connect.url");
+        String user = (String)properties.get("jdbc.connect.login");
+ String pass = (String)properties.get ("jdbc.connect.password");
+
+        String table = (String)properties.get("jdbc.sql.table");
+ this.idColumn = (String)properties.get ("jdbc.sql.key.column"); + this.languageColumn = (String)properties.get ("jdbc.sql.locale.column");
+
+        Class.forName(driver);
+        Connection conn = null;
+        try {
+            conn = DriverManager.getConnection(url, user, pass);
+            init(conn, table);
+        }
+        finally {
+            if(conn != null)
+                conn.close();
+        }
+    }
+
+ //////////////////////////////////////////////////////////////// ///////
+    // Methods for initialization
+ //////////////////////////////////////////////////////////////// ///////
+
+ private void init(Connection conn, String table) throws SQLException {
+        Statement stmt = null;
+        ResultSet rs = null;
+        try {
+            stmt = conn.createStatement();
+            rs = stmt.executeQuery("SELECT * FROM " + table);
+            String[] valueColumns = getValueColumns(rs);
+            while(rs.next()) {
+                String id = rs.getString(idColumn);
+                Locale locale = getLocale(rs);
+                Map entries = new HashMap();
+                for(int i = 0; i < valueColumns.length; i++) {
+ entries.put(valueColumns[i], rs.getString(valueColumns[i]));
+                }
+                Map localeMap = (Map)locales.get(locale);
+ if(localeMap == null) { // If first record for this Locale
+                    localeMap = new HashMap();
+                    locales.put(locale, localeMap);
+                }
+                localeMap.put(id, entries);
+            }
+        }
+        finally {
+            if(stmt != null)
+                stmt.close();
+            if(rs != null)
+              rs.close();
+        }
+    }
+
+    /**
+ * Get a String of all the column names, except the ID column and the
+     * language column.
+ * @param rs A <code>ResultSet</code> ready for reading meta data.
+     * @return A String array with the text value column names.
+     * @throws SQLException If an SQL error occurs.
+     */
+ protected String[] getValueColumns(ResultSet rs) throws SQLException {
+        List output = new LinkedList();
+        ResultSetMetaData metadata = rs.getMetaData();
+        int count = metadata.getColumnCount();
+        for(int i = 0; i < count; i++) {
+ String columnName = metadata.getColumnName(i+1); // (Count from 1) + if(! idColumn.equals(columnName) && ! languageColumn.equals(columnName) )
+                output.add(columnName);
+        }
+        return (String[])output.toArray(new String[0]);
+    }
+
+    /**
+ * Get <code>Locale</code> for the current record in the ResultSet. May be overridden + * by subclasses to allow for proprietary interpretation of language data. + * The default implementation assumes the column with the name provided as languageColumn
+     * for the constructor contains the ISO-639 code.
+ * @return The <code>Locale</code> of the current <code>ResultSet</code> record.
+     */
+    protected Locale getLocale(ResultSet rs) throws SQLException {
+        return new Locale(rs.getString(languageColumn));
+    }
+
+ //////////////////////////////////////////////////////////////// ///////
+    // Methods to implement MessageProvider
+ //////////////////////////////////////////////////////////////// ///////
+
+ public String getText(String id, String entry, Locale locale) throws MessageNotFoundException {
+        // TODO: Add Logging
+        Map entries = getEntries(id, locale);
+        if(entries != null) {
+ // TODO: Consider whether we need to recurse up if entries does not contain requested entry
+            return (String)entries.get(entry);
+        }
+        else
+            return null;
+    }
+
+ public Map getEntries(String id, Locale locale) throws MessageNotFoundException {
+        Map entries = findEntriesRecursively(id,locale);
+ if(entries == null) // If not found by using specified locale, try to use default + entries = findEntriesRecursively(id,Locale.getDefault ());
+        return entries;
+    }
+
+    /**
+ * Find entries by looking at the parent locale (language, country, variant -> + * language, country -> language) until entry is found. If entry not found for topmost
+     * Locale (language only), null is returned.
+     */
+    private Map findEntriesRecursively(String id, Locale locale) {
+        Map localeIds = (Map)locales.get(locale);
+        if(localeIds != null) {
+            Map entries = (Map)localeIds.get(id);
+            if(entries != null)
+              return entries;
+        }
+        Locale parentLocale = I18nUtils.getParentLocale(locale);
+        if(parentLocale == null)
+            return null;
+        else
+ return findEntriesRecursively(id, parentLocale); // Recursive call
+    }
+
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]




---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]




---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to