Android Open Source - BestBoard Board






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 . ja  v  a  2  s.c  o m*/
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Rect;
import digitalgarden.magicmerlin.scribe.Scribe;
import digitalgarden.magicmerlin.utils.Screen;

/** 
 * BoardDescriptor class describes the soft-keyboard.
 * It has two sources:
 * - populates all the board parameters from a description file;
 * - get information about the actual screen and device.
 * Provided data and services:
 * - created soft-keyboard layouts (bitmaps)
 * - key-drawing on a canvas 
 * - touchCode (identified by coordinate-pairs)
 * - boardCode (identified by touchCode, and othe data)
 * BoardDescriptor will not detect touches or generate key-events.
 */
public class Board
  {
  /**
   ** PREFERENCES - settings per device
   **/ 
  
  /** 
   * Size of the outer rim on buttons.
   * Touch movement (stroke) will not fire from the outer rim, but touch down will do. 
   */
  public int outerRimPercent = 20;
  
  
  /**
   ** CLASS VARIABLES - mostly loaded from descriptor file
   **/
  
  /**
   * The board numbers LAYOUT layouts. All layouts has exactly identical dimensions, 
   * identical number of buttons, but one button-position can contain different buttons in different layouts. 
   * Not all of the layouts should be in use. 
   * Layout 0 is the normal layout, Layout 1 is the shifted pair.
   * Alt layouts (2, 4, 6...) also has shifted pairs (3, 5, 7... respectively)
   */
  public final static int LAYOUTS = 8;
  
  
  /** board width in pixels (equals to screen's lower diameter) */
  private int boardWidthInPixels;
  /** board hight in pixels (calculated from width) */
  private int boardHeightInPixels; 
  
  /** number of full hexagons in one row of the board */
  private int boardWidthInHexagons;
  /** number of hexagon rows (lower quater is missing from the bottom row) */
  private int boardHeightInHexagons;
  
  /** number of half hexagons in one row (mostly this value is used for calculations) */
  private int boardWidthInGrids;
  /** number of quater hexagon rows (mostly this value is used for calculations) */
  private int boardHeightInGrids;

  /** 0 if odd rows aligned to the left, 1 if odd rows aligned to the right */
  private int oddRowsAlignOffset;
  
  
  private Paint shortLabelPaint = new Paint();
  private int shortLabelOffset;
  private Paint longLabelPaint = new Paint();
  private int longLabelOffset;

  private Paint upperShortLabelPaint = new Paint();
  private int upperShortLabelOffset;
  private Paint upperLongLabelPaint = new Paint();
  private int upperLongLabelOffset;
  
  private Paint lowerShortLabelPaint = new Paint();
  private int lowerShortLabelOffset;
  private Paint lowerLongLabelPaint = new Paint();
  private int lowerLongLabelOffset;
  
  
  /** Hexagons fill paint will be set in constructor, color is variable */
  private Paint hexagonFillPaint = new Paint();
  
  /** Hexagons stroke paint will be set in constructor, color is variable */
  private Paint hexagonStrokePaint = new Paint();

  
  /** Map contains the touchCodes for all layouts */
  private Bitmap layoutMap;
  
  /** Describe one button. Class variables can be reached directly from Board class. */
  private class ButtonDescriptor
    {
    private int columnInHexagons;
    private int rowInHexagons;
    private int color;
    private String label;
    private int labelColor;
    private String upperLabel;
    private int upperLabelColor;
    private char boardCode;
    }
  
  /** Buttons of the layouts - will be initialized in constructor */
  private ButtonDescriptor[][] buttons;
  
  /**
   * Areas without button will give EMPTY_TOUCH_CODE.
   * More buttons than EMPTY_TOUCH_CODE cannot be definied.
   */
  public final static int EMPTY_TOUCH_CODE = 0x3FF;
  
  /** Layout skin for the current layout. */
  private Bitmap layoutSkin = null;
  
  /** This layout can be found in the layoutSkin bitmap (when bitmap is not null) */
  private int layout;
  
  
  /**
   * Calculates touchCode from the hexagonal position.
   * Important, that the touchCodes should be identical in all layouts and in the map.
   * TouchCodes are the indexes of button[] in LayoutDescription.  
   * @param hexagonCol column of the button (x coord in hexagons) 
   * @param hexagonRow row of the button (y coord in hexagons)
   * @return touchCode of the button 
   */
  private int touchCodeFromPosition( int hexagonRow, int hexagonCol )
    {
    return hexagonRow * boardWidthInHexagons + hexagonCol; 
    }
  
  /**
   ** CONSTRUCTOR
   **/

  /** 
   * The number of buttons on one layout is maximalized
   * The 'last' code (which can be represented on the map) determines the maximum number of buttons 
   */
  public static final int MAX_BUTTONS = EMPTY_TOUCH_CODE;
  
  /** Maximal layout width in hexagons */
  public static final int MAX_BOARD_WIDTH_IN_HEXAGONS = 24;

  /** Maximal layout height in hexagons */
  public static final int MAX_BOARD_HEIGHT_IN_HEXAGONS = 12;
  
  /**
   * True if a board can be created with these parameters.
   * This can be checked before creating board or constructor will use the same method 
   */
  public static boolean isValidDimension( int boardWidthInHexagons, int boardHeightInHexagons )
    {
    if ( boardWidthInHexagons < 1 || boardWidthInHexagons > MAX_BOARD_WIDTH_IN_HEXAGONS )
      return false;
    if ( boardHeightInHexagons < 1 || boardHeightInHexagons > MAX_BOARD_HEIGHT_IN_HEXAGONS )
      return false;
    if ( boardWidthInHexagons * boardHeightInHexagons > MAX_BUTTONS )
      return false;
    
    return true;
    }

  /**
   * Constructor needs data to generate a keyboard with button-holes.
   * It needs information about the screen (context) and about the measures of the board.
   * All specific data (buttons etc.) will be added later.
   * @param context context to get the exact screen resolution
   * @param boardWidthInHexagons width in full hexagons
   * @param boardHeightInHexagons height in full hexagons
   * @param portrait true: portrait, false: landscape 
   * @param oddRowsAlignedLeft odd rows aligned to the left
   * @throws IllegalArgumentException if board cannot be created with this dimension
   * Dimension can be checked previously with isValidDimension()
   */
  public Board( Context context, int boardWidthInHexagons, int boardHeightInHexagons, boolean portrait, boolean oddRowsAlignedLeft ) throws IllegalArgumentException
    {
    if ( !isValidDimension(boardWidthInHexagons, boardHeightInHexagons) )
      {
      throw new IllegalArgumentException("Board cannot be created with these arguments!");
      }
  
    // NON SCREEN-SPECIFIC DATA
    
    this.oddRowsAlignOffset = oddRowsAlignedLeft ? 0 : 1;
    
    this.boardWidthInHexagons = boardWidthInHexagons;
    this.boardHeightInHexagons = boardHeightInHexagons;
    
    // Each row has one more half hexagon column
    this.boardWidthInGrids = boardWidthInHexagons * 2 + 1;
    // Each row has three quaters of hexagonal height; the lowest quater height is missing from the bottom
    this.boardHeightInGrids = boardHeightInHexagons * 3;

    // INITIALIZE BUTTONS' ARRAY
    
    // this two-dimensional array will be populated later
    // null: non-deinied (empty) button
    
    buttons = new ButtonDescriptor[ LAYOUTS ][ boardWidthInHexagons * boardHeightInHexagons ];
    
    // GENERATE SCREEN SPECIFIC VALUES

    // PORTRAIT: shorter screen diameter, LANDSCAPE (optional): longer screen diameter
    this.boardWidthInPixels = portrait ? Screen.getShorterDiameter(context) : Screen.getLongerDiameter(context);
    
    // Board pixel height is calculated with the ratio of a regular hexagon
    // after this point all the measurements are calculated from pixelHeight backwards
    this.boardHeightInPixels = ( (boardHeightInGrids * boardWidthInPixels * 1000) / (boardWidthInGrids * 1732) );

    // As PORTRAIT has to work also in LANDSCAPE mode
    // Board height cannot be higher as 3/4 * shorter screen diameter (screen height in landscape mode) 
    int maximalBoardHeightInPixels = Screen.getShorterDiameter(context) * 3 / 4;

    // If board height exceeds maximal value, board will be distorted
    if ( this.boardHeightInPixels > maximalBoardHeightInPixels )
      this.boardHeightInPixels = maximalBoardHeightInPixels;

    // CALCULATE FONT PARAMETERS
    
    // Font Size should be calculated
    // There are two sizes: 
    // - one character - one grid height
    // - string (MMMMM) - two grid width ( it will be lower than one grid !! What if board is distrorted ?? )
    Rect bounds = new Rect();
    shortLabelPaint.setTextSize( 1000f );
    shortLabelPaint.getTextBounds("MMMMMMM", 0, 4, bounds);
    
    // intendedHeightinPixels can be ( boardHeightInPixels / boardHeightInGrids )
    // Ratio => SIZE : intendedHeightInPixels = 1000f : bounds.height()  
    int textSize = 2000 * boardHeightInPixels / (boardHeightInGrids * bounds.height()) ;
    // width for 5M: textSize =  2 * 1000 * boardWidthInPixels / (boardWidthInGrids * bounds.width());
    int gridHeightInPixels = boardHeightInPixels / boardHeightInGrids;
    
    shortLabelOffset = gridHeightInPixels * 10 / 20; // 6
    shortLabelPaint.setTextSize( textSize * 10 / 20 ); // 6
    shortLabelPaint.setTextAlign( Align.CENTER );
    shortLabelPaint.setFlags( Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG | Paint.LINEAR_TEXT_FLAG );
    
    longLabelOffset = gridHeightInPixels * 9 / 20; // 5
    longLabelPaint.setTextSize( textSize * 7 / 20 ); // 5
    longLabelPaint.setTextAlign( Align.CENTER );
    longLabelPaint.setFlags( Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG | Paint.LINEAR_TEXT_FLAG 
        | Paint.FAKE_BOLD_TEXT_FLAG );
    
    upperShortLabelOffset = gridHeightInPixels * -16 / 20; // 4
    upperShortLabelPaint.setTextSize( textSize * 7 / 20 ); // 4
    upperShortLabelPaint.setTextAlign( Align.CENTER );
    upperShortLabelPaint.setFlags( Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG | Paint.LINEAR_TEXT_FLAG );

    lowerShortLabelOffset = gridHeightInPixels * 16 / 20; // 13
    lowerShortLabelPaint.setTextSize( textSize * 10 / 20 ); // 5
    lowerShortLabelPaint.setTextAlign( Align.CENTER );
    lowerShortLabelPaint.setFlags( Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG | Paint.LINEAR_TEXT_FLAG );
      
    upperLongLabelOffset = gridHeightInPixels * -10 / 20; 
    upperLongLabelPaint.setTextSize( textSize * 5 / 20 );
    upperLongLabelPaint.setTextAlign( Align.CENTER );
    lowerLongLabelPaint.setFlags( Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG | Paint.LINEAR_TEXT_FLAG 
      | Paint.FAKE_BOLD_TEXT_FLAG );
      
    lowerLongLabelOffset = gridHeightInPixels * 12 / 20; 
    lowerLongLabelPaint.setTextSize( textSize * 7 / 20 );
    lowerLongLabelPaint.setTextAlign( Align.CENTER );
    lowerLongLabelPaint.setFlags( Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG | Paint.LINEAR_TEXT_FLAG 
      | Paint.FAKE_BOLD_TEXT_FLAG );
      
    // SETTING UP OTHER PAINT PARAMETERS
    
    hexagonFillPaint.setStyle( Style.FILL );
    
    hexagonStrokePaint.setStyle( Style.STROKE );
    hexagonStrokePaint.setStrokeWidth( 0f );
    hexagonStrokePaint.setColor( Color.BLACK );
    }

  
  /**
   ** DRAWINGS 
   **/

  private int getGridX( int row, int column )
    {
    return ( (row + oddRowsAlignOffset) %2 ) + column * 2 + 1;
    }
  
  private int getGridY( int row )
    {
    return row * 3 + 2;
    }
      
  // a "grid" a flszles/negyedmagas rcspontoknak felel meg
  // ez a kt fggvny a pontos pixelt adja vissza
  private int getPixelX( int gridX )
    {
    return gridX * boardWidthInPixels / boardWidthInGrids;
    }
  
  private int getPixelY( int gridY )
    {
    return gridY * boardHeightInPixels / boardHeightInGrids;
    }
      
  // A kzp rcspont alapjn rajzolunk hatszget, a krnyez? rcspontokra
  private Path hexagonPath( int gridX, int gridY )
    {
        Path path = new Path();
    
        path.moveTo( getPixelX( gridX ), getPixelY( gridY-2 ) );
        path.lineTo( getPixelX( gridX+1 ), getPixelY( gridY-1 ) );
        path.lineTo( getPixelX( gridX+1 ), getPixelY( gridY+1 ) );
        path.lineTo( getPixelX( gridX ), getPixelY( gridY+2 ) );
        path.lineTo( getPixelX( gridX-1 ), getPixelY( gridY+1 ) );
        path.lineTo( getPixelX( gridX-1 ), getPixelY( gridY-1 ) );
        path.close();
    
        return path;
    }
  
  // A kzp rcspont alapjn rajzolunk hatszget, a krnyez? rcspontokra
  private Path hexagonPath( int gridX, int gridY, int pixelHalfWidth, int pixelQuaterHeight )
    {
    int pixelX = getPixelX( gridX );
    int pixelY = getPixelY( gridY );
        Path path = new Path();
    
        path.moveTo( pixelX, pixelY - 2 * pixelQuaterHeight );
        path.lineTo( pixelX + pixelHalfWidth, pixelY - pixelQuaterHeight );
        path.lineTo( pixelX + pixelHalfWidth, pixelY + pixelQuaterHeight );
        path.lineTo( pixelX, pixelY + 2 * pixelQuaterHeight );
        path.lineTo( pixelX - pixelHalfWidth, pixelY + pixelQuaterHeight );
        path.lineTo( pixelX - pixelHalfWidth, pixelY - pixelQuaterHeight );
        path.close();
    
        return path;
    }
  
  
  /**
   ** CREATE BOARD MAP
   **/
  
  private int colorFromTouchCode( int touchCode, boolean outerRim )
    {
    // TouchCode 5 + 5 bit > R 5 bit (G) B 5 bit
    // R byte : 5bit << 3 + 5
    // B byte : 5bit << 3 + 5
    
    int red = ((touchCode & 0x3E0) << 14) + 0x50000; // >> 5 << 3 << 16 + 5 << 16;
    
    int green = outerRim ? 0 : 0xFF00 ;
    
    int blue = ((touchCode & 0x1F) << 3) + 5;
    
    return  0xFF000000 | red | green | blue;
    }
  
  private int touchCodeFromColor( int color )
    {
    // 5 bit : byte >> 3
    // R >> 16 >> 3 << 5
    // B >> 3
    
    return ((color & 0xF80000) >> 14) | ((color & 0xF8) >> 3);
    }
  
  private boolean outerRimFromColor(int color )
    {
    return (color & 0xFF00) == 0;
    }
  
  
  private void createMap()
    {
    layoutMap = Bitmap.createBitmap( boardWidthInPixels, boardHeightInPixels, Bitmap.Config.RGB_565);
    layoutMap.eraseColor( colorFromTouchCode( 0xFF, false) );
    
    Canvas canvas = new Canvas( layoutMap );
    
    Paint paint = new Paint();
        paint.setStyle( Paint.Style.FILL );
        paint.setAntiAlias( false );
        paint.setDither( false );
        
    int gridX;
    int gridY;
    
    int pixelQuaterHeight = (boardHeightInPixels * (100 - outerRimPercent)) / (boardHeightInGrids * 100);
    int pixelHalfWidth = (boardWidthInPixels * (100 - outerRimPercent)) / (boardWidthInGrids * 100); 
    
    // hatszg "sorok"
    for ( int row = 0; row < boardHeightInHexagons; row++ )
      {
      // grid-kzppont magassgok
      gridY = getGridY( row );
      // hatszg oszlopok
      for ( int col = 0; col < boardWidthInHexagons; col++ )
        {
        // grid kzppont x-ek
        gridX = getGridX(row, col );
        paint.setColor( colorFromTouchCode( touchCodeFromPosition( row, col ), false ) );
        canvas.drawPath( hexagonPath(gridX, gridY), paint);
        
        paint.setColor( colorFromTouchCode( touchCodeFromPosition( row, col ), true ) );
        canvas.drawPath( hexagonPath(gridX, gridY, pixelHalfWidth, pixelQuaterHeight), paint);
        
        Scribe.debug("touchCode: " + touchCodeFromPosition( row, col ) +
            " ret: " + touchCodeFromColor(layoutMap.getPixel(getPixelX( gridX ), getPixelY( gridY ) )) +
            " color: " + Integer.toHexString( colorFromTouchCode( touchCodeFromPosition( row, col ), true ) ) +
            " r: " + row + " c: " + col); 
        }
      }
    }
  
  public Bitmap getMap()
    {
    createMap();
    return layoutMap;
    }
  
  /**
   ** CREATE LAYOUT SKIN
   **/
  
  /*
   * Not all layout can be stored as bitmap because of memory problems.
   * Now only one layout is cached in layoutSkin.
   * The process could be quicker, if bitmaps (same size!) could be reused.
   * Now a new bitmap will be generated for every bitmap.
   */
  public Bitmap getLayoutSkin( int layout )
    {
    if ( !isValidLayout(layout) )
      layout = 0;
    
    if ( layoutSkin != null)
      {
      if ( this.layout == layout )
        return layoutSkin;
      
      else
        layoutSkin.recycle();
      }
    
    layoutSkin = createLayoutSkin( layout );      
    this.layout = layout;
    
    return layoutSkin;
    }
  
  public static final int BOARD_BACKGROUND = 0xFF777777;
  
  private Bitmap createLayoutSkin( int layout )
    {
    Bitmap skin = Bitmap.createBitmap( boardWidthInPixels, boardHeightInPixels, Bitmap.Config.RGB_565);
    Canvas canvas = new Canvas( skin );
    skin.eraseColor( BOARD_BACKGROUND );
    
    for (ButtonDescriptor buttonDescriptor : buttons[layout] )
      {
      drawButton( canvas, buttonDescriptor );
      }
    
    return skin;
    }
  
  private void drawButton( Canvas canvas, ButtonDescriptor buttonDescriptor )
    {
    if ( buttonDescriptor == null )
      return;
    
    // index (in buttons[][index]) == touchCode (this is always true)
    // Theoretically from index/touchCode the buttons position can be calculated.
    // BUT this is NOT obligatory!! So the buttons will store their position.

    // Would be a better idea to store gridX/gridY coords for buttons.
    int gridX = getGridX( buttonDescriptor.rowInHexagons, buttonDescriptor.columnInHexagons );
    int gridY = getGridY( buttonDescriptor.rowInHexagons );
    
    hexagonFillPaint.setColor( buttonDescriptor.color );
    canvas.drawPath( hexagonPath(gridX, gridY), hexagonFillPaint );
    canvas.drawPath( hexagonPath(gridX, gridY), hexagonStrokePaint );
    

    int pixelX = getPixelX( gridX );
    int pixelY = getPixelY( gridY );

    if ( buttonDescriptor.upperLabel == null )
      {
      // Short-label
      if ( buttonDescriptor.label.length() == 1 )
        {
        shortLabelPaint.setColor(buttonDescriptor.labelColor );
        canvas.drawText( buttonDescriptor.label , pixelX, pixelY + shortLabelOffset, shortLabelPaint );
        }
      // Long-label
      else
        {
        longLabelPaint.setColor(buttonDescriptor.labelColor );
        canvas.drawText( buttonDescriptor.label , pixelX, pixelY + longLabelOffset, longLabelPaint );
        }
      }
    else
      {
      // Lower short-label
      if ( buttonDescriptor.label.length() == 1 )
        {
        lowerShortLabelPaint.setColor(buttonDescriptor.labelColor );
        canvas.drawText( buttonDescriptor.label , pixelX, pixelY + lowerShortLabelOffset, lowerShortLabelPaint );
        }
      // Lower long-label
      else
        {
        lowerLongLabelPaint.setColor(buttonDescriptor.labelColor );
        canvas.drawText( buttonDescriptor.label , pixelX, pixelY + lowerLongLabelOffset, lowerLongLabelPaint );
        }
      
      // Upper short-label
      if ( buttonDescriptor.upperLabel.length() == 1 )
        {
        upperShortLabelPaint.setColor(buttonDescriptor.upperLabelColor );
        canvas.drawText( buttonDescriptor.upperLabel , pixelX, pixelY + upperShortLabelOffset, upperShortLabelPaint );
        }
      // Upper long-label
      else
        {
        upperLongLabelPaint.setColor(buttonDescriptor.upperLabelColor );
        canvas.drawText( buttonDescriptor.upperLabel , pixelX, pixelY + upperLongLabelOffset, upperLongLabelPaint );
        }
      }
    }
  
  /**
   * True if layout is a valid.
   * This can be checked before adding a button 
   */
  public static boolean isValidLayout( int layout )
    {
    if ( layout < 0 || layout >= LAYOUTS )
      return false;
    return true;
    }
  
  /**
   * True if this hexagonal position is a valid on this board
   * This can be checked before adding a button  
   */
  public boolean isValidPosition( int row, int column )
    {
    if ( row < 0 || row >= boardHeightInHexagons )
      return false;
    if ( column < 0 || column >= boardWidthInHexagons )
      return false;
    return true;
    }
  
  public static String StringPrelude( String string, int prelude )
    {
    if ( string == null )
      return null;
    if ( prelude >= string.length() )
      return string;
    if ( prelude <= 0 )
      return "";
    return string.substring( 0, prelude );
    }
  
  /**
   * 
   * @param layout
   * @param row
   * @param column
   * @param code
   * @param backgroundColor
   * @param label
   * @param labelColor
   * @param upperLabel
   * @param upperLabelColor
   * @return
   * @throws IllegalArgumentException
   */
  public boolean addButton( int layout, int row, int column,  
      char code,
      int backgroundColor, 
      String label, int labelColor, 
      String upperLabel, int upperLabelColor ) 
      throws IllegalArgumentException
    {
    if ( !isValidLayout( layout ) )
      {
      throw new IllegalArgumentException("This layout is not valid! Button cannot be added!");
      }
  
    if ( !isValidPosition( row, column ) )
      {
      throw new IllegalArgumentException("This button position is not valid! Button cannot be added!");
      }
    
    boolean ret = true;
    ButtonDescriptor button = new ButtonDescriptor();
    
    button.rowInHexagons = row;
    button.columnInHexagons = column;

    button.boardCode = code;

    button.color = backgroundColor;

    button.label = StringPrelude( label, 10 );
    button.labelColor = labelColor;

    button.upperLabel = StringPrelude( upperLabel, 10 );
    button.upperLabelColor = upperLabelColor;
    
    // put in its position
    int index = touchCodeFromPosition( row, column );
    
    if ( buttons[layout][index] != null )
      ret = false;
    
    buttons[layout][index] = button;
  
    return ret;
    }
  
  }




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