Listing of Source ../source/GOF/GofMainFrameSplitHostFieldIdentifier.java
package se.entra.phantom.server;

import java.util.Enumeration;
import java.util.Vector;
import java.util.NoSuchElementException;

/**
 * This class takes all the host fields and creates GofHostFields from them. When creating the 
 * GofHostFields, it will not take into account if the area to identify is smaller than the 
 * actual screen, or if there is a popup window on the screen. These limitations will be taken 
 * into account when fetching the GofHostFields with one of the two methods for fetching.
 * @author J. Bergström
 */
public class GofMainFrameSplitHostFieldIdentifier extends GofHostFieldIdentifierAdapter
{
  // ---------------
  // CLASS VARIABLES
  // ---------------

  /**
   * The characters that are accepted as a numeric characters.
   */
  private static final String NUM_CHARS = "0123456789-+,. ";

  // ------------------
  // INSTANCE VARIABLES
  // ------------------

  private GofHostField[][] sortGofHostField;

  // ----------------
  // INSTANCE METHODS
  // ----------------

  /**
   * This method must create the GofHostFields from the HostScreen's HostFields. It is up this 
   * method to decide if a HostField should be split up to several GofHostFields. The newly 
   * created GofHostFields must be stored internally in the class, since the method GetHostFields 
   * must be able to return them.
   * @param screen The host screen to process.
   */
  @Override
  public void identifyHostFields( HostScreen screen )
  {
    gofHostFields = new Vector<GofHostField>( );
    Vector<GofHostField> tempGofHostFields = new Vector<GofHostField>( );

    int w = screen.getWidth( );
    int h = screen.getHeight( );

    sortGofHostField = new GofHostField[h][w];
    for( int i = 0; i < h; i++ )
    {
      for( int j = 0; j < w; j++ )
      {
        sortGofHostField[i][j] = null;
      }
    }

    int hfX, hfY, restLen;
    String hfText;
    for( HostField hf = screen.getFirstFieldAbsolute( 0 ); hf != null; hf = hf.getNextField( ) )
    {
      hfX = hf.x;
      hfY = hf.y;
      restLen = hf.length;
      hfText = screen.getStringAbsolute( hfX, hfY, restLen );
      while( restLen > 0 )
      {
        int startpos = hf.length - restLen;
        int hfCx = restLen;
        if( hfX + restLen > w )
          hfCx = w - hfX;
        restLen = restLen - hfCx;

        GofHostField gofHostField = new GofHostField( hf, 
                                                      hfX, 
                                                      hfY, 
                                                      hfCx, 
                                                      hfText.substring( startpos, startpos + hfCx ), 
                                                      hf.isEmpty( screen ) );
        tempGofHostFields.addElement( gofHostField );

        if( restLen > 0 )
        {
          hfX = 0;
          // Must be the host screen height (e.g. 80x24), not screen height (e.g. 80x25).
          // Fix this later!
          hfY = ( hfY + 1 ) % h;
        }
      }
    }

    Vector<GofColumnElement>[] columnElements = createColumnElements( tempGofHostFields, w );

    for( int i = 0, s = columnElements.length; i < s; i++ )
    {
      for( Enumeration<GofColumnElement> e1 = columnElements[i].elements( ); e1.hasMoreElements( ); )
      {
        GofColumnElement gce = e1.nextElement( );
        if( gce.colsGofHostFields.size( ) == 1 )
        {
          GofHostField gofHostField = gce.colsGofHostFields.elementAt( 0 );
          if( gofHostField.isProtected( ) == false )
            addToSorter( gofHostField );
          else
            splitGofHostField( screen, gofHostField );
        }
        else
        {
          if( gce.isEditable( ) == true )
          {
            for( Enumeration<GofHostField> e2 = gce.colsGofHostFields.elements( ); e2.hasMoreElements( ); )
            {
              GofHostField gofHostField = e2.nextElement( );
              addToSorter( gofHostField );
            }
          }
          else
          {
            splitColumnElement( gce );
          }
        }
      }
    }
    for( int i = 0; i < h; i++ )
    {
      for( int j = 0; j < w; j++ )
      {
        if( sortGofHostField[i][j] != null )
        {
          gofHostFields.addElement( sortGofHostField[i][j] );
        }
      }
    }
  }

  /**
   * Creates column elements from GofHostFields. These column elements will later be analyzed to
   * find logical words that spans over all the lines in the column.
   * @param gofHostFields The GofHostFields to create column elements from.
   * @param w             The width of the screen in characters.
   * @return An array of Vectors containing the column elements.
   */
  @SuppressWarnings("unchecked")
  private Vector<GofColumnElement>[] createColumnElements( Vector<GofHostField> gofHostFields, int w )
  {
    Vector<GofColumnElement>[] gceList = new Vector [w];
    for( int x = 0; x < w; x++ )
      gceList[x] = new Vector<GofColumnElement>( );

    for( int i = 0, s = gofHostFields.size( ); i < s; i++ )
    {
      GofHostField ghf = gofHostFields.elementAt( i );
      int x = ghf.getX( );
      int y = ghf.getY( );

      GofColumnElement prevGce;
      try
      {
        prevGce = gceList[x].lastElement( );
      }
      catch( NoSuchElementException e )
      {
        prevGce = null;
      }

      int cx = ghf.getCx( );
      if( prevGce != null && ( prevGce.startLine + prevGce.height ) == y && prevGce.width == cx )
      {
        prevGce.addGofHostField( y, ghf );
      }
      else
      {
        GofColumnElement gce = new GofColumnElement( x, y, ghf );
        gceList[x].addElement( gce );
      }
    }
    return gceList;
  }

  /**
   * Splits a single GofHostField into several GofHostFields.
   * @param screen       The HostScreen to which the GofHostField belongs to.
   * @param gofHostField The GofHostField that should be split.
   */
  private void splitGofHostField( HostScreen screen, GofHostField gofHostField )
  {
    String text = gofHostField.getText( );
    int hfStart = gofHostField.getX( );

    Vector<GofHostFieldWord> words = getWords( text );
    boolean doSplit;
    for( int i = 0, s = words.size( ); i < s; i++ )
    {
      doSplit = false;
      GofHostFieldWord ghfw1 = words.elementAt( i );
      int startCol = hfStart + ghfw1.startpos;
      if( i == s - 1 )
      {
        // Last word.
        doSplit = true;
      }
      else
      {
        GofHostFieldWord ghfw2 = words.elementAt( i + 1 );
        
        if( ghfw2.startpos - ( ghfw1.startpos + ghfw1.text.length( ) ) > 1 )
        {
          doSplit = true;
        }
        else
        {
          if( ( ghfw1.isNumeric( ) && !ghfw2.isNumeric( ) ) ||
              ( !ghfw1.isNumeric( ) && ghfw2.isNumeric( ) ) ||
              ( ghfw1.isNumeric( ) && ghfw2.isNumeric( ) ) )
          {
            doSplit = true;
          }
          else
          {
            ghfw2.text = ghfw1.text + " " + ghfw2.text;
            ghfw2.startpos = ghfw1.startpos;
          }
        }
      }
      if( doSplit == true )
      {
        GofHostField ghf = new GofHostField( gofHostField.getHostField( ), 
                                             startCol, 
                                             gofHostField.getY( ), 
                                             ghfw1.text.length( ), 
                                             ghfw1.text,
                                             gofHostField.isEmpty( ) );
        addToSorter( ghf );
      }
    }
  }

  /**
   * Splits the GofHostFields that belongs to a column element into several GofHostFields.
   * @param gce The GofColumnElement to split up.
   */
  private void splitColumnElement( GofColumnElement gce )
  {
    int w = gce.width;

    int[] c = new int[w];
    for( int i = 0; i < w; i++ )
      c[i] = 0;

    for( int i = 0, s1 = gce.colsGofHostFields.size( ); i < s1; i++ )
    {
      GofHostField ghf = gce.colsGofHostFields.elementAt( i );
      String text = ghf.getText( );
      for( int j = 0, s2 = text.length( ); j < s2; j++ )
      {
        if( text.charAt( j ) != ' ' )
          c[j] = 1;
      }
    }

    for( int i = 0, s1 = gce.colsGofHostFields.size( ); i < s1; i++ )
    {
      GofHostField ghf = gce.colsGofHostFields.elementAt( i );

      int start = getNextStart( c, 0 );
      int oldStart = start;
      int end = 0;
      boolean doSplit;
      while( start > -1 )
      {
        end = getNextEnd( c, start );
        start = oldStart;
        String text = ghf.getText( ).substring( start, end/* + 1*/ );

        doSplit = false;
        int nextStart = getNextStart( c, end + 1);
        if( nextStart != -1 )
        {
          int nextEnd = getNextEnd( c, nextStart );
          String nextText = ghf.getText( ).substring( nextStart, nextEnd/* + 1*/ );

          if( nextStart - end > 1 )
            doSplit = true;
          else
          {
            boolean isTextNumeric = isNumeric( text );
            boolean isNextTextNumeric = isNumeric( nextText );

            if( ( isTextNumeric && !isNextTextNumeric ) ||
                ( !isTextNumeric && isNextTextNumeric ) ||
                ( isTextNumeric && isNextTextNumeric ) )
              doSplit = true;
          }
        }
        else
          doSplit = true;
        if( doSplit == true )
        {
          GofHostField gofHostField = new GofHostField( ghf.getHostField( ), 
                                                        gce.startCol + start, 
                                                        ghf.getY( ), 
                                                        end - start + 1, 
                                                        ghf.getText( ).substring( start, end + 1 ),
                                                        ghf.isEmpty( ) );
          addToSorter( gofHostField );
          oldStart = nextStart;
        }
        else
        {
          oldStart = start;
        }
        start = nextStart;
      }
    }
  }

  /**
   * 
   */
  private int getNextStart( int[] c, int p )
  {
    int start = -1;
    for( int i = p; i < c.length; i++ )
    {
      if( c[i] == 1 )
      {
        start = i;
        break;
      }
    }
    return start;
  }

  /**
   * 
   */
  private int getNextEnd( int[] c, int p )
  {
    int end = c.length - 1;
    for( int i = p; i < c.length; i++ )
    {
      if( c[i] == 0 )
      {
        end = i;
        break;
      }
    }
    return end;
  }

  /**
   * Checks if a string is numeric.
   * @param text The text to check.
   * @return <code>true</code> if the string is numeric, <code>false</code> otherwise.
   */
  private boolean isNumeric( String text )
  {
    for( int i = 0, s = text.length( ); i < s; i++ )
    {
      char c = text.charAt( i );
      if( NUM_CHARS.indexOf( c ) == -1 )
        return false;
    }
    return true;
  }

  /**
   * Splits up a string into GofHostFieldWords.
   * @param text The string to split up.
   * @return A Vector containing the GofHostWords.
   */
  private Vector<GofHostFieldWord> getWords( String text )
  {
    Vector<GofHostFieldWord> words = new Vector<GofHostFieldWord>( );
    int wordStart = -1;
    for( int i = 0, s = text.length( ); i < s; i++ )
    {
      if( text.charAt( i ) == ' ' )
      {
        if( wordStart > -1 )
        {
          GofHostFieldWord ghfw = new GofHostFieldWord( text.substring( wordStart, i ), wordStart );
          words.addElement( ghfw );
          wordStart = -1;
        }
      }
      else
      {
        if( wordStart == -1 )
          wordStart = i;
      }
    }
    if( wordStart > -1 && wordStart < text.length( ) )
    {
      GofHostFieldWord ghfw = new GofHostFieldWord( text.substring( wordStart ), wordStart );
      words.addElement( ghfw );
    }

    return words;
  }

  /**
   *
   */
  private void addToSorter( GofHostField ghf )
  {
    int y = ghf.getY( );
    int x = ghf.getX( );
    sortGofHostField[y][x] = ghf;
  }

  // ==================================================================
  // GofHostFieldWord subclass
  // ==================================================================

  /**
   * A private inner class representing a single word in a host field.
   */
  private class GofHostFieldWord
  {
    /**
     * The words text.
     */
    private String text = "";

    /**
     * The start position within the host fields text.
     */
    private int startpos = -1;

	  // -----------
	  // CONSTRUCTOR
	  // -----------

    private GofHostFieldWord( String text, int startpos )
    {
      this.text = text;
      this.startpos = startpos;
    }

    // ----------------
    // INSTANCE METHODS
    // ----------------

    /**
     * Checks if the word is numeric or not.
     * @return <code>true</code> if the word is numeric,
     *         <code>false</code> otherwise.
     */
    private boolean isNumeric( )
    {
      for( int i = 0, s = text.length( ); i < s; i++ )
      {
        char c = text.charAt( i );
        if( NUM_CHARS.indexOf( c ) == -1 )
          return false;
      }
      return true;
    }
  }

  // ==================================================================
  // GofColumnElement subclass
  // ==================================================================

  /**
   * The GofColumnElement is a private inner class that is used to store information 
   * about the column elements created during the identification process of the list 
   * box. A column element is a column of one or more GofHostFields that starts in 
   * the same x-position, and has the same width.
   * <p>
   * The used GofColumnElements will be deleted when a new host screen is processed.
   */
  private class GofColumnElement
  {
    // ------------------
    // INSTANCE VARIABLES
    // ------------------

    /**
     * A Vector containing all GofHostFields that is part of this column element.
     */
    private Vector<GofHostField> colsGofHostFields;

    /** 
     * The column elements start column on the host screen.
     */
    private int startCol;

    /** 
     * The column elements start line on the host screen.
     */
    private int startLine;

    /**
     * The width of the column element in characters.
     */
    private int width;

    /**
     * The height of the column element in lines.
     */
    private int height;

    // -----------
    // CONSTRUCTOR
    // -----------

    /**
     * Creates a new Gof column element.
     * @param x   The column element's start column on the host screen.
     * @param y   The column element's start line on the host screen.
     * @param ghf The first GofHostField that belongs to this column element.
     */
    private GofColumnElement( int x, int y, GofHostField ghf )
    {
      startCol = x;
      startLine = y;
      width = ghf.getCx( );
      height = 1;

      colsGofHostFields = new Vector<GofHostField>( );
      colsGofHostFields.addElement( ghf );
    }

    // ----------------
    // INSTANCE METHODS
    // ----------------

    /**
     * Adds a GofHostField to this column element. The new GofHostField must be on 
     * the line following the last line in this column element.
     * @param y   The line on which this column element is located.
     * @param ghf The GofHostField to add.
     */
    private void addGofHostField( int y, GofHostField ghf )
    {
      height = y - startLine + 1;

      colsGofHostFields.addElement( ghf );
    }

    /**
     * Checks if the column is editable. The column is considered editable if it has one
     * ore more fields that is not protected.
     * @return <code>true</code> if the column is editable, <code>false</code> otherwise.
     */
    private boolean isEditable( )
    {
      boolean isEditable = false;
      for( int i = 0, s = colsGofHostFields.size( ); i < s; i++ )
      {
        GofHostField ghf = colsGofHostFields.elementAt( i );
        if( ghf.isProtected( ) == false )
          isEditable = true;
      }
      return isEditable;
    }
  }
}