Event definition loader

Implemented the ImportEvents command line tool, which allows event
definitions to be imported. In all cases the tool will try to import all
definitions; if an error occurs, the process continues but the
transaction is rolled back. It skips existing definitions rather than
taking the risk of doing something inappropriate (e.g. deletion of
existing events).
This commit is contained in:
Emmanuel BENOîT 2012-06-30 14:52:55 +02:00
parent c8f19a4c06
commit 75c5245764
15 changed files with 1334 additions and 0 deletions

View file

@ -31,6 +31,7 @@ cat > data-source.xml <<EOF
EOF
java legacyworlds-server-main-*.jar --run-tool ImportText data/i18n-text.xml || exit 1
java legacyworlds-server-main-*.jar --run-tool ImportEvents data/event-definitions.xml || exit 1
java legacyworlds-server-main-*.jar --run-tool ImportResources data/resources.xml || exit 1
java legacyworlds-server-main-*.jar --run-tool ImportTechs data/techs.xml || exit 1
java legacyworlds-server-main-*.jar --run-tool ImportTechGraph data/tech-graph.xml || exit 1

View file

@ -0,0 +1,715 @@
package com.deepclone.lw.cli;
import java.io.File;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
import org.postgresql.util.PGobject;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import com.deepclone.lw.cli.xmlimport.EventDefinitionLoader;
import com.deepclone.lw.cli.xmlimport.data.DataImportException;
import com.deepclone.lw.cli.xmlimport.data.evdef.EntityEventField;
import com.deepclone.lw.cli.xmlimport.data.evdef.EventDefinition;
import com.deepclone.lw.cli.xmlimport.data.evdef.EventDefinitions;
import com.deepclone.lw.cli.xmlimport.data.evdef.EventFieldBase;
import com.deepclone.lw.cli.xmlimport.data.evdef.IntegerEventField;
import com.deepclone.lw.cli.xmlimport.data.evdef.RealEventField;
import com.deepclone.lw.cli.xmlimport.data.evdef.TextEventField;
import com.deepclone.lw.utils.StoredProc;
public class ImportEvents
extends CLITool
{
/**
* Error codes that may be returned by the various import stored procedures; see the
* corresponding SQL code for details about each value.
*/
private static enum SPErrorCode {
OK ,
BAD_ID ,
BAD_STRINGS ,
DUPLICATE ,
BAD_SPEC ,
NO_DATA
};
/** Logging system */
private final Logger logger = Logger.getLogger( ImportResources.class );
/** File to read the definitions from */
private File file;
/** Spring transaction template */
private TransactionTemplate tTemplate;
/** Error counter */
private int errors = 0;
/** Skipped definitions counter */
private int skipped = 0;
/** Successful imports counter */
private int successful = 0;
/** Stored procedure that starts recording an event definition */
private StoredProc fEvdefStart;
/** Stored procedure that finalises an event definition's recording */
private StoredProc fEvdefFinalise;
/** Stored procedure that defines a boolean field */
private StoredProc fEvdefAddfldBool;
/** Stored procedure that defines an I18N reference field */
private StoredProc fEvdefAddfldI18N;
/** Stored procedure that defines an entity reference field */
private StoredProc fEvdefAddfldEntity;
/** Unbounded integer field definition procedure */
private StoredProc fEvdefAddfldInt;
/** Stored procedure that defines an integer field with a lower bound */
private StoredProc fEvdefAddfldIntMin;
/** Stored procedure that defines an integer field with a higher bound */
private StoredProc fEvdefAddfldIntMax;
/** Stored procedure that defines a bounded integer field */
private StoredProc fEvdefAddfldIntRange;
/** Unbounded double precision field definition procedure */
private StoredProc fEvdefAddfldReal;
/** Stored procedure that defines a double precision field with a lower bound */
private StoredProc fEvdefAddfldRealMin;
/** Stored procedure that defines a double precision field with a higher bound */
private StoredProc fEvdefAddfldRealMax;
/** Stored procedure that defines a bounded double precision field */
private StoredProc fEvdefAddfldRealRange;
/** Unbounded text field definition procedure */
private StoredProc fEvdefAddfldText;
/** Stored procedure that defines a text field with a minimum length */
private StoredProc fEvdefAddfldTextMin;
/** Stored procedure that defines a text field with a maximal length */
private StoredProc fEvdefAddfldTextMax;
/** Stored procedure that defines a bounded text field */
private StoredProc fEvdefAddfldTextRange;
/**
* Obtain the name of the definitions file
*
* <p>
* Check the command line options, setting the definitions file accordingly.
*/
@Override
public boolean setOptions( String... options )
{
if ( options.length != 1 ) {
return false;
}
this.file = new File( options[ 0 ] );
if ( ! ( this.file.isFile( ) && this.file.canRead( ) ) ) {
return false;
}
return true;
}
/**
* Run the event definitions import tool
*
* <p>
* Loads the data file, then connects to the database and creates all missing definitions.
*/
@Override
public void run( )
{
EventDefinitions data;
try {
data = new EventDefinitionLoader( this.file ).load( );
} catch ( DataImportException e ) {
this.logger.error( "Error while loading event definitions" , e );
return;
}
AbstractApplicationContext ctx = this.createContext( );
this.createTemplates( ctx );
this.executeImportTransaction( data );
ToolBase.destroyContext( ctx );
}
/**
* Create the Spring context
*
* <p>
* Load the definition of the data source as a Spring context, then adds the transaction
* management component.
*
* @return the Spring application context
*/
private ClassPathXmlApplicationContext createContext( )
{
FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext( new String[] {
this.getDataSource( )
} );
ctx.refresh( );
return new ClassPathXmlApplicationContext( new String[] {
"configuration/transactions.xml"
} , true , ctx );
}
/**
* Create database access templates
*
* <p>
* Initialise the transaction template and the four stored procedure definitions.
*
* @param ctx
* the Spring application context
*/
private void createTemplates( ApplicationContext ctx )
{
DataSource dSource = ctx.getBean( DataSource.class );
PlatformTransactionManager tManager = ctx.getBean( PlatformTransactionManager.class );
this.fEvdefStart = new StoredProc( dSource , "events" , "evdef_start" )
.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_prio" , java.sql.Types.INTEGER )
.addParameter( "_adj" , java.sql.Types.BOOLEAN ).addParameter( "_i18n" , java.sql.Types.VARCHAR )
.addOutput( "_error" , java.sql.Types.OTHER );
this.fEvdefFinalise = new StoredProc( dSource , "events" , "evdef_finalise" ).addOutput( "_error" ,
java.sql.Types.OTHER ).addOutput( "_fld" , java.sql.Types.VARCHAR );
this.fEvdefAddfldBool = new StoredProc( dSource , "events" , "evdef_addfld_bool" )
.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
.addOutput( "_error" , java.sql.Types.OTHER );
this.fEvdefAddfldI18N = new StoredProc( dSource , "events" , "evdef_addfld_i18n" )
.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
.addOutput( "_error" , java.sql.Types.OTHER );
this.fEvdefAddfldEntity = new StoredProc( dSource , "events" , "evdef_addfld_entity" )
.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
.addParameter( "_etype" , "events.entity_field_type" ).addOutput( "_error" , java.sql.Types.OTHER );
this.fEvdefAddfldInt = new StoredProc( dSource , "events" , "evdef_addfld_int" )
.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
.addOutput( "_error" , java.sql.Types.OTHER );
this.fEvdefAddfldIntMin = new StoredProc( dSource , "events" , "evdef_addfld_int_min" )
.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
.addParameter( "_min" , java.sql.Types.INTEGER ).addOutput( "_error" , java.sql.Types.OTHER );
this.fEvdefAddfldIntMax = new StoredProc( dSource , "events" , "evdef_addfld_int_max" )
.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
.addParameter( "_max" , java.sql.Types.INTEGER ).addOutput( "_error" , java.sql.Types.OTHER );
this.fEvdefAddfldIntRange = new StoredProc( dSource , "events" , "evdef_addfld_int_range" )
.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
.addParameter( "_min" , java.sql.Types.INTEGER ).addParameter( "_max" , java.sql.Types.INTEGER )
.addOutput( "_error" , java.sql.Types.OTHER );
this.fEvdefAddfldReal = new StoredProc( dSource , "events" , "evdef_addfld_real" )
.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
.addOutput( "_error" , java.sql.Types.OTHER );
this.fEvdefAddfldRealMin = new StoredProc( dSource , "events" , "evdef_addfld_real_min" )
.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
.addParameter( "_min" , java.sql.Types.DOUBLE ).addOutput( "_error" , java.sql.Types.OTHER );
this.fEvdefAddfldRealMax = new StoredProc( dSource , "events" , "evdef_addfld_real_max" )
.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
.addParameter( "_max" , java.sql.Types.DOUBLE ).addOutput( "_error" , java.sql.Types.OTHER );
this.fEvdefAddfldRealRange = new StoredProc( dSource , "events" , "evdef_addfld_real_range" )
.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
.addParameter( "_min" , java.sql.Types.DOUBLE ).addParameter( "_max" , java.sql.Types.DOUBLE )
.addOutput( "_error" , java.sql.Types.OTHER );
this.fEvdefAddfldText = new StoredProc( dSource , "events" , "evdef_addfld_text" )
.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
.addOutput( "_error" , java.sql.Types.OTHER );
this.fEvdefAddfldTextMin = new StoredProc( dSource , "events" , "evdef_addfld_text_min" )
.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
.addParameter( "_min" , java.sql.Types.INTEGER ).addOutput( "_error" , java.sql.Types.OTHER );
this.fEvdefAddfldTextMax = new StoredProc( dSource , "events" , "evdef_addfld_text_max" )
.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
.addParameter( "_max" , java.sql.Types.INTEGER ).addOutput( "_error" , java.sql.Types.OTHER );
this.fEvdefAddfldTextRange = new StoredProc( dSource , "events" , "evdef_addfld_text_range" )
.addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN )
.addParameter( "_min" , java.sql.Types.INTEGER ).addParameter( "_max" , java.sql.Types.INTEGER )
.addOutput( "_error" , java.sql.Types.OTHER );
this.tTemplate = new TransactionTemplate( tManager );
}
/**
* Execute the event definitions importation transaction
*
* <p>
* Run a transaction and execute the importation code inside it. Roll back if anything goes
* wrong.
*
* @param data
* the event definitions top-level instance
*/
private void executeImportTransaction( final EventDefinitions data )
{
boolean rv = this.tTemplate.execute( new TransactionCallback< Boolean >( ) {
@Override
public Boolean doInTransaction( TransactionStatus status )
{
boolean rv = ImportEvents.this.doTransaction( data );
if ( !rv ) {
status.setRollbackOnly( );
}
return rv;
}
} );
if ( rv ) {
this.logger.info( "Import complete; " + this.successful + " new type(s), " + this.skipped + " skipped" );
} else {
this.logger.error( this.errors + " error(s) occurred, no changes were made" );
}
}
/**
* Import transaction body
*
* <p>
* Import all definitions and handle exceptions.
*
* @param data
* the event definitions top-level instance
* @return <code>true</code> on success, <code>false</code> otherwise
*/
private boolean doTransaction( EventDefinitions data )
{
try {
this.importDefinitions( data );
return this.errors == 0;
} catch ( RuntimeException e ) {
this.logger.error( "Caught runtime exception" , e );
this.errors ++;
}
return false;
}
/**
* Extract the error code from a stored procedure's return value
*
* <p>
* This method is used to extract the error code from the maps returned by a definition stored
* procedure's execution.
*
* @param result
* the map returned by the stored procedure's execution
*
* @return the error code
*/
private SPErrorCode getSPResult( Map< String , Object > result )
{
return SPErrorCode.valueOf( ( (PGobject) result.get( "_error" ) ).getValue( ) );
}
/**
* Import all event definitions from the top-level importable instance
*
* <p>
* This method iterates through all of the definitions, trying to import each of them.
*
* @param data
* the top-level importable instance
*/
private void importDefinitions( EventDefinitions data )
{
for ( EventDefinition evdef : data ) {
this.importDefinition( evdef );
}
}
/**
* Try to import an event definition
*
* <p>
* This method will attempt to import an event definition. It will increase the state counters (
* {@link #errors}, {@link #skipped} and {@link #successful}) depending on the results.
*
* @param evdef
* the event definition to import
*/
private void importDefinition( EventDefinition evdef )
{
boolean ok;
// Start the import
try {
this.startImport( evdef );
ok = true;
} catch ( DataImportException e ) {
this.logger.error( e.getMessage( ) );
ok = false;
}
// Import all fields
if ( ok ) {
for ( EventFieldBase field : evdef ) {
ok = ok && this.importField( evdef , field );
if ( !ok ) {
break;
}
}
}
// Finalise the operation
boolean wasNew = false;
try {
wasNew = this.endImport( evdef );
} catch ( DataImportException e ) {
if ( ok ) {
this.logger.error( e.getMessage( ) );
ok = false;
}
}
// Update counters and print to log
if ( ok ) {
if ( wasNew ) {
this.logger.info( evdef.getIdentifier( ) + ": new event definition imported" );
this.successful++;
} else {
this.logger.warn( evdef.getIdentifier( ) + ": event definition exists (skipped)" );
this.skipped++;
}
} else {
this.errors++;
}
}
/**
* Start importing an event definition
*
* <p>
* This method calls the <code>events.evdef_start()</code> stored procedure to initialise the
* event definition registration, and handles its return value.
*
* @param evdef
* the event definition instance from the XML definition
*
* @throws DataImportException
* if the definition's identifier is invalid or if one of the internationalised
* strings is missing
*/
private void startImport( EventDefinition evdef )
throws DataImportException
{
SPErrorCode ec;
ec = this.getSPResult( this.fEvdefStart.execute( evdef.getIdentifier( ) , evdef.getPriority( ) ,
evdef.isAdjustable( ) , evdef.getI18NStrings( ) ) );
switch ( ec ) {
case OK:
break;
case BAD_ID:
throw new DataImportException( evdef.getIdentifier( ) + ": bad event type identifier" );
case BAD_STRINGS:
throw new DataImportException( evdef.getIdentifier( ) + ": I18N strings not found" );
default:
throw new RuntimeException( "Unsupported return value for events.evdef_start(): " + ec.toString( ) );
}
}
/**
* Import a field definition
*
* <p>
* Call the appropriate field definition importation method depending on the field's type.
*
* @param evdef
* the event definition
* @param field
* the field definition
*
* @return <code>true</code> on success, <code>false</code> if something went wrong
*/
private boolean importField( EventDefinition evdef , EventFieldBase field )
{
SPErrorCode ec;
switch ( field.getType( ) ) {
case BOOLEAN:
ec = this.importBooleanField( field );
break;
case I18N:
ec = this.importI18NField( field );
break;
case ENTITY:
ec = this.importEntityField( (EntityEventField) field );
break;
case INTEGER:
ec = this.importIntegerField( (IntegerEventField) field );
break;
case REAL:
ec = this.importRealField( (RealEventField) field );
break;
case TEXT:
ec = this.importTextField( (TextEventField) field );
break;
default:
throw new RuntimeException( "Unsupported field type " + field.getType( ).toString( ) );
}
switch ( ec ) {
case OK:
return true;
case BAD_ID:
this.logger.error( evdef.getIdentifier( ) + ": bad field identifier '" + field.getIdentifier( ) + "'" );
break;
default:
throw new RuntimeException( "Unsupported return value for field definition procedure: " + ec.toString( ) );
}
return false;
}
/**
* Import a boolean field
*
* <p>
* This method calls <code>events.evdef_addfld_bool</code> to register a new boolean field.
*
* @param field
* the field's definition
*
* @return the stored procedure's error code
*/
private SPErrorCode importBooleanField( EventFieldBase field )
{
return this.getSPResult( this.fEvdefAddfldBool.execute( field.getIdentifier( ) , !field.isRequired( ) ) );
}
/**
* Import an I18N reference field
*
* <p>
* This method calls <code>events.evdef_addfld_i18n</code> to register a new I18N reference
* field.
*
* @param field
* the field's definition
*
* @return the stored procedure's error code
*/
private SPErrorCode importI18NField( EventFieldBase field )
{
return this.getSPResult( this.fEvdefAddfldI18N.execute( field.getIdentifier( ) , !field.isRequired( ) ) );
}
/**
* Import an entity reference field
*
* <p>
* This method calls <code>events.evdef_addfld_entity</code> to register a new entity reference
* field.
*
* @param field
* the field's definition
*
* @return the stored procedure's error code
*/
private SPErrorCode importEntityField( EntityEventField field )
{
return this.getSPResult( this.fEvdefAddfldEntity.execute( field.getIdentifier( ) , !field.isRequired( ) ,
field.getEntityType( ) ) );
}
/**
* Import an integer field definition
*
* <p>
* This method calls one of the integer field definitions stored procedures, depending on the
* definition's parameters.
*
* @param field
* the field's definition
*
* @return the stored procedure's error code
*/
private SPErrorCode importIntegerField( IntegerEventField field )
{
String id = field.getIdentifier( );
boolean opt = !field.isRequired( );
Integer min = field.getMin( );
Integer max = field.getMax( );
Map< String , Object > result;
if ( min == null && max == null ) {
result = this.fEvdefAddfldInt.execute( id , opt );
} else if ( min == null ) {
result = this.fEvdefAddfldIntMax.execute( id , opt , max );
} else if ( max == null ) {
result = this.fEvdefAddfldIntMin.execute( id , opt , min );
} else {
result = this.fEvdefAddfldIntRange.execute( id , opt , min , max );
}
return this.getSPResult( result );
}
/**
* Import a double precision field definition
*
* <p>
* This method calls one of the double precision field definitions stored procedures, depending
* on the definition's parameters.
*
* @param field
* the field's definition
*
* @return the stored procedure's error code
*/
private SPErrorCode importRealField( RealEventField field )
{
String id = field.getIdentifier( );
boolean opt = !field.isRequired( );
Double min = field.getMin( );
Double max = field.getMax( );
Map< String , Object > result;
if ( min == null && max == null ) {
result = this.fEvdefAddfldReal.execute( id , opt );
} else if ( min == null ) {
result = this.fEvdefAddfldRealMax.execute( id , opt , max );
} else if ( max == null ) {
result = this.fEvdefAddfldRealMin.execute( id , opt , min );
} else {
result = this.fEvdefAddfldRealRange.execute( id , opt , min , max );
}
return this.getSPResult( result );
}
/**
* Import a text field definition
*
* <p>
* This method calls one of the text field definitions stored procedures, depending on the
* definition's parameters.
*
* @param field
* the field's definition
*
* @return the stored procedure's error code
*/
private SPErrorCode importTextField( TextEventField field )
{
String id = field.getIdentifier( );
boolean opt = !field.isRequired( );
Integer min = field.getMinLength( );
Integer max = field.getMaxLength( );
Map< String , Object > result;
if ( min == null && max == null ) {
result = this.fEvdefAddfldText.execute( id , opt );
} else if ( min == null ) {
result = this.fEvdefAddfldTextMax.execute( id , opt , max );
} else if ( max == null ) {
result = this.fEvdefAddfldTextMin.execute( id , opt , min );
} else {
result = this.fEvdefAddfldTextRange.execute( id , opt , min , max );
}
return this.getSPResult( result );
}
/**
* Finalise the current event definition
*
* <p>
* This method calls the <code>events.evdef_finalise()</code> stored procedure, and handles its
* return values.
*
* @param evdef
* the event definition from the XML file
*
* @return <code>true</code> if the definition was finalised or if it had no effect (except for
* cleanup) due to a previous error, <code>false</code> if the definition already
* existed and was skipped.
*
* @throws DataImportException
* if the event's specification is invalid, if a field name is duplicated, or if a
* field's specification is invalid.
*/
private boolean endImport( EventDefinition evdef )
throws DataImportException
{
Map< String , Object > result = this.fEvdefFinalise.execute( );
SPErrorCode ec = this.getSPResult( result );
String field = (String) result.get( "_fld" );
switch ( ec ) {
case OK:
case NO_DATA:
return true;
case BAD_SPEC:
if ( field == null ) {
throw new DataImportException( evdef.getIdentifier( ) + ": bad event type specification" );
}
throw new DataImportException( evdef.getIdentifier( ) + ": bad field specification (" + field + ")" );
case DUPLICATE:
if ( field == null ) {
return false;
}
throw new DataImportException( evdef.getIdentifier( ) + ": duplicate field name " + field );
default:
throw new RuntimeException( "Unsupported return value for events.evdef_finalise(): " + ec.toString( ) );
}
}
}

View file

@ -0,0 +1,123 @@
package com.deepclone.lw.cli.xmlimport;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import com.deepclone.lw.cli.xmlimport.data.DataImportException;
import com.deepclone.lw.cli.xmlimport.data.evdef.*;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.XStreamException;
/**
* Event definitions loader
*
* <p>
* This class implements loading event definitions from XML files. The definitions will be extracted
* and verified.
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
public class EventDefinitionLoader
{
/** The file to read the XML from */
private final File file;
/**
* Initialise the loader
*
* @param file
* the XML file that contains the definitions
*/
public EventDefinitionLoader( File file )
{
this.file = file.getAbsoluteFile( );
}
/**
* Initialise the necessary XStream instance
*
* <p>
* Initialise the XStream instance by processing annotations in all event definition importable
* data classes.
*
* @return the XStream instance to use when loading the data
*/
private XStream initXStream( )
{
XStream xstream = new XStream( );
xstream.processAnnotations( EventDefinitions.class );
xstream.processAnnotations( BooleanEventField.class );
xstream.processAnnotations( IntegerEventField.class );
xstream.processAnnotations( RealEventField.class );
xstream.processAnnotations( TextEventField.class );
xstream.processAnnotations( I18NEventField.class );
xstream.processAnnotations( EntityEventField.class );
return xstream;
}
/**
* Load the event definitions
*
* <p>
* Load the XML file and process the definitions file using XStream.
*
* @return the top-level importable data instance
*
* @throws DataImportException
* if reading from the file or parsing its contents fail
*/
private EventDefinitions loadXMLFile( )
throws DataImportException
{
FileInputStream fis;
try {
fis = new FileInputStream( this.file );
} catch ( FileNotFoundException e ) {
throw new DataImportException( "Unable to load event definitions" , e );
}
try {
try {
XStream xstream = this.initXStream( );
return (EventDefinitions) xstream.fromXML( fis );
} finally {
fis.close( );
}
} catch ( IOException e ) {
throw new DataImportException( "Input error while loading event definitions" , e );
} catch ( XStreamException e ) {
throw new DataImportException( "XML error while loading event definitions" , e );
}
}
/**
* Load and process I18N definition
*
* <p>
* Attempt to load all I18N definitions, make sure they are valid, then set the original file's
* path.
*
* @return the top-level importable data instance
*
* @throws DataImportException
* if loading or verifying the data fails
*/
public EventDefinitions load( )
throws DataImportException
{
EventDefinitions events = this.loadXMLFile( );
events.verifyData( );
events.setReadFrom( this.file );
return events;
}
}

View file

@ -0,0 +1,26 @@
package com.deepclone.lw.cli.xmlimport.data.evdef;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* A boolean field definition
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
@XStreamAlias( "boolean" )
@SuppressWarnings( "serial" )
public class BooleanEventField
extends EventFieldBase
{
/** @return {@link EventFieldType#BOOLEAN} */
@Override
public EventFieldType getType( )
{
return EventFieldType.BOOLEAN;
}
}

View file

@ -0,0 +1,39 @@
package com.deepclone.lw.cli.xmlimport.data.evdef;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
/**
* An entity reference field definition
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
@XStreamAlias( "entity" )
@SuppressWarnings( "serial" )
public class EntityEventField
extends EventFieldBase
{
/** The type of entities this field references */
@XStreamAsAttribute
@XStreamAlias( "entity-type" )
private EventFieldEntityType entityType;
public EventFieldEntityType getEntityType( )
{
return this.entityType;
}
/** @return {@link EntityFieldType#ENTITY} */
@Override
public EventFieldType getType( )
{
return EventFieldType.ENTITY;
}
}

View file

@ -0,0 +1,101 @@
package com.deepclone.lw.cli.xmlimport.data.evdef;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.deepclone.lw.cli.xmlimport.data.DataImportException;
import com.deepclone.lw.cli.xmlimport.data.ImportableData;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
/**
* Event definition
*
* <p>
* This class represents an event definition entry from the data file. An event definition consists
* in a few properties, and a potentially empty list of event field definitions.
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*
*/
@SuppressWarnings( "serial" )
public class EventDefinition
extends ImportableData
implements Iterable< EventFieldBase >
{
/** The event type's identifier */
@XStreamAsAttribute
@XStreamAlias( "id" )
private String identifier;
/** The default priority */
@XStreamAsAttribute
private Integer priority;
/** Whether users can override the events' priority */
@XStreamAsAttribute
private Boolean adjustable;
/** Prefix of the name and template strings */
@XStreamAsAttribute
@XStreamAlias( "i18n-strings" )
private String i18nStrings;
/** The list of fields */
@XStreamImplicit
private List< EventFieldBase > fields = new ArrayList< EventFieldBase >( );
public String getIdentifier( )
{
return this.identifier;
}
public int getPriority( )
{
return ( this.priority == null ) ? 2 : this.priority;
}
public boolean isAdjustable( )
{
return ( this.adjustable == null ) ? true : this.adjustable;
}
public String getI18NStrings( )
{
return this.i18nStrings;
}
@Override
public Iterator< EventFieldBase > iterator( )
{
return Collections.unmodifiableList( this.fields ).iterator( );
}
@Override
public void verifyData( )
throws DataImportException
{
super.verifyData( );
if ( this.fields == null ) {
this.fields = new ArrayList< EventFieldBase >( );
} else {
for ( EventFieldBase f : this.fields ) {
f.verifyData( );
}
}
}
}

View file

@ -0,0 +1,58 @@
package com.deepclone.lw.cli.xmlimport.data.evdef;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import com.deepclone.lw.cli.xmlimport.data.DataImportException;
import com.deepclone.lw.cli.xmlimport.data.ImportableData;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
/**
* Event type definitions
*
* <p>
* This class represents the list of event type definitions as it is found in the appropriate data
* file.
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
@SuppressWarnings( "serial" )
@XStreamAlias( "lw-event-definitions" )
public class EventDefinitions
extends ImportableData
implements Iterable< EventDefinition >
{
/** The list of event definitions */
@XStreamImplicit( itemFieldName = "evdef" )
private List< EventDefinition > definitions = new LinkedList< EventDefinition >( );
/** Iterate over event definitions */
@Override
public Iterator< EventDefinition > iterator( )
{
return Collections.unmodifiableList( this.definitions ).iterator( );
}
@Override
public void verifyData( )
throws DataImportException
{
super.verifyData( );
if ( this.definitions == null ) {
throw new DataImportException( "No definitions in this file" );
}
for ( EventDefinition def : this.definitions ) {
def.verifyData( );
}
}
}

View file

@ -0,0 +1,52 @@
package com.deepclone.lw.cli.xmlimport.data.evdef;
import com.deepclone.lw.cli.xmlimport.data.ImportableData;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
/**
* Common event field definition
*
* <p>
* This abstract class is used as the base for event field definitions.
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
@SuppressWarnings( "serial" )
public abstract class EventFieldBase
extends ImportableData
{
/** The field's identifier */
@XStreamAsAttribute
@XStreamAlias( "id" )
private String identifier;
/** Whether the field is required or optional */
@XStreamAsAttribute
private Boolean required;
/**
* Obtain the type of the field
*
* @return the field's type.
*/
public abstract EventFieldType getType( );
public String getIdentifier( )
{
return this.identifier;
}
public boolean isRequired( )
{
return this.required == null ? true : this.required;
}
}

View file

@ -0,0 +1,21 @@
package com.deepclone.lw.cli.xmlimport.data.evdef;
/** Types of entities which can be referenced through entity fields */
public enum EventFieldEntityType {
/** Empires */
EMP ,
/** Planets */
PLN ,
/** Fleets */
FLT ,
/** Alliances */
ALL ,
/** Battles */
BAT ,
/** Administrators */
ADM ,
/** Bug reports */
BUG
}

View file

@ -0,0 +1,25 @@
package com.deepclone.lw.cli.xmlimport.data.evdef;
/** Types of event fields as they are stored in the definition file */
public enum EventFieldType {
/** An integer field */
INTEGER ,
/** An double precision real number field */
REAL ,
/** A string field */
TEXT ,
/** A boolean field */
BOOLEAN ,
/** A field that references an internationalised string */
I18N ,
/** A field that references some game entity */
ENTITY
}

View file

@ -0,0 +1,26 @@
package com.deepclone.lw.cli.xmlimport.data.evdef;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* Definition of a field containing an I18N reference
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
@XStreamAlias( "i18n" )
@SuppressWarnings( "serial" )
public class I18NEventField
extends EventFieldBase
{
/** @return {@link EventFieldType#I18N} */
@Override
public EventFieldType getType( )
{
return EventFieldType.I18N;
}
}

View file

@ -0,0 +1,47 @@
package com.deepclone.lw.cli.xmlimport.data.evdef;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
/**
* An integer event field definition
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
@XStreamAlias( "integer" )
@SuppressWarnings( "serial" )
public class IntegerEventField
extends EventFieldBase
{
/** Minimal value allowed for the field */
@XStreamAsAttribute
private Integer min;
/** Maximal value allowed for the field */
@XStreamAsAttribute
private Integer max;
public Integer getMin( )
{
return this.min;
}
public Integer getMax( )
{
return this.max;
}
/** @return {@link EventFieldType#INTEGER} */
@Override
public EventFieldType getType( )
{
return EventFieldType.INTEGER;
}
}

View file

@ -0,0 +1,48 @@
package com.deepclone.lw.cli.xmlimport.data.evdef;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
/**
* A real field definition
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
@XStreamAlias( "real" )
@SuppressWarnings( "serial" )
public class RealEventField
extends EventFieldBase
{
/** Minimal value for the field */
@XStreamAsAttribute
private Double min;
/** Maximal value for the field */
@XStreamAsAttribute
private Double max;
public Double getMin( )
{
return this.min;
}
public Double getMax( )
{
return this.max;
}
/** @return {@link EventFieldType#REAL} */
@Override
public EventFieldType getType( )
{
return EventFieldType.REAL;
}
}

View file

@ -0,0 +1,50 @@
package com.deepclone.lw.cli.xmlimport.data.evdef;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
/**
* A text event field definition
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
@XStreamAlias( "text" )
@SuppressWarnings( "serial" )
public class TextEventField
extends EventFieldBase
{
/** The field's minimum length */
@XStreamAsAttribute
@XStreamAlias( "min-length" )
private Integer minLength;
/** The field's maximum length */
@XStreamAsAttribute
@XStreamAlias( "max-length" )
private Integer maxLength;
public Integer getMinLength( )
{
return this.minLength;
}
public Integer getMaxLength( )
{
return this.maxLength;
}
/** @return {@link EventFieldType#TEXT} */
@Override
public EventFieldType getType( )
{
return EventFieldType.TEXT;
}
}

View file

@ -39,6 +39,8 @@ from the root of the server's distribution:
java -jar legacyworlds-server-main-1.0.0-0.jar \
--run-tool ImportText data/i18n-text.xml
java -jar legacyworlds-server-main-1.0.0-0.jar \
--run-tool ImportEvents data/event-definitions.xml
java -jar legacyworlds-server-main-1.0.0-0.jar \
--run-tool ImportTechGraph data/tech-graph.xml
java -jar legacyworlds-server-main-1.0.0-0.jar \