NonsenseBuilder.java
package net.zer0bandwidth.android.lib.nonsense;
import android.content.Context;
import net.zer0bandwidth.android.lib.R;
import java.util.Random;
/**
* The canonical implementation of {@link NonsenseGenerator}.
*
* <p>The builder will assemble a sentence of the following form:
* <i>adjective</i> <b>subject</b> <i>adverb</i> <b>verb</b> <i>adjective</i>
* <b>object</b> <i>phrase</i>. The terms listed in bold (subject, verb, object)
* will always be rendered, while the terms in italics (adjectives, adverbs,
* additional phrases) will be added randomly based on the builder's
* configuration, which can be tuned by creating a custom instance of the
* {@link NonsenseBuilder.Configuration} inner class.</p>
*
* <p>The class uses the following string resources to get its random words:</p>
*
* <ul>
* <li>{@link R.array#asNonsenseNouns}</li>
* <li>{@link R.array#asNonsenseVerbs}</li>
* <li>{@link R.array#asNonsenseAdjectives}</li>
* <li>{@link R.array#asNonsenseAdverbs}</li>
* <li>{@link R.array#asNonsensePhrases}</li>
* </ul>
*
* <p>Applications using this class may choose to have overrides for any or all
* of these string resources, in order to customize the text that might be
* rendered in the randomized sentences.</p>
*
* @since zer0bandwidth-net/android 0.0.1 (#7)
*/
@SuppressWarnings({ "unused", "WeakerAccess" }) // This is a library.
public class NonsenseBuilder
implements NonsenseGenerator
{
/**
* This class controls aspects of a {@link NonsenseBuilder} related to the
* probability of certain random features.
*
* To set non-default values, construct an instance of the class, and then
* use its {@code set*()} methods to change any or all of the desired
* settings. This may be done inline as follows:
*
* <pre>
* NonsenseBuilder xyzzy = new NonsenseBuilder( ctx,
* (new NonsenseBuilder.Configuration())
* .setSubjectAdjectiveChance(25)
* .setAdverbChance( NonsenseBuilder.Configuration.ALWAYS )
* .setObjectAdjectiveChance(42)
* .setObjectPhraseChance( NonsenseBuilder.Configuration.NEVER )
* );
* </pre>
*
* @since zer0bandwidth-net/android 0.0.1 (#7)
*/
public static class Configuration
{
/**
* When specifying a probability, this constant indicates that a word
* should <i>always</i> be added to the sentence.
*/
public static final int ALWAYS = 100 ;
/**
* When specifying a probability, this constant indicates that a word
* should <i>never</i> be added to the sentence.
*/
public static final int NEVER = 0 ;
/**
* The percentage chance that an adjective will be added to modify the
* subject of the sentence.
*/
public int m_nSubjectAdjectiveChance = 50 ;
/**
* The percentage chance that an adverb will be added to modify the verb
* of the sentence.
*/
public int m_nAdverbChance = 50 ;
/**
* The percentage chance that an adjective will be added to modify the
* object of the sentence.
*/
public int m_nObjectAdjectiveChance = 50 ;
/**
* The percentage chance that a prepositional phrase will be added to
* modify the object of the sentence.
*/
public int m_nObjectPhraseChance = 50 ;
/**
* Sets the probability that an adjective will be added to modify the
* subject of the sentence.
* If the parameter given is less than 0 or greater than 100, then the
* new value will be silently ignored.
* @param n an integer between 0 and 100, inclusive
* @return (fluid)
*/
public Configuration setSubjectAdjectiveChance( int n )
{
if( n < 0 || n > 100 ) return this ; // trivially
m_nSubjectAdjectiveChance = n ;
return this ;
}
/**
* Sets the probability that an adverb will be added to modify the verb
* of the sentence.
* If the parameter given is less than 0 or greater than 100, then the
* new value will be silently ignored.
* @param n an integer between 0 and 100, inclusive
* @return (fluid)
*/
public Configuration setAdverbChance( int n )
{
if( n < 0 || n > 100 ) return this ; // trivially
m_nAdverbChance = n ;
return this ;
}
/**
* Sets the probability that an adjective will be added to modify the
* object of the sentence.
* If the parameter given is less than 0 or greater than 100, then the
* new value will be silently ignored.
* @param n an integer between 0 and 100, inclusive
* @return (fluid)
*/
public Configuration setObjectAdjectiveChance( int n )
{
if( n < 0 || n > 100 ) return this ; // trivially
m_nObjectAdjectiveChance = n ;
return this ;
}
/**
* Sets the probability that a prepositional phrase will be added to
* modify the object of the sentence.
* If the parameter given is less than 0 or greater than 100, then the
* new value will be silently ignored.
* @param n an integer between 0 and 100, inclusive
* @return (fluid)
*/
public Configuration setObjectPhraseChance( int n )
{
if( n < 0 || n > 100 ) return this ;
m_nObjectPhraseChance = n ;
return this ;
}
}
/**
* A canonical instance of a {@link NonsenseBuilder.Configuration}, using
* the default values defined in that inner class.
*/
protected static final Configuration CANONICAL_CONFIGURATION =
new Configuration() ;
/**
* The internal RNG of the builder, used to determine the value selected for
* each item in the sentence, and the presence of certain optional items.
*/
protected static final Random RANDOM = new Random() ;
/**
* A context in which string resources are available.
*/
protected Context m_ctx = null ;
/**
* The configuration settings that will control the random elements of the
* builder. This defaults to the {@link #CANONICAL_CONFIGURATION}.
*/
protected Configuration m_cfg = CANONICAL_CONFIGURATION ;
/**
* (noun) The subject of the sentence.
* This is always set by the builder.
*/
protected String m_sSubject = null ;
/**
* (adjective) A modifier of the subject.
* This is randomly set by the builder.
*/
protected String m_sSubjectAdjective = null ;
/**
* (verb) The action verb in the sentence.
* This is always set by the builder.
*/
protected String m_sVerb = null ;
/**
* (adverb) A modifier of the sentence's verb.
* This is randomly set by the builder.
*/
protected String m_sVerbAdverb = null ;
/**
* (noun) The object of the sentence.
* This is always set by the builder.
*/
protected String m_sObject = null ;
/**
* (adjective) A modifier of the object of the sentence.
* This is randomly set by the builder.
*/
protected String m_sObjectAdjective = null ;
/**
* (prepositional phrase) A phrase further modifying the object of the
* sentence.
* This is randomly set by the builder.
*/
protected String m_sObjectModifier = null ;
/**
* A constructor which sets the resource context.
* @param ctx a context in which string resources are available
*/
public NonsenseBuilder( Context ctx )
{ this.setContext( ctx ) ; }
/**
* A constructor which sets both the resource context and a custom
* configuration to control the random aspects of the builder.
* @param ctx a context in which string resources are available
* @param cfg the configuration parameters of the builder
*/
public NonsenseBuilder( Context ctx, NonsenseBuilder.Configuration cfg )
{ this.setContext( ctx ).setConfiguration( cfg ) ; }
/**
* Sets the configuration parameters of the builder.
* @param cfg specifies the randomization parameters for the builder
* @return (fluid)
*/
public NonsenseBuilder setConfiguration( NonsenseBuilder.Configuration cfg )
{
if( cfg == null ) m_cfg = CANONICAL_CONFIGURATION ;
else m_cfg = cfg ;
return this ;
}
@Override
public NonsenseBuilder setContext( Context ctx )
{ m_ctx = ctx ; return this ; }
/**
* Locks a value for the subject of the sentence.
*
* If {@code null} is specified, then the builder's subject will be
* "cleared", meaning that it will be chosen at random.
*
* @param sSubject a specific subject for the sentence, or {@code null} to
* ensure that the subject is randomized
* @return (fluid)
*/
public NonsenseBuilder setSubject( String sSubject )
{ m_sSubject = sSubject ; return this ; }
/**
* Locks a value for the adjective modifying the subject of the sentence.
*
* If {@code null} is specified, then the builder might or might not add a
* random adjective, as specified by
* {@link Configuration#m_nSubjectAdjectiveChance}.
*
* @param sAdj a specific adjective to modify the subject of the sentence,
* or {@code null} to randomize the presence and value of the
* adjective
* @return (fluid)
*/
public NonsenseBuilder setSubjectAdjective( String sAdj )
{ m_sSubjectAdjective = sAdj ; return this ; }
/**
* Locks a value for the verb of the sentence.
*
* If {@code null} is specified, then the builder's verb will be "cleared",
* meaning that it will be chosen at random.
*
* @param sVerb a specific verb for the sentence, or {@code null} to ensure
* that the verb is randomized
* @return (fluid)
*/
public NonsenseBuilder setVerb( String sVerb )
{ m_sVerb = sVerb ; return this ; }
/**
* Locks a value for the adverb modifying the verb of the sentence.
*
* If {@code null} is specified, then the builder might or might not add a
* random adverb, as specified by {@link Configuration#m_nAdverbChance}.
*
* @param sAdverb a specific adverb to modify the verb of the sentence, or
* {@code null} to randomize the presence and value of the
* adverb
* @return (fluid)
*/
public NonsenseBuilder setAdverb( String sAdverb )
{ m_sVerbAdverb = sAdverb ; return this ; }
/**
* Locks a value for the object of the sentence.
*
* If {@code null} is specified, then the builder's object will be
* "cleared", meaning that it will be chosen at random.
*
* @param sObject a specific object for the sentence, or {@code null} to
* ensure that the object is randomized
* @return (fluid)
*/
public NonsenseBuilder setObject( String sObject )
{ m_sObject = sObject ; return this ; }
/**
* Locks a value for the adjective modifying the object of the sentence.
*
* If {@code null} is specified, then the builder might or might not add a
* random adjective, as specified by
* {@link Configuration#m_nObjectAdjectiveChance}.
*
* @param sAdj a specific adjective to modify the object of the sentence, or
* {@code null} to randomize the presence and value of the
* adjective
* @return (fluid)
*/
public NonsenseBuilder setObjectAdjective( String sAdj )
{ m_sObjectAdjective = sAdj ; return this ; }
/**
* Locks a value for additional text (expected to be a prepositional phrase)
* modifying the object of the sentence.
*
* If {@code null} is specified, then the builder might or might not add a
* random phrase, as specified by
* {@link Configuration#m_nObjectPhraseChance}.
*
* @param sPhrase specific text to modify the object of the sentence, or
* {@code null} to randomize the presence and value of such
* text
* @return (fluid)
*/
public NonsenseBuilder setObjectModifier( String sPhrase )
{ m_sObjectModifier = sPhrase ; return this ; }
@Override
public String getString()
{
StringBuilder sb = new StringBuilder() ;
this.appendSubject( sb )
.appendVerb( sb )
.appendObject( sb )
;
return sb.toString() ;
}
/**
* Appends the subject and its adjective (if any) to the buffer in which the
* sentence is being constructed.
* @param sb the buffer in which the sentence is being constructed
* @return (fluid)
*/
protected NonsenseBuilder appendSubject( StringBuilder sb )
{
final String sSubject = ( m_sSubject == null ?
this.getRandomNonsense( R.array.asNonsenseNouns,
Configuration.ALWAYS ) :
m_sSubject
);
final String sAdj = ( m_sSubjectAdjective == null ?
this.getRandomNonsense( R.array.asNonsenseAdjectives,
m_cfg.m_nSubjectAdjectiveChance ) :
m_sSubjectAdjective
);
if( sAdj == null )
{ // Just capitalize and append the subject as-is and move on.
sb.append( sSubject.substring(0,1).toUpperCase() )
.append( sSubject.substring(1) )
;
}
else if( Utils.startsWithArticle( sSubject ) )
{ // Split the article from the subject string and insert the adjective.
String[] asTokens = sSubject.split( " ", 2 ) ;
switch( asTokens[0] )
{
case "a":
case "an":
sb.append( Utils.whichIndefiniteArticle( sAdj, true ) )
.append( ' ' )
;
break ;
case "the":
sb.append( "The " ) ;
break ;
default:
sb.append( asTokens[0].substring(0,1).toUpperCase() )
.append( asTokens[0].substring(1) )
.append( ' ' )
;
}
sb.append( sAdj )
.append( ' ' )
.append( asTokens[1] )
;
}
else
{ // Capitalize the adjective and stick it before the noun.
sb.append( sAdj.substring( 0, 1 ).toUpperCase() )
.append( sAdj.substring(1) )
.append( ' ' )
.append( sSubject )
;
}
sb.append( ' ' ) ;
return this ;
}
/**
* Appends the verb and its adverb (if any) to the buffer in which the
* sentence is being constructed.
* @param sb the buffer in which the sentence is being constructed
* @return (fluid)
*/
protected NonsenseBuilder appendVerb( StringBuilder sb )
{
final String sVerb = ( m_sVerb == null ?
this.getRandomNonsense( R.array.asNonsenseVerbs,
Configuration.ALWAYS ) :
m_sVerb
);
final String sAdverb = ( m_sVerbAdverb == null ?
this.getRandomNonsense( R.array.asNonsenseAdverbs,
m_cfg.m_nAdverbChance ) :
m_sVerbAdverb
);
if( sAdverb == null )
sb.append( sVerb ) ;
else
sb.append( sAdverb ).append( ' ' ).append( sVerb ) ;
sb.append( ' ' ) ;
return this ;
}
/**
* Appends the object and its adjective and modifier phrase (if any) to the
* buffer in which the sentence is being constructed.
* @param sb the buffer in which the sentence is being constructed
* @return (fluid)
*/
protected NonsenseBuilder appendObject( StringBuilder sb )
{
final String sObject = ( m_sObject == null ?
this.getRandomNonsense( R.array.asNonsenseNouns,
Configuration.ALWAYS ) :
m_sObject
);
final String sAdj = ( m_sObjectAdjective == null ?
this.getRandomNonsense( R.array.asNonsenseAdjectives,
m_cfg.m_nObjectAdjectiveChance ) :
m_sObjectAdjective
);
final String sPhrase = ( m_sObjectModifier == null ?
this.getRandomNonsense( R.array.asNonsensePhrases,
m_cfg.m_nObjectPhraseChance ) :
m_sObjectModifier
);
if( sAdj == null )
sb.append( sObject ) ;
else if( Utils.startsWithArticle( sObject ) )
{
String[] asTokens = sObject.split( " ", 2 ) ;
switch( asTokens[0] )
{
case "a":
case "an":
sb.append( Utils.whichIndefiniteArticle( sAdj, false ) )
.append( ' ' )
;
break ;
default:
sb.append( asTokens[0] ).append( ' ' ) ;
}
sb.append( sAdj )
.append( ' ' )
.append( asTokens[1] )
;
}
else
sb.append( sAdj ).append( ' ' ).append( sObject ) ;
if( sPhrase != null )
sb.append( ' ' ).append( sPhrase ) ;
sb.append( '.' ) ;
return this ;
}
/**
* Randomly selects a string resource from the selected string array.
* @param resStrings the resource ID of a string array
* @param nChance chance that we should return anything at all, expressed as
* a percentage (expected range [0,100])
* @return a random string from that array of strings
*/
public String getRandomNonsense( int resStrings, int nChance )
{
if( nChance < Configuration.ALWAYS && RANDOM.nextInt(100) >= nChance )
return null ;
final String[] asStrings =
m_ctx.getResources().getStringArray( resStrings ) ;
final int nIndex = RANDOM.nextInt( asStrings.length ) ;
return asStrings[nIndex] ;
}
}