All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rathravane.till.persistence.Builder Maven / Gradle / Ivy

There is a newer version: 2.1.1
Show newest version
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 findClass ( String className ) throws ClassNotFoundException { Class 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 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 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 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 fContextClass; private LinkedList fSearchPath; private static final Logger log = LoggerFactory.getLogger ( Builder.class ); }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy