NonsenseBuilder.java

  1. package net.zer0bandwidth.android.lib.nonsense;

  2. import android.content.Context;

  3. import net.zer0bandwidth.android.lib.R;

  4. import java.util.Random;

  5. /**
  6.  * The canonical implementation of {@link NonsenseGenerator}.
  7.  *
  8.  * <p>The builder will assemble a sentence of the following form:
  9.  * <i>adjective</i> <b>subject</b> <i>adverb</i> <b>verb</b> <i>adjective</i>
  10.  * <b>object</b> <i>phrase</i>. The terms listed in bold (subject, verb, object)
  11.  * will always be rendered, while the terms in italics (adjectives, adverbs,
  12.  * additional phrases) will be added randomly based on the builder's
  13.  * configuration, which can be tuned by creating a custom instance of the
  14.  * {@link NonsenseBuilder.Configuration} inner class.</p>
  15.  *
  16.  * <p>The class uses the following string resources to get its random words:</p>
  17.  *
  18.  * <ul>
  19.  *     <li>{@link R.array#asNonsenseNouns}</li>
  20.  *     <li>{@link R.array#asNonsenseVerbs}</li>
  21.  *     <li>{@link R.array#asNonsenseAdjectives}</li>
  22.  *     <li>{@link R.array#asNonsenseAdverbs}</li>
  23.  *     <li>{@link R.array#asNonsensePhrases}</li>
  24.  * </ul>
  25.  *
  26.  * <p>Applications using this class may choose to have overrides for any or all
  27.  * of these string resources, in order to customize the text that might be
  28.  * rendered in the randomized sentences.</p>
  29.  *
  30.  * @since zer0bandwidth-net/android 0.0.1 (#7)
  31.  */
  32. @SuppressWarnings({ "unused", "WeakerAccess" })            // This is a library.
  33. public class NonsenseBuilder
  34. implements NonsenseGenerator
  35. {
  36. /**
  37.  * This class controls aspects of a {@link NonsenseBuilder} related to the
  38.  * probability of certain random features.
  39.  *
  40.  * To set non-default values, construct an instance of the class, and then
  41.  * use its {@code set*()} methods to change any or all of the desired
  42.  * settings. This may be done inline as follows:
  43.  *
  44.  * <pre>
  45.  *     NonsenseBuilder xyzzy = new NonsenseBuilder( ctx,
  46.  *             (new NonsenseBuilder.Configuration())
  47.  *                 .setSubjectAdjectiveChance(25)
  48.  *                 .setAdverbChance( NonsenseBuilder.Configuration.ALWAYS )
  49.  *                 .setObjectAdjectiveChance(42)
  50.  *                 .setObjectPhraseChance( NonsenseBuilder.Configuration.NEVER )
  51.  *         );
  52.  * </pre>
  53.  *
  54.  * @since zer0bandwidth-net/android 0.0.1 (#7)
  55.  */
  56. public static class Configuration
  57. {
  58. /**
  59.  * When specifying a probability, this constant indicates that a word
  60.  * should <i>always</i> be added to the sentence.
  61.  */
  62. public static final int ALWAYS = 100 ;

  63. /**
  64.  * When specifying a probability, this constant indicates that a word
  65.  * should <i>never</i> be added to the sentence.
  66.  */
  67. public static final int NEVER = 0 ;

  68. /**
  69.  * The percentage chance that an adjective will be added to modify the
  70.  * subject of the sentence.
  71.  */
  72. public int m_nSubjectAdjectiveChance = 50 ;
  73. /**
  74.  * The percentage chance that an adverb will be added to modify the verb
  75.  * of the sentence.
  76.  */
  77. public int m_nAdverbChance = 50 ;
  78. /**
  79.  * The percentage chance that an adjective will be added to modify the
  80.  * object of the sentence.
  81.  */
  82. public int m_nObjectAdjectiveChance = 50 ;
  83. /**
  84.  * The percentage chance that a prepositional phrase will be added to
  85.  * modify the object of the sentence.
  86.  */
  87. public int m_nObjectPhraseChance = 50 ;

  88. /**
  89.  * Sets the probability that an adjective will be added to modify the
  90.  * subject of the sentence.
  91.  * If the parameter given is less than 0 or greater than 100, then the
  92.  * new value will be silently ignored.
  93.  * @param n an integer between 0 and 100, inclusive
  94.  * @return (fluid)
  95.  */
  96. public Configuration setSubjectAdjectiveChance( int n )
  97. {
  98. if( n < 0 || n > 100 ) return this ; // trivially
  99. m_nSubjectAdjectiveChance = n ;
  100. return this ;
  101. }

  102. /**
  103.  * Sets the probability that an adverb will be added to modify the verb
  104.  * of the sentence.
  105.  * If the parameter given is less than 0 or greater than 100, then the
  106.  * new value will be silently ignored.
  107.  * @param n an integer between 0 and 100, inclusive
  108.  * @return (fluid)
  109.  */
  110. public Configuration setAdverbChance( int n )
  111. {
  112. if( n < 0 || n > 100 ) return this ; // trivially
  113. m_nAdverbChance = n ;
  114. return this ;
  115. }

  116. /**
  117.  * Sets the probability that an adjective will be added to modify the
  118.  * object of the sentence.
  119.  * If the parameter given is less than 0 or greater than 100, then the
  120.  * new value will be silently ignored.
  121.  * @param n an integer between 0 and 100, inclusive
  122.  * @return (fluid)
  123.  */
  124. public Configuration setObjectAdjectiveChance( int n )
  125. {
  126. if( n < 0 || n > 100 ) return this ; // trivially
  127. m_nObjectAdjectiveChance = n ;
  128. return this ;
  129. }

  130. /**
  131.  * Sets the probability that a prepositional phrase will be added to
  132.  * modify the object of the sentence.
  133.  * If the parameter given is less than 0 or greater than 100, then the
  134.  * new value will be silently ignored.
  135.  * @param n an integer between 0 and 100, inclusive
  136.  * @return (fluid)
  137.  */
  138. public Configuration setObjectPhraseChance( int n )
  139. {
  140. if( n < 0 || n > 100 ) return this ;
  141. m_nObjectPhraseChance = n ;
  142. return this ;
  143. }
  144. }

  145. /**
  146.  * A canonical instance of a {@link NonsenseBuilder.Configuration}, using
  147.  * the default values defined in that inner class.
  148.  */
  149. protected static final Configuration CANONICAL_CONFIGURATION =
  150. new Configuration() ;

  151. /**
  152.  * The internal RNG of the builder, used to determine the value selected for
  153.  * each item in the sentence, and the presence of certain optional items.
  154.  */
  155. protected static final Random RANDOM = new Random() ;

  156. /**
  157.  * A context in which string resources are available.
  158.  */
  159. protected Context m_ctx = null ;

  160. /**
  161.  * The configuration settings that will control the random elements of the
  162.  * builder. This defaults to the {@link #CANONICAL_CONFIGURATION}.
  163.  */
  164. protected Configuration m_cfg = CANONICAL_CONFIGURATION ;

  165. /**
  166.  * (noun) The subject of the sentence.
  167.  * This is always set by the builder.
  168.  */
  169. protected String m_sSubject = null ;
  170. /**
  171.  * (adjective) A modifier of the subject.
  172.  * This is randomly set by the builder.
  173.  */
  174. protected String m_sSubjectAdjective = null ;
  175. /**
  176.  * (verb) The action verb in the sentence.
  177.  * This is always set by the builder.
  178.  */
  179. protected String m_sVerb = null ;
  180. /**
  181.  * (adverb) A modifier of the sentence's verb.
  182.  * This is randomly set by the builder.
  183.  */
  184. protected String m_sVerbAdverb = null ;
  185. /**
  186.  * (noun) The object of the sentence.
  187.  * This is always set by the builder.
  188.  */
  189. protected String m_sObject = null ;
  190. /**
  191.  * (adjective) A modifier of the object of the sentence.
  192.  * This is randomly set by the builder.
  193.  */
  194. protected String m_sObjectAdjective = null ;
  195. /**
  196.  * (prepositional phrase) A phrase further modifying the object of the
  197.  * sentence.
  198.  * This is randomly set by the builder.
  199.  */
  200. protected String m_sObjectModifier = null ;

  201. /**
  202.  * A constructor which sets the resource context.
  203.  * @param ctx a context in which string resources are available
  204.  */
  205. public NonsenseBuilder( Context ctx )
  206. { this.setContext( ctx ) ; }

  207. /**
  208.  * A constructor which sets both the resource context and a custom
  209.  * configuration to control the random aspects of the builder.
  210.  * @param ctx a context in which string resources are available
  211.  * @param cfg the configuration parameters of the builder
  212.  */
  213. public NonsenseBuilder( Context ctx, NonsenseBuilder.Configuration cfg )
  214. { this.setContext( ctx ).setConfiguration( cfg ) ; }

  215. /**
  216.  * Sets the configuration parameters of the builder.
  217.  * @param cfg specifies the randomization parameters for the builder
  218.  * @return (fluid)
  219.  */
  220. public NonsenseBuilder setConfiguration( NonsenseBuilder.Configuration cfg )
  221. {
  222. if( cfg == null ) m_cfg = CANONICAL_CONFIGURATION ;
  223. else m_cfg = cfg ;
  224. return this ;
  225. }

  226. @Override
  227. public NonsenseBuilder setContext( Context ctx )
  228. { m_ctx = ctx ; return this ; }

  229. /**
  230.  * Locks a value for the subject of the sentence.
  231.  *
  232.  * If {@code null} is specified, then the builder's subject will be
  233.  * "cleared", meaning that it will be chosen at random.
  234.  *
  235.  * @param sSubject a specific subject for the sentence, or {@code null} to
  236.  *                 ensure that the subject is randomized
  237.  * @return (fluid)
  238.  */
  239. public NonsenseBuilder setSubject( String sSubject )
  240. { m_sSubject = sSubject ; return this ; }

  241. /**
  242.  * Locks a value for the adjective modifying the subject of the sentence.
  243.  *
  244.  * If {@code null} is specified, then the builder might or might not add a
  245.  * random adjective, as specified by
  246.  * {@link Configuration#m_nSubjectAdjectiveChance}.
  247.  *
  248.  * @param sAdj a specific adjective to modify the subject of the sentence,
  249.  *             or {@code null} to randomize the presence and value of the
  250.  *             adjective
  251.  * @return (fluid)
  252.  */
  253. public NonsenseBuilder setSubjectAdjective( String sAdj )
  254. { m_sSubjectAdjective = sAdj ; return this ; }

  255. /**
  256.  * Locks a value for the verb of the sentence.
  257.  *
  258.  * If {@code null} is specified, then the builder's verb will be "cleared",
  259.  * meaning that it will be chosen at random.
  260.  *
  261.  * @param sVerb a specific verb for the sentence, or {@code null} to ensure
  262.  *              that the verb is randomized
  263.  * @return (fluid)
  264.  */
  265. public NonsenseBuilder setVerb( String sVerb )
  266. { m_sVerb = sVerb ; return this ; }

  267. /**
  268.  * Locks a value for the adverb modifying the verb of the sentence.
  269.  *
  270.  * If {@code null} is specified, then the builder might or might not add a
  271.  * random adverb, as specified by {@link Configuration#m_nAdverbChance}.
  272.  *
  273.  * @param sAdverb a specific adverb to modify the verb of the sentence, or
  274.  *                {@code null} to randomize the presence and value of the
  275.  *                adverb
  276.  * @return (fluid)
  277.  */
  278. public NonsenseBuilder setAdverb( String sAdverb )
  279. { m_sVerbAdverb = sAdverb ; return this ; }

  280. /**
  281.  * Locks a value for the object of the sentence.
  282.  *
  283.  * If {@code null} is specified, then the builder's object will be
  284.  * "cleared", meaning that it will be chosen at random.
  285.  *
  286.  * @param sObject a specific object for the sentence, or {@code null} to
  287.  *                ensure that the object is randomized
  288.  * @return (fluid)
  289.  */
  290. public NonsenseBuilder setObject( String sObject )
  291. { m_sObject = sObject ; return this ; }

  292. /**
  293.  * Locks a value for the adjective modifying the object of the sentence.
  294.  *
  295.  * If {@code null} is specified, then the builder might or might not add a
  296.  * random adjective, as specified by
  297.  * {@link Configuration#m_nObjectAdjectiveChance}.
  298.  *
  299.  * @param sAdj a specific adjective to modify the object of the sentence, or
  300.  *             {@code null} to randomize the presence and value of the
  301.  *             adjective
  302.  * @return (fluid)
  303.  */
  304. public NonsenseBuilder setObjectAdjective( String sAdj )
  305. { m_sObjectAdjective = sAdj ; return this ; }

  306. /**
  307.  * Locks a value for additional text (expected to be a prepositional phrase)
  308.  * modifying the object of the sentence.
  309.  *
  310.  * If {@code null} is specified, then the builder might or might not add a
  311.  * random phrase, as specified by
  312.  * {@link Configuration#m_nObjectPhraseChance}.
  313.  *
  314.  * @param sPhrase specific text to modify the object of the sentence, or
  315.  *                {@code null} to randomize the presence and value of such
  316.  *                text
  317.  * @return (fluid)
  318.  */
  319. public NonsenseBuilder setObjectModifier( String sPhrase )
  320. { m_sObjectModifier = sPhrase ; return this ; }

  321. @Override
  322. public String getString()
  323. {
  324. StringBuilder sb = new StringBuilder() ;

  325. this.appendSubject( sb )
  326.     .appendVerb( sb )
  327.     .appendObject( sb )
  328.     ;

  329. return sb.toString() ;
  330. }

  331. /**
  332.  * Appends the subject and its adjective (if any) to the buffer in which the
  333.  * sentence is being constructed.
  334.  * @param sb the buffer in which the sentence is being constructed
  335.  * @return (fluid)
  336.  */
  337. protected NonsenseBuilder appendSubject( StringBuilder sb )
  338. {
  339. final String sSubject = ( m_sSubject == null ?
  340. this.getRandomNonsense( R.array.asNonsenseNouns,
  341. Configuration.ALWAYS ) :
  342. m_sSubject
  343. );
  344. final String sAdj = ( m_sSubjectAdjective == null ?
  345. this.getRandomNonsense( R.array.asNonsenseAdjectives,
  346. m_cfg.m_nSubjectAdjectiveChance ) :
  347. m_sSubjectAdjective
  348. );

  349. if( sAdj == null )
  350. { // Just capitalize and append the subject as-is and move on.
  351. sb.append( sSubject.substring(0,1).toUpperCase() )
  352.   .append( sSubject.substring(1) )
  353.   ;
  354. }
  355. else if( Utils.startsWithArticle( sSubject ) )
  356. { // Split the article from the subject string and insert the adjective.
  357. String[] asTokens = sSubject.split( " ", 2 ) ;
  358. switch( asTokens[0] )
  359. {
  360. case "a":
  361. case "an":
  362. sb.append( Utils.whichIndefiniteArticle( sAdj, true ) )
  363.   .append( ' ' )
  364.   ;
  365. break ;
  366. case "the":
  367. sb.append( "The " ) ;
  368. break ;
  369. default:
  370. sb.append( asTokens[0].substring(0,1).toUpperCase() )
  371.   .append( asTokens[0].substring(1) )
  372.   .append( ' ' )
  373.   ;
  374. }
  375. sb.append( sAdj )
  376.   .append( ' ' )
  377.   .append( asTokens[1] )
  378.   ;
  379. }
  380. else
  381. { // Capitalize the adjective and stick it before the noun.
  382. sb.append( sAdj.substring( 0, 1 ).toUpperCase() )
  383.   .append( sAdj.substring(1) )
  384.   .append( ' ' )
  385.   .append( sSubject )
  386.   ;
  387. }

  388. sb.append( ' ' ) ;
  389. return this ;
  390. }

  391. /**
  392.  * Appends the verb and its adverb (if any) to the buffer in which the
  393.  * sentence is being constructed.
  394.  * @param sb the buffer in which the sentence is being constructed
  395.  * @return (fluid)
  396.  */
  397. protected NonsenseBuilder appendVerb( StringBuilder sb )
  398. {
  399. final String sVerb = ( m_sVerb == null ?
  400. this.getRandomNonsense( R.array.asNonsenseVerbs,
  401. Configuration.ALWAYS ) :
  402. m_sVerb
  403. );
  404. final String sAdverb = ( m_sVerbAdverb == null ?
  405. this.getRandomNonsense( R.array.asNonsenseAdverbs,
  406. m_cfg.m_nAdverbChance ) :
  407. m_sVerbAdverb
  408. );

  409. if( sAdverb == null )
  410. sb.append( sVerb ) ;
  411. else
  412. sb.append( sAdverb ).append( ' ' ).append( sVerb ) ;

  413. sb.append( ' ' ) ;
  414. return this ;
  415. }

  416. /**
  417.  * Appends the object and its adjective and modifier phrase (if any) to the
  418.  * buffer in which the sentence is being constructed.
  419.  * @param sb the buffer in which the sentence is being constructed
  420.  * @return (fluid)
  421.  */
  422. protected NonsenseBuilder appendObject( StringBuilder sb )
  423. {
  424. final String sObject = ( m_sObject == null ?
  425. this.getRandomNonsense( R.array.asNonsenseNouns,
  426. Configuration.ALWAYS ) :
  427. m_sObject
  428. );
  429. final String sAdj = ( m_sObjectAdjective == null ?
  430. this.getRandomNonsense( R.array.asNonsenseAdjectives,
  431. m_cfg.m_nObjectAdjectiveChance ) :
  432. m_sObjectAdjective
  433. );
  434. final String sPhrase = ( m_sObjectModifier == null ?
  435. this.getRandomNonsense( R.array.asNonsensePhrases,
  436. m_cfg.m_nObjectPhraseChance ) :
  437. m_sObjectModifier
  438. );

  439. if( sAdj == null )
  440. sb.append( sObject ) ;
  441. else if( Utils.startsWithArticle( sObject ) )
  442. {
  443. String[] asTokens = sObject.split( " ", 2 ) ;
  444. switch( asTokens[0] )
  445. {
  446. case "a":
  447. case "an":
  448. sb.append( Utils.whichIndefiniteArticle( sAdj, false ) )
  449.   .append( ' ' )
  450.   ;
  451. break ;
  452. default:
  453. sb.append( asTokens[0] ).append( ' ' ) ;
  454. }
  455. sb.append( sAdj )
  456.   .append( ' ' )
  457.   .append( asTokens[1] )
  458.   ;
  459. }
  460. else
  461. sb.append( sAdj ).append( ' ' ).append( sObject ) ;

  462. if( sPhrase != null )
  463. sb.append( ' ' ).append( sPhrase ) ;

  464. sb.append( '.' ) ;
  465. return this ;
  466. }

  467. /**
  468.  * Randomly selects a string resource from the selected string array.
  469.  * @param resStrings the resource ID of a string array
  470.  * @param nChance chance that we should return anything at all, expressed as
  471.  *                a percentage (expected range [0,100])
  472.  * @return a random string from that array of strings
  473.  */
  474. public String getRandomNonsense( int resStrings, int nChance )
  475. {
  476. if( nChance < Configuration.ALWAYS && RANDOM.nextInt(100) >= nChance )
  477. return null ;

  478. final String[] asStrings =
  479. m_ctx.getResources().getStringArray( resStrings ) ;
  480. final int nIndex = RANDOM.nextInt( asStrings.length ) ;
  481. return asStrings[nIndex] ;
  482. }

  483. }