Project: * Clean-up (Eclipse cruft, unused files, etc...) * Git-specific changes * Maven POMs clean-up and changes for the build system * Version set to 1.0.0-0 in the development branches * Maven plug-ins updated to latest versions * Very partial dev. documentation added

This commit is contained in:
Emmanuel BENOîT 2011-12-09 08:07:33 +01:00
parent c74e30d5ba
commit 0665a760de
1439 changed files with 1020 additions and 1649 deletions

View file

@ -0,0 +1,16 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>legacyworlds-server-beans</artifactId>
<groupId>com.deepclone.lw</groupId>
<version>1.0.0</version>
<relativePath>../legacyworlds-server-beans/pom.xml</relativePath>
</parent>
<artifactId>legacyworlds-server-beans-i18n</artifactId>
<name>Legacy Worlds - Server - Components - Internationalisation</name>
<description>This package defines the components which control server-side internationalised text management.</description>
<version>${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}</version>
</project>

View file

@ -0,0 +1,232 @@
package com.deepclone.lw.beans.i18n;
import java.util.Map;
import java.util.Set;
import com.deepclone.lw.interfaces.i18n.*;
/**
* The implementation of the I18N administrative session uses the manager's shared {@link I18NData}
* instance to modify the I18N database, logging all actions in the process. It also provides some
* read access to languages and translations, but (as opposed to access through the
* {@link Translator} bean) completely ignores language support requirements, allowing new or
* incomplete languages to be managed.
*
* @author tseeker
*/
class I18NAdministrationImpl
implements I18NAdministration
{
/** The shared I18N data instance */
private I18NData data;
private int administrator;
/**
* Copies the various required references
*
* @param data
* shared I18N data instance
* @param administrator
*/
I18NAdministrationImpl( I18NData data, int administrator )
{
this.data = data;
this.administrator = administrator;
}
/* Documented in I18NAdministration interface */
@Override
public Set< String > getLanguages( )
{
this.data.readLock( ).lock( );
try {
return this.data.getLanguages( );
} finally {
this.data.readLock( ).unlock( );
}
}
/* Documented in I18NAdministration interface */
@Override
public String getLanguageName( String language )
throws UnknownLanguageException
{
String l;
this.data.readLock( ).lock( );
try {
l = this.data.getLanguageName( language );
} finally {
this.data.readLock( ).unlock( );
}
if ( l == null ) {
throw new UnknownLanguageException( language );
}
return l;
}
/* Documented in I18NAdministration interface */
@Override
public double getLanguageSupport( String language )
throws UnknownLanguageException
{
int sCount;
double lSize;
this.data.readLock( ).lock( );
try {
if ( !this.data.hasLanguage( language ) ) {
throw new UnknownLanguageException( language );
}
lSize = this.data.getLanguageSize( language );
sCount = this.data.getStringsCount( );
} finally {
this.data.readLock( ).unlock( );
}
if ( sCount == 0 ) {
return 0;
}
return lSize / (double) sCount;
}
/* Documented in I18NAdministration interface */
@Override
public void createLanguage( String language , String name )
throws DuplicateLanguageException
{
this.data.writeLock( ).lock( );
try {
if ( !this.data.addLanguage( this.administrator , language , name ) ) {
throw new DuplicateLanguageException( language );
}
} finally {
this.data.writeLock( ).unlock( );
}
}
/* Documented in I18NAdministration interface */
@Override
public void setLanguageName( String language , String name )
throws UnknownLanguageException
{
String oldName;
this.data.writeLock( ).lock( );
try {
oldName = this.data.updateLanguage( this.administrator , language , name );
if ( oldName == null ) {
throw new UnknownLanguageException( language );
}
} finally {
this.data.writeLock( ).unlock( );
}
}
@Override
public Set< String > getStrings( )
{
this.data.readLock( ).lock( );
try {
return this.data.getStrings( );
} finally {
this.data.readLock( ).unlock( );
}
}
/* Documented in I18NAdministration interface */
@Override
public String getTranslation( String language , String string )
throws UnknownStringException , UnknownLanguageException
{
this.data.readLock( ).lock( );
try {
if ( !this.data.hasLanguage( language ) ) {
throw new UnknownLanguageException( language );
}
if ( !this.data.hasString( string ) ) {
throw new UnknownStringException( string );
}
return this.data.getTranslation( language , string );
} finally {
this.data.readLock( ).unlock( );
}
}
@Override
public boolean updateTranslation( String language , String string , String translation )
throws UnknownStringException , UnknownLanguageException
{
String oldString;
this.data.writeLock( ).lock( );
try {
if ( !this.data.hasLanguage( language ) ) {
throw new UnknownLanguageException( language );
}
if ( !this.data.hasString( string ) ) {
throw new UnknownStringException( string );
}
oldString = this.data.setTranslation( this.administrator , language , string , translation );
} finally {
this.data.writeLock( ).unlock( );
}
return ( oldString == null );
}
/* Documented in I18NAdministration interface */
@Override
public void createString( String string , Map< String , String > translations )
throws DuplicateStringException , UnknownLanguageException , InvalidUpdateException
{
Set< String > languages;
this.data.writeLock( ).lock( );
try {
// Check for duplicate string ID
if ( this.data.hasString( string ) ) {
throw new DuplicateStringException( string );
}
// Make sure all specified languages actually exist
languages = this.data.getLanguages( );
for ( String lId : translations.keySet( ) ) {
if ( !languages.contains( lId ) ) {
throw new UnknownLanguageException( lId );
}
languages.remove( lId );
}
// Make sure no 100% supported language becomes unsupported because a translation is
// missing
int sCount = this.data.getStringsCount( );
for ( String lId : languages ) {
if ( this.data.getLanguageSize( lId ) < sCount ) {
languages.remove( lId );
}
}
if ( !languages.isEmpty( ) ) {
throw new InvalidUpdateException( languages );
}
// Create the string
this.data.createString( this.administrator , string , translations );
} finally {
this.data.writeLock( ).unlock( );
}
}
}

View file

@ -0,0 +1,350 @@
package com.deepclone.lw.beans.i18n;
import java.sql.Types;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.sql.DataSource;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.deepclone.lw.utils.StoredProc;
/**
* This class is used by all parts of the I18N support system; it centralises all I18N-related data,
* providing both read and write access. It implements the {@link ReadWriteLock} interface, although
* only as a convenience.
*
* @author tseeker
*/
class I18NData
implements ReadWriteLock
{
/** The instance's read/write lock */
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock( );
/** Database interface */
private final SimpleJdbcTemplate dTemplate;
/** Transaction manager interface */
private final TransactionTemplate tTemplate;
/** String definitions, by identifier */
private HashSet< String > strings;
/** Languages by identifier. Each language is represented by a {@link LanguageStore} */
private HashMap< String , LanguageStore > languages;
private StoredProc fUocLanguage;
private StoredProc fUocTranslation;
/** Copies the required references then loads all data */
I18NData( DataSource dataSource , TransactionTemplate tTemplate )
{
this.dTemplate = new SimpleJdbcTemplate( dataSource );
this.tTemplate = tTemplate;
this.fUocLanguage = new StoredProc( dataSource , "defs" , "uoc_language" );
this.fUocLanguage.addParameter( "language" , Types.VARCHAR );
this.fUocLanguage.addParameter( "name" , Types.VARCHAR );
this.fUocLanguage.addParameter( "admin_id" , Types.INTEGER );
this.fUocTranslation = new StoredProc( dataSource , "defs" , "uoc_translation" );
this.fUocTranslation.addParameter( "language" , Types.VARCHAR );
this.fUocTranslation.addParameter( "string_id" , Types.VARCHAR );
this.fUocTranslation.addParameter( "contents" , Types.VARCHAR );
this.fUocTranslation.addParameter( "admin_id" , Types.INTEGER );
this.loadAll( );
}
/**
* (Re)initialises the strings and languages definition maps, then starts a loader transaction
* to fill them from the database's contents.
*/
void loadAll( )
{
this.strings = new HashSet< String >( );
this.languages = new HashMap< String , LanguageStore >( );
LoaderTransaction trans = new LoaderTransaction( this.dTemplate , this.strings , this.languages );
this.tTemplate.execute( trans );
}
/** @return the set of known language identifiers */
Set< String > getLanguages( )
{
return new HashSet< String >( this.languages.keySet( ) );
}
/**
* @param identifier
* a language's identifier
* @return whether a language exists or not
*/
boolean hasLanguage( String identifier )
{
return this.languages.containsKey( identifier );
}
/**
* @param string
* a string identifier
* @return whether such a string definition exists or not
*/
boolean hasString( String string )
{
return this.strings.contains( string );
}
/**
* @param identifier
* a language's identifier
* @return the amount of translations defined for the specified language, or 0 if the language
* does not exist
*/
int getLanguageSize( String identifier )
{
if ( !this.hasLanguage( identifier ) ) {
return 0;
}
return this.languages.get( identifier ).getTranslationsCount( );
}
/** @return the amount of defined string identifiers */
int getStringsCount( )
{
return this.strings.size( );
}
/**
* Checks whether a language is fully supported.
*
* @param identifier
* the language's identifier
* @return true if there's a translation for each string definition in the specified language.
*/
boolean isLanguageComplete( String identifier )
{
return this.getLanguageSize( identifier ) == this.getStringsCount( );
}
/** @return the set of defined string identifiers */
Set< String > getStrings( )
{
return new HashSet< String >( this.strings );
}
String getLanguageName( String id )
{
if ( !this.hasLanguage( id ) ) {
return null;
}
return this.languages.get( id ).getLanguageName( );
}
/**
* Creates a new language definition in the database.
*
* @param id
* the new language's identifier
* @param name
* the new language's name
* @return <code>true</code> on success, <code>false</code> otherwise
*/
boolean addLanguage( int administrator , String id , String name )
{
if ( this.hasLanguage( id ) ) {
return false;
}
this.uocLanguage( administrator , id , name );
this.languages.put( id , new LanguageStore( id , name ) );
return true;
}
/**
* Modifies the name of a language.
*
* @param administrator
*
* @param id
* the language's identifier
* @param name
* the language's new name
* @return null if the language does not exist, or the old name of the language if it does
*/
String updateLanguage( int administrator , String id , String name )
{
LanguageStore ls = this.languages.get( id );
if ( ls == null ) {
return null;
}
String oldName = ls.getLanguageName( );
this.uocLanguage( administrator , id , name );
ls.setLanguageName( name );
return oldName;
}
private void uocLanguage( final int administrator , final String id , final String name )
{
this.tTemplate.execute( new TransactionCallbackWithoutResult( ) {
@Override
protected void doInTransactionWithoutResult( TransactionStatus status )
{
fUocLanguage.execute( id , name , administrator );
}
} );
}
/**
* Accesses the {@link Translation} object for a given language/string identifier pair.
*
* @param language
* the language's identifier
* @param string
* the string's identifier
* @return the translation
* @throws NullPointerException
* if the language does not exist
*/
String getTranslation( String language , String string )
{
return this.languages.get( language ).getTranslation( string );
}
/**
* Sets or creates the translation for a given language/string identifier pair.
*
* @param administrator
*
* @param language
* the language's identifier
* @param string
* the string's identifier
* @param translation
* the translated text for the string
* @return the old translated text if the translation existed or null if a new translation was
* created
* @throws NullPointerException
* if the language does not exist
* @throws IllegalArgumentException
* if the string does not exist
*/
String setTranslation( final int administrator , final String language , final String string , final String translation )
{
// Get existing translation
LanguageStore store = this.languages.get( language );
String old = store.getTranslation( string );
if ( !this.strings.contains( string ) ) {
throw new IllegalArgumentException( );
}
// Create or update the database record
this.tTemplate.execute( new TransactionCallbackWithoutResult( ) {
@Override
protected void doInTransactionWithoutResult( TransactionStatus status )
{
fUocTranslation.execute( language , string , translation , administrator );
}
} );
// Store the new translation
store.addTranslation( string , translation );
return old;
}
/**
* Creates a string from scratch, storing initial translations along with it.
*
* @param string
* the new string's identifier
* @param translations
* a language identifiers -> translated text map to use as the string's initial
* translations.
* @throws ConstraintViolationException
* if the string identifier already existed in the DB
* @throws NullPointerException
* if one of the languages does not exist
* @throws IllegalArgumentException
* if the string exists
*/
void createString( final int administrator , final String string , final Map< String , String > translations )
{
if ( this.strings.contains( string ) ) {
throw new IllegalArgumentException( );
}
// Map language stores to translations
Map< LanguageStore , String > nTrans = new HashMap< LanguageStore , String >( );
for ( String lId : translations.keySet( ) ) {
LanguageStore store = this.languages.get( lId );
nTrans.put( store , translations.get( lId ) );
}
// Update the database
this.tTemplate.execute( new TransactionCallbackWithoutResult( ) {
@Override
protected void doInTransactionWithoutResult( TransactionStatus status )
{
for ( Map.Entry< String , String > entry : translations.entrySet( ) ) {
fUocTranslation.execute( entry.getKey( ) , string , entry.getValue( ) , administrator );
}
}
} );
// Add the string definition to the local cache
this.strings.add( string );
// Add the various translations to each language's store
for ( Map.Entry< LanguageStore , String > entry : nTrans.entrySet( ) ) {
entry.getKey( ).addTranslation( string , entry.getValue( ) );
}
}
/** @return the instance's read lock */
@Override
public Lock readLock( )
{
return this.lock.readLock( );
}
/** @return the instance's write lock */
@Override
public Lock writeLock( )
{
return this.lock.writeLock( );
}
}

View file

@ -0,0 +1,97 @@
package com.deepclone.lw.beans.i18n;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import com.deepclone.lw.interfaces.i18n.I18NAdministration;
import com.deepclone.lw.interfaces.i18n.I18NManager;
/**
* The I18N manager bean creates the {@link I18NData} instance on initialisation, which it then
* shares with translator beans and administrative session instances.
*
* @author tseeker
*/
public class I18NManagerBean
implements I18NManager , InitializingBean
{
/** Transaction manager interface */
private TransactionTemplate tTemplate;
/** Database interface */
private DataSource dataSource;
/** Data store shared amongst the translator beans and administrative session instances */
private I18NData data;
/**
* Sets the transaction manager interface (DI)
*
* @param manager
* the transaction manager
*/
@Autowired( required = true )
public void setTransactionManager( DataSourceTransactionManager manager )
{
this.tTemplate = new TransactionTemplate( manager );
}
/**
* Sets the JBDC interface (DI)
*
* @param dSource
* the data source
*/
@Autowired( required = true )
public void setDataSource( DataSource dSource )
{
this.dataSource = dSource;
}
/** Creates the shared {@link I18NData} instance on initialisation */
@Override
public void afterPropertiesSet( )
{
this.data = new I18NData( this.dataSource , this.tTemplate );
}
/** @return the shared {@link I18NData} instance */
I18NData getData( )
{
return this.data;
}
/* Documentation in I18NManager interface */
@Override
public I18NAdministration getAdminSession( int administrator )
{
return new I18NAdministrationImpl( this.data , administrator );
}
/* Documentation in I18NManager interface */
@Override
public void reload( )
{
this.data.writeLock( ).lock( );
try {
this.data.loadAll( );
} finally {
this.data.writeLock( ).unlock( );
}
}
}

View file

@ -0,0 +1,79 @@
package com.deepclone.lw.beans.i18n;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
/**
* A language store encapsulates all data that defines a language - from the language definition
* itself to the various translations available in this language.
*
* @author tseeker
*/
class LanguageStore
implements Serializable
{
private static final long serialVersionUID = 1L;
private final String languageId;
private String languageName;
private final Map<String,String> translations = new HashMap< String , String >( );
public LanguageStore( String languageId , String languageName )
{
this.languageId = languageId;
this.languageName = languageName;
}
/** @return the store's language identifier */
String getLanguageIdentifier( )
{
return this.languageId;
}
/** @return the name of the language encapsulated by the store */
String getLanguageName( )
{
return this.languageName;
}
/** @return the amount of translations available from the store */
int getTranslationsCount( )
{
return this.translations.size( );
}
/**
* @param string
* a string identifier
* @return the translated string
*/
String getTranslation( String string )
{
return this.translations.get( string );
}
void addTranslation( String stringId , String translation )
{
this.translations.put( stringId , translation );
}
void setLanguageName( String name )
{
this.languageName = name;
}
}

View file

@ -0,0 +1,86 @@
package com.deepclone.lw.beans.i18n;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import com.deepclone.lw.sqld.i18n.Translation;
/**
* The I18N data loader transaction reads all string definitions and language definitions from the
* database, creating {@link LanguageStore} instances for each language.
*
* @author tseeker
*/
class LoaderTransaction
extends TransactionCallbackWithoutResult
{
/** Database interface */
private final SimpleJdbcTemplate dTemplate;
/** String definition map being initialised */
private final HashSet< String > strings;
/** Language definition map being initialised */
private final HashMap< String , LanguageStore > languages;
/** Copies the required references */
LoaderTransaction( SimpleJdbcTemplate dTemplate , HashSet< String > strings ,
HashMap< String , LanguageStore > languages )
{
this.dTemplate = dTemplate;
this.strings = strings;
this.languages = languages;
}
/**
* The transaction loads all string definitions, storing them in the string definition map, then
* loads all languages, creating a {@link LanguageStore} for each instance.
*/
@Override
protected void doInTransactionWithoutResult( TransactionStatus status )
{
String sql;
RowMapper< Translation > mapper = new RowMapper< Translation >( ) {
@Override
public Translation mapRow( ResultSet rs , int rowNum )
throws SQLException
{
Translation t = new Translation( );
t.setLanguageId( rs.getString( "language_id" ) );
t.setLanguageName( rs.getString( "language_name" ) );
t.setStringId( rs.getString( "string_id" ) );
t.setTranslation( rs.getString( "translation" ) );
return t;
}
};
sql = "SELECT language_id , language_name , string_id , translation FROM defs.translations_view";
for ( Translation trans : this.dTemplate.query( sql , mapper ) ) {
this.strings.add( trans.getStringId( ) );
LanguageStore ls = this.languages.get( trans.getLanguageId( ) );
if ( ls == null ) {
ls = new LanguageStore( trans.getLanguageId( ) , trans.getLanguageName( ) );
this.languages.put( trans.getLanguageId( ) , ls );
}
ls.addTranslation( trans.getStringId( ) , trans.getTranslation( ) );
}
}
}

View file

@ -0,0 +1,122 @@
package com.deepclone.lw.beans.i18n;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import com.deepclone.lw.interfaces.i18n.Translator;
import com.deepclone.lw.interfaces.i18n.UnknownLanguageException;
import com.deepclone.lw.interfaces.i18n.UnknownStringException;
/**
* The translator bean's implementation uses the contents of the {@link I18NData} instance, which it
* only accesses in read-only mode.
*
* @author tseeker
*/
public class TranslatorBean
implements Translator
{
private I18NData data;
/**
* Sets the bean's I18N manager (DI)
*
* @param manager
* the I18N manager to use
*/
@Autowired( required = true )
public void setI18NManager( I18NManagerBean manager )
{
this.data = manager.getData( );
}
/* Documentation in Translator interface */
@Override
public Set< String > getSupportedLanguages( )
{
Set< String > supported = new HashSet< String >( );
int sCount , lSize;
this.data.readLock( ).lock( );
try {
sCount = this.data.getStringsCount( );
if ( sCount == 0 ) {
return supported;
}
for ( String lId : this.data.getLanguages( ) ) {
lSize = this.data.getLanguageSize( lId );
if ( lSize == sCount ) {
supported.add( lId );
}
}
return supported;
} finally {
this.data.readLock( ).unlock( );
}
}
/* Documentation in Translator interface */
@Override
public boolean isLanguageSupported( String language )
{
int lSize , sCount;
this.data.readLock( ).lock( );
try {
lSize = this.data.getLanguageSize( language );
sCount = this.data.getStringsCount( );
return ( sCount > 0 && lSize == sCount );
} finally {
this.data.readLock( ).unlock( );
}
}
/* Documentation in Translator interface */
@Override
public String translate( String language , String string )
throws UnknownStringException , UnknownLanguageException
{
this.data.readLock( ).lock( );
try {
if ( !this.data.hasString( string ) ) {
throw new UnknownStringException( string );
}
if ( !this.data.isLanguageComplete( language ) ) {
throw new UnknownLanguageException( language );
}
return this.data.getTranslation( language , string );
} finally {
this.data.readLock( ).unlock( );
}
}
/* Documentation in Translator interface */
@Override
public String getLanguageName( String language )
throws UnknownLanguageException
{
this.data.readLock( ).lock( );
try {
if ( !this.data.isLanguageComplete( language ) ) {
throw new UnknownLanguageException( language );
}
return this.data.getLanguageName( language );
} finally {
this.data.readLock( ).unlock( );
}
}
}

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<import resource="i18n/i18n-manager-bean.xml" />
<import resource="i18n/i18n-translator-bean.xml" />
</beans>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="i18nManager" class="com.deepclone.lw.beans.i18n.I18NManagerBean" />
</beans>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="i18nTranslator" class="com.deepclone.lw.beans.i18n.TranslatorBean" />
</beans>