SQLiteHouseSignalAPI.java
package net.zer0bandwidth.android.lib.database.sqlitehouse.content;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import net.zer0bandwidth.android.lib.content.IntentUtils;
import net.zer0bandwidth.android.lib.database.sqlitehouse.SQLightable;
import net.zer0bandwidth.android.lib.database.sqlitehouse.content.exceptions.SQLiteContentException;
import net.zer0bandwidth.android.lib.database.sqlitehouse.exceptions.IntrospectionException;
import net.zer0bandwidth.android.lib.database.sqlitehouse.exceptions.SchematicException;
import net.zer0bandwidth.android.lib.util.CollectionsZ;
/**
* Defines the contract of signals between implementations of
* {@link SQLiteHouseKeeper} and {@link SQLiteHouseRelay}.
* @since zer0bandwidth-net/android 0.1.7 (#50)
*/
public abstract class SQLiteHouseSignalAPI
{
public static final String LOG_TAG =
SQLiteHouseSignalAPI.class.getSimpleName() ;
/** Name suffix for the extra that contains the schematic class name. */
public static final String EXTRA_SCHEMA_CLASS_NAME = "CLASS" ;
/** Name suffix for the extra that contains the schematic class data. */
public static final String EXTRA_SCHEMA_CLASS_DATA = "DATA" ;
/** Name suffix for the extra that contains the inserted row ID. */
public static final String EXTRA_INSERT_ROW_ID = "ROW_ID" ;
/**
* Name suffix for the extra that contains the number of rows selected,
* updated, or deleted.
*/
public static final String EXTRA_RESULT_ROW_COUNT = "ROW_COUNT" ;
/**
* Name suffix for the extra that contains the bundle that defines a set of
* selection parameters.
*/
public static final String EXTRA_SELECTION_QUERY_SPEC = "SELECTION_SPEC" ;
/** The default format string for constructing intent extra tags. */
public static final String DEFAULT_EXTRA_TAG_FORMAT = "%s.extra.%s" ;
/** Action suffix for inserting an object. */
public static final String KEEPER_INSERT = "INSERT" ;
/** Action suffix for selecting objects. */
public static final String KEEPER_SELECT = "SELECT" ;
/** Action suffix for updating objects. */
public static final String KEEPER_UPDATE = "UPDATE" ;
/** Action suffix for deleting objects. */
public static final String KEEPER_DELETE = "DELETE" ;
/** The set of actions which are supported by the keeper implementation. */
public static final String[] KEEPER_ACTIONS =
{ KEEPER_INSERT, KEEPER_SELECT, KEEPER_UPDATE, KEEPER_DELETE } ;
/** The default format string for constructing keeper actions. */
public static final String DEFAULT_KEEPER_ACTION_FORMAT =
"%s.keeper.action.%s" ;
/** Action suffix for notifying a relay that a record was inserted. */
public static final String RELAY_NOTIFY_INSERT = "NOTIFY_INSERT" ;
/** Action suffix for notifying a relay that an insertion failed. */
public static final String RELAY_NOTIFY_INSERT_FAILED =
"NOTIFY_INSERT_FAILED" ;
/** Action suffix when sending a result set to a relay. */
public static final String RELAY_RECEIVE_SELECTION = "RECEIVE_SELECTION" ;
/** Action suffix when notifying a relay that a selection failed. */
public static final String RELAY_NOTIFY_SELECT_FAILED =
"NOTIFY_SELECT_FAILED" ;
/** Action suffix for notifying a relay that a record was updated. */
public static final String RELAY_NOTIFY_UPDATE = "NOTIFY_UPDATE" ;
/** Action suffix for notifying a relay that an update failed. */
public static final String RELAY_NOTIFY_UPDATE_FAILED =
"NOTIFY_UPDATE_FAILED" ;
/** Action suffix for notifying a relay that a record was deleted. */
public static final String RELAY_NOTIFY_DELETE = "NOTIFY_DELETE" ;
/** Action suffix for notifying a relay that a deletion failed. */
public static final String RELAY_NOTIFY_DELETE_FAILED =
"NOTIFY_DELETE_FAILED" ;
/** The set of actions which are supported by the relay implementation. */
public static final String[] RELAY_ACTIONS =
{
RELAY_NOTIFY_INSERT, RELAY_NOTIFY_INSERT_FAILED,
RELAY_RECEIVE_SELECTION, RELAY_NOTIFY_SELECT_FAILED,
RELAY_NOTIFY_UPDATE, RELAY_NOTIFY_UPDATE_FAILED,
RELAY_NOTIFY_DELETE, RELAY_NOTIFY_DELETE_FAILED
};
/** The default format string for constructing relay actions. */
public static final String DEFAULT_RELAY_ACTION_FORMAT =
"%s.relay.action.%s" ;
/**
* Cache of reflections that have been pushed through the relay.
* Populated by
*/
protected SQLightable.ReflectionMap m_mapReflections =
new SQLightable.ReflectionMap() ;
/**
* A set of custom actions expected to be supported by a
* {@link SQLiteHouseKeeper} implementation.
*/
protected String[] m_asCustomKeeperActions = null ;
/**
* The format string to be used when creating keeper action tags.
* Defaults to {@link #DEFAULT_KEEPER_ACTION_FORMAT}.
*/
protected String m_sKeeperActionFormat = DEFAULT_KEEPER_ACTION_FORMAT ;
/**
* A set of custom actions expected to be supported by a
* {@link SQLiteHouseRelay} implementation.
*/
protected String[] m_asCustomRelayActions = null ;
/**
* The format string to be used when creating relay action tags.
* Defaults to {@link #DEFAULT_RELAY_ACTION_FORMAT}.
*/
protected String m_sRelayActionFormat = DEFAULT_RELAY_ACTION_FORMAT ;
/**
* The format string to be used when creating intent extra tags. This is a
* consistent format across both sides of the API.
*/
protected String m_sExtraTagFormat = DEFAULT_EXTRA_TAG_FORMAT ;
/**
* Reveals the "domain" under which the keeper and relay will operate.
* This string forms the root segments of the {@code Intent} action tokens.
* @return the domain linking a keeper and relay
*/
protected abstract String getIntentDomain() ;
/**
* Accesses the set of keeper actions which will be registered.
*
* By default, this is the value of {@link #KEEPER_ACTIONS}. If the
* {@link SQLiteHouseKeeper} implementation also supports custom actions,
* then they should be registered with {@link #setKeeperActions}, which will
* <i>append</i> the custom list to the default.
*
* @return the list of actions supported by the keeper implementation
*/
public final String[] getKeeperActions()
{
if( m_asCustomKeeperActions == null ) return KEEPER_ACTIONS ;
else return CollectionsZ.of( String.class )
.arrayConcat( KEEPER_ACTIONS, m_asCustomKeeperActions ) ;
}
/**
* Adds a list of custom actions which are expected from a
* {@link SQLiteHouseKeeper} implementation.
* @param asCustomActions the list of custom actions
* @return (fluid)
*/
public final SQLiteHouseSignalAPI setKeeperActions( String[] asCustomActions )
{ m_asCustomKeeperActions = asCustomActions ; return this ; }
/**
* Accesses the format string used to construct keeper actions.
* @return the format for keeper actions
*/
public final String getKeeperActionFormat()
{ return m_sKeeperActionFormat ; }
/**
* Sets the format string used to construct actions for {@link Intent}s that
* are dispatched to {@link SQLiteHouseKeeper}s. This format string
* <i>must</i> have two string variables.
*
* Defaults to {@link #DEFAULT_KEEPER_ACTION_FORMAT} if a null or empty
* value is supplied.
*
* @param sFormat the format string
* @return (fluid)
*/
public final SQLiteHouseSignalAPI setKeeperActionFormat( String sFormat )
{
if( sFormat == null || sFormat.isEmpty() )
m_sKeeperActionFormat = DEFAULT_KEEPER_ACTION_FORMAT ;
else
m_sKeeperActionFormat = sFormat ;
return this ;
}
/**
* Creates a fully-formed action name for the keeper.
* @param sToken the action token (suffix) to be appended
* @return an action token to be broadcast to a keeper
*/
public final String getFormattedKeeperAction( String sToken )
{
return String.format( this.getKeeperActionFormat(),
this.getIntentDomain(), sToken ) ;
}
/**
* Extracts the action token from a fully-formatted keeper action string.
* The default implementation takes the substring after the last period.
* @param sAction the fully-formatted keeper action
* @return the action token
*/
public String getTokenFromKeeperAction( String sAction )
{ return sAction.substring( sAction.lastIndexOf(".") + 1 ) ; }
/**
* Constructs the {@link IntentFilter} for the {@link SQLiteHouseKeeper}
* implementation.
* @return an intent filter for the keeper
*/
public final IntentFilter getKeeperIntentFilter()
{
return IntentUtils.getActionListIntentFilter(
this.getKeeperActionFormat(),
this.getIntentDomain(),
this.getKeeperActions()
);
}
/**
* Accesses the set of relay actions which will be registered.
*
* By default, this is the value of {@link #RELAY_ACTIONS}. If the
* {@link SQLiteHouseRelay} implementation also supports custom actions,
* then they should be registered with {@link #setRelayActions}, which will
* <i>append</i> the custom list to the default.
*
* @return the list of actions supported by the relay implementation
*/
public final String[] getRelayActions()
{
if( m_asCustomRelayActions == null ) return RELAY_ACTIONS ;
else return CollectionsZ.of( String.class )
.arrayConcat( RELAY_ACTIONS, m_asCustomRelayActions ) ;
}
/**
* Adds a list of custom actions which are expected from a
* {@link SQLiteHouseRelay} implementation.
* @param asCustomActions the list of custom actions
* @return (fluid)
*/
public final SQLiteHouseSignalAPI setRelayActions( String[] asCustomActions )
{ m_asCustomRelayActions = asCustomActions ; return this ; }
/**
* Accesses the format string used to construct relay actions.
* @return the format for relay actions
*/
public final String getRelayActionFormat()
{ return m_sRelayActionFormat ; }
/**
* Sets the format string used to construct actions for {@link Intent}s that
* are dispatched to {@link SQLiteHouseRelay}s. This format string
* <i>must</i> have two string variables.
*
* Defaults to {@link #DEFAULT_RELAY_ACTION_FORMAT} if a null or empty
* value is supplied.
*
* @param sFormat the format string
* @return (fluid)
*/
public final SQLiteHouseSignalAPI setRelayActionFormat( String sFormat )
{
if( sFormat == null || sFormat.isEmpty() )
m_sRelayActionFormat = DEFAULT_RELAY_ACTION_FORMAT ;
else
m_sRelayActionFormat = sFormat ;
return this ;
}
/**
* Creates a fully-formed action name for the relay.
* @param sToken the action token (suffix) to be appended
* @return an action token to be broadcast to a relay
*/
public final String getFormattedRelayAction( String sToken )
{
return String.format( this.getRelayActionFormat(),
this.getIntentDomain(), sToken ) ;
}
/**
* Extracts the action token from a fully-formatted relay action string.
* The default implementation takes the substring after the last period.
* @param sAction the fully-formatted relay action
* @return the action token
*/
public String getTokenFromRelayAction( String sAction )
{ return sAction.substring( sAction.lastIndexOf(".") + 1 ) ; }
/**
* Constructs the {@link IntentFilter} for the {@link SQLiteHouseRelay}
* implementation.
* @return an intent filter for the relay
*/
public final IntentFilter getRelayIntentFilter()
{
return IntentUtils.getActionListIntentFilter(
this.getRelayActionFormat(),
this.getIntentDomain(),
this.getRelayActions() ) ;
}
/**
* Accesses the format string used to construct name tags for {@link Intent}
* extras.
* @return the format for intent extra tags
*/
public final String getExtraTagFormat()
{ return m_sExtraTagFormat ; }
/**
* Sets the format string used to construct tags for {@link Intent} extras
* that are exchanged between the keeper and the relay.
*
* Defaults to {@link #DEFAULT_EXTRA_TAG_FORMAT} if a null or empty value is
* supplied.
*
* @param sFormat the format string
* @return (fluid)
*/
public final SQLiteHouseSignalAPI setExtraTagFormat( String sFormat )
{
if( sFormat == null || sFormat.isEmpty() )
m_sExtraTagFormat = DEFAULT_EXTRA_TAG_FORMAT ;
else
m_sExtraTagFormat = sFormat ;
return this ;
}
/**
* Constructs the full name tag for an {@link Intent} extra to be exchanged
* between a keeper and a relay.
* @param sToken the significant part of the extra token which makes it
* unique among the extras in this API
* @return the fully-formed extra tag
*/
public final String getFormattedExtraTag( String sToken )
{
return String.format( this.getExtraTagFormat(),
this.getIntentDomain(), sToken ) ;
}
/**
* Shorthand to retrieve the schematic class name, which is used by almost
* every keeper and relay function.
* @param sig the intent
* @return the schematic class name as given in the intent
*/
public String getExtraSchemaClassName( Intent sig )
{
return sig.getStringExtra( this.getFormattedExtraTag(
EXTRA_SCHEMA_CLASS_NAME ) ) ;
}
/**
* Discovers and returns the schematic class definition that is named in an
* intent sent from a relay to a keeper.
* @param sig the intent
* @param <SC> the schematic class
* @return the schematic class definition
* @throws SQLiteContentException if the intent is malformed, in that the
* class name extra cannot be found or is empty
* @throws IntrospectionException if something goes wrong while discovering
* the class definition
* @throws SQLiteContentException if the extra that's supposed to have the
* class name in it can't be found
*/
public <SC extends SQLightable> Class<SC> getClassFromExtra( Intent sig )
throws IntrospectionException, SQLiteContentException
{
String sClass = this.getExtraSchemaClassName(sig) ;
if( sClass == null || sClass.isEmpty() )
throw SQLiteContentException.noClassSpecified(null) ;
try
{
Class<?> cls = Class.forName( sClass ) ;
if( SQLightable.class.isAssignableFrom(cls) )
{
// noinspection unchecked - caught explicitly below
return ((Class<SC>)(cls)) ;
}
else
{
throw IntrospectionException
.illegalClassSpecification( cls, null ) ;
}
}
catch( ClassNotFoundException | ClassCastException x )
{ throw IntrospectionException.illegalClassSpecification( sClass, x ); }
}
/**
* Discovers and extracts an instance of a schematic class from an extra
* provided in an intent from a relay to a keeper.
* @param sig the intent
* @param cls the schematic class
* @param <SC> the schematic class
* @return an instance of the schematic class, with data from the intent
* @throws SQLiteContentException if the intent is malformed, in that the
* data extra cannot be found or is empty
* @throws IntrospectionException if an instance of the schematic class
* can't be constructed
* @throws SchematicException if a problem occurs while processing the
* schematic class
*/
public <SC extends SQLightable> SC getDataFromBundle( Intent sig, Class<SC> cls )
throws SQLiteContentException, IntrospectionException, SchematicException
{
if( cls == null )
throw SQLiteContentException.noClassSpecified(null) ;
String sExtra = this.getFormattedExtraTag( EXTRA_SCHEMA_CLASS_DATA ) ;
if( ! sig.hasExtra( sExtra ) )
throw SQLiteContentException.expectedExtraNotFound( sExtra, null ) ;
Bundle bndl = sig.getBundleExtra( sExtra ) ;
if( bndl == null )
throw SQLiteContentException.expectedExtraNotFound( sExtra, null ) ;
SQLightable.Reflection<SC> tbl = this.reflect(cls) ;
return tbl.fromBundle(bndl) ;
}
/**
* Uses a cached {@link SQLightable.Reflection} if available; otherwise,
* caches a new one, then returns it.
* @param cls the schematic class
* @param <SC> the schematic class
* @return the class's reflection
*/
protected <SC extends SQLightable> SQLightable.Reflection<SC> reflect( Class<SC> cls )
{
if( ! m_mapReflections.containsKey(cls) )
m_mapReflections.put( cls ) ;
return m_mapReflections.get(cls) ;
}
/**
* Uses a cached {@link SQLightable.Reflection} if available; otherwise,
* caches a new one, then returns it.
* @param o an instance of the schematic class
* @param <SC> the schematic class
* @return the class's reflection
*/
protected <SC extends SQLightable> SQLightable.Reflection<SC> reflect( SC o )
{
// noinspection unchecked - guaranteed by signature
return this.reflect( ((Class<SC>)( o.getClass() )) ) ;
}
}