Android Open Source - BestBoard Board Parser






From Project

Back to project page BestBoard.

License

The source code is released under:

MIT License

If you think the Android project BestBoard listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package digitalgarden.bestboard;
/*from ww w .  j  ava 2  s.c om*/
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import android.content.Context;
import digitalgarden.magicmerlin.scribe.Scribe;
import digitalgarden.magicmerlin.utils.Tokenizer;

/**
 * BoardGenerator create boards from a descriptor file.
 * Descriptor file - as a reader strem - will be tokenized.
 * Every line starts with a command. 
 * These commands are processed by the parseDescriptorTokens() method (command cycle).
 * Available commands:
 * <ul>
 * <li> NAME </li>
 * <li> VERSION </li>
 * <li> AUTHOR </li>
 * <li> TAGS </li>
 * <li> LABEL </li>
 * <li> BOARD </li>
 * <li> BUTTON </li>
 * <li> ROLLER </li>
 * </ul>
 * Empty lines (EOL token) will be skipped, and of file (EOF) will finish the process.
 * All other tokens will generate an log error.
 * <p>
 * The commands can have parameters.
 * Every command will do its own parameter evaluation (parameter cycle). 
 * <ul>
 * <li> Parameter/value pairs - parameter will determine the type of the value(s).
 * Only string and integer(long) values are used, character tokens can be used as strings.
 * EOL/EOF will be checked only in parameter evaluation (command end), 
 * missing values push back EOL/EOF token (after generating an error) to the parameter cycle.</li>
 * <li> Standalone parameter - it has no value.
 * Unknown/malformed parameters will be skipped as standalone parameter - 
 * next token will be evaluated as parameter token, not as a value.</li>
 * <li> String list parameter - list of string (or character) tokens, terminated by EOL/EOF. 
 * Non-string  tokens will generate a log message, but will not stop further evaluation.</li>
 * <li> No parameters at all - command will skip the remaining line.</li>
 * </ul>
 * File errors will throw exception, but parsing errors will not stop the process.
 * A message log will be generated in case of any mistakes. 
 */
public class BoardParser
  {
  /**
   ** TOKEN CODES OF KEYWORDS USED IN DESCRIPTOR FILE
   **/
  
  private static final long COMMAND_NAME = 0x11ff91L;
  private static final long COMMAND_VERSION = 0x12c1c5d965L;
  private static final long COMMAND_AUTHOR = 0x2cc5c115L;
  private static final long COMMAND_TAGS = 0x16a1efL;
  
  private static final long COMMAND_LABEL = 0x2608355L;
  
  private static final long COMMAND_BOARD = 0x14d5881L;
  
    private static final long PARAMETER_HEXAGONAL = 0x379824b8b630L;
    private static final long PARAMETER_PORTRAIT = 0x2375cbd8761L;
    private static final long PARAMETER_LANDSCAPE = 0x44010ff837b4L;
    private static final long PARAMETER_WIDTH = 0x3a15171L;
    private static final long PARAMETER_HEIGHT = 0x47e266ffL;
    private static final long PARAMETER_W = 0x20L;
    private static final long PARAMETER_H = 0x11L;
    private static final long PARAMETER_ODDS_LEFT = 0x4dd31502826dL;
    private static final long PARAMETER_ODDS_RIGHT = 0xb3f820a0b8a95L;
    private static final long PARAMETER_LEFT = 0x108847L;
    private static final long PARAMETER_RIGHT = 0x3126317L;
    
  private static final long COMMAND_POSITION = 0x2375fa6c735L;
  
    private static final long PARAMETER_DIR_PORT = 0x12a8b5f5517L;
    private static final long PARAMETER_DP = 0x1faL;
    private static final long PARAMETER_DIR_LAND = 0x12a8b5bf221L;
    private static final long PARAMETER_DL = 0x1f6L;
    private static final long PARAMETER_DIR_BOTH = 0x12a8b54833fL;
    private static final long PARAMETER_DPL = 0x4937L;
    private static final long PARAMETER_LAYOUT = 0x5804f908L;
    private static final long PARAMETER_L = 0x15L;
    private static final long PARAMETER_ROW = 0x93fbL;
    private static final long PARAMETER_R = 0x1bL;
    private static final long PARAMETER_COLUMN = 0x34587768L;
    private static final long PARAMETER_C = 0xcL;
    private static final long PARAMETER_NEXT = 0x12169bL;

  private static final long COMMAND_BUTTON = 0x30e81c12L;

    private static final long PARAMETER_CODE = 0x9c8a3L;
    private static final long PARAMETER_LABEL = 0x2608355L;

    private static final long PARAMETER_LOFT = 0x10bdc1L;
    private static final long PARAMETER_COLOR = 0x16a2be4L;
    private static final long PARAMETER_LABEL_COLOR = 0x16b836af4986f95L;
    private static final long PARAMETER_LOFT_COLOR = 0xa00398d10ce61L;
  
  private static final long COMMAND_ROLLER = 0x7257d89eL;
    
    private static final long PARAMETER_INDEX = 0x214cf79L;
    private static final long PARAMETER_I = 0x12L;
    private static final long PARAMETER_ADD = 0x3768L;
  
  private static final long COMMAND_COLOR = 0x16a2be4L;
    
    private static final long PARAMETER_BOARD = 0x14d5881L;
    private static final long PARAMETER_BUTTON = 0x30e81c12L;
  //  private static final long PARAMETER_LABEL = 0x2608355L;
  //  private static final long PARAMETER_LOFT = 0x10bdc1L;
      
    private static final long PARAMETER_TOUCH = 0x3508240L;
    private static final long PARAMETER_META = 0x115017L;
    private static final long PARAMETER_META_LOCK = 0x478ed3ae3850L;
    
  private static final long COMMAND_ORIGO = 0x2c39791L;

  //  private static final long PARAMETER_DIR_PORT = 0x12a8b5f5517L;
  //  private static final long PARAMETER_DP = 0x1faL;
  //  private static final long PARAMETER_DIR_LAND = 0x12a8b5bf221L;
  //  private static final long PARAMETER_DL = 0x1f6L;
  //  private static final long PARAMETER_ROW = 0x93fbL;
  //  private static final long PARAMETER_R = 0x1bL;
  //  private static final long PARAMETER_COLUMN = 0x34587768L;
  //  private static final long PARAMETER_C = 0xcL;

  /*  NOT IMPLEMETNED YET !!
   *  private static final long COMMAND_ACTION = 0x2ac30578L;
    *    private static final long PARAMETER_DONE = 0xa8ff2L;
   *    private static final long PARAMETER_GO = 0x268L;
   *    // private static final long PARAMETER_NEXT = 0x12169bL;
   *    private static final long PARAMETER_SEARCH = 0x7553994cL;
   *    private static final long PARAMETER_SEND = 0x15f26aL;
   */

  private static final long LABEL_TRUE = 0x16fed0L;
  private static final long LABEL_FALSE = 0x1b52528L;


  /**
   ** SOURCES
   **/
  
  /** Board descriptor file source contains board data in human readable format */
  private File descriptorFile;
  
  /** Context is needed for board generation only; boards have to know screen's dimensions */
  private Context context;
  
  /**
   ** LABELS OF THE DESCRIPTOR LANGUGAE
   ** (could be used insted of constans)
   **/
  
  /** 
   * Private class describing label values.
   * Integer (long) type: longValue valid, stringValue null
   * String type: longValue -1L, stringValue valid
   * Character type: longValue valid (0-FFFF), stringValue valid (one character long  string)
   * getParameter will know which value is needed:
   * String: stringValue cannot be null
   * Integer(long): 
   * !! NOT GOOD, THREE VARIABLES ARE BETTER: STRING CHAR AND LONG
   * Only one value is valid according to type 
   */
  private class LabelValue
    {
    LabelValue(long longValue)
      {
      this.longValue = longValue;
      }
    
    LabelValue(String stringValue)
      {
      this.stringValue = stringValue;
      }
    
    long longValue;
    String stringValue;
    }
  
  /** 
   * Labels can be used instead of constants in descriptor file.
   * Label keys (as long tokenCodes) and their values are stored in a HashMap.
   * Different type of the values are choosen by token type.
   * TYPE_INTEGER is stored as long,
   * TYPE_STRING and TYPE_CHARACTER are stored as String.
   * If stored String is null, then this is a long. If String is valid, then this is a String.
   * Common labels are set in constructor.
   * User definied labels can be set by 'LABEL labelID = value' command.   
   */
  private HashMap<Long, LabelValue> labels = new HashMap<Long, LabelValue>(); 

  
  /**
   ** DATA CREATED FROM THE DESCRIPTOR FILE
   **/

  public class Information
    {
    public String boardName = "";
    public String boardVersion = "";
    public String boardAuthor = "";
    public List<String> boardTags = new ArrayList<String>();
    }
  
  /** General information about the soft-keyboard */
  private Information information = new Information();

  
  /** Board used in PORTRAIT mode (or in both modes) */
  private final static int PORTRAIT = 0;

  /** Board used in LANDSCAPE mode (if different boards are needed) */
  private final static int LANDSCAPE = 1;
  
  /** This value is used only for button creations. Buttons will be created for both PORTRAIT and LANDSCAPE boards */
  private final static int BOTH = 2;
  
  /**
   * There are two types of board: PORTRAIT [0] and LANDSCAPE [1]
   * PORTRAIT board is required. If board is null, then the soft-keyboard cannot work.
   * board is generated from the information of the descriptor file
   * LANDSCAPE board is optional. If missing then PORTRAIT board will be used even in LANDSCAPE mode.
   * Board will have the same pixel dimension in both modes, it will be centered in LANDSCAPE mode.
   * boardLandscape is generated (if necessary) from the information of the descriptor file
   */
  private Board[] board = new Board[2];
  
  
  /**
   * Roller contains different character-strings, which work as wheels. 
   * The characters in this wheel can be changed to their succesor in a cyclic manner.
   * In this class rollers will be populated with character-wheels.    
   * Rollers can be used with both board types.
   * There are more (MAX_ROLLERS) rollers, which can be used independently.
   * Rollers array is ready, but the items will be initialized during the first character-string load.
   */
  private Roller[] rollers = new Roller[Roller.MAX_ROLLER];
  

  /** return PORTRAIT board (or null if there are no boards definied) */
  public Board getPortraitBoard()
    {
    return board[PORTRAIT];
    }

  /** return LANDSCAPE board (can be identical with PORTRAIT board)
   *  null if there are no boards definied */
  public Board getLandscapeBoard()
    {
    if ( board[LANDSCAPE] != null )
      return board[LANDSCAPE];
    return board[PORTRAIT];
    }
  
  /** return ROLLERS array (the rollers themselves can be null) */
  public Roller[] getRollers()
    {
    return rollers;
    }
  
  /** return supplemetray INFORMATION for both boards */
  public Information getInformation()
    {
    return information;
    }
  
  
  /**
   ** TEMPORARY DATA USED BY PARSER
   **/
  
  /*** POSITION OF BUTTON CREATION ***/
  
  /**
   * Different board widths for different directions (copy of board widths)
   * Direction: 0 - PORTRAIT 1 - LANDSCAPE 2 - BOTH PORTRAIT AND LANDSCAPE 
   * BOTH: shorter width will be used 
   * Default value will be set during board creation 
   * Values without valid board cannot be used.
   */
  private int[] boardWidthInHexagons = { 0, 0, 0 };

  /**
   * Different board heights for different directions (copy of board heights)
   * Direction: 0 - PORTRAIT 1 - LANDSCAPE 2 - BOTH PORTRAIT AND LANDSCAPE 
   * BOTH: shorter height will be used 
   * Default value will be set during board creation 
   * Values without valid board cannot be used.
   */
  private int[] boardHeightInHexagons = { 0, 0, 0 };
  
  /** Row offset for buttons in 0 - PORTRAIT 1 - LANDSCAPE directions */
  private int[] origoRowInHexagons = {0, 0};
  
  /** Column offset for buttons in 0 - PORTRAIT 1 - LANDSCAPE directions */
  private int[] origoColumnInHexagons = {0, 0};
  
  /** 
   * Direction: 0 - PORTRAIT 1 - LANDSCAPE 2 - BOTH PORTRAIT AND LANDSCAPE (-1 INVALID) 
   * Default value will be set during board creation 
   * Values without valid board cannot be used.
   */
  private int boardDirection = -1;
  
  /** Active layout level, default: 0 */
  private int buttonLayout = 0;

  /** Active row, default: 0 */
  private int buttonRowInHexagons = 0;

  /** Active column, default: 0 */
  private int buttonColumnInHexagons = 0;
  
  
  /*** DEFAULT COLORS set by COLOR command ***/

  /** Board's background color */
  private int defaultBoardColor = 0xFFBBBBBB;
  
  /** Button's default background color - needed BEFORE Button creation */
  private int defaultButtonColor = 0xFF888888;

  /** Color of button's main label - needed BEFORE Button creation */
  private int defaultLabelColor = 0xFF000000;

  /** Color of button's supplementary label - needed BEFORE Button creation */
  private int defaultLoftColor = 0xFF000000;
  
  /** Color of touched button */
  private int touchColor = 0xFFBBBBFF;

  /** Color of pressed meta-key */
  private int metaColor = 0xFFBBFFBB;

  /** Color of pressed and loccked meta-key */
  private int metaLockColor = 0xFFFFBBBB;
  
  
  /**
   ** CONSTRUCTOR
   **/
  
  // File directory = new File(Environment.getExternalStorageDirectory(), applicationContext.getString(R.string.directory));
  // File inputFile = new File(directory, applicationContext.getString(R.string.file));
  /** 
   * Constructor checks if descriptor file is valid
   * and sets up common labels.
   * This class owns the decriptor file.
   * This is important, because inner variables (labels) will be populated from file. 
   * Other descriptor file's variables/labels cannot be mixed with these. 
   * @param context Context is not needed directly but boards should calculate with screen dimensions 
   * @param descriptorFile descriptor file
   * @throws IOException if could not find valid descriptor file 
   */
  public BoardParser( Context context, File descriptorFile ) throws IOException
    {
    // Context for screen dimensions 
    this.context = context;
    
    // Descriptor file check
    if ( descriptorFile==null || !descriptorFile.exists() || !descriptorFile.isFile())
      {
         throw new IOException("Could not find valid descriptor file!");
      }
    
    this.descriptorFile = descriptorFile;
    
    // Populating labels table with commonly used labels
    labels.put( LABEL_TRUE, new LabelValue( -1L ));
    labels.put( LABEL_FALSE, new LabelValue( 0L ));
    }
  
  
  /**
   ** COMMAND CYCLE - PARSING THE FIRST (COMMAND) TOKEN
   **/
  
  // File directory = new File(Environment.getExternalStorageDirectory(), applicationContext.getString(R.string.directory));
  // File inputFile = new File(directory, applicationContext.getString(R.string.file));

  /**
   * Creates a tokenizer stream from the descriptor file, and start parsing.
   * File errors will generate IOException. 
   * Tokenizer and parser errors will send log messages, and will be counted.
   * Boards and other internal data can be read after parsing.
   * In the case of fatal parsing errors no boards will be generated.
   * @return number of parsing errors (Error messages can be found in the log) 
   * @throws IOException if reading fails
   */
  public int parseDescriptorFile( ) throws IOException
    {
    BufferedReader reader = null;
    Tokenizer tokenizer;
    
    try
      {
      reader = new BufferedReader( new InputStreamReader( new FileInputStream( descriptorFile ), "UTF-8" ) );
      tokenizer = new Tokenizer( reader );
      parseCommands( tokenizer );
      }
    // 
      //IOException is not catched!
    finally 
      {
      if (reader != null) 
        {
        try 
          {
          reader.close();
          }
        catch (IOException ioe)
          {
             Scribe.error("ERROR IN CLOSE (Descriptor file processing) " + ioe.toString());
          }
        }
      }
    
    // Both error counts (from the tokenizer and from the parser) will be returned 
    return tokenizer.getErrorCount() + getErrorCount();
    }

  
  /**
   * COMMAND CYCLE - Identifies command tokens, and forwards recognition to their parse* methods. 
   * @param tokenizer tokenizer of the descriptor file
   * @throws IOException if reading fails
   */
  private void parseCommands( Tokenizer tokenizer ) throws IOException
    {
    int tokenType;
    
    while(true)  
      {
      tokenType = tokenizer.nextToken();
      
      // Only keywords are allowed on the command (first) position
      if ( tokenType == Tokenizer.TYPE_KEYWORD )
        {
        long tokenCode = tokenizer.getIntegerToken();
        
        if ( tokenCode == COMMAND_NAME )
          {
          try
            {
            information.boardName = getStringParameter(tokenizer);
            note(tokenizer, "Name: " + information.boardName);
            tokenizer.skipThisLine();
            }
          catch (IllegalArgumentException iae)
            {
            ; // Do nothing, log was already sent
            }
          }
        
        else if ( tokenCode == COMMAND_VERSION )
          {
          try
            {
            information.boardVersion = getStringParameter(tokenizer);
            note(tokenizer, "Version: " + information.boardVersion);
            tokenizer.skipThisLine();
            }
          catch (IllegalArgumentException iae)
            {
            ; // Do nothing, log was already sent
            }
          }
        
        else if ( tokenCode == COMMAND_AUTHOR )
          {
          try
            {
            information.boardAuthor = getStringParameter(tokenizer);
            note(tokenizer, "Author: " + information.boardAuthor);
            tokenizer.skipThisLine();
            }
          catch (IllegalArgumentException iae)
            {
            ; // Do nothing, log was already sent
            }
          }
        
        else if ( tokenCode == COMMAND_TAGS )
          {
          note( tokenizer, "Tags: " );
          getStringListParameter(tokenizer, information.boardTags);
          }
        
        else if ( tokenCode == COMMAND_LABEL )
          {
          note( tokenizer, "Label: " );
          parseLabel( tokenizer );
          }
        
        else if ( tokenCode == COMMAND_BOARD )
          {
          note( tokenizer, "Board: " );
          parseBoard( tokenizer );
          }
        
        else if ( tokenCode == COMMAND_POSITION )
          {
          note( tokenizer, "Position: ");
          parseButton( tokenizer, false );
          }
        
        else if ( tokenCode == COMMAND_BUTTON )
          {
          note( tokenizer, "Button: ");
          parseButton( tokenizer, true );
          }
        
        else if ( tokenCode == COMMAND_ROLLER )
          {
          note( tokenizer, "Roller: " );
          parseRoller( tokenizer );
          }
        
        else if ( tokenCode == COMMAND_COLOR )
          {
          note( tokenizer, "Colors: ");
          parseColor( tokenizer );
          }
        
        else if ( tokenCode == COMMAND_ORIGO )
          {
          note( tokenizer, "Origo: ");
          parseOrigo( tokenizer );
          }
        
        else
          {
          error(tokenizer, "Cannot recognize command:" +  tokenizer.getStringToken() + "! Line skipped.");
          tokenizer.skipThisLine();
          }
        }

      // Empty line - do nothing
      else if ( tokenType == Tokenizer.TYPE_EOL )
        {
        ;
        }

      // End-of-file reached DESCRIPTOR FILE WAS PROCESSED COMPLETELY
      else if ( tokenType == Tokenizer.TYPE_EOF )
        {
        return;
        }

      else
        {
        error(tokenizer, "Command (" +  tokenizer.getStringToken() + ") is not a valid keyword token! Line skipped.");
        tokenizer.skipThisLine();
        }
      }
    }

  
  /**
   ** PARAMETER CYCLE - PARSING METHODS FOR THE PARAMETER LINE OF EACH COMMAND
   **/
  
  
  /**
   * Parses line after LABEL command.
   * Parameters are special labelkey-labelvalue pairs, where labelkey is a keyword, 
   * and labelvalue is one of TYPE_STRING, TYPE_CHARACTER, TYPE_INTEGER token.
   * String and character tokens are stored as String, Numeric values are stored as long in labels.
   * These parameters are specially handled, because their type is not known previously, 
   * and keywords (labels) are not accepted!
   * @throws IOException if reading fails
   */
  private void parseLabel( Tokenizer tokenizer ) throws IOException
    {
    long key;
    LabelValue value;
    
    int tokenType; 
    
    String keyAsString; // just for logging

    while (true)
      {
      // Reading label key
      tokenType = tokenizer.nextToken();
      
      // Valid keyword == label key was identified
      if ( tokenType == Tokenizer.TYPE_KEYWORD )
        {
        // correct label key
        key = tokenizer.getIntegerToken();
        keyAsString = tokenizer.getStringToken();

        // Reading label value
        tokenType = tokenizer.nextToken();
        
        if ( tokenType == Tokenizer.TYPE_STRING || tokenType == Tokenizer.TYPE_CHARACTER )
          {
          value = new LabelValue( tokenizer.getStringToken() );
          }
        else if ( tokenType == Tokenizer.TYPE_INTEGER )
          {
          value = new LabelValue( tokenizer.getIntegerToken() );
          }

        // Line (and label key) is abrupted without label value 
        else if ( tokenizer.isEndOfSectionToken() )
          {
          error( tokenizer, "Label (" + keyAsString + ") has no value. Label is skipped.");
          return;
          }
        
        // Label value is invalid (not string/char/integer)
        else
          {
          error( tokenizer, "Label (" + keyAsString + ") value (" + tokenizer.getStringToken() + ") is not accepeted. Label is skipped.");
          continue;
          }
        
        // We have a correct label key-value pair
        value = labels.put(key, value);
        note( tokenizer, "- " + keyAsString + ": " + tokenizer.getStringToken() + " added");

        // This label key was previously set!
        if ( value != null )
          {
          error( tokenizer, "Warning! Label's (" + keyAsString + ") previous value (" +
            (value.stringValue != null ? value.stringValue : value.longValue) + 
            ") was overwritten!");
          }
        }
      
      // End of line - command finished
      else if ( tokenizer.isEndOfSectionToken() )
        {
        return;
        }

      else
        {
        // incorrect label key, try with the next one 
        error( tokenizer, "Key (keyword) expected after LABEL. Can not accept: " + tokenizer.getStringToken());
        }
      }
    
    }
    
  /**
   * Parses line after BOARD command. Expected parameters:
   * <ul>
   * <li> HEXAGONAL - optional, because only hexagonal boards are in use </li>
   * <li> PORTRAIT / LANDSCAPE - optional, default is PORTRAIT </li>
   * <li> WIDTH or W = integer </li> - board width in hexagons (columns)
   * <li> HEIGHT or H = integer </li> - board height in hexagons (row) 
   * <li> ODDS_LEFT or LEFT / ODDS_RIGHT or RIGHT 
   * - optional, odd rows start on the left (default) / right side </li>
   * </ul>
   * BOARD command should preceed any BUTTON commands.
   * Only ONE Board can be created with ONE BUTTON command.
   * Generally one PORTRAIT and optionally one LANDSCAPE Board will be created.
   * @throws IOException if reading fails
   */
  private void parseBoard( Tokenizer tokenizer ) throws IOException
    {
    int width = -1; // obligate parameter
    int height = -1; // obligate parameter
    int direction = PORTRAIT; // optional parameter PORTRAIT or LANDSCAPE, default: PORTRAIT
    boolean oddRowsLeft = true; // optional parameter, default: left
    // always hexagonal type, skip it

    int tokenType; 
    long tokenCode;
    
    while (true)
      {
      // Reading parameter key
      tokenType = tokenizer.nextToken();
      
      if ( tokenType == Tokenizer.TYPE_KEYWORD )
        {
        tokenCode = tokenizer.getIntegerToken();
        
        try
          {
          // OBLIGATORY PARAMETERS
          if ( tokenCode == PARAMETER_WIDTH || tokenCode == PARAMETER_W )
            {
            width = (int)getLongParameter( tokenizer );
            note( tokenizer, "- width in hexagons: " + width);
            }
          else if ( tokenCode == PARAMETER_HEIGHT || tokenCode == PARAMETER_H )
            {
            height = (int)getLongParameter( tokenizer );
            note( tokenizer, "- height in hexagons: " + height);
            }
          
          // OPTIONAL PARAMETERS
          else if ( tokenCode == PARAMETER_HEXAGONAL )
            {
            ; // Nothing to do, this is the only possibility
            }
          else if ( tokenCode == PARAMETER_PORTRAIT )
            {
            direction = PORTRAIT;
            note( tokenizer, "- portrait");
            }
          else if ( tokenCode == PARAMETER_LANDSCAPE )
            {
            direction = LANDSCAPE;
            note( tokenizer, "- landscape");
            }
          else if ( tokenCode == PARAMETER_ODDS_LEFT || tokenCode == PARAMETER_LEFT )
            {
            oddRowsLeft = true;
            note( tokenizer, "- odd rows are pushed to the left");
            }
          else if ( tokenCode == PARAMETER_ODDS_RIGHT || tokenCode == PARAMETER_RIGHT )
            {
            oddRowsLeft = false;
            note( tokenizer, "- odd rows are pushed to the right");          
            }
          else
            {
            error( tokenizer, "Can not recognize: " + tokenizer.getStringToken() + " as parameter of BOARD.");
            }
          }
        catch( IllegalArgumentException iae )
          {
          ; // Do nothing, log was already sent
          }
        }

      // Parameter list is ended, finish parsing
      else if ( tokenizer.isEndOfSectionToken() )
        {
        break;
        }

      // Invalid parameter (not a keyword) 
      else
        {
        error( tokenizer, "Parameter (keyword) expected, can not accept: " + tokenizer.getStringToken() + " as parameter. Token skipped.");
        }
      }

    // Perform command using temporary data: 
    if ( Board.isValidDimension( width, height) )
      {
      if ( board[direction] != null )
        {
        error( tokenizer, "Board already definied! Previous board - including all key definitions - will be overwritten!");
        }
      board[direction] = new Board( context, width, height, direction==PORTRAIT, oddRowsLeft );
      note( tokenizer, " - new board was created.");
      
      // Update board specific class variables
      // Default direction used for next button creations
      if ( boardDirection == -1 )       // no board was set previously 
        boardDirection = direction;
      else if ( boardDirection != direction )  // other direction (or both directions) was already set
        boardDirection = BOTH;        // default values will be BOTH directions for button creation
      
      // Width and Height used for button creations
      boardWidthInHexagons[direction] = width;
      boardHeightInHexagons[direction] = height;
      
      boardWidthInHexagons[BOTH] = Math.min(boardWidthInHexagons[PORTRAIT], boardWidthInHexagons[LANDSCAPE]);
      boardHeightInHexagons[BOTH] = Math.min(boardHeightInHexagons[PORTRAIT], boardHeightInHexagons[LANDSCAPE]);
      }
    else
      {
      error( tokenizer, "Board cannot be created with these dimensinos!");
      }
    }

  /**
   * Parses line after BUTTON and POSITION command. 
   * POSITION command only sets button position, but do not create buttons.
   * Parameters of POSITION command (all optional): 
   * <ul>
   * <li> DIR_PORT or DP / DIR_LAND or DL / DIR_BOTH or DPL 
   * - portarait/landscape or both board (default: all created boards)</li>
   * <li> LAYOUT or L = integer - layout level (default: 0)</li> 
   * <li> ROW or R = integer - button row (default : 0)</li> 
   * <li> COLUMN or C = integer - button column (default: 0)</li> 
   * <li> NEXT - steps to the next button (next C than next R than next L)
   * If both boards are affected then the shorter width and the shorter height are used in calculations</li>
   * </ul>
   * All parameters of position can be used with BUTTON command.
   * These parameters are processed sequentially, the last parameter will be valid.
   * Button will be created AFTER all position parameters were evaluated.
   * Position will be also changed when there are NO button creation!  
   * Parameters for button creation - both parameters are obligatory!
   * BOARD command should preceed BUTTON creation!
   * <ul>
   * <li> CODE = character - button's boardCode </li>
   * <li> LABEL = string - button's main label </li> 
   * </ul>
   * Optional parameters for button creation:
   * <ul>
   * <li> LOFT = string - supplementary label </li>
   * <li> COLOR = integer - button's background color </li>
   * <li> LABEL_COLOR = integer - main label's color </li> 
   * <li> LOFT_COLOR = integer - supplementary label's color </li> 
   * </ul>
   * Default colors can be set with the COLOR command.
   * @throws IOException if reading fails
   */
  private void parseButton( Tokenizer tokenizer, boolean buttonCreation ) throws IOException
    {
    // Temporary data with default values
    int code = -1;
    String label = null; 
    String loft = null;
    
    int buttonColor = defaultButtonColor;
    int labelColor = defaultLabelColor;
    int loftColor = defaultLoftColor;
    
    int tokenType; 
    long tokenCode;
    
    // PARAMETER CYCLE
    while (true)
      {
      // Reading parameter key
      tokenType = tokenizer.nextToken();
      
      // First token has to be a parameter key == keyword
      if ( tokenType == Tokenizer.TYPE_KEYWORD )
        {
        // Not the string but the tokenCode will be compared
        tokenCode = tokenizer.getIntegerToken();
        
        try
          {
          //   *** PARSER PART - FOR POSITION ***
          if ( tokenCode == PARAMETER_DIR_PORT || tokenCode == PARAMETER_DP )
            {
            if ( board[PORTRAIT] != null )
              {
              boardDirection = PORTRAIT;
              note( tokenizer, " - for PORTRAIT board");
              }
            else
              {
              error( tokenizer, "PORTRAIT Board was not definied and cannot be used!");
              }
            }
          else if ( tokenCode == PARAMETER_DIR_LAND || tokenCode == PARAMETER_DL )
            {
            if ( board[LANDSCAPE] != null )
              {
              boardDirection = LANDSCAPE;
              note( tokenizer, " - for LANDSCAPE board");
              }
            else
              {
              error( tokenizer, "LANDSCAPE Board was not definied and cannot be used!");
              }
            }
          else if ( tokenCode == PARAMETER_DIR_BOTH || tokenCode == PARAMETER_DPL )
            {
            if ( board[PORTRAIT] != null && board[LANDSCAPE] != null )
              {
              boardDirection = BOTH;
              note( tokenizer, " - for both PORTRAIT and LANDSCAPE board");
              }
            else if ( board[PORTRAIT] != null ) // LANDSCAPE is NULL !
              {
              boardDirection = PORTRAIT;
              error( tokenizer, "LANDSCAPE Board was not definied and cannot be used! PORTRAIT was set.");
              }
            else if ( board[LANDSCAPE] != null ) // PORTRAIT is NULL !
              {
              boardDirection = LANDSCAPE;
              error( tokenizer, "PORTRAIT Board was not definied and cannot be used! LANDSCAPE was set.");
              }
            else // No boards definied yet !
              {
              boardDirection = -1;
              error( tokenizer, "No Board was not definied. This command cannot be used!");
              }
            }
          else if ( tokenCode == PARAMETER_LAYOUT || tokenCode == PARAMETER_L )
            {
            int layout = (int)getLongParameter(tokenizer);
            
            if ( Board.isValidLayout(layout) )
              {
              buttonLayout = layout;
              note( tokenizer, " - layout: " + layout );
              }
            else
              {
              buttonLayout = 0;
              error( tokenizer, "Layout (" + layout + ") is not valid! Default (0) layout will be used.");
              }
            }
          else if ( tokenCode == PARAMETER_ROW || tokenCode == PARAMETER_R )
            {
            // Any row can be given here, because it will modified by offset!
            buttonRowInHexagons = (int)getLongParameter(tokenizer);
            note( tokenizer, " - row: " + buttonRowInHexagons );
            }
          else if ( tokenCode == PARAMETER_COLUMN || tokenCode == PARAMETER_C )
            {
            // Any row can be given here, because it will modified by offset!
            buttonColumnInHexagons = (int)getLongParameter(tokenizer);
            note( tokenizer, " - column: " + buttonColumnInHexagons );
            }
          else if ( tokenCode == PARAMETER_NEXT )
            {
            // For this calculation we need a valid board
            // NEXT will step form button to button WITHOUT any OFFSET!
            if ( boardDirection != -1 )
              {
              buttonColumnInHexagons++;
              if ( buttonColumnInHexagons >= boardWidthInHexagons[boardDirection] )
                {
                buttonColumnInHexagons = 0;
                buttonRowInHexagons++;
                if ( buttonRowInHexagons >= boardHeightInHexagons[boardDirection] )
                  {
                  buttonRowInHexagons = 0;
                  buttonLayout++;
                  if ( buttonLayout >= Board.LAYOUTS )
                    {
                    buttonLayout = 0;
                    error( tokenizer, "Warning! NEXT stepped away from last button. First button on default layout will be the next one.");
                    }
                  }
                }
              note( tokenizer, " - layout: " + buttonLayout +
                  ", row: " + buttonRowInHexagons +
                  ", column: " + buttonColumnInHexagons +
                  " after performing NEXT.");
              }
            else
              {
              error( tokenizer, " At least one board is necessary to perform NEXT command!");
              }
            }
          
          else if ( !buttonCreation )
            {
            error( tokenizer, "Can not recognize: " + tokenizer.getStringToken() + " as parameter of POSITION." + 
                "Please, use BUTTON command instead of POSITION to create buttons!");
            }

          //   *** PARSER PART - FOR BUTTON CREATION - ONLY ONE BUTTON CAN BE CREATED ***
          else if ( tokenCode == PARAMETER_CODE )
            {
            String codeString = getStringParameter(tokenizer);
            if ( codeString.length() != 1)
              error( tokenizer, "One character is needed as code. Button skipped!");
            else
              {
              code = codeString.charAt( 0 );
              note( tokenizer, " - board code: " + Integer.toHexString(code));
              }
            }
          else if ( tokenCode == PARAMETER_LABEL )
            {
            label = getStringParameter(tokenizer);
            note( tokenizer, " - label: " + label);
            }
          else if ( tokenCode == PARAMETER_LOFT )
            {
            loft = getStringParameter(tokenizer);
            note( tokenizer, " - supplementary label: " + loft);
            }
          else if ( tokenCode == PARAMETER_COLOR )
            {
            buttonColor = (int)getLongParameter(tokenizer);
            note( tokenizer, " - background: " + Integer.toHexString(buttonColor));
            }
          else if ( tokenCode == PARAMETER_LABEL_COLOR )
            {
            labelColor = (int)getLongParameter(tokenizer);
            note( tokenizer, " - label's color: " + Integer.toHexString(labelColor));
            }
          else if ( tokenCode == PARAMETER_LOFT_COLOR )
            {
            loftColor = (int)getLongParameter(tokenizer);
            note( tokenizer, " - loft's color: " + Integer.toHexString(loftColor));
            }
          //   *** PARSER PART ENDS HERE ***

          // ERROR ! Keyword token is not recognized
          else
            {
            error( tokenizer, "Can not recognize: " + tokenizer.getStringToken() + " as parameter of BUTTON.");
            }
          }
        
        // ERROR ! Parameter value was not valid, this parameter was skipped
        catch( IllegalArgumentException iae )
          {
          ; // Do nothing, log was already sent
          }
        }
  
      // READY ! Parameter list finished, also finish parsing
      else if ( tokenizer.isEndOfSectionToken() )
        {
        break;
        }
  
      // ERROR ! Parameter key was not valid (not a keyword) 
      else
        {
        error( tokenizer, "Parameter (keyword) expected, can not accept: " + tokenizer.getStringToken() + " as parameter. Token skipped.");
        }
      }

    // Perform command using temporary data 
    
    // All data is available to create a new button,
    // But each parameter should be checked!

    // !! Button creation could be done in a separate helper method !!
    
    if ( buttonCreation )
      {
      if ( code == -1 )
        {
        error( tokenizer, "Button cannot be created without valid board Code!");
        }

      if ( label == null )
        {
        error( tokenizer, "Button cannot be created without valid label!");
        }

      // code and label are obligatory!
      if ( code != -1 && label != null )
        {
        // Create button on PORTRAIT board
        if ( boardDirection == PORTRAIT || boardDirection == BOTH )
          {
          int row = buttonRowInHexagons + origoRowInHexagons[PORTRAIT];
          int column = buttonColumnInHexagons + origoColumnInHexagons[PORTRAIT];
          
          if ( board[PORTRAIT] == null )
            {
            // Theoretically this cannot happen, because boardDirection is always checked
            error( tokenizer, "PORTRAIT Board was not generated, cannot add button!");
            }
          else if ( !Board.isValidLayout(buttonLayout) )
            {
            // Theoretically this cannot happen, because buttonLayout is always checked
            error( tokenizer, "Layout (" + buttonLayout + ") is invalid, cannot add button to PORTRAIT Board!");
            }
          else if ( !board[PORTRAIT].isValidPosition(row, column) )
            {
            // This can happen, because offset+position was never checked before
            error( tokenizer, "Row, column (" + row + ", " + column + ") is invalid, cannot add button to PORTRAIT Board!");
            }
          else
            {
            // EVERITHING IS READY TO ADD BUTTON TO PORTRAIT BOARD
            board[PORTRAIT].addButton( buttonLayout, row, column, (char)code, buttonColor, label, labelColor, loft, loftColor);
            note( tokenizer, "Button on PORTRAIT Board was created.");
            }
          }
        
        // Create button on LANDSCAPE board
        if ( boardDirection == LANDSCAPE || boardDirection == BOTH )
          {
          int row = buttonRowInHexagons + origoRowInHexagons[LANDSCAPE];
          int column = buttonColumnInHexagons + origoColumnInHexagons[LANDSCAPE];
          
          if ( board[LANDSCAPE] == null )
            {
            // Theoretically this cannot happen, because boardDirection is always checked
            error( tokenizer, "LANDSCAPE Board was not generated, cannot add button!");
            }
          else if ( !Board.isValidLayout(buttonLayout) )
            {
            // Theoretically this cannot happen, because buttonLayout is always checked
            error( tokenizer, "Layout (" + buttonLayout + ") is invalid, cannot add button to LANDSCAPE Board!");
            }
          else if ( !board[LANDSCAPE].isValidPosition(row, column) )
            {
            // This can happen, because offset+position was never checked before
            error( tokenizer, "Row, column (" + row + ", " + column + ") is invalid, cannot add button to LANDSCAPE Board!");
            }
          else
            {
            // EVERITHING IS READY TO ADD BUTTON TO PORTRAIT BOARD
            board[LANDSCAPE].addButton( buttonLayout, row, column, (char)code, buttonColor, label, labelColor, loft, loftColor);
            note( tokenizer, "Button on LANDSCAPE Board was created.");
            }
          }
        }
      }
    }
  
  /**
   * Parses line after ROLLER command. Expected parameters:
   * <ul>
   * <li> REGISTER or R = integer - which register to add to. Optional, default: 0 </li>
   * <li> ADD stringList - characterWheels (as string) to add </li>
   * </ul>
   * If register > MAX_ROLLER then register = 0 will be used.
   * @throws IOException if reading fails
   */
  private void parseRoller( Tokenizer tokenizer ) throws IOException
    {
    int tokenType; 
    long tokenCode;
    
    int register = 0;

    while (true)
      {
      // Reading label key
      tokenType = tokenizer.nextToken();
      
      if ( tokenType == Tokenizer.TYPE_KEYWORD )
        {
        tokenCode = tokenizer.getIntegerToken();
        
        try
          {
          
          if ( tokenCode == PARAMETER_INDEX || tokenCode == PARAMETER_I )
            {
            register = (int)getLongParameter( tokenizer );
            if ( Roller.isValidRegister(register) )
              {
              note( tokenizer, "- register: " + register);
              }
            else
              {
              error( tokenizer, "Register " + register + " is invalid, default (0) will be used.");
              register = 0;
              }
            }
          
          else if ( tokenCode == PARAMETER_ADD )
            {
            String wheel = getStringParameter(tokenizer);
            
            // Create new roller if needed
            if (rollers[register] == null)
            rollers[register] = new Roller();

            // Add wheel to roller
            if ( rollers[register].addCharacterWheel( wheel ) )
              note( tokenizer, " - " + wheel + " (" + register + ") added");
            else
              error( tokenizer, "Wheel (" + wheel + ") is not valid! Wheel skipped!");
            }
          
          else
            {
            error( tokenizer, "Can not recognize: " + tokenizer.getStringToken() + " as parameter of ROLLER.");
            }
          }
        catch( IllegalArgumentException iae )
          {
          ; // Do nothing, log was already sent
          }
        }

      // Parameter list is ended, finish parsing
      else if ( tokenizer.isEndOfSectionToken() )
        {
        break;
        }

      // Invalid parameter (not a keyword) 
      else
        {
        error( tokenizer, "Parameter (keyword) expected, can not accept: " + tokenizer.getStringToken() + " as parameter. Token skipped.");
        }
      }
    }

  /**
   * Parses line after COLOR command. Available parameters:
   * <ul>
   * <li> BOARD = integer - board's default background color </li>
   * <li> BUTTON = integer - buttons' default background color </li>
   * <li> LABEL = integer - labels' default color </li>
   * <li> LOFT = integer - supplemetary labels' default color </li>
   * <li> TOUCH = integer - background color of touched keys </li>
   * <li> META = integer - background color of active meta-keys </li>
   * <li> META_LOCK = integer - background color of locked meta-keys </li>
   * </ul>
   * Colors used for button creation will be used for buttons created after this COLOR command.
   * (These colors can be changed several times in the descriptor file.)
   * Colors used for board creation will be used during run-time board creation.
   * (Only the last value will be used.)
   * @throws IOException if reading fails
   */
  private void parseColor( Tokenizer tokenizer ) throws IOException
    {
    int tokenType; 
    long tokenCode;
    
    // PARAMETER CYCLE
    while (true)
      {
      // Reading parameter key
      tokenType = tokenizer.nextToken();
      
      // First token has to be a parameter key == keyword
      if ( tokenType == Tokenizer.TYPE_KEYWORD )
        {
        // Not the string but the tokenCode will be compared
        tokenCode = tokenizer.getIntegerToken();
        
        try
          {
          //   *** PARSER PART - IF/ELSE IF BRANCHES ***
          if ( tokenCode == PARAMETER_BOARD )
            {
            defaultBoardColor = (int)getLongParameter(tokenizer); 
            note( tokenizer, "- board's default background color: " + Integer.toHexString(defaultBoardColor));
            }
          else if ( tokenCode == PARAMETER_BUTTON )
            {
            defaultButtonColor = (int)getLongParameter(tokenizer); 
            note( tokenizer, "- buttons' default background color: " + Integer.toHexString(defaultButtonColor));
            }
          else if ( tokenCode ==  PARAMETER_LABEL )
            {
            defaultLabelColor = (int)getLongParameter(tokenizer); 
            note( tokenizer, "- labels' default color: " + Integer.toHexString(defaultLabelColor));
            }
          else if ( tokenCode == PARAMETER_LOFT )
            {
            defaultLoftColor = (int)getLongParameter(tokenizer); 
            note( tokenizer, "- supplemetary labels' default color: " + Integer.toHexString(defaultLoftColor));
            }
          
          else if ( tokenCode == PARAMETER_TOUCH )
            {
            touchColor = (int)getLongParameter(tokenizer); 
            note( tokenizer, "- background color of touched keys: " + Integer.toHexString(touchColor));
            }
          else if ( tokenCode == PARAMETER_META )
            {
            metaColor = (int)getLongParameter(tokenizer); 
            note( tokenizer, "- background color of active meta-keys: " + Integer.toHexString(metaColor));
            }
          else if ( tokenCode == PARAMETER_META_LOCK )
            {
            metaLockColor = (int)getLongParameter(tokenizer); 
            note( tokenizer, "- background color of locked meta-keys: " + Integer.toHexString(metaLockColor));
            }
          //   *** PARSER PART ENDs HERE ***

          // ERROR ! Keyword token is not recognized
          else
            {
            error( tokenizer, "Can not recognize: " + tokenizer.getStringToken() + " as parameter of COMMAND.");
            }
          }
        
        // ERROR ! Parameter value was not valid, this parameter was skipped
        catch( IllegalArgumentException iae )
          {
          ; // Do nothing, log was already sent
          }
        }
  
      // READY ! Parameter list finished, also finish parsing
      else if ( tokenizer.isEndOfSectionToken() )
        {
        break;
        }
  
      // ERROR ! Parameter key was not valid (not a keyword) 
      else
        {
        error( tokenizer, "Parameter (keyword) expected, can not accept: " + tokenizer.getStringToken() + " as parameter. Token skipped.");
        }
      }

    // Perform command using temporary data 
    }
  
  /**
   * Parses line after ORIGO command. Expected parameters:
   * <ul>
   * <li> DIR_PORT or DP / DIR_LAND or DL - should be chosen before setting origo </li>
   * <li> ROW or R = integer - offset for ROWs. Can be negative, but cannot be longer than Board max. height </li>
   * <li> COLUMN or C = integer - offset for COLUMNs. Can be negative, but cannot be longer than Board max. width </li>
   * </ul>
   * Offset will be used during BUTTON and POSITION commands
   * @throws IOException if reading fails
   */
  private void parseOrigo( Tokenizer tokenizer ) throws IOException
    {
    // Temporary data with default values
    int direction = -1; // 0 - PORTRAIT 1 - LANDSCAPE -1 INVALID : No default value, should be set before setting offset
    
    int tokenType; 
    long tokenCode;
    
    // PARAMETER CYCLE
    while (true)
      {
      // Reading parameter key
      tokenType = tokenizer.nextToken();
      
      // First token has to be a parameter key == keyword
      if ( tokenType == Tokenizer.TYPE_KEYWORD )
        {
        // Not the string but the tokenCode will be compared
        tokenCode = tokenizer.getIntegerToken();
        
        try
          {
          //   *** PARSER PART - IF/ELSE IF BRANCHES ***
          if ( tokenCode == PARAMETER_DIR_PORT || tokenCode == PARAMETER_DP )
            {
            direction = PORTRAIT;
            note( tokenizer, "- for PORTRAIT direction ");
            }
          else if ( tokenCode == PARAMETER_DIR_LAND || tokenCode == PARAMETER_DL )
            {
            direction = LANDSCAPE;
            note( tokenizer, "- for LANDSCAPE direction ");
            }
          else if ( tokenCode == PARAMETER_ROW || tokenCode == PARAMETER_R )
            {
            long row = getLongParameter(tokenizer);
            if ( direction < 0)
              error( tokenizer, "Direction was not choosen before setting row origo!");
            else if ( row < -Board.MAX_BOARD_HEIGHT_IN_HEXAGONS || row > Board.MAX_BOARD_HEIGHT_IN_HEXAGONS )
              error( tokenizer, "Row offset (" + row + ") is longer than " + Board.MAX_BOARD_HEIGHT_IN_HEXAGONS + ". Offset is not set.");
            else
              {
              // ROW OFFSET READY
              origoRowInHexagons[direction] = (int)row;
              note( tokenizer, " - row offset: " + origoRowInHexagons[direction]);
              }
            }
          else if ( tokenCode == PARAMETER_COLUMN || tokenCode == PARAMETER_C )
            {
            long column = getLongParameter(tokenizer);
            if ( direction < 0)
              error( tokenizer, "Direction was not choosen before setting column origo!");
            else if ( column < -Board.MAX_BOARD_WIDTH_IN_HEXAGONS || column > Board.MAX_BOARD_WIDTH_IN_HEXAGONS )
              error( tokenizer, "Column offset (" + column + ") is longer than " + Board.MAX_BOARD_WIDTH_IN_HEXAGONS + ". Offset is not set.");
            else
              {
              // COLUMN OFFSET READY
              origoColumnInHexagons[direction] = (int)column;
              note( tokenizer, " - column offset: " + origoColumnInHexagons[direction]);
              }
            }

          //   *** PARSER PART ENDs HERE ***

          // ERROR ! Keyword token is not recognized
          else
            {
            error( tokenizer, "Can not recognize: " + tokenizer.getStringToken() + " as parameter of COMMAND.");
            }
          }
        
        // ERROR ! Parameter value was not valid, this parameter was skipped
        catch( IllegalArgumentException iae )
          {
          ; // Do nothing, log was already sent
          }
        }
  
      // READY ! Parameter list finished, also finish parsing
      else if ( tokenizer.isEndOfSectionToken() )
        {
        break;
        }
  
      // ERROR ! Parameter key was not valid (not a keyword) 
      else
        {
        error( tokenizer, "Parameter (keyword) expected, can not accept: " + tokenizer.getStringToken() + " as parameter. Token skipped.");
        }
      }

    // Perform command using temporary data: 
    // Do nothing, offset was already set
    }
  
  
  /**
   ** PARSING PARAMETER VALUES
   **/
  
  /**
   * Expecting numeric parameter value: next token should be an integer (long precision).
   * It can be a valid TYPE_INTEGER token, or a keyword identifying a label with long value.
   * All other tokens, missing labels or labels with string value will throw IllegalArgumentException
   * (after sending a log error)
   * EOL/EOF tokens will be pushed back, for further evaluation.
   * @param tokenizer tokenizer of the descriptor file
   * @return integer parameter value (long precision)
   * @throws IOException if reading fails
   */
  private long getLongParameter( Tokenizer tokenizer ) throws IOException
    {
    String forParameter = " for " + tokenizer.getStringToken();
    int tokenType = tokenizer.nextToken();

    // Integer token
    if ( tokenType == Tokenizer.TYPE_INTEGER )
      {
      // RETURN VALID INTEGER
      return tokenizer.getIntegerToken();
      }
    
    // Label token
    if ( tokenType == Tokenizer.TYPE_KEYWORD )
      {
      long key = tokenizer.getIntegerToken();
      
      LabelValue value = labels.get(key);
      
      if ( value == null )
        {
        error( tokenizer, "Could not find label (" + tokenizer.getStringToken() + ")" + forParameter );
        throw new IllegalArgumentException("Label missing");
        }
      
      if ( value.stringValue != null )
        {
        error( tokenizer, "Label (" + tokenizer.getStringToken() + ")" + forParameter + " has not got integer value" );
        throw new IllegalArgumentException("Invalid label value");
        }
      
      // RETURN VALID LABEL
      return value.longValue;
      }
    
    // EOL/EOF token - should be pushed back. There can be more parameter pairs.
    if ( tokenizer.isEndOfSectionToken() )
      {
      tokenizer.pushBackLastToken();
      error( tokenizer, "Parameter value is missing for" + forParameter );
      throw new IllegalArgumentException("Parameter value missing");
      }
    
    // Anything else is invalid for a numeric parameter value
    error( tokenizer, "Parameter value (" + tokenizer.getStringToken() + ") is invalid" + forParameter );
    throw new IllegalArgumentException("Invalid parameter value");
    }
  
  /**
   * Convenience method to get stringList parameter.
   * String lists are consecutive string values always terminated by EOL/EOF 
   * @param tokenizer tokenizer of the descriptor file
   * @param stringList string values will be added to this list. Cannot be null!
   * @throws IOException if reading fails
   */
  private void getStringListParameter( Tokenizer tokenizer, List<String> stringList ) throws IOException
    {
    String parameterValue;
    while(true)
      {
      try
        {
        parameterValue = getStringParameter(tokenizer, true);

        // EOL/EOF - String list has finished
        if ( parameterValue == null )
          break;

        stringList.add( parameterValue );
        note(tokenizer, "- " + parameterValue + " added");
        }
      catch (IllegalArgumentException iae)
        {
        ; // Do nothing, log was already sent. Only this parameter value will be skipped
        }
      }
    }

  /**
   * Convenience method to get standalone string parameter
   * @param tokenizer tokenizer of the descriptor file
   * @return string parameter value
   * @throws IOException if reading fails
   * @see getStringParameter( Tokenizer tokenizer, boolean stringList )
   */
  private String getStringParameter( Tokenizer tokenizer ) throws IOException
    {
    return getStringParameter(tokenizer, false);
    }

  /**
   * Expecting string parameter value: next token should be a string (or character treated as string).
   * It can be a valid TYPE_STRING or TYPE_CHARACTER token, or a keyword identifying a label with string value.
   * All other tokens, missing labels or labels with integer value will throw IllegalArgumentException
   * (after sending a log error)
   * If this is a standalone string parameter then 
   * EOL/EOF tokens (before throwing exception) will be pushed back for further evaluation.
   * If this string is a part of a string list then
   * EOL/EOF tokens mean the end of the stringList, null is returned (instead of throwing exception.) 
   * @param tokenizer tokenizer of the descriptor file
   * @param stringList true if this parameter value is part of a stringList
   * @return string parameter value
   * @throws IOException if reading fails
   */
  private String getStringParameter( Tokenizer tokenizer, boolean stringList ) throws IOException
    {
    // If this is not a stringList, then previous token is the parameter key. It is needed for error log.
    String forParameter = stringList ? "" : " for " + tokenizer.getStringToken();
    int tokenType = tokenizer.nextToken();

    // String or character token
    if ( tokenType == Tokenizer.TYPE_STRING || tokenType == Tokenizer.TYPE_CHARACTER )
      {
      // RETURN VALID STRING ( OR CHARACTER AS STRING )
      return tokenizer.getStringToken();
      }
    
    // Label token
    if ( tokenType == Tokenizer.TYPE_KEYWORD )
      {
      long key = tokenizer.getIntegerToken();
      
      LabelValue value = labels.get(key);
      
      if ( value == null )
        {
        error( tokenizer, "Could not find label (" + tokenizer.getStringToken() + ")" + forParameter );
        throw new IllegalArgumentException("Label missing");
        }
      
      if ( value.stringValue == null )
        {
        error( tokenizer, "Label (" + tokenizer.getStringToken() + ")" + forParameter + " has not got string value" );
        throw new IllegalArgumentException("Invalid label value");
        }
      
      // RETURN VALID LABEL
      return value.stringValue;
      }
    
    // EOL/EOF token 
    if ( tokenizer.isEndOfSectionToken() )
      {
      // If there is a list of string parameters then EOL/EOF is not an error, but the end of the list 
      if ( stringList )  return null;
      
      // EOL/EOF - should be pushed back. There can be more parameter pairs.
      tokenizer.pushBackLastToken();
      error( tokenizer, "Parameter value is missing for" + forParameter );
      throw new IllegalArgumentException("Parameter value missing");
      }
    
    // Anything else is invalid for a string parameter value
    error( tokenizer, "Parameter value (" + tokenizer.getStringToken() + ") is invalid" + forParameter );
    throw new IllegalArgumentException("Invalid parameter value");
    }
  
  
  /**
   ** ERROR HANDLING
   **/
  
  /** Count format errors */
  private int errorCount = 0;

  /** Returns the number of errors. 0 == no errors, correctly formatted input. */ 
  public int getErrorCount()
    {
    return errorCount;
    }

  /**
   * Internal error handling - all error messages come through this method.
   * It is easier to send error messages to a common output, and to count format mistakes.
   * @param message error message, will be completed with line number
   */
  private void error( Tokenizer token, String message )
    {
    errorCount++;
    Scribe.error( message + " (line: " + token.getLineNumber() + ") ");
    }
  
  private void note( Tokenizer token, String message )
    {
    Scribe.note( message + " (line: " + token.getLineNumber() + ") ");
    }
  
  }




Java Source Code List

digitalgarden.bestboard.BoardParser.java
digitalgarden.bestboard.Board.java
digitalgarden.bestboard.MainActivity.java
digitalgarden.bestboard.Roller.java
digitalgarden.bestboard.TokenizerTest.java
digitalgarden.magicmerlin.scribe.Scribe.java
digitalgarden.magicmerlin.utils.Keyboard.java
digitalgarden.magicmerlin.utils.Rest.java
digitalgarden.magicmerlin.utils.Screen.java
digitalgarden.magicmerlin.utils.Tokenizer.java