Back to project page BestBoard.
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.
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() + ") "); } }