// AsciiFactoryImpl.java
// $Id: AsciiFactoryImpl.java,v 1.7 1997/07/31 09:59:28 bmahe Exp $  
// (c) COPYRIGHT MIT and INRIA, 1997.
// Please first read the full copyright statement in file COPYRIGHT.html

package w3c.tools.resources.ascii;

import java.util.*;

import w3c.util.*;
import w3c.tools.resources.*;
import w3c.tools.resources.converter.*;

class AsciiField extends java.beans.PropertyDescriptor {
  Class type = null;
  int fieldno = -1;

  protected String toString(Object value) 
    throws NoSuchConverterException
  {
    return ConvertRegistry.toString(value);
  }

  protected Object toObject(String str) 
    throws NoSuchConverterException
  {
    return ConvertRegistry.toObject(type,str);
  }

  protected boolean check(Object value) {
    return (type.isInstance(value));
  }

  protected Object defaultValue() {
    try {
      return ConvertRegistry.defaultValue(type);
    } catch (NoSuchConverterException ex) {
      System.out.println(ex.getMessage());
      ex.printStackTrace();
      return null;
    }
  }

  AsciiField(String name, String type, int fieldno) 
    throws java.beans.IntrospectionException
  {
    super(name, null, null);
    try {
      this.type    = Class.forName(type);
      this.fieldno = fieldno;
    } catch (Exception e) {
      throw new java.beans.IntrospectionException(e.getMessage());
    }
  }
}

/**
 * 
 * The specification is a string:
 * <pre>
 * spec      := (item-decl)*
 * item-decl := empty | (name '=' type)
 * type      := 'i' | 'l' | 'b' | 's'
 * </pre>
 * <p>The specification string <code>name=s</code> means that the first field
 * of the file is to represent attribute <em>name</em> as a String.
 */

public class AsciiFactoryImpl implements AsciiResourceFactory {
  /**
   * The specification of the source for the corresponding saver.
   */
  protected String spec = null;
  /**
   * Parsed factory specification (maps prop name to type/index info)
   */
  protected Hashtable parsedSpec = null;
  /**
   * Orderered sequence of fields within a record.
   */
  protected AsciiField fields[] = null;
  /**
   * The ASCII resource saver this factory is attached to.
   */
  protected AsciiResourceSaver saver = null;

  /**
   * Parse the given specification.
   * Output both the <code>parsedSpec</code> hashtable, and the
   * <code>fields</code> sequence of fields in records.
   */

  protected void parseSpecification() {
    StringTokenizer st      = new StringTokenizer(spec, ":");
    int             fieldno = 0;
    while ( st.hasMoreTokens() ) {
      String item = (String) st.nextToken();
      StringTokenizer it = new StringTokenizer(item, "=");
      String name = (String) it.nextToken();
      String type = (String) it.nextToken();
      // FIXME checks omitted here
      AsciiField field = null;
      try {
	field = new AsciiField(name, type, fieldno);
      } catch (Exception ex) {
	ex.printStackTrace();
      }
      parsedSpec.put(name, field);
      AsciiField nf[] = new AsciiField[fieldno+1];
      System.arraycopy(fields, 0, nf, 0, fields.length);
      fields = nf;
      fields[fieldno++] = field;
    }
  }

  public Enumeration getProperties() {
    return parsedSpec.elements();
  }

  /**
   * Create a resource out of a set of String attributes.
   * @param items The String items to build the resource from.
   * @param off Offset in above array.
   * @param len Length of usefull fields.
   */
 
  public Object[] readResource(String items[], int off, int len) 
    throws ResourceInitException
  {
    try {
      // Convert strings into appropriate values:
      Object values[] = new Object[len];
      for (int i = 0 ; --len >= 0 ; i++) 
	values[i] = fields[i].toObject(items[i]);
      return values;
    } catch (NoSuchConverterException ex){
      System.out.println(ex.getMessage());
      ex.printStackTrace();
      throw new ResourceInitException("read resource : "+ex.getMessage());
    }
  }

  public Object[] getDefaultValues() {
    Object values[] = new Object[fields.length];
    int i = 0;
    while (i < fields.length) {
      values[i] = fields[i].defaultValue();
      i++;
    }
    return values;
  }

  /**
   * Write that resource back as a String.
   * @param values The set of values to be written back.
   * @return A String representation for that resource.
   */

  public String writeResource(Object values[]) 
  {
    // Checks:
    if ( values.length != fields.length )
      throw new IllegalArgumentException("incorrect size");
    // Stringify:
    try {
      StringBuffer sb = new StringBuffer();
      for (int i = 0 ; i < values.length-1 ; i++) {
	sb.append(fields[i].toString(values[i]));
	sb.append(saver.separator);
      }
      sb.append(fields[fields.length-1].toString(values[fields.length-1]));
      return sb.toString();
    } catch (NoSuchConverterException ex) {
      throw new IllegalArgumentException(ex.getMessage());
    }
  }

  /**
   * Fetch the given property out of the given values.
   * @param name Name of the property to fetch.
   * @param values Set of values to fetch the property from.
   * @return An Object instance.
   */

  public Object getValue(Object values[], String name) 
    throws IllegalAccessException
  {
    AsciiField field = (AsciiField) parsedSpec.get(name);
    if ( field == null )
      throw new IllegalAccessException("no field \""+name+"\"");
    return values[field.fieldno];
  }


  public Object[] getValues(Object values[], String names[]) 
    throws IllegalAccessException
  {
    Object result[] = new Object[names.length];
    for (int i = 0 ; i < names.length ; i++) {
      AsciiField field = (AsciiField) parsedSpec.get(names[i]);
      if ( field == null )
	throw new IllegalAccessException("no field \""+names[i]+"\"");
      result[i] = values[field.fieldno];
    }
    return result;
  }

    
  /**
   * Set the given property to the given value.
   * @param name The name of the property to set.
   * @param values The set of values to write to.
   * @param value The new value for that property.
   */
    
  public void setValue(Object values[], String name, Object value) 
    throws IllegalAccessException
  {
    AsciiField field = (AsciiField) parsedSpec.get(name);
    if ( field == null )
      throw new IllegalAccessException("no field \""+name+"\"");
    if ( ! field.check(value) ) 
      throw new IllegalArgumentException("invalid value \""+value+"\"");
    values[field.fieldno] = value;
    //	saver.modified = true;
  }
    
  public void setValues(Object values[], String names[], Object newvalues[]) 
    throws IllegalAccessException, IllegalArgumentException
  {
    if ( names.length != newvalues.length )
      throw new IllegalArgumentException("names and values don't match");
    for (int i = 0 ; i < names.length ; i++) {
      AsciiField field = (AsciiField) parsedSpec.get(names[i]);
      if ( field == null )
	throw new IllegalAccessException("no field \""+names[i]+"\"");
      if ( ! field.check(values[i]) ) 
	throw new IllegalArgumentException("invalid value");
      values[field.fieldno] = values[i];
    }
    //	saver.modified = true;
  }
     
  /**
   * The spec describes individual items of each line of the ASCII file.
   * @param spec The specification to parse the source of the saver.
   */

  protected AsciiFactoryImpl(AsciiResourceSaver saver, String spec) {
    this.saver      = saver;
    this.spec       = spec;
    this.fields     = new AsciiField[0];
    this.parsedSpec = new Hashtable(11);
    parseSpecification();
  }
}
