You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by df...@apache.org on 2005/07/24 14:53:33 UTC

svn commit: r224613 - in /jakarta/commons/sandbox/i18n/trunk: ./ src/examples/ src/examples/org/apache/i18n/examples/ src/java/org/apache/commons/i18n/ src/test/org/apache/commons/i18n/ xdocs/

Author: dflorey
Date: Sun Jul 24 05:53:05 2005
New Revision: 224613

URL: http://svn.apache.org/viewcvs?rev=224613&view=rev
Log:
Apply patch from Mattias Jiderhamn to resolve remaining issues

Added:
    jakarta/commons/sandbox/i18n/trunk/src/examples/i18n-jdbc.properties
    jakarta/commons/sandbox/i18n/trunk/src/examples/org/apache/i18n/examples/QualifiedJdbcExample.java
    jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/I18nUtils.java
    jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/JdbcMessageProvider.java
    jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/I18nUtilsTest.java
    jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/JdbcMessageProviderTest.java
Modified:
    jakarta/commons/sandbox/i18n/trunk/project.properties
    jakarta/commons/sandbox/i18n/trunk/project.xml
    jakarta/commons/sandbox/i18n/trunk/src/examples/org/apache/i18n/examples/LocalizedExceptionExample.java
    jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/LocalizedError.java
    jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/LocalizedException.java
    jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/LocalizedRuntimeException.java
    jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/MessageManager.java
    jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/MessageProvider.java
    jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/ResourceBundleMessageProvider.java
    jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/XMLMessageProvider.java
    jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/MessageManagerTest.java
    jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/MockProviderTestBase.java
    jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/ResourceBundleMessageProviderTest.java
    jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/XMLMessageProviderTest.java
    jakarta/commons/sandbox/i18n/trunk/xdocs/examples.xml

Modified: jakarta/commons/sandbox/i18n/trunk/project.properties
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/project.properties?rev=224613&r1=224612&r2=224613&view=diff
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/project.properties (original)
+++ jakarta/commons/sandbox/i18n/trunk/project.properties Sun Jul 24 05:53:05 2005
@@ -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

Modified: jakarta/commons/sandbox/i18n/trunk/project.xml
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/project.xml?rev=224613&r1=224612&r2=224613&view=diff
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/project.xml (original)
+++ jakarta/commons/sandbox/i18n/trunk/project.xml Sun Jul 24 05:53:05 2005
@@ -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>

Added: jakarta/commons/sandbox/i18n/trunk/src/examples/i18n-jdbc.properties
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/examples/i18n-jdbc.properties?rev=224613&view=auto
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/examples/i18n-jdbc.properties (added)
+++ jakarta/commons/sandbox/i18n/trunk/src/examples/i18n-jdbc.properties Sun Jul 24 05:53:05 2005
@@ -0,0 +1,7 @@
+jdbc.connect.driver=org.hsqldb.jdbcDriver
+jdbc.connect.url=jdbc:hsqldb:.
+jdbc.connect.login=sa
+jdbc.connect.password=
+jdbc.sql.table=messages
+jdbc.sql.locale.column=language
+jdbc.sql.key.column=id
\ No newline at end of file

Modified: jakarta/commons/sandbox/i18n/trunk/src/examples/org/apache/i18n/examples/LocalizedExceptionExample.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/examples/org/apache/i18n/examples/LocalizedExceptionExample.java?rev=224613&r1=224612&r2=224613&view=diff
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/examples/org/apache/i18n/examples/LocalizedExceptionExample.java (original)
+++ jakarta/commons/sandbox/i18n/trunk/src/examples/org/apache/i18n/examples/LocalizedExceptionExample.java Sun Jul 24 05:53:05 2005
@@ -25,6 +25,7 @@
 
 import org.apache.commons.i18n.LocalizedException;
 import org.apache.commons.i18n.XMLMessageProvider;
+import org.apache.commons.i18n.MessageManager;
 import org.apache.commons.i18n.bundles.ErrorBundle;
 
 /**
@@ -37,9 +38,9 @@
 
     public static void main(String[] args) {
         // Install the file providing the required messages for this example
-        XMLMessageProvider.install("org.apache.commons-i18n.examples", Thread
-                .currentThread().getContextClassLoader().getResourceAsStream(
-                        "exampleMessages.xml"));
+        MessageManager.addMessageProvider("org.apache.commons-i18n.examples",
+                new XMLMessageProvider(Thread.currentThread().getContextClassLoader().getResourceAsStream(
+                        "exampleMessages.xml")));
 
         // Simulate the locale of the current user in a multi-user environment
         // such as a web application

Added: jakarta/commons/sandbox/i18n/trunk/src/examples/org/apache/i18n/examples/QualifiedJdbcExample.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/examples/org/apache/i18n/examples/QualifiedJdbcExample.java?rev=224613&view=auto
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/examples/org/apache/i18n/examples/QualifiedJdbcExample.java (added)
+++ jakarta/commons/sandbox/i18n/trunk/src/examples/org/apache/i18n/examples/QualifiedJdbcExample.java Sun Jul 24 05:53:05 2005
@@ -0,0 +1,155 @@
+/*
+ *
+ * ====================================================================
+ *
+ * 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.i18n.examples;
+
+import org.apache.commons.i18n.MessageManager;
+import org.apache.commons.i18n.JdbcMessageProvider;
+import org.apache.commons.i18n.LocalizedRuntimeException;
+import org.apache.commons.i18n.bundles.TextBundle;
+import org.apache.commons.i18n.bundles.ErrorBundle;
+
+import java.sql.Connection;
+import java.sql.Statement;
+import java.sql.SQLException;
+import java.sql.DriverManager;
+import java.util.Locale;
+import java.util.Properties;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+/**
+ * This example shows
+ * a) how to use qualified message providers, instead of searching through all available providers.
+ * b) how to use i18n for messages stored in databases. For the sake of the example, we will create
+ * an in memory database holding the i18n messages using HSQLDB.
+ * @author Mattias Jiderhamn
+ * @see <a href="http://hsqldb.org/">HSQLDB</a>
+ */
+public class QualifiedJdbcExample {
+    public static void main(String[] args) throws Exception {
+        /////////////////////////////////////////////////////
+        // Prepare example
+        /////////////////////////////////////////////////////
+
+        // Set up in-memory data for the sake of the example
+        prepareTables();
+
+        /////////////////////////////////////////////////////
+        // Example of initialization
+        /////////////////////////////////////////////////////
+
+        // We can initialize JDBC message provider using a properties file
+        Properties props = new Properties();
+        props.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("i18n-jdbc.properties"));
+        MessageManager.addMessageProvider("messages", new JdbcMessageProvider(props));
+
+        // We can also initialize JDBC message provider by providing connection (or DataSource)
+        Connection conn = getNewConnection();
+        MessageManager.addMessageProvider("errors", new JdbcMessageProvider(conn, "errors", "id", "language"));
+        conn.close();
+
+        /////////////////////////////////////////////////////
+        // Example of usage
+        /////////////////////////////////////////////////////
+
+        // Simulate the locale of the current user in a multi-user environment
+        // such as a web application
+        Locale currentUsersLocale = new Locale("sv"); // Assume Swedish
+
+        try {
+            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+
+            // Get i18n text qualifying the message source (i.e. the MessageProvider) to use; messages
+            TextBundle textBundle = new TextBundle("messages", "enterFirstName");
+            System.out.println(textBundle.getText(currentUsersLocale) + ":");
+            String firstName = reader.readLine();
+
+            textBundle = new TextBundle("messages", "enterLastName");
+            System.out.println(textBundle.getText(currentUsersLocale) + ":");
+            String lastName = reader.readLine();
+
+            validateNames(firstName, lastName);
+        }
+        catch(LocalizedRuntimeException lrex) {
+            // Retrieve the detailed localized error message
+            ErrorBundle errorMessage = lrex.getErrorMessage();
+
+            // Print summary and details using the current users locale
+            System.out.println("-- " + errorMessage.getSummary(currentUsersLocale) + " --");
+            System.out.println(errorMessage.getDetails(currentUsersLocale));
+        }
+    }
+
+    private static void validateNames(String firstname, String lastname) {
+        if(firstname.equals(lastname))
+            throw new LocalizedRuntimeException(new ErrorBundle("errors", "identicalNames"));
+    }
+
+    ///////////////////////////////////////////////////////////////////////
+    // Utility methods for the example
+    ///////////////////////////////////////////////////////////////////////
+
+    /**
+     * Create connection to in-memory HSQLDB database
+     * @return
+     * @throws ClassNotFoundException
+     * @throws SQLException
+     */
+    private static Connection getNewConnection() throws ClassNotFoundException, SQLException {
+        Class.forName("org.hsqldb.jdbcDriver"); // Load HSQLDB database driver
+        return DriverManager.getConnection("jdbc:hsqldb:.", "sa", ""); // Connect to in-memory database
+    }
+
+    /**
+     * Create tables and insert messages
+     */
+    private static void prepareTables() throws ClassNotFoundException, SQLException {
+        Connection conn = getNewConnection();
+        Statement stmt = conn.createStatement();
+        stmt.execute(
+                "CREATE TABLE messages ( " +
+                "  'id' VARCHAR(30), " +
+                "  'language' VARCHAR(2), " +
+                "  'text' VARCHAR(100)" +
+                ")");
+        stmt.execute("INSERT INTO messages VALUES ('enterFirstName', 'en', 'Please enter your first name')");
+        stmt.execute("INSERT INTO messages VALUES ('enterLastName', 'en', 'Please enter your last name')");
+        stmt.execute("INSERT INTO messages VALUES ('enterFirstName', 'sv', 'Vänligen ange ditt förnamn')");
+        stmt.execute("INSERT INTO messages VALUES ('enterLastName', 'sv', 'Vänligen ange ditt efternamn')");
+        stmt.execute(
+                "CREATE TABLE errors ( " +
+                "  'id' VARCHAR(30), " +
+                "  'language' VARCHAR(2), " +
+                "  'summary' VARCHAR(100), " +
+                "  'details' VARCHAR(100) " +
+                ")");
+        stmt.execute("INSERT INTO errors VALUES (" +
+                "  'identicalNames', 'en', " +
+                "  'Error! Identical names.', 'You entered the same name as both first name and last name'" +
+                ")");
+        stmt.execute("INSERT INTO errors VALUES (" +
+                "  'identicalNames', 'sv', " +
+                "  'Fel! Identiska namn.', 'Du angav samma namn som både förnamn och efternamn'" +
+                ")");
+        stmt.close();
+        conn.close();
+    }
+}
\ No newline at end of file

Added: jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/I18nUtils.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/I18nUtils.java?rev=224613&view=auto
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/I18nUtils.java (added)
+++ jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/I18nUtils.java Sun Jul 24 05:53:05 2005
@@ -0,0 +1,51 @@
+/*
+*
+* ====================================================================
+*
+* 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;
+import java.util.ResourceBundle;
+
+/**
+ * This class holds utility methods useful when working with i18n.
+ * @author Mattias Jiderhamn
+ */
+public class I18nUtils {
+
+    static final String INTERNAL_MESSAGE_NOT_FOUND = "Internal I18n error: Message not found";
+    static final String MESSAGE_NOT_FOUND = "messageNotFound";
+    static final String NO_MESSAGE_ENTRIES_FOUND = "noMessageEntriesFound";
+    static final String MESSAGE_ENTRY_NOT_FOUND = "messageEntryNotFound";
+    static final String RESOURCE_BUNDLE_NOT_FOUND = "resourceBundleNotFound";
+    public static final String MESSAGE_PARSING_ERROR = "messageParsingError";
+
+    public static final ResourceBundle INTERNAL_MESSAGES = ResourceBundle.getBundle("i18n-messages", Locale.getDefault());
+
+    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

Added: jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/JdbcMessageProvider.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/JdbcMessageProvider.java?rev=224613&view=auto
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/JdbcMessageProvider.java (added)
+++ jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/JdbcMessageProvider.java Sun Jul 24 05:53:05 2005
@@ -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).toLowerCase());
+    }
+
+    ///////////////////////////////////////////////////////////////////////
+    // Methods to implement MessageProvider
+    ///////////////////////////////////////////////////////////////////////
+
+    public String getText(String id, String entry, Locale locale) {
+        // 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) {
+        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
+    }
+
+}

Modified: jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/LocalizedError.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/LocalizedError.java?rev=224613&r1=224612&r2=224613&view=diff
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/LocalizedError.java (original)
+++ jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/LocalizedError.java Sun Jul 24 05:53:05 2005
@@ -52,7 +52,7 @@
         super(errorMessage.getSummary(
                 Locale.getDefault(), 
                         MessageFormat.format(
-                                MessageManager.INTERNAL_MESSAGES.getString(MessageManager.MESSAGE_ENTRY_NOT_FOUND),
+                                I18nUtils.INTERNAL_MESSAGES.getString(I18nUtils.MESSAGE_ENTRY_NOT_FOUND),
                                 new String[] { errorMessage.getId(), ErrorBundle.SUMMARY }))); 
         this.errorMessage = errorMessage;
     }

Modified: jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/LocalizedException.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/LocalizedException.java?rev=224613&r1=224612&r2=224613&view=diff
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/LocalizedException.java (original)
+++ jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/LocalizedException.java Sun Jul 24 05:53:05 2005
@@ -51,7 +51,7 @@
         super(errorMessage.getSummary(
                 Locale.getDefault(), 
                 MessageFormat.format(
-                        MessageManager.INTERNAL_MESSAGES.getString(MessageManager.MESSAGE_ENTRY_NOT_FOUND),
+                        I18nUtils.INTERNAL_MESSAGES.getString(I18nUtils.MESSAGE_ENTRY_NOT_FOUND),
                         new String[] { errorMessage.getId(), ErrorBundle.SUMMARY }))); 
         this.errorMessage = errorMessage;
     }

Modified: jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/LocalizedRuntimeException.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/LocalizedRuntimeException.java?rev=224613&r1=224612&r2=224613&view=diff
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/LocalizedRuntimeException.java (original)
+++ jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/LocalizedRuntimeException.java Sun Jul 24 05:53:05 2005
@@ -50,7 +50,7 @@
         super(errorMessage.getSummary(
                 Locale.getDefault(), 
                 MessageFormat.format(
-                        MessageManager.INTERNAL_MESSAGES.getString(MessageManager.MESSAGE_ENTRY_NOT_FOUND),
+                        I18nUtils.INTERNAL_MESSAGES.getString(I18nUtils.MESSAGE_ENTRY_NOT_FOUND),
                         new String[] { errorMessage.getId(), ErrorBundle.SUMMARY }))); 
         this.errorMessage = errorMessage;
     }

Modified: jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/MessageManager.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/MessageManager.java?rev=224613&r1=224612&r2=224613&view=diff
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/MessageManager.java (original)
+++ jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/MessageManager.java Sun Jul 24 05:53:05 2005
@@ -46,16 +46,7 @@
  * 
  */
 public class MessageManager {
-    static final String INTERNAL_MESSAGE_NOT_FOUND = "Internal I18n error: Message not found";
-    
-    static final String MESSAGE_NOT_FOUND = "messageNotFound";
-    static final String NO_MESSAGE_ENTRIES_FOUND = "noMessageEntriesFound";
-    static final String MESSAGE_ENTRY_NOT_FOUND = "messageEntryNotFound";
-    static final String RESOURCE_BUNDLE_NOT_FOUND = "resourceBundleNotFound";
-    public static final String MESSAGE_PARSING_ERROR = "messageParsingError";
 
-    public static final ResourceBundle INTERNAL_MESSAGES = ResourceBundle.getBundle("i18n-messages", Locale.getDefault());
-    
     private static Map messageProviders = new LinkedHashMap();
 
     /**
@@ -115,17 +106,16 @@
             Locale locale) throws MessageNotFoundException {
         if(messageProviders.isEmpty())
             throw new MessageNotFoundException("No MessageProvider registered");
-        MessageNotFoundException exception = null;
         for (Iterator i = messageProviders.values().iterator(); i.hasNext();) {
-            try {
-                String text = ((MessageProvider) i.next()).getText(id, entry,
-                        locale);
-                return MessageFormat.format(text, arguments);
-            } catch (MessageNotFoundException e) {
-                exception = e;
-            }
+            String text = ((MessageProvider) i.next()).getText(id, entry,
+                    locale);
+            if(text != null)
+                return (arguments != null && arguments.length > 0) ?
+                        MessageFormat.format(text, arguments) : text;
         }
-        throw exception;
+        throw new MessageNotFoundException(MessageFormat.format(
+                I18nUtils.INTERNAL_MESSAGES.getString(I18nUtils.MESSAGE_ENTRY_NOT_FOUND),
+                new String[] { id, entry }));
     }
 
     /**
@@ -153,7 +143,8 @@
         try {
             return getText(id, entry, arguments, locale);
         } catch (MessageNotFoundException e) {
-            return MessageFormat.format(defaultText, arguments);
+            return (arguments != null && arguments.length > 0) ?
+                    MessageFormat.format(defaultText, arguments) : defaultText;
         }
     }
 
@@ -182,7 +173,13 @@
         if(provider == null)
             throw new MessageNotFoundException("Provider '" + providerId + "' not installed");
         String text = provider.getText(id, entry, locale);
-        return MessageFormat.format(text, arguments);
+        if(text != null)
+            return (arguments != null && arguments.length > 0) ?
+                    MessageFormat.format(text, arguments) : text;
+        else
+            throw new MessageNotFoundException(MessageFormat.format(
+                    I18nUtils.INTERNAL_MESSAGES.getString(I18nUtils.MESSAGE_ENTRY_NOT_FOUND),
+                    new String[] { id, entry }));
     }
 
     /**
@@ -211,7 +208,8 @@
         try {
             return getText(providerId, id, entry, arguments, locale);
         } catch (MessageNotFoundException e) {
-            return MessageFormat.format(defaultText, arguments);
+            return (arguments != null && arguments.length > 0) ?
+                    MessageFormat.format(defaultText, arguments) : defaultText;
         }
     }
 

Modified: jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/MessageProvider.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/MessageProvider.java?rev=224613&r1=224612&r2=224613&view=diff
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/MessageProvider.java (original)
+++ jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/MessageProvider.java Sun Jul 24 05:53:05 2005
@@ -36,11 +36,12 @@
      * @param id unique id that specifies a particular message  
      * @param entry specifies a particular entry in the specified message 
      * @param locale the locale for which this text should be provided
-     * @return returns the localized message entry matching the given message id, entry key and locale
-     * @throws MessageNotFoundException thrown if no message exists matching the given id or if 
-     * the specified message does not contain the desired entry 
+     * @return returns the localized message entry matching the given message id, entry key and locale. If
+     * no match is found for the given locale, the parent locale is used, and finally the default. If the
+     * id is found but the entry is not, null is returned. 
+     * @throws MessageNotFoundException thrown if no message exists matching the given id
      */
-    public String getText(String id, String entry, Locale locale) throws MessageNotFoundException;
+    public String getText(String id, String entry, Locale locale);
     
     /**
      * @param id unique id that specifies a particular message  

Modified: jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/ResourceBundleMessageProvider.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/ResourceBundleMessageProvider.java?rev=224613&r1=224612&r2=224613&view=diff
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/ResourceBundleMessageProvider.java (original)
+++ jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/ResourceBundleMessageProvider.java Sun Jul 24 05:53:05 2005
@@ -39,8 +39,23 @@
 
     private final String baseName;
 
-    public ResourceBundleMessageProvider(String baseName) {
+    /**
+     *
+     * @throws MessageNotFoundException Thrown if the resource bundle does not exist.
+     */
+    public ResourceBundleMessageProvider(String baseName) throws MessageNotFoundException {
         this.baseName = baseName;
+        // Test if resource exists
+        try {
+            ResourceBundle.getBundle(baseName);
+        }
+        catch ( MissingResourceException e ) {
+            String msg = MessageFormat.format(
+                    I18nUtils.INTERNAL_MESSAGES.getString(I18nUtils.RESOURCE_BUNDLE_NOT_FOUND),
+                    new String[]{baseName});
+            logger.log(Level.WARNING, msg);
+            throw new MessageNotFoundException(msg);
+        }
     }
 
     /* (non-Javadoc)
@@ -55,12 +70,10 @@
             logger.log(
                     Level.WARNING,
                     MessageFormat.format(
-                            MessageManager.INTERNAL_MESSAGES.getString(MessageManager.RESOURCE_BUNDLE_NOT_FOUND),
-                            new String[] { baseName }));
+                            I18nUtils.INTERNAL_MESSAGES.getString(I18nUtils.NO_MESSAGE_ENTRIES_FOUND),
+                            new String[] { id }));
+            return null;
         }
-        throw new MessageNotFoundException(MessageFormat.format(
-            MessageManager.INTERNAL_MESSAGES.getString(MessageManager.NO_MESSAGE_ENTRIES_FOUND),
-            new String[] { id }));
     }
 
     /* (non-Javadoc)
@@ -69,64 +82,22 @@
     public Map getEntries(String id, Locale locale) {
         String messageIdentifier = id+".";
         Map entries = null;
-        try {
-            ResourceBundle resourceBundle = ResourceBundle.getBundle(baseName, locale);
-            Enumeration keys = resourceBundle.getKeys();
-            while ( keys.hasMoreElements() ) {
-                String key = (String)keys.nextElement();
-                if ( key.startsWith(messageIdentifier) ) {
-                    if ( entries == null ) {
-                        entries = new HashMap();
-                    }
-                    entries.put(key.substring(messageIdentifier.length()), resourceBundle.getString(key));
+        ResourceBundle resourceBundle = ResourceBundle.getBundle(baseName, locale);
+        Enumeration keys = resourceBundle.getKeys();
+        while ( keys.hasMoreElements() ) {
+            String key = (String)keys.nextElement();
+            if ( key.startsWith(messageIdentifier) ) {
+                if ( entries == null ) {
+                    entries = new HashMap();
                 }
+                entries.put(key.substring(messageIdentifier.length()), resourceBundle.getString(key));
             }
-        } catch ( MissingResourceException e ) {
-            logger.log(
-                    Level.WARNING,
-                    MessageFormat.format(
-                            MessageManager.INTERNAL_MESSAGES.getString(MessageManager.RESOURCE_BUNDLE_NOT_FOUND),
-                            new String[] { baseName }));
-            // TODO: Consider uninstalling
         }
         if ( entries == null ) {
             throw new MessageNotFoundException(MessageFormat.format(
-                    MessageManager.INTERNAL_MESSAGES.getString(MessageManager.NO_MESSAGE_ENTRIES_FOUND),
+                    I18nUtils.INTERNAL_MESSAGES.getString(I18nUtils.NO_MESSAGE_ENTRIES_FOUND),
                     new String[] { id })); 
         }
         return entries;
-    }
-    
-    /**
-     * Installs a new resource bundle containing messages for a specific locale 
-     * with the given name.
-     * 
-     * @param baseName the name of the resource bundle. This name will be used to 
-     * find the resource bundle by using the java mechanisms for loading resource bundles
-     * and to uninstall or update the messages contained in the resource bundle.
-     * 
-     */
-    public static void install(String baseName) {
-        MessageManager.addMessageProvider(baseName,
-                new ResourceBundleMessageProvider(baseName));
-    }
-    
-    /**
-     * @param baseName unique identifier for the resource bundle to uninstall
-     */
-    public static void uninstall(String baseName) {
-        MessageManager.removeMessageProvider(baseName); // TODO: Consider checkning type
-    }
-    
-    /**
-     * Update the resources specified by the given unique identifier.
-     * As the messages defined in resource bundles are not cached by the
-     * current implementation, calling this method has no effect.
-     * This may change in future versions. 
-     * @param baseName unique identifier for the resource bundle to update
-     */
-    public static void update(String baseName) {
-        uninstall(baseName);
-        install(baseName);
     }
 }

Modified: jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/XMLMessageProvider.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/XMLMessageProvider.java?rev=224613&r1=224612&r2=224613&view=diff
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/XMLMessageProvider.java (original)
+++ jakarta/commons/sandbox/i18n/trunk/src/java/org/apache/commons/i18n/XMLMessageProvider.java Sun Jul 24 05:53:05 2005
@@ -43,12 +43,9 @@
 
     private static SAXParserFactory factory = SAXParserFactory.newInstance();
     
-    private final String id;
-
     private Map messages = new HashMap();
 
-    public XMLMessageProvider(String id, InputStream inputStream) {
-        this.id = id;
+    public XMLMessageProvider(InputStream inputStream) {
         try {
             Map applicationMessages = new HashMap();
             SAXParser parser = factory.newSAXParser();
@@ -58,10 +55,9 @@
             applicationMessages.putAll(parsedMessages);
             messages.putAll(applicationMessages);
         } catch (Exception exception) {
-            logger.log(Level.SEVERE,
-                    MessageFormat.format(
-                    MessageManager.INTERNAL_MESSAGES.getString(MessageManager.MESSAGE_PARSING_ERROR),
-                    new String[] { id }), exception);
+            // TODO: Fix message without ID
+            logger.log(Level.SEVERE,I18nUtils.INTERNAL_MESSAGES.getString(I18nUtils.MESSAGE_PARSING_ERROR),exception);
+            // TODO: Consider throwing exception
         }
     }
 
@@ -81,33 +77,6 @@
         return message.getEntries();
     }
 
-    /**
-     * Installs a bunch of messages provided by the given <code>InputStream</code> for all locales specifed 
-     * in the given source. The messages can be uninstalled or updated by using the given unique identifier.
-     * 
-     * @param id the unique identifier for unistalling or updating the provided messages
-     * @param inputStream providing the messages in the required XML format 
-     */
-    public static void install(String id, InputStream inputStream) {
-        MessageManager.addMessageProvider(id, new XMLMessageProvider(id, inputStream));
-    }
-    
-    /**
-     * @param id unique identifier for the messages to uninstall
-     */
-    public static void uninstall(String id) {
-        MessageManager.removeMessageProvider(id);
-    }
-    
-    /**
-     * @param id unique identifier for the messages to update
-     * @param inputStream providing the updated messages in the required XML format
-     */
-    public static void update(String id, InputStream inputStream) {
-        uninstall(id);
-        install(id, inputStream);
-    }
-        
     private Message findMessage(String id, Locale locale) {
         Message message = lookupMessage(id, locale);
         if (message == null) {
@@ -115,17 +84,19 @@
         }
         if (message == null ) throw new MessageNotFoundException(
                 MessageFormat.format(
-                        MessageManager.INTERNAL_MESSAGES.getString(MessageManager.MESSAGE_NOT_FOUND),
-                        new String[] { id })); 
+                        I18nUtils.INTERNAL_MESSAGES.getString(I18nUtils.MESSAGE_NOT_FOUND),
+                        new String[] { id }));
         return message;
     }
 
     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;
     }

Added: jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/I18nUtilsTest.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/I18nUtilsTest.java?rev=224613&view=auto
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/I18nUtilsTest.java (added)
+++ jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/I18nUtilsTest.java Sun Jul 24 05:53:05 2005
@@ -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));
+    }
+}

Added: jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/JdbcMessageProviderTest.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/JdbcMessageProviderTest.java?rev=224613&view=auto
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/JdbcMessageProviderTest.java (added)
+++ jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/JdbcMessageProviderTest.java Sun Jul 24 05:53:05 2005
@@ -0,0 +1,170 @@
+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("helloWorld", "foobar", Locale.ENGLISH));
+        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"));
+    }
+}

Modified: jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/MessageManagerTest.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/MessageManagerTest.java?rev=224613&r1=224612&r2=224613&view=diff
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/MessageManagerTest.java (original)
+++ jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/MessageManagerTest.java Sun Jul 24 05:53:05 2005
@@ -50,7 +50,9 @@
             fail("Mock provider should throw Exception");
         }
         catch(MessageNotFoundException mnfex) {
-            assertEquals("Error text", "Mock exception from getText()", mnfex.getMessage());
+            // TODO: What error message is most correct (here and elsewhere)?
+            // assertEquals("Error text", "No message entries found for bundle with key dummyId", mnfex.getMessage());
+            assertEquals("Error text", "Message bundle with key dummyId does not contain an entry with key dummyEntry", mnfex.getMessage());
         }
 
         addMockProvider(); // Add mock provider
@@ -97,6 +99,17 @@
 
         assertEquals("Normal qualified lookup", "Source=mockProvider2 Id=id Entry=entry Locale=en_US",
                 MessageManager.getText("mockProvider2", "id", "entry", null, Locale.US));
+
+        // Named provider found, but fails to find message
+        addThrowingMockProvider(); // Add mock provider returning null for unknown entries
+        try {
+            MessageManager.getText("throwingMock", "dummyId", "dummyEntry", null, Locale.US);
+            fail("Unknown provider should throw Exception");
+        }
+        catch(MessageNotFoundException mnfex) {
+            assertEquals("Error text", "Message bundle with key dummyId does not contain an entry with key dummyEntry", mnfex.getMessage());
+        }
+
     }
 
     public void testGetEntries() {

Modified: jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/MockProviderTestBase.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/MockProviderTestBase.java?rev=224613&r1=224612&r2=224613&view=diff
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/MockProviderTestBase.java (original)
+++ jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/MockProviderTestBase.java Sun Jul 24 05:53:05 2005
@@ -70,7 +70,7 @@
     protected void addThrowingMockProvider() {
         MessageManager.addMessageProvider("throwingMock", new MessageProvider() {
             public String getText(String id, String entry, Locale locale) throws MessageNotFoundException {
-                throw new MessageNotFoundException("Mock exception from getText()");
+                return null;
             }
 
             public Map getEntries(String id, Locale locale) throws MessageNotFoundException {

Modified: jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/ResourceBundleMessageProviderTest.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/ResourceBundleMessageProviderTest.java?rev=224613&r1=224612&r2=224613&view=diff
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/ResourceBundleMessageProviderTest.java (original)
+++ jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/ResourceBundleMessageProviderTest.java Sun Jul 24 05:53:05 2005
@@ -42,10 +42,7 @@
 
     public void tearDown() {
         /* Uninstall resource bundles after every test */
-        ResourceBundleMessageProvider.uninstall("messageBundle");
-        ResourceBundleMessageProvider.uninstall("messageBundle2");
-        ResourceBundleMessageProvider.uninstall("nonExistentBundle");
-        ResourceBundleMessageProvider.uninstall("org.apache.commons.i18n.MyListResourceBundle");
+        MessageManager.clearMessageProviders();
     }
 
     public void testInstallResourceBundle() {
@@ -59,15 +56,11 @@
             assertEquals("No MessageProvider registered", mnfex.getMessage());
         }
 
-        ResourceBundleMessageProvider.install("messageBundle");
+        MessageManager.addMessageProvider("messageBundle", new ResourceBundleMessageProvider("messageBundle"));
 
         assertEquals("Hallo Welt", testMessage.getTitle(Locale.GERMAN));
 
-        ResourceBundleMessageProvider.update("messageBundle");
-
-        assertEquals("OK after update", "Hallo Welt", testMessage.getTitle(Locale.GERMAN));
-
-        ResourceBundleMessageProvider.uninstall("messageBundle");
+        MessageManager.removeMessageProvider("messageBundle");
 
         try {
             testMessage.getTitle(Locale.GERMAN);
@@ -95,34 +88,16 @@
         assertEquals("Value from ListResourceBundle", "listResourceValue", listResourceBundleProvider.getText("helloWorld", "title", Locale.US));
         assertEquals("Value from ListResourceBundle", "1", listResourceBundleProvider.getText("helloWorld", "text", Locale.US));
 
-        try {
-            rbmp.getText("nonExistentId", "nonExistentEntry", Locale.US);
-            fail("ID does not exist, should throw exception");
-        }
-        catch(MessageNotFoundException mnfex) {
-            assertEquals("No message entries found for bundle with key nonExistentId", mnfex.getMessage());
-        }
+        assertNull(rbmp.getText("nonExistentId", "nonExistentEntry", Locale.US));
 
-        try {
-            rbmp.getText("helloWorld", "nonExistentEntry", Locale.US);
-            fail("Entry does not exist, should throw exception");
-        }
-        catch(MessageNotFoundException mnfex) {
-            assertEquals("No message entries found for bundle with key helloWorld", mnfex.getMessage());
-        }
+        assertNull(rbmp.getText("helloWorld", "nonExistentEntry", Locale.US));
 
-        // Test unexisting bundle which should throw MissingResourceException
-        ResourceBundleMessageProvider.install("nonExistentBundle"); // Install non-existent bundle
-        ResourceBundleMessageProvider.update("messageBundle"); // Place last in list
-        rbmp.getText("helloWorld", "title", Locale.US); // Should not throw Exception
-
-        ResourceBundleMessageProvider nonExistentBundleProvider = new ResourceBundleMessageProvider("nonExistentBundle");
         try {
-            nonExistentBundleProvider.getText("fooBar", "text", Locale.GERMAN);
+            new ResourceBundleMessageProvider("nonExistentBundle");
             fail("Bundle does not exist and should cause error");
         }
         catch(MessageNotFoundException mnfex) {
-            assertEquals("No message entries found for bundle with key fooBar", mnfex.getMessage());
+            assertEquals("Could not find resource bundle with base name nonExistentBundle, uninstalling it", mnfex.getMessage());
         }
     }
 
@@ -146,17 +121,21 @@
         assertEquals("Fallback locale, text", "I wish you a merry christmas!", frenchEntries.get("text"));
         assertEquals("This entry is not translated to any other languages", frenchEntries.get("notTranslated"));
 
-
-        // Test unexisting bundle which should throw MissingResourceException
-//        ResourceBundleMessageProvider.install("nonExistentBundle"); // Install non-existent bundle
-//        ResourceBundleMessageProvider.update("messageBundle"); // Place last in list
-        ResourceBundleMessageProvider nonExistentBundleProvider = new ResourceBundleMessageProvider("nonExistentBundle");
         try {
-            nonExistentBundleProvider.getEntries("fooBar", Locale.GERMAN);
+            ResourceBundleMessageProvider provider = new ResourceBundleMessageProvider("messageBundle");
+            provider.getEntries("fooBar", Locale.GERMAN);
             fail("Bundle does not exist and should cause error");
         }
         catch(MessageNotFoundException mnfex) {
             assertEquals("No message entries found for bundle with key fooBar", mnfex.getMessage());
+        }
+
+        try {
+            new ResourceBundleMessageProvider("nonExistentBundle");
+            fail("Bundle does not exist and should cause error");
+        }
+        catch(MessageNotFoundException mnfex) {
+            assertEquals("Could not find resource bundle with base name nonExistentBundle, uninstalling it", mnfex.getMessage());
         }
     }
 }

Modified: jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/XMLMessageProviderTest.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/XMLMessageProviderTest.java?rev=224613&r1=224612&r2=224613&view=diff
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/XMLMessageProviderTest.java (original)
+++ jakarta/commons/sandbox/i18n/trunk/src/test/org/apache/commons/i18n/XMLMessageProviderTest.java Sun Jul 24 05:53:05 2005
@@ -39,9 +39,7 @@
 
     public void tearDown() {
         /* Uninstall resource bundles after every test */
-        XMLMessageProvider.uninstall("org.apache.commons-i18n.test");
-        XMLMessageProvider.uninstall("org.apache.commons-i18n.error");
-        XMLMessageProvider.uninstall("org.apache.commons-i18n.variants");
+        MessageManager.clearMessageProviders();
     }
 
     public void testInstallResourceBundle() {
@@ -55,17 +53,12 @@
             assertEquals("No MessageProvider registered", mnfex.getMessage());
         }
 
-        XMLMessageProvider.install("org.apache.commons-i18n.test",
-                Thread.currentThread().getContextClassLoader().getResourceAsStream("testMessages.xml"));
+        MessageManager.addMessageProvider("org.apache.commons-i18n.test", new XMLMessageProvider(
+                    Thread.currentThread().getContextClassLoader().getResourceAsStream("testMessages.xml")));
 
         assertEquals("Hallo Welt", testMessage.getTitle(Locale.GERMAN));
 
-        XMLMessageProvider.update("org.apache.commons-i18n.test",
-                Thread.currentThread().getContextClassLoader().getResourceAsStream("testMessages.xml"));
-
-        assertEquals("OK after update", "Hallo Welt", testMessage.getTitle(Locale.GERMAN));
-
-        XMLMessageProvider.uninstall("org.apache.commons-i18n.test");
+        MessageManager.removeMessageProvider("org.apache.commons-i18n.test");
 
         try {
             testMessage.getTitle(Locale.GERMAN);
@@ -76,14 +69,13 @@
         }
 
         // Try to parse non-XML file
-        XMLMessageProvider.install("org.apache.commons-i18n.error",
-                Thread.currentThread().getContextClassLoader().getResourceAsStream("messageBundle.properties"));
+        new XMLMessageProvider(Thread.currentThread().getContextClassLoader().getResourceAsStream("messageBundle.properties"));
     }
     
     public void testGetText() {
 //        XMLMessageProvider.install("org.apache.commons-i18n.test",
 //                Thread.currentThread().getContextClassLoader().getResourceAsStream("testMessages.xml"));
-        XMLMessageProvider xmlmp = new XMLMessageProvider("org.apache.commons-i18n.test",
+        XMLMessageProvider xmlmp = new XMLMessageProvider(
                 Thread.currentThread().getContextClassLoader().getResourceAsStream("testMessages.xml"));
 
         assertEquals("Default locale", "hello world", xmlmp.getText("helloWorld", "title", Locale.US));
@@ -124,7 +116,7 @@
     public void testGetTextVariants() {
 //        XMLMessageProvider.install("org.apache.commons-i18n.variants",
 //                Thread.currentThread().getContextClassLoader().getResourceAsStream("variantTestMessages.xml"));
-        XMLMessageProvider xmlmp = new XMLMessageProvider("org.apache.commons-i18n.variants",
+        XMLMessageProvider xmlmp = new XMLMessageProvider(
                 Thread.currentThread().getContextClassLoader().getResourceAsStream("variantTestMessages.xml"));
 
         assertEquals("hello world", xmlmp.getText("variants", "theKey", Locale.ENGLISH));
@@ -136,7 +128,7 @@
     public void testGetEntries() {
 //        XMLMessageProvider.install("org.apache.commons-i18n.test",
 //                Thread.currentThread().getContextClassLoader().getResourceAsStream("testMessages.xml"));
-        Map usEntries = new XMLMessageProvider("org.apache.commons-i18n.test",
+        Map usEntries = new XMLMessageProvider(
                 Thread.currentThread().getContextClassLoader().getResourceAsStream("testMessages.xml")).
                     getEntries("helloWorld", Locale.US);
         assertEquals("Default locale, no of entries", 5, usEntries.size());
@@ -150,7 +142,7 @@
                 usEntries.get("details"));
         assertEquals("This entry is not translated to any other languages (XML)", usEntries.get("notTranslated"));
 
-        Map germanEntries = new XMLMessageProvider("org.apache.commons-i18n.test",
+        Map germanEntries = new XMLMessageProvider(
                 Thread.currentThread().getContextClassLoader().getResourceAsStream("testMessages.xml")).
                     getEntries("helloWorld", Locale.GERMAN);
         assertEquals("No of entries", 4, germanEntries.size());
@@ -162,7 +154,7 @@
                 germanEntries.get("details"));
 //        assertEquals("This entry is not translated to any other languages", germanEntries.get("notTranslated"));
 
-        Map japaneseEntries = new XMLMessageProvider("org.apache.commons-i18n.test",
+        Map japaneseEntries = new XMLMessageProvider(
                 Thread.currentThread().getContextClassLoader().getResourceAsStream("testMessages.xml")).
                     getEntries("helloWorld", Locale.JAPANESE);
         assertEquals("Fallback locale, no of entries", 5, japaneseEntries.size());

Modified: jakarta/commons/sandbox/i18n/trunk/xdocs/examples.xml
URL: http://svn.apache.org/viewcvs/jakarta/commons/sandbox/i18n/trunk/xdocs/examples.xml?rev=224613&r1=224612&r2=224613&view=diff
==============================================================================
--- jakarta/commons/sandbox/i18n/trunk/xdocs/examples.xml (original)
+++ jakarta/commons/sandbox/i18n/trunk/xdocs/examples.xml Sun Jul 24 05:53:05 2005
@@ -1,4 +1,4 @@
-<?xml version="1.0"?>
+<?xml version="1.0" encoding="ISO-8859-1"?>
 
 <document>
 
@@ -26,9 +26,9 @@
 
     public static void main(String[] args) {
         // Install the file providing the required messages for this example
-        XMLMessageProvider.install("org.apache.commons-i18n.examples", Thread
-                .currentThread().getContextClassLoader().getResourceAsStream(
-                        "exampleMessages.xml"));
+        MessageManager.addMessageProvider("org.apache.commons-i18n.examples",
+                new XMLMessageProvider(Thread.currentThread().getContextClassLoader().getResourceAsStream(
+                        "exampleMessages.xml")));
 
         // Simulate the locale of the current user in a multi-user environment
         // such as a web application
@@ -74,6 +74,127 @@
         String userCausingTheException = "Daniel";
         throw new LocalizedException(new ErrorBundle("theCauseOfThisException",
                 new String[]{userCausingTheException}));
+    }
+}
+</source>
+</section>
+
+<section name="Using JDBC provider and qualifying message source">
+<p>
+This example shows how to use qualified message providers, instead of searching
+through all available providers and how to use i18n for messages stored in databases.
+For the sake of the example, we will create an in memory database holding the i18n
+messages using HSQLDB.
+</p>
+<source>
+public class QualifiedJdbcExample {
+    public static void main(String[] args) throws Exception {
+        /////////////////////////////////////////////////////
+        // Prepare example
+        /////////////////////////////////////////////////////
+
+        // Set up in-memory data for the sake of the example
+        prepareTables();
+
+        /////////////////////////////////////////////////////
+        // Example of initialization
+        /////////////////////////////////////////////////////
+
+        // We can initialize JDBC message provider using a properties file
+        Properties props = new Properties();
+        props.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("i18n-jdbc.properties"));
+        MessageManager.addMessageProvider("messages", new JdbcMessageProvider(props));
+
+        // We can also initialize JDBC message provider by providing connection (or DataSource)
+        Connection conn = getNewConnection();
+        MessageManager.addMessageProvider("errors", new JdbcMessageProvider(conn, "errors", "id", "language"));
+        conn.close();
+
+        /////////////////////////////////////////////////////
+        // Example of usage
+        /////////////////////////////////////////////////////
+
+        // Simulate the locale of the current user in a multi-user environment
+        // such as a web application
+        Locale currentUsersLocale = new Locale("sv"); // Assume Swedish
+
+        try {
+            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+
+            // Get i18n text qualifying the message source (i.e. the MessageProvider) to use; messages
+            TextBundle textBundle = new TextBundle("messages", "enterFirstName");
+            System.out.println(textBundle.getText(currentUsersLocale) + ":");
+            String firstName = reader.readLine();
+
+            textBundle = new TextBundle("messages", "enterLastName");
+            System.out.println(textBundle.getText(currentUsersLocale) + ":");
+            String lastName = reader.readLine();
+
+            validateNames(firstName, lastName);
+        }
+        catch(LocalizedRuntimeException lrex) {
+            // Retrieve the detailed localized error message
+            ErrorBundle errorMessage = lrex.getErrorMessage();
+
+            // Print summary and details using the current users locale
+            System.out.println("-- " + errorMessage.getSummary(currentUsersLocale) + " --");
+            System.out.println(errorMessage.getDetails(currentUsersLocale));
+        }
+    }
+
+    private static void validateNames(String firstname, String lastname) {
+        if(firstname.equals(lastname))
+            throw new LocalizedRuntimeException(new ErrorBundle("errors", "identicalNames"));
+    }
+
+    ///////////////////////////////////////////////////////////////////////
+    // Utility methods for the example
+    ///////////////////////////////////////////////////////////////////////
+
+    /**
+     * Create connection to in-memory HSQLDB database
+     * @return
+     * @throws ClassNotFoundException
+     * @throws SQLException
+     */
+    private static Connection getNewConnection() throws ClassNotFoundException, SQLException {
+        Class.forName("org.hsqldb.jdbcDriver"); // Load HSQLDB database driver
+        return DriverManager.getConnection("jdbc:hsqldb:.", "sa", ""); // Connect to in-memory database
+    }
+
+    /**
+     * Create tables and insert messages
+     */
+    private static void prepareTables() throws ClassNotFoundException, SQLException {
+        Connection conn = getNewConnection();
+        Statement stmt = conn.createStatement();
+        stmt.execute(
+                "CREATE TABLE messages ( " +
+                "  'id' VARCHAR(30), " +
+                "  'language' VARCHAR(2), " +
+                "  'text' VARCHAR(100)" +
+                ")");
+        stmt.execute("INSERT INTO messages VALUES ('enterFirstName', 'en', 'Please enter your first name')");
+        stmt.execute("INSERT INTO messages VALUES ('enterLastName', 'en', 'Please enter your last name')");
+        stmt.execute("INSERT INTO messages VALUES ('enterFirstName', 'sv', 'Vänligen ange ditt förnamn')");
+        stmt.execute("INSERT INTO messages VALUES ('enterLastName', 'sv', 'Vänligen ange ditt efternamn')");
+        stmt.execute(
+                "CREATE TABLE errors ( " +
+                "  'id' VARCHAR(30), " +
+                "  'language' VARCHAR(2), " +
+                "  'summary' VARCHAR(100), " +
+                "  'details' VARCHAR(100) " +
+                ")");
+        stmt.execute("INSERT INTO errors VALUES (" +
+                "  'identicalNames', 'en', " +
+                "  'Error! Identical names.', 'You entered the same name as both first name and last name'" +
+                ")");
+        stmt.execute("INSERT INTO errors VALUES (" +
+                "  'identicalNames', 'sv', " +
+                "  'Fel! Identiska namn.', 'Du angav samma namn som både förnamn och efternamn'" +
+                ")");
+        stmt.close();
+        conn.close();
     }
 }
 </source>



---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org