base.text.LocalText Maven / Gradle / Ivy
/**
* Creative commons Attribution-NonCommercial license.
*
* http://creativecommons.org/licenses/by-nc/2.5/au/deed.en_GB
*
* NO WARRANTY IS GIVEN OR IMPLIED, USE AT YOUR OWN RISK.
*/
package base.text;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.sql.DataSource;
/**
* Cache all available translations of text strings discovered within the
* application. Each newly discovered string may be persisted to the
* database. Each string persisted in the database may have a corresponding
* translation in any language.
*
* Initialise on web application startup as follows:
*
*
* LocalText.instance(dataSource);
*
*
* At run time, text strings are looked up using the static helper function:
*
*
* LocalText.local("Sample text", locale);
*
*
* Request that any recently newly discovered text is persisted back into the
* database:
*
*
* LocalText.instance().persistNewTextForTranslation();
*
*
* Translation helper functions
* The following methods are used to view and update text translations.
*
* Search for a particular keyword in any string in any language:
*
* List items = LocalText.instance().search("keyword");
*
*
* Search for a particular keyword in any string in a pre-defined language, or null for the default text:
*
* List items = LocalText.instance().search("en", "keyword");
*
*
* Update the application with a new translation of a particular text string:
*
*
* LocalText.instance().translate(hash, "en", "This is the english version of the string");
*
*/
public class LocalText {
private static LocalText instance;
public static LocalText instance(DataSource ds) throws IOException {
if(instance == null) {
instance = new LocalText(ds);
}
return instance;
}
public static String local(String text, String language) {
if(instance == null) {
throw new IllegalStateException("LocalText.instance() must be called before LocalText.local()");
}
return instance.getTranslation(text, language);
}
public static String local(String text, Locale locale) {
if(instance == null) {
throw new IllegalStateException("LocalText.instance() must be called before LocalText.local()");
}
return instance.getTranslation(text, locale.getLanguage() +"_" + locale.getCountry());
}
private Map defaults = new Hashtable();
private Map> strings = new Hashtable>();
private Set databaseInsertQueue = new HashSet();
private DataSource ds;
/**
* Instantiate an instance of a LocalText cache, backed by a JDBC data source.
*
* @param ds
* @throws IOException
*/
public LocalText(DataSource ds) throws IOException {
this.ds = ds;
loadTranslationsFromDatabase();
}
/**
* If a translation exists for the `text` parameter, return the translation,
* otherwise return the `text` parameter itself.
*
* @param text Default text to display to the end user.
* @param language Language to check for translated version of the text.
* @return Returns the `text` input parameter unless a translation exists.
*/
public String getTranslation(String text, String language) {
String result = null;
Integer hash = text.hashCode();
if(strings.containsKey(language)) {
result = strings.get(language).get(hash);
}
if(result == null) {
result = text;
if(!defaults.containsKey(hash)) {
defaults.put(hash, text);
registerNewTextForTranslation(text);
}
}
return result;
}
public String getTranslation(String text, Locale locale) {
return local(text, locale.getLanguage() +"_" + locale.getCountry());
}
/**
* Should be called on a regular scheduled interval by the web application, to ensure
* any as yet untranslated text is added to the list of text strings.
* @throws SQLException
*/
public void persistNewTextForTranslation() throws IOException {
Connection c = null;
PreparedStatement p1 = null;
PreparedStatement p2 = null;
ResultSet r = null;
Set uncommitted = new HashSet();
try {
c = ds.getConnection();
p2 = c.prepareStatement("select t_hash from translations where t_hash=? and t_lang='default'");
p1 = c.prepareStatement("insert into translations (t_hash,t_text,t_lang) values(?,?,'default')");
Set items = newTextForTranslation();
uncommitted.addAll(items);
for(String text : items) {
p2.setInt(1, text.hashCode());
r = p2.executeQuery();
if(!r.next()) {
p1.setInt(1, text.hashCode());
p1.setString(2, text);
p1.execute();
uncommitted.remove(text);
}
r.close();
r = null;
}
} catch(SQLException e) {
throw new IOException(e);
} finally {
if(r != null) { try { r.close(); } catch(SQLException e) {} }
if(p1 != null) { try { p1.close(); } catch(SQLException e) {} }
if(p2 != null) { try { p2.close(); } catch(SQLException e) {} }
if(c != null) { try { c.close(); } catch(SQLException e) {} }
// If some sort of error results in saved text not being persisted, put
// it back on the queue for later.
for(String item : uncommitted) {
registerNewTextForTranslation(item);
}
}
}
public void translate(int hash, String language, String newText) throws SQLException {
if(language == null || language.equalsIgnoreCase("default")) {
throw new IllegalArgumentException("May not directly define default text at this time.");
}
Connection c = null;
PreparedStatement p1 = null;
PreparedStatement p2 = null;
PreparedStatement p3 = null;
ResultSet r = null;
try {
c = ds.getConnection();
p3 = c.prepareStatement("select t_hash from translations where t_hash=? and t_lang='default'");
p3.setInt(1, hash);
r = p3.executeQuery();
boolean hasDefault = true;
if(!r.next()) {
hasDefault = false;
}
r.close();
r = null;
p3.close();
p3 = null;
if(!hasDefault) {
throw new IllegalArgumentException("Attempting to define a translation for a hash with no default text string.");
}
p2 = c.prepareStatement("select t_hash from translations where t_hash=? and t_lang=?");
p2.setInt(1, hash);
p2.setString(2, language);
r = p2.executeQuery();
if(!r.next()) {
if(newText != null) {
p1 = c.prepareStatement("insert into translations (t_hash,t_text,t_lang) values(?,?,?)");
p1.setInt(1, hash);
p1.setString(2, newText);
p1.setString(3, language);
p1.execute();
}
} else {
if(newText == null) {
p1 = c.prepareStatement("delete from translations where t_hash=? and t_lang=?");
p1.setInt(1, hash);
p1.setString(2, language);
p1.execute();
} else {
p1 = c.prepareStatement("update translations set t_text=? where t_hash=? and t_lang=?");
p1.setString(1, newText);
p1.setInt(2, hash);
p1.setString(3, language);
p1.execute();
}
}
r.close();
r = null;
} finally {
if(r != null) { r.close(); }
if(p1 != null) { p1.close(); }
if(p2 != null) { p2.close(); }
if(p3 != null) { p3.close(); }
if(c != null) { c.close(); }
}
if(!strings.containsKey(language)) {
strings.put(language, new Hashtable());
}
if(newText != null) {
strings.get(language).put(hash, newText);
} else {
strings.get(language).remove(hash);
}
}
/**
* Called on web application startup to trigger loading all available translations
* from the database.
*
* @throws IOException
*/
private void loadTranslationsFromDatabase() throws IOException {
Connection c = null;
PreparedStatement p = null;
ResultSet r = null;
try {
c = ds.getConnection();
c.setAutoCommit(true);
createTable(c);
p = c.prepareStatement("select t_hash, t_text, t_lang from translations");
r = p.executeQuery();
while(r.next()) {
int hash = r.getInt(1);
String text = r.getString(2);
String language = r.getString(3);
if(text == null) {
continue;
}
if(language.equalsIgnoreCase("default")) {
defaults.put(hash, text);
} else {
if(!strings.containsKey(language)) {
strings.put(language, new Hashtable());
}
strings.get(language).put(hash, text);
}
}
r.close();
r = null;
} catch(SQLException e) {
throw new IOException(e);
} finally {
if(r != null) { try { r.close(); } catch(SQLException e) {} }
if(p != null) { try { p.close(); } catch(SQLException e) {} }
if(c != null) { try { c.close(); } catch(SQLException e) {} }
}
}
/**
*
* @throws IOException
*/
public List search(String keyword) throws IOException {
Connection c = null;
PreparedStatement p = null;
ResultSet r = null;
List results = new LinkedList();
try {
c = ds.getConnection();
p = c.prepareStatement("select t_hash, t_text, t_lang from translations where t_text like lower(?) order by t_hash");
if(keyword == null || keyword.length() == 0 || keyword.equals("%") || keyword.equals("*")) {
keyword = "%";
} else {
if(!keyword.contains("%")) {
keyword = "%" + keyword.toLowerCase() + "%";
} else {
keyword = keyword.toLowerCase();
}
}
p.setString(1, keyword);
r = p.executeQuery();
while(r.next()) {
results.add(new LocalTranslation(r.getInt(1), r.getString(3), r.getString(2)));
}
} catch(SQLException e) {
throw new IOException(e);
} finally {
if(r != null) { try { r.close(); } catch(SQLException e) {} }
if(p != null) { try { p.close(); } catch(SQLException e) {} }
if(c != null) { try { c.close(); } catch(SQLException e) {} }
}
return results;
}
/**
*
* @throws IOException
*/
public Collection export() throws IOException {
Connection c = null;
PreparedStatement p = null;
ResultSet r = null;
Map results = new Hashtable();
try {
c = ds.getConnection();
p = c.prepareStatement(
"select t_hash, t_text, t_lang from translations where t_lang='default' " +
"union " +
"select t_hash, t_text, t_lang from translations where t_lang!='default' "
);
r = p.executeQuery();
while(r.next()) {
if(r.getString(3).equals("default")) {
results.put(r.getInt(1), new LocalTranslationSet(r.getInt(1), r.getString(2)));
} else {
LocalTranslationSet i = results.get(r.getInt(1));
if(i == null) {
System.err.println("Missing default text for string: " + r.getString(2) + ":" + r.getString(3));
} else {
i.addText(r.getString(3), r.getString(2));
}
}
}
} catch(SQLException e) {
throw new IOException(e);
} finally {
if(r != null) { try { r.close(); } catch(SQLException e) {} }
if(p != null) { try { p.close(); } catch(SQLException e) {} }
if(c != null) { try { c.close(); } catch(SQLException e) {} }
}
return results.values();
}
/**
*
* @throws IOException
*/
public List search(int hash) throws IOException {
Connection c = null;
PreparedStatement p = null;
ResultSet r = null;
List results = new LinkedList();
try {
c = ds.getConnection();
p = c.prepareStatement("select t_hash, t_text, t_lang from translations where t_hash=? order by t_lang");
p.setInt(1, hash);
r = p.executeQuery();
while(r.next()) {
results.add(new LocalTranslation(r.getInt(1), r.getString(3), r.getString(2)));
}
} catch(SQLException e) {
throw new IOException(e);
} finally {
if(r != null) { try { r.close(); } catch(SQLException e) {} }
if(p != null) { try { p.close(); } catch(SQLException e) {} }
if(c != null) { try { c.close(); } catch(SQLException e) {} }
}
return results;
}
/**
* Wipe all information about translations from memory and from the database. Used by
* the test cases to reset test data.
*
* @throws SQLException
*/
public void wipe() throws SQLException {
Connection c = null;
PreparedStatement p = null;
try {
c = ds.getConnection();
c.setAutoCommit(true);
createTable(c);
p = c.prepareStatement("delete from translations");
p.executeUpdate();
defaults = new Hashtable<>();
strings = new Hashtable<>();
databaseInsertQueue = new HashSet<>();
} finally {
if(p != null) { p.close(); }
if(c != null) { c.close(); }
}
}
/**
* Add new strings that require translation.
*
* @param text
*/
private synchronized void registerNewTextForTranslation(String text) {
databaseInsertQueue.add(text);
}
/**
* Retrieve the list of recently discovered strings that require translation.
*
* @return List of recently discovered strings that need translation.
*/
private synchronized Set newTextForTranslation() {
Setitems = databaseInsertQueue;
databaseInsertQueue = new HashSet<>();
return items;
}
/**
* Create the translations database table. Rather than trying to guess which
* database type we are talking to, just issue create statements for both MySQL
* and/or Oracle to see which one sticks.
*
* @param c
*/
private void createTable(Connection c) {
PreparedStatement s = null;
try {
s = c.prepareStatement(
"create table translations (" +
" t_hash integer, "+
" t_lang nvarchar2(32), " +
" t_text nvarchar2(254), " +
" CONSTRAINT translations_pk PRIMARY KEY (t_hash,t_lang)) ");
s.execute();
} catch(SQLException e) {
// Fails if table already exists, or this is not Oracle.
} finally {
if(s != null) { try { s.close(); s = null; } catch(Exception f){} }
}
try {
s = c.prepareStatement(
"create table if not exists translations (" +
" t_hash integer, " +
" t_lang varchar(32), "+
" t_text varchar(254), "+
" PRIMARY KEY(t_hash,t_lang)) ENGINE=InnoDB DEFAULT CHARSET=utf8");
s.execute();
} catch(SQLException e) {
// Fails if this is not MySQL
} finally {
if(s != null) { try { s.close(); } catch(Exception f){} }
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy