com.rathravane.till.persistence.Builder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of silt Show documentation
Show all versions of silt Show documentation
A small collection of classes used in various Rathravane systems.
package com.rathravane.till.persistence;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.rathravane.till.nv.rrNvReadable;
public class Builder
{
public static class BuildFailure extends Exception
{
public BuildFailure ( Throwable t ) { super ( t ); }
public BuildFailure ( String msg ) { super ( msg ); }
private static final long serialVersionUID = 1L;
}
/**
* Construct a build for a given base class.
* @param base
*/
private Builder ( Class base )
{
fBase = base;
fClassName = null;
fClassNameInData = false;
fJson = null;
fSettings = null;
fStringData = null;
fContext = null;
fContextClass = null;
}
/**
* construct a builder
* @param base
* @return
*/
public static Builder withBaseClass ( Class base )
{
return new Builder ( base );
}
/**
* Set the class to use
* @param classname
* @return
*/
public Builder usingClassName ( String classname )
{
fClassName = classname;
fClassNameInData = false;
return this;
}
/**
* Pull the classname from the data object
* @return
*/
public Builder withClassNameInData ()
{
fClassName = null;
fClassNameInData = true;
return this;
}
/**
* Construct the object using the given JSON data
* @param o
* @return
*/
public Builder usingData ( JSONObject o )
{
fJson = o;
fSettings = null;
fStringData = null;
return this;
}
/**
* Construct the object using the given JSON data
* @param o
* @return the builder
*/
public Builder usingData ( rrNvReadable o )
{
fJson = null;
fSettings = o;
fStringData = null;
return this;
}
/**
* Build the object from the given string data
* @param s
* @return the builder
*/
public Builder fromString ( String s )
{
fJson = null;
fSettings = null;
fStringData = s;
return this;
}
/**
* If provided, the context object is passed to the constructor of the target
* class as its first argument OR passed to a fromJson/fromSettings/fromString class
* as the last argument, provided one of these can be found.
*
* @param context
* @return the builder
*/
public Builder providingContext ( Object context )
{
fContext = context;
fContextClass = context.getClass ();
return this;
}
/**
* If provided, the target class name will be sought first as a simple
* string, then appended to the package name provided here. This call
* can be made multiple times to establish a list.
* @param packageName
* @return the builder
*/
public Builder searchingPath ( String packageName )
{
if ( fSearchPath == null )
{
fSearchPath = new LinkedList ();
}
fSearchPath.add ( packageName );
return this;
}
/**
* Build an instance from a classname and the given data. The instance is
* initialized in one these ways, in the following order:
*
* calling a constructor with the settings object as a single parameter
* calling a static method 'fromSettings'/'fromJson' that takes a settings/JSONObject instance and returns and instance
* calling a default constructor, then calling 'fromSettings' with the settings instance
*
* Note that the class is found prior to attempting initialization. If the search path
* finds packageA.Foo ahead of packageB.Foo, only packageA.Foo is considered, even if
* packageA.Foo can't be initialized but packageB.Foo might have been.
*
* @return an instance
* @throws BuildFailure
*/
public T build () throws BuildFailure
{
try
{
// which class?
String cn = null;
if ( fClassNameInData && fJson != null )
{
cn = fJson.getString ( "class" );
}
else if ( fClassNameInData && fSettings != null )
{
cn = fSettings.getString ( "class", null );
}
else if ( !fClassNameInData && fClassName != null )
{
cn = fClassName;
}
if ( cn == null )
{
throw new BuildFailure ( "No class name provided." );
}
if ( fJson != null )
{
return build ( cn, JSONObject.class, "fromJson", fJson, fContextClass, fContext );
}
else if ( fSettings != null )
{
return build ( cn, rrNvReadable.class, "fromSettings", fSettings, fContextClass, fContext );
}
else if ( fStringData != null )
{
return build ( cn, String.class, "fromString", fStringData, fContextClass, fContext );
}
else
{
return findClass ( cn ).newInstance ();
}
}
catch ( ClassNotFoundException e )
{
throw new BuildFailure ( e );
}
catch ( InstantiationException e )
{
throw new BuildFailure ( e );
}
catch ( IllegalAccessException e )
{
throw new BuildFailure ( e );
}
}
private Class extends T> findClass ( String className ) throws ClassNotFoundException
{
Class extends T> c = null;
try
{
log.trace ( "Builder looking for " + className + " as " + fBase.getName () );
c = Class.forName ( className ).asSubclass ( fBase );
}
catch ( ClassNotFoundException x1 )
{
if ( fSearchPath == null )
{
log.trace ( "Didn't find " + className + ". No additional search path." );
throw x1;
}
for ( String path : fSearchPath )
{
final StringBuilder sb = new StringBuilder ();
sb.append ( path );
if ( !path.endsWith(".") ) sb.append ( '.' );
sb.append ( className );
final String newClassName = sb.toString ();
log.trace ( "Builder looking for " + newClassName + " as " + fBase.getName () );
try
{
c = Class.forName ( newClassName ).asSubclass ( fBase );
break;
}
catch ( ClassNotFoundException x2 )
{
// ignore
log.trace ( "Didn't find " + newClassName + "." );
}
}
// if still not found, bail out
if ( c == null )
{
log.trace ( "Didn't find " + className + ", even after using search path." );
throw x1;
}
}
return c;
}
@SuppressWarnings("unchecked")
private T build ( String className, Class dataClass, String initerName, D data, Class> contextClass, Object context ) throws BuildFailure
{
try
{
// find the target class
Class extends T> c = findClass ( className );
// try a static init method that'll take the context class and the data class
for ( Method m : c.getMethods () )
{
// if this method is named properly and returns an instance we can use...
if ( m.getName ().equals ( initerName ) && fBase.isAssignableFrom ( m.getReturnType () ) )
{
final boolean isStatic = Modifier.isStatic ( m.getModifiers () );
// if it just has the data class as arg, try that
final Class>[] params = m.getParameterTypes ();
if ( params.length == 1 && params[0].isAssignableFrom ( dataClass ) )
{
if ( isStatic )
{
return (T) m.invoke ( null, data );
}
else
{
T t = c.newInstance ();
m.invoke ( t, data );
return t;
}
}
else if ( params.length == 2 && params[0].isAssignableFrom ( dataClass ) && contextClass != null && params[1].isAssignableFrom ( contextClass ) )
{
if ( isStatic )
{
return (T) m.invoke ( null, data, context );
}
else
{
T t = c.newInstance ();
m.invoke ( t, data, context );
return t;
}
}
}
}
// next try a constructor with the data and context...
if ( context != null )
{
Class> contextClassToTry = contextClass;
while ( contextClassToTry != null )
{
try
{
final Constructor extends T> cc = c.getConstructor ( contextClassToTry, dataClass );
return cc.newInstance ( context, data );
}
catch ( NoSuchMethodException e )
{
// move on
contextClassToTry = contextClassToTry.getSuperclass ();
}
}
}
// next try a constructor with just the data
try
{
final Constructor extends T> cc = c.getConstructor ( dataClass );
return cc.newInstance ( data );
}
catch ( NoSuchMethodException e )
{
// move on
}
// out of options
throw new BuildFailure ( "Could not find a suitable constructor/creator for class [" + className + "]" );
}
catch ( IllegalArgumentException e )
{
throw new BuildFailure ( e );
}
catch ( InvocationTargetException e )
{
final Throwable target = e.getTargetException ();
if ( target instanceof BuildFailure )
{
final BuildFailure f = (BuildFailure) target;
throw f;
}
if ( target == null )
{
throw new BuildFailure ( e );
}
throw new BuildFailure ( target );
}
catch ( ClassNotFoundException e )
{
throw new BuildFailure ( e );
}
catch ( InstantiationException e )
{
throw new BuildFailure ( e );
}
catch ( IllegalAccessException e )
{
throw new BuildFailure ( e );
}
catch ( SecurityException e )
{
throw new BuildFailure ( e );
}
}
private final Class fBase;
private String fClassName;
private boolean fClassNameInData;
private JSONObject fJson;
private rrNvReadable fSettings;
private String fStringData;
private Object fContext;
private Class extends Object> fContextClass;
private LinkedList fSearchPath;
private static final Logger log = LoggerFactory.getLogger ( Builder.class );
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy