Package de.desy.tine.server.equipment

Source Code of de.desy.tine.server.equipment.TEquipmentModule$usrNameHndlr

/*
* Created on Nov 5, 2004
*
* To change the template for this generated file go to
* Window>Preferences>Java>Code Generation>Code and Comments
*
* CHANGES
* 2005-02-09 JW * main() removed; see de.desy.tine.client.test.sine
* 2005-02-10 JW * Constructors made public
*               * registerProperty(), registerDeviceName() made public
*          * getExportPro[pertyNames(), getExportDeviceNames() added (wrappers)
*               * Made (almost...) independent from TEquipmentModuleFactory-Singleton
*          * SetBackgroundTask removed; belongs to EqmFactory.
*          * CreateEquipmentModule removed / replaced by initialize().
* 2004-02-14 JW * old stock properties replaced by TPropertyList
*               * Stock Equipment function removed, replaced by property handlers
* 2004-02-15 JW * registerStockProperties changed; register only those properties which
*           are actually supported.
* 13.7.05 * PD added getExportInformationFromFile()
*
*/
package de.desy.tine.server.equipment;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentLinkedQueue;

import de.desy.tine.addrUtils.TSrvEntry;
import de.desy.tine.client.*;
import de.desy.tine.csvUtils.*;
import de.desy.tine.dataUtils.TDataTime;
import de.desy.tine.dataUtils.TDataType;
import de.desy.tine.definitions.*;
import de.desy.tine.endianUtils.Swap;
import de.desy.tine.headers.TContract;
import de.desy.tine.queryUtils.PropertyQuery;
import de.desy.tine.queryUtils.TPropertyQuery;
import de.desy.tine.queryUtils.XPropertyQuery;
import de.desy.tine.server.alarms.*;
import de.desy.tine.server.connections.TClient;
import de.desy.tine.server.connections.TClientEntry;
import de.desy.tine.server.connections.TContractTable;
import de.desy.tine.server.devices.TDevice;
import de.desy.tine.server.devices.TDeviceList;
import de.desy.tine.server.histories.THistoryRecord;
import de.desy.tine.server.histories.THistoryRecordStruct;
import de.desy.tine.server.histories.THistorySpecification;
import de.desy.tine.server.logger.*;
import de.desy.tine.server.properties.TExportProperty;
import de.desy.tine.server.properties.TMetaProperties;
import de.desy.tine.server.properties.TPropertyDescription;
import de.desy.tine.server.properties.TPropertyEGU;
import de.desy.tine.server.properties.TPropertyHandler;
import de.desy.tine.server.properties.TPropertyList;
import de.desy.tine.server.properties.TStockProperties;
import de.desy.tine.startup.TInitializer;
import de.desy.tine.startup.TInitializerFactory;
import de.desy.tine.stringUtils.StringToName;
import de.desy.tine.stringUtils.WildcardMatch;
import de.desy.tine.structUtils.TStructRegistry;
import de.desy.tine.types.*;
import de.desy.tine.xmlUtils.AlarmDefinitionCfg;
import de.desy.tine.xmlUtils.DeviceCfg;
import de.desy.tine.xmlUtils.EqmCfg;
import de.desy.tine.xmlUtils.FecCfg;
import de.desy.tine.xmlUtils.NameCfgList;
import de.desy.tine.xmlUtils.PropertyCfg;

/**
* A server handles all data requests through its equipment module. 
*
* An exported device server refers to one and only one equipment module. The
* equipment module will have a property list and a device list associated with it.
* @author duval
*/
public class TEquipmentModule
{
  public Object eqmMutex = new Object();
  private String context; // the server context (necessary here?)
  private String subsystem;
  private String exportName = null; // the device server name
  private String moduleName = null; // the 'local' name
  private String groupName = null; // the device group (if any)
  private String groupDevicePrefix = null; // group device prefix (if any)
  private String groupDevicePostfix = null; // group device postfix (if any)
  private int groupIndex = 0// the device group index (if any)
  private String master; // failover master (if set)
  private String slaveMaster; // failover slave master (if set and eqm is a slave)
  private int failoverType = TEquipmentModuleFactory.FAILOVER_NONE;
  public int getFailoverType() { return failoverType; }
  protected TEquipmentModuleFactory gEqmFactory = null;
  public TEquipmentModuleFactory getEquipmentModuleFactory()
  {
    if (gEqmFactory == null) gEqmFactory = TEquipmentModuleFactory.getInstance();
    return gEqmFactory;
  }
  public class ACLGroup
  {
    protected String groupName;
    protected LinkedList<String> members = new LinkedList<String>();
  }
  protected LinkedList<String> gRegisteredUsersList = new LinkedList<String>();
  protected LinkedList<String> gRegisteredGroupsList = new LinkedList<String>();
  protected LinkedList<String> gRegisteredNetsList = new LinkedList<String>();
  protected LinkedList<String> gGCastNetsList = new LinkedList<String>();
  protected HashMap<Integer,TAlarmDefinition> alarmDefinitionList = new HashMap<Integer,TAlarmDefinition>();
  protected boolean gRejectAllNets = false;
  public class PrpDbaItem
  {
    String prp;
    String usr;
    long lastAccess;
    InetAddress addr;
    int deadband;
    int access;
    PrpDbaItem(String property,int access,int deadbandInMilliseconds)
    {
      prp = new String(property);
      this.access = access;
      deadband = deadbandInMilliseconds;
    }
  }
  protected HashMap<String,PrpDbaItem> dbgList = new HashMap<String,PrpDbaItem>();
  protected void addPrpDbaItem(String property,int access,int deadbandInMilliseconds)
  {
    PrpDbaItem dba = null;
    if (dbgList.containsKey(property))
    {
      dba = dbgList.get(property);
      dba.deadband = deadbandInMilliseconds;
      dba.access = access;
    }
    else
    {
      dba = new PrpDbaItem(property,access,deadbandInMilliseconds);
      dbgList.put(property, dba);
    }
  }
  protected PrpDbaItem getPrpDbaItem(String property)
  {
    return dbgList.get(property);
  }
  public void dumpDeadbands()
  {
    Set<String> prpSet = dbgList.keySet();
    if (prpSet == null)
    {
      TLinkFactory.dbgPrint("there are no registered deadband access properties!");
      return;
    }
    TLinkFactory.dbgPrint("current deadband access for "+getExportName()+":");
    TLinkFactory.dbgPrint("\tProperty : deadband, applies to, last access");
    Iterator<String> it = prpSet.iterator();
    String prp;
    PrpDbaItem dba;
    while (it.hasNext())
    {
      prp = it.next();
      if ((dba=dbgList.get(prp)) == null) continue;
      TLinkFactory.dbgPrint("\t"+prp+" : "+dba.deadband+" ms, "+
        TAccess.toString((short)dba.access)+", "+
          TDataTime.toString(dba.lastAccess)+", "+
          dba.usr+", "+dba.addr.getHostAddress());
    }   
  }
  public HashMap<Integer, TAlarmDefinition> getAlarmDefinitionList() { return alarmDefinitionList; }
  protected HashMap<String,TPropertySignalHandler> prpSigHdlrList = new HashMap<String,TPropertySignalHandler>();
  /**
   * Registers a property signal handler for the given property
   *
   * Passing a 'null' handler will remove any property signal handler which
   * might be associated with the property in question.
   *
   * @param property is the designated property for which signals are to be
   * raised.
   * @param handler is the property signal handler function which should be called
   * when the property in question is being accessed.
   *
   * @return 0 upon success or a TINE error code.
   */
  public int registerPropertySignalHandler(String property,TPropertySignalHandler handler)
  {
    if (!propertyList.hasProperty(property)) return TErrorList.illegal_property;
    if (handler == null)
    {
      prpSigHdlrList.remove(property);
    }
    else
    {
      prpSigHdlrList.put(property, handler);
    }
    return 0;
  }
  protected void sendPropertySignal(String prp,TContract con,int sig,int sts)
  {
    TPropertySignalHandler psh = prpSigHdlrList.get(prp);
    if (psh == null) return;
    psh.handler(sig, prp, con, sts);
  }
  /**
   * Inserts an alarm definition into the alarm definition table.
   *
   * As an alternative to the <local name>-alarms.csv configuration file,
   * the front end server can make use of this API call in order to fill
   * in the alarm definition table describing locally generated alarms.
   * This is particularly useful for embedded platforms where there is
   * no file system, or where a TINE server is used as a translation layer
   * and needs to map a given alarm system onto the TINE alarm system.
   *
   * @param code is a specific alarm code to apply the alarm definition to.
   *        (Use the method without this parameter to simply apply the
   *        code registered in the TAlarmDefinition instance.)
   * @param adef is an instance of an Alarm Definition Structure (ADS)
   *        containing the alarm table information which is to be appended
   *        to the alarm definition table
   *       
   * @return 0 or a TINE error code.
   */
  public int addAlarmDefinition(int code,TAlarmDefinition adef)
  {
    if (code < 0 || adef == null) return TErrorList.argument_list_error;
    alarmDefinitionList.put(new Integer(code),adef);
    return 0;
  }
  /**
   * Inserts an alarm definition into the alarm definition table.
   *
   * As an alternative to the <local name>-alarms.csv configuration file,
   * the front end server can make use of this API call in order to fill
   * in the alarm definition table describing locally generated alarms.
   * This is particularly useful for embedded platforms where there is
   * no file system, or where a TINE server is used as a translation layer
   * and needs to map a given alarm system onto the TINE alarm system.
   *
   * @param adef is an instance of an Alarm Definition Structure (ADS)
   *        containing the alarm table information which is to be appended
   *        to the alarm definition table
   *       
   * @return 0 or a TINE error code.
   */
  public int addAlarmDefinition(TAlarmDefinition adef)
  {
    if (adef == null) return TErrorList.argument_list_error;
    return addAlarmDefinition(adef.getAlarmCode(),adef);
  }
  public TAlarmDefinition getAlarmDefinition(int code)
  {
    return alarmDefinitionList.get(new Integer(code));
  }
  TPropertyList stockList; // = new TPropertyList(); // a list or a query fcn
  TPropertyList propertyList = new TPropertyList(); // a list or a query fcn
  TDeviceList deviceList = new TDeviceList();
  int numDevicesFromExportsFile = 0;
  public int getNumberDevicesFromExportsFile() { return numDevicesFromExportsFile; }
  public TDeviceList getDeviceList() { return deviceList; }
  public TPropertyList getPropertyList() { return propertyList; }
  /**
   * Obtains a TDevice instance for the input device name
   *
   * @param devName is the device name for which the TDevice
   *        instance is desired
   *       
   * @return a TDevice Instance or null
   */
  public TDevice getDevice(String devName)
  {
    return deviceList.getDevice(devName);
  }
  /**
   * Obtains a TDevice instance for the input device number
   *
   * @param devNumber is the device number for which the TDevice
   *        instance is desired
   *       
   * @return a TDevice Instance or null
   */
  public TDevice getDevice(int devNumber)
  {
    return deviceList.getDevice(devNumber);
  }
  public int getDeviceNumber(String devName)
  {
    try
    {
      if (devName.startsWith("#")) return Integer.parseInt(devName.substring(1));
      return deviceList.getDeviceNumber(devName);
    }
    catch (Exception e)
    {
      MsgLog.log("getDeviceNumber",e.toString(),TErrorList.code_failure,e,0);                         
      return -1;
    }
  }
  public int getDeviceNumber(String devName,String devProperty)
  {
    try
    {
      if (devName == null) return -TErrorList.argument_list_error;
      if (devName.startsWith("#")) return Integer.parseInt(devName.substring(1));
      if (deviceList.isPropertyOriented() && devProperty != null)
      {
        TExportProperty prp = propertyList.getFirstProperty(devProperty);
        if (prp != null)
        {
          ArrayList<String> lst = prp.getDeviceList();
          if (lst != null) return lst.indexOf(devName);
        }
      }
      return deviceList.getDeviceNumber(devName);
    }
    catch (Exception e)
    {
      MsgLog.log("getDeviceNumber",e.toString(),TErrorList.code_failure,e,0);                         
      return -1;
    }
  }
  protected TAlarmDynSet almDynSet = new TAlarmDynSet();
  protected String[] gPropertyNameList = null;
  protected LinkedList<TAlarmWatchEntry> gAlarmWatchList = new LinkedList<TAlarmWatchEntry>();
  protected LinkedList<THistoryRecord> gLclHstList = new LinkedList<THistoryRecord>();
  private boolean useMonthlyHistoryFiles = false;
  public void setUseMSecHistoryTimestamps(boolean useMsecHistoryTimestamps)
  {
    useMinimalStorage = !useMsecHistoryTimestamps;
  }
  private boolean useMinimalStorage = false;
  public boolean isUseMinimalStorage() { return useMinimalStorage; }
  public boolean isUseMSecHistoryTimestamps() { return !useMinimalStorage; }
  public void setUseMonthlyHistoryFiles(boolean useMonthlyFiles)
  {
    useMonthlyHistoryFiles = useMonthlyFiles;
  }
  public boolean isUseMonthlyHistoryFiles() { return useMonthlyHistoryFiles; }
  public THistoryRecord getLocalHistoryRecord(String property,String device)
  {
    return getLocalHistoryRecord(property,device,0,TFormat.CF_NULL);
  }
  public THistoryRecord getLocalHistoryRecord(String property,String device,int size,short format)
  {
    int listsize = gLclHstList.size();
    THistoryRecord lhr = null;
    if (property == null || property.length() == 0) return null;
    for (int i=0; i<listsize; i++)
    {
      lhr = (THistoryRecord)gLclHstList.get(i);
      if (lhr.getPrp().compareTo(property) != 0) continue;
      if (device != null && device.length() > 0 &&
          !device.startsWith("#") &&
          lhr.getDev().compareToIgnoreCase(device) != 0)
        continue;
      if (size > 0 && lhr.getArraySize() != size) continue;
      if (format != TFormat.CF_NULL && lhr.getFmt() != (byte)format) continue;
      return lhr;
    }
    return null;
  }
  /**
   * Inserts a property to be monitored into the local alarm server's Watch Table.
   *
   * Certain alarms are to be set whenever the value of a property exceeds a
   * definable threshold. Such alarms can be managed automatically by the
   * local alarm server if the alarm criteria are entered into the alarm watch
   * table. This can be achieved by calling this routine (or supplying a
   * startup configuration file almwatch.csv).
   *
   * @param deviceName is the device name associated with the property to be
   *        called by the local alarm server.
   * @param propertyName is the property which is to be called by the
   *        local alarm server.
   * @param dataSize is the data array size to be called by the local alarm server.
   * @param dataFormat is the TINE data format to be called by the local alarm server
   * @param alarmSystem is the alarm system identifier to be associated with the alarm.
   *        The value of '0' signals the Central Alarm Server to apply the designated
   *        alarm system code for the server in question.
   * @param alarmSeverity is the severity of the alarm issued when the data returned
   *        by the call exceed the given thresholds.
   * @param alarmCode is the designated alarm code to apply to any alarm condition.
   *        Pass a '0' to distinguish between the alarmCodeHigh and alarmCodeLow
   *        conditions (next 2 parameters).
   * @param alarmCodeHigh is the alarm code to apply when the high threshold value
   *        is exceeded by the readback data. (parameter alarmCode must be '0').
   *        If both alarmCode and alarmCodeHigh are '0', then the code
   *        TErrorList.value_too_high (or TErrorList.warn_too_high) is applied.
   * @param alarmCodeLow is the alarm code to apply when the low threshold value
   *        is exceeded by the readback data. (parameter alarmCode must be '0').
   *        If both alarmCode and alarmCodeLow are '0', then the code
   *        TErrorList.value_too_low (or TErrorList.warn_too_low) is applied.
   * @param alarmWatchThreshold is an intance of a TAlarmWatchThreshold which
   *        defines the threshold conditions for applying an alarm.
   * @return 0 or an TINE error code (e.g. TErrorList.illegal_property).
   */
  public int addAlarmWatchTableEntry(String deviceName,String propertyName,int dataSize,int dataFormat,
               int alarmSystem,int alarmSeverity,int alarmCode,int alarmCodeHigh,int alarmCodeLow,
               TAlarmWatchThreshold alarmWatchThreshold)
  {
    TExportProperty p = propertyList.getFirstProperty(propertyName);
    if (p == null) return TErrorList.illegal_property;
    TPropertyDescription pd = p.getDescription();
    short arrayType = pd == null ? TArrayType.AT_UNKNOWN : pd.getArrayType();   
    TAlarmWatchEntry awe = new TAlarmWatchEntry(this,deviceName,
        propertyName,dataSize,dataFormat,
        arrayType,alarmSystem,
        alarmSeverity,alarmCode,
        alarmCodeHigh,alarmCodeLow,alarmWatchThreshold);       
    gAlarmWatchList.add(awe);
    return 0;
  }
  /**
   * Inserts a property to be monitored into the local alarm server's Watch Table.
   *
   * Certain alarms are to be set whenever the value of a property exceeds a
   * definable threshold. Such alarms can be managed automatically by the
   * local alarm server if the alarm criteria are entered into the alarm watch
   * table. This can be achieved by calling this routine (or supplying a
   * startup configuration file almwatch.csv).
   *
   * @param deviceName is the device name associated with the property to be
   *        called by the local alarm server.
   * @param propertyName is the property which is to be called by the
   *        local alarm server.
   * @param dataSize is the data array size to be called by the local alarm server.
   * @param dataFormat is the TINE data format to be called by the local alarm server
   * @param alarmSeverity is the severity of the alarm issued when the data returned
   *        by the call exceed the given thresholds.
   * @param alarmCodeHigh is the alarm code to apply when the high threshold value
   *        is exceeded by the readback data.
   *        If alarmCodeHigh is '0', then the code
   *        TErrorList.value_too_high (or TErrorList.warn_too_high) is applied.
   * @param alarmCodeLow is the alarm code to apply when the low threshold value
   *        is exceeded by the readback data.
   *        If alarmCodeLow is '0', then the code
   *        TErrorList.value_too_low (or TErrorList.warn_too_low) is applied.
   * @param alarmWatchThreshold is an intance of a TAlarmWatchThreshold which
   *        defines the threshold conditions for applying an alarm.
   * @return 0 or an TINE error code (e.g. TErrorList.illegal_property).
   */
  public int addAlarmWatchTableEntry(String deviceName,String propertyName,int dataSize,int dataFormat,
      int alarmSeverity,int alarmCodeHigh,int alarmCodeLow,
      TAlarmWatchThreshold alarmWatchThreshold)
  {
    return addAlarmWatchTableEntry(deviceName,propertyName,dataSize,dataFormat,
        0,alarmSeverity,0,alarmCodeHigh,alarmCodeLow,alarmWatchThreshold);
  }
  /**
   * Inserts a property to be monitored into the local alarm server's Watch Table.
   *
   * Certain alarms are to be set whenever the value of a property exceeds a
   * definable threshold. Such alarms can be managed automatically by the
   * local alarm server if the alarm criteria are entered into the alarm watch
   * table. This can be achieved by calling this routine (or supplying a
   * startup configuration file almwatch.csv).
   *
   * @param deviceName is the device name associated with the property to be
   *        called by the local alarm server.
   * @param propertyName is the property which is to be called by the
   *        local alarm server.
   * @param dataSize is the data array size to be called by the local alarm server.
   * @param dataFormat is the TINE data format to be called by the local alarm server
   * @param alarmSeverity is the severity of the alarm issued when the data returned
   *        by the call exceed the given thresholds.
   * @param alarmCode is the designated alarm code to apply to any alarm condition.
   *        Pass a '0' to distinguish between the alarmCodeHigh and alarmCodeLow
   *        conditions (next 2 parameters).
   * @param alarmWatchThreshold is an instance of a TAlarmWatchThreshold which
   *        defines the threshold conditions for applying an alarm.
   * @return 0 or an TINE error code (e.g. TErrorList.illegal_property).
   */
  public int addAlarmWatchTableEntry(String deviceName,String propertyName,int dataSize,int dataFormat,
      int alarmSeverity,int alarmCode,
      TAlarmWatchThreshold alarmWatchThreshold)
  {
    return addAlarmWatchTableEntry(deviceName,propertyName,dataSize,dataFormat,
        0,alarmSeverity,alarmCode,0,0,alarmWatchThreshold);
  }
  /**
   * Inserts a local history element into the local history server.
   *
   * A server can instruct the local history server to keep a history of
   * the given property by utilizing this call. The local history server
   * will periodically call the property as specified according to the
   * following input parameters.
   *
   * @param recordIndex is the local history index to be identified with
   *        this local history element (Note: this must be unique with this
   *        server process and within the history data repository).
   * @param device is the device name to be associated with the property
   *        name supplied as the second parameter.
   * @param property is the requested property for which a history is to
   *        be kept
   * @param dataSize is the length of the local history call.
   * @param dataFormat is the TINE format of the local history call.
   *
   * @note This call will use the default history acquisition specifications
   * of 1 Hz polling and archive intervals, 5 minutes short term and 1 month
   * long term depths, 30 minutes archive heartbeat and 10 percent tolerance.
   *
   * @return
   */
  public int addLocalHistoryRecord(int recordIndex,String device,String property,int dataSize,int dataFormat)
  {
    short arrayType = TArrayType.AT_UNKNOWN;
    TExportProperty p = propertyList.getFirstProperty(property);
    if (p != null)
    {
      TPropertyDescription pd = p.getDescription();
      arrayType = pd == null ? TArrayType.AT_UNKNOWN : pd.getArrayType();
    }
    return addLocalHistoryRecord(recordIndex,device,property,dataSize,dataFormat,arrayType);
  }
  public int addLocalHistoryRecord(int recordIndex,String device,String property,int dataSize,int dataFormat,int arrayType)
  {
    THistorySpecification spc = THistorySpecification.getDefaultInstance();
    return addLocalHistoryRecord(recordIndex,device,property,dataSize,dataFormat,arrayType,spc);
  }
  /**
   * Inserts a local history element into the local history server.
   *
   * A server can instruct the local history server to keep a history of
   * the given property by utilizing this call. The local history server
   * will periodically call the property as specified according to the
   * following input parameters.
   *
   * @param recordIndex is the local history index to be identified with
   *        this local history element (Note: this must be unique with this
   *        server process and within the history data repository).
   * @param device is the device name to be associated with the property
   *        name supplied as the second parameter.
   * @param property is the requested property for which a history is to
   *        be kept
   * @param dataSize is the length of the local history call.
   * @param dataFormat is the TINE format of the local history call.
   * @param arrayType is the input property's array type.  Passing a value
   *        of '0' (AT_UNKNOWN) will instruct the subsystem to determine
   *        the property's array type from the property registry.
   * @param histSpec is an instance of t THistorySpecification object
   *        which instructs the local history subsystem how to maintain
   *        the local history information (e.g. short and long term
   *        storage depths, filtering conditions, polling and archive
   *        intervals).
   *
   * @return
   */
  public int addLocalHistoryRecord(int recordIndex,String device,String property,int dataSize,int dataFormat,int arrayType,THistorySpecification histSpec)
  {
    if (device == null || property == null || dataSize < 1 || recordIndex < 0) return TErrorList.invalid_parameter;
    if (isHstRecordInLst(device,property,dataSize,dataFormat)) return 0;
    if (arrayType == TArrayType.AT_UNKNOWN)
    {
      TExportProperty p = propertyList.getFirstProperty(property);
      if (p == null) return TErrorList.illegal_property;
      TPropertyDescription pd = p.getDescription();
      arrayType = pd == null ? TArrayType.AT_UNKNOWN : pd.getArrayType();   
    }
    THistoryRecord hst = new THistoryRecord(this,device,property,dataSize,dataFormat,arrayType,recordIndex,histSpec);
    if (useMinimalStorage) hst.setUseMinimalStorage(true);
    if (useMonthlyHistoryFiles) hst.setUseMonthlyHistoryFiles(true);
    gLclHstList.add(hst);
    if (histSpec.getDepthLong() <= 0 && histSpec.getDepthShortInSeconds() > 3600 * 6)
    { // no long term storage and more than 6 hours worth of short-term !
      hst.needsToFillSTS = true;
    }
    return 0;   
  }
  private boolean isHstRecordInLst(String dev,String prp,int siz,int fmt)
  {
    if (gLclHstList == null) return false;
    THistoryRecord thr;
    Iterator<THistoryRecord> it = gLclHstList.iterator();
    while (it.hasNext())
    {
      thr = it.next();
      if (thr.getPrp().compareTo(prp) != 0) continue;
      if (thr.getDev().compareTo(dev) != 0) continue;
      if (thr.getArraySize() != siz) continue;
      if (thr.getFmt() != fmt) continue;
      return true;
    }
    return false;
  }
  private boolean isIdle = false;
  public boolean isIdleState() { return isIdle; }
  public void setIdleState(boolean value)
  {
    isIdle = value;
    Iterator<TEquipmentBackgroundTask> li = bkgTasks.iterator();
    while (li.hasNext())
    { // pass on to any associated background tasks ...
      li.next().setIdleState(value);
    }
  }
  public boolean hasInitialized = false;
  public boolean isRegistered; // registered with name server
  public boolean hasCasAttached = false;
  public boolean casReadRequired = false;
  public boolean grpRegistered = false;
  public boolean registerAsMaster = false;
  private String casName;
  public String getCasName() { return casName; }
  //TAlarmTable alarms; // active alarms + alarm definitions
  int numClients;
  int numContracts;
  boolean isCasInClientList()
  {
    if (casName == null) return false;
    TClientEntry[] tce = getClientList();
    for (int i=0; i<tce.length; i++)
      if (tce[i].cln.userName.compareTo(casName) == 0)
      {
        casReadRequired = true;
        hasCasAttached = true;
        return true;
      }
    return false;
  }
  public boolean hasExclusiveRead(String propertyName,boolean checkLocked)
  {
    if (checkLocked)
    { // check exclusive read condition in a locked state
      if (accessLock.getLockType() == AccessLock.LOCK_UNLOCKED)
      { // not locked !
        return false;     
      }
    }
    TExportProperty txp = propertyList.getFirstProperty(propertyName);
    if (txp == null) return false;
    if (!checkLocked) return txp.hasUnlockedExclusiveRead;
    return txp.hasExclusiveRead;
  }
  public boolean hasExclusiveRead(String propertyName)
  {
    return hasExclusiveRead(propertyName,true);
  }
  class AccessLock
  {
    static final int LOCK_UNLOCKED = 0;
    static final int LOCK_PREEMPTIVE = 1;
    static final int LOCK_PERSISTENT = 2;
    static final int LOCK_ABORT = 3;
    static final int LOCK_XREAD = 0x100;
    private TClient cln;
    private int lockType = LOCK_UNLOCKED;
    private long lockDuration = 0;
    private long lockStart;
    long getLockDuration() { return lockDuration; }
    int getLockType() { return lockType; }
    long getLockStart() { return lockStart; }
    void checkAccessLocks()
    {
      long t = System.currentTimeMillis();
      if (t > lockStart + lockDuration)
      {
        lockStart = 0;
        lockType = LOCK_UNLOCKED;
      }
    }
    boolean clientCanWrite(TClient tc)
    {
      if (lockType == LOCK_UNLOCKED) return true;
      if (tc.IPaddress == cln.IPaddress &&
          tc.userName.compareTo(cln.userName) == 0 &&
          tc.port == cln.port)
        return true;
      return false;
    }
    int setAccessLock(TClient tc,int type,int duration)
    {
      boolean passed = false;
      int baseType = type & 0xff;
      if (baseType < LOCK_UNLOCKED || baseType > LOCK_ABORT) return TErrorList.out_of_range;
      if (clientCanWrite(tc)) passed = true;
      else if ((lockType & 0xff) != LOCK_PERSISTENT) passed = true;
      if (!passed) return TErrorList.access_denied;
      if (duration < 0) duration = 0;
      if (duration > TEquipmentModuleFactory.MAXIMUM_LOCK_DURATION)
        duration = TEquipmentModuleFactory.MAXIMUM_LOCK_DURATION;
      if (!getEquipmentModuleFactory().gPutCommandsInFeclog)
      {
        String cmdString = "COMMAND ACCESSLOCK" + " called by " + tc.userName +
          " from addr " + tc.IPaddress.getHostAddress() + " : ";
        if (lockType != type) TFecLog.log(cmdString);
      }
      if (baseType == LOCK_ABORT || baseType == LOCK_UNLOCKED)
      {
        lockStart = 0;
        lockType = LOCK_UNLOCKED;
      }
      else
      {
        cln = tc;
        lockType = type;
        lockDuration = duration * 1000;
        lockStart = System.currentTimeMillis();       
      }
      return 0;
    }
    String[] getAccessLockAsString()
    {
      String[] s = new String[3];
      if (lockType == LOCK_UNLOCKED)
      {
        s[0] = s[1] = "";
      }
      else
      {
        int secs = (int)((lockStart + lockDuration - System.currentTimeMillis())/1000);
        if (secs < 0) secs = 0;
        s[0] = cln.userName;
        s[1] = cln.IPaddress.getHostAddress();
        s[2] = new String("" + secs + " sec remaining");
      }
      return s;
    }
  }
  public AccessLock accessLock = new AccessLock();
  // REMOVED TEquipmentFunction stkMod; // stock meta property eqp. fcn.
  //TEquipmentFunction stkFcn; // stock property eqp. fcn.
  TEquipmentFunction eqpFcn = null;
  //TEquipmentBackgroundTask eqpBkg;
  private TEquipmentInitRoutine eqpIni;
  public TEquipmentInitRoutine getInitializationRoutine() { return eqpIni; }
  public void setInitializationRoutine(TEquipmentInitRoutine initRoutine)
  {
    eqpIni = initRoutine;
  }
  private TEquipmentExitRoutine eqpExi;
  public TEquipmentExitRoutine getExitRoutine() { return eqpExi; }
  public void setExitRoutine(TEquipmentExitRoutine exitRoutine)
  {
    eqpExi = exitRoutine;
  }
  public NAME16[] getRegisteredUsers() { return StringToName.stringSetToName16(gRegisteredUsersList); }
  public int addRegisteredUser(String newUser)
  {
    if (newUser == null) return TErrorList.argument_list_error;
    newUser = newUser.toUpperCase();
    synchronized (gRegisteredUsersList)
    {
      if (gRegisteredUsersList.contains(newUser)) return TErrorList.already_assigned;
      gRegisteredUsersList.add(newUser);
    }
    return 0;
  }
  public int addRegisteredUser(LinkedList<String> lst, String newUser)
  {
    if (lst == null || newUser == null) return TErrorList.argument_list_error;
    if (!newUser.startsWith("<")) newUser = newUser.toUpperCase();
    synchronized (lst)
    {
      if (lst.contains(newUser)) return TErrorList.already_assigned;
      lst.add(newUser);
    }
    return 0;
  }
  public int removeRegisteredUser(String oldUser)
  {
    if (oldUser == null) return TErrorList.argument_list_error;   
    oldUser = oldUser.toUpperCase();
    synchronized (gRegisteredUsersList)
    {
      if (!gRegisteredUsersList.contains(oldUser)) return TErrorList.invalid_name;   
      gRegisteredUsersList.remove(oldUser);   
    }
    return 0;
  }
  public void removeAllRegisteredUsers()
  {
    synchronized (gRegisteredUsersList)
    {
      gRegisteredUsersList.clear();
    }
  }
  public int addRegisteredNet(LinkedList<String> lst,String newNet)
  {
    if (lst == null || newNet == null) return TErrorList.argument_list_error;
    synchronized (lst)
    {
      if (lst.contains(newNet)) return TErrorList.already_assigned;
      lst.add(newNet);   
    }
    return 0;
  }
  public int addRegisteredNet(String newNet)
  {
    if (newNet == null) return TErrorList.argument_list_error;
    synchronized (gRegisteredNetsList)
    {
      if (gRegisteredNetsList.contains(newNet)) return TErrorList.already_assigned;
      gRegisteredNetsList.add(newNet);   
    }
    return 0;
  }
  public int removeRegisteredNet(String oldNet)
  {
    if (oldNet == null) return TErrorList.argument_list_error;
    synchronized (gRegisteredNetsList)
    {
      if (!gRegisteredNetsList.contains(oldNet)) return TErrorList.invalid_name;   
      gRegisteredNetsList.remove(oldNet);
    }
    return 0;
  }
  public void removeAllRegisteredNets()
  {
    synchronized (gRegisteredNetsList)
    {
      gRegisteredNetsList.clear();
    }
  }
  public NAME16[] getRegisteredNets() { return StringToName.stringSetToName16(gRegisteredNetsList); }
  public int addGCastNet(String newNet)
  {
    if (gGCastNetsList.contains(newNet)) return TErrorList.already_assigned;
    gGCastNetsList.add(newNet);
    return 0;
  }
  public int removeGCastNet(String oldNet)
  {
    if (!gGCastNetsList.contains(oldNet)) return TErrorList.invalid_name;   
    gGCastNetsList.remove(oldNet);
    return 0;
  }
  public void removeAllGCastNets()
  {
    gGCastNetsList.clear();
  }
  public NAME16[] getGCastNets() { return StringToName.stringSetToName16(gGCastNetsList); }
  public void setCompletionString(String completionString)
  {
    TContractTable tct = this.getTEqmFactory().getCurrentContractEntry();
    if (tct != null) tct.compString = completionString;
  }
  /**
   * Sets the desired local equipment module name for this equipment
   * module.
   *
   * This name is not directly visible to client-side API calls
   * outside the server process, although it will show up in log files
   * and in address information.  Client API calls make use of the
   * 'exported' device server name.
   * It largely serves as an internal
   * reference name and is currently restricted to 6 characters.
   * This must
   * be set prior to initializing the server with systemInit() from
   * TEquipmentModuleFactory or prior to adding the equipment module to
   * the Factory in case it has already been initialized.     *
   * @param localName is the desired local equipment module name.
   *
   * @return 0 upon success.
   *
   * @see setExportName().
   */
  public int setLocalName(String localName)
  {
    moduleName = localName;
    if (localName.length() > TStrings.EQM_NAME_SHORTSIZE)
    {
      moduleName = localName.substring(0,TStrings.EQM_NAME_SHORTSIZE);
      TFecLog.log("module name "+localName+" too long; truncating to "+moduleName);
    }
    TStrings.validateLocalName(moduleName);
    getEquipmentModuleFactory().registrationPending = true;
    return 0;
  }
  /**
   * @return the current setting of the local equipment module name.
   */
  public String getLocalName() { return moduleName; }
  public String getModuleName() { return moduleName; }
  /**
  * Directly sets the export name to use for this equipment module, i.e.
  * the 'device server' name.
  *
  * @param exportName is the desired export name.  This must
  * be set prior to initializing the server with systemInit() from
  * TEquipmentModuleFactory or prior to adding the equipment module to
  * the Factory in case it has already been initialized.  Also the current setting
  * must be a null string, otherwise it will not be accepted.  Such
  * parameters may also be established via a configuration database.
  *
  * @return 0 upon success.
  */
  public int setExportName(String exportName)
  {
    if (this.exportName == null)
    {
      this.exportName = new String(exportName);
      TStrings.validateExportName(this.exportName);
      return 0;
    }
    return TErrorList.already_assigned;
  }
  /**
   * @return the current setting for the server's export name.
   */
  public String getExportName()
  {
    return exportName;
  }
  /**
   * Sets an optional group server name which this server should join.
   *
   * A device server exported under a particular name represents a physical
   * device server managing its devices and properties itself (unless explicitly
   * redirecting).  A device server can also join a 'group server' by
   * calling this routine at initialization.  The group server will be
   * created at the GENS if it does not already exist and the device server
   * will become a member of the group.  The metric order of the device server's
   * devices can be set by calling setGroupIndex().  The device names can
   * also be pre-pended or post-pended with specific tags (if necessary) by
   * calling setGroupDevicePrefix() or setGroupDevicePostfix().
   *
   * @param grpName is the name of the group server to join. A null or empty value (true in most cases) indicates that the
   * server is not to be a member of a group server.
   */
  public void setGroupName(String grpName)
  {
    if (groupName == null)
    {
      groupName = new String(grpName);
      TStrings.validateExportName(groupName);
      grpRegistered = false;
      getEquipmentModuleFactory().registrationPending = true;
    }
  }
  /**
   * Gets the current group server name setting.
   *
   * A device server exported under a particular name represents a physical
   * device server managing its devices and properties itself (unless explicitly
   * redirecting).  A device server can also join a 'group server' by
   * calling setGroupName() at initialization.  The group server will be
   * created at the GENS if it does not already exist and the device server
   * will become a member of the group. 
   *
   * @return the name of the group server with this device server will or has
   * joined.  A null or empty value (true in most cases) indicates that the
   * server is not a member of a group server.
   */
  public String getGroupName() { return groupName; }
  /**
   * Sets the device name prefix to use if the device server joins a group.
   *
   * @param prefix is the desired device name prefix.
   *
   * @see setGroupName
   */
  public void setGroupDevicePrefix(String prefix)
  {
    if (prefix != null && prefix.length() > 8)
    {
      prefix = prefix.substring(0, 8);
    }
    groupDevicePrefix = prefix;
  }
  /**
   * Gets the device name prefix to use if the device server joins a group.
   *
   * @return the current setting of the device name prefix.
   *
   * @see setGroupName
   */
  public String getGroupDevicePrefix()
  {
    return groupDevicePrefix;
  }
  /**
   * Sets the device name postfix to use if the device server joins a group.
   *
   * @param postfix is the desired device name postfix.
   *
   * @see setGroupName
   */
  public void setGroupDevicePostfix(String postfix)
  {
    if (postfix != null && postfix.length() > 8)
    {
      postfix = postfix.substring(0, 8);
    }
    groupDevicePostfix = postfix;
  }
  /**
   * Gets the device name postfix to use if the device server joins a group.
   *
   * @return the current setting of the device name postfix.
   *
   * @see setGroupName
   */
  public String getGroupDevicePostfix()
  {
    return groupDevicePostfix;
  }
  /**
   * Sets the device list metric to use if the device server joins a group.
   *
   * @param metric is the desired device group metric.  This determines
   * the order of the device server devices in the group server's device
   * list.  (default = 0).  Group members with the same metric will have
   * their devices listed in the order they were originally received by
   * the group server.
   *
   * @see setGroupName
   */
  public void setGroupIndex(int metric)
  {
    groupIndex =  metric;
  }
  /**
   * Gets the device list metric used if the device server joins a group.
   *
   * @return the current setting of this value.
   */
  public int getGroupIndex() { return groupIndex; }
  /**
   * Directly sets the export context to use for this equipment module.
   *
   * @param exportContext is the desired export context.  This must
   * be set prior to initializing the server with systemInit() from
   * TEquipmentModuleFactory or prior to adding the equipment module to
   * the Factory in case it has already been initialized. Such
   * parameters may also be established via a configuration database.
   *
   * @return 0 upon success.
   */
  public int setContext(String exportContext)
  {
    if (context == null && exportContext != null)
    {
      context = exportContext;
      TStrings.validateContextName(exportContext);
      return 0;
    }
    return TErrorList.already_assigned;
  }
  /**
   * @return the current setting for the export context.
   */
  public String getContext()
  {
    return context;
  }
  public String getPrimaryContext()
  {
    if (context == null) return context;
    if (subsystem == null || subsystem.length() == 0) return context;
    int idx = context.indexOf('.');
    if (idx < 1) return context;
    return context.substring(0, idx);
  }
  public int setMaster(String expMaster)
  {
    if (master == null && expMaster != null)
    {
      master = expMaster;
      TStrings.validateExportName(expMaster);
      if (master.length() > 0)
      {
        failoverType = TEquipmentModuleFactory.FAILOVER_MASTER;
        registerAsMaster = true;
      }
      else
      {
        failoverType = TEquipmentModuleFactory.FAILOVER_NONE;
        registerAsMaster = false;
      }
    }
    return 0;
  }
  public String getMaster() { return master; }
  public int setSlaveMaster(String expMaster, String slvMaster)
  {
    if (master != null && expMaster != null)
    {
      master = expMaster; slaveMaster = slvMaster;
      TStrings.validateExportName(expMaster);
      TStrings.validateExportName(slvMaster);
      if (master.length() > 0 && slaveMaster.length() > 0)
      {
        failoverType = TEquipmentModuleFactory.FAILOVER_SLAVE;
        startFailoverMonitor();
      }
      else
      {
        failoverType = TEquipmentModuleFactory.FAILOVER_NONE;
      }
      registerAsMaster = false;
    }
    return 0;
  }
  public String getSlaveMaster() { return slaveMaster; }
  private int foPollingInterval = 1000;
  private int foThreshold = 5;
  private boolean foMonitorStarted = false;
  class FailOverCallback implements TLinkCallback
  {
    int errcnt=0;
    int last_cc=0;
    boolean ismaster = false;
    public void callback(TLink link)
    {
      int cc = link.getLinkStatus();
      if (last_cc != cc)
      {
        TFecLog.log("failover slave link status changed from "+
            TErrorList.getErrorString(last_cc)+" to "+TErrorList.getErrorString(cc));
        last_cc = cc;
      }
      if (cc != 0)
      {
        errcnt++;
        if (errcnt > foThreshold && !ismaster)
        { // need to take over as the master !
          getEquipmentModuleFactory().registrationPending = true;
          registerAsMaster = true;
          ismaster = true;
          ClsLog.log(getExportName()+" is taking over as "+getMaster()+" master",
              getContext(),"FAILOVER",gEqmFactory.getFecName(),ClsLog.ClogPriority.CLOG_PRIORITY_IMPORTANT,ClsLog.ClogStatus.CLOG_STATUS_WARN);
        }
        return;
      }
      errcnt = 0;
      ismaster = false;
    }   
  }
  private int startFailoverMonitor()
  {
    if (foMonitorStarted) return TErrorList.already_assigned;
    if (failoverType != TEquipmentModuleFactory.FAILOVER_SLAVE) return TErrorList.not_allowed;
    String mstr = getSlaveMaster();
    if (mstr == null || mstr.length() == 0) return TErrorList.not_allowed;
    String tgt = "/"+getContext()+"/"+mstr;
    int[] lval = new int[1];
    TDataType dout = new TDataType(lval);
    TLink lnk = new TLink(tgt,"SRVSTARTTIME",dout,null,TAccess.CA_READ);
    int id = lnk.attach(TMode.CM_TIMER, new FailOverCallback(), foPollingInterval);
    foMonitorStarted = true;
    TFecLog.log("failover slave link to "+tgt+" : "+(id < 0 ? lnk.getLastError() : "success"));
    return ( id < 0) ? -id : 0;
  }
  public TEquipmentModuleFactory getTEqmFactory() { return gEqmFactory; }
  public TClient getCaller()
  {
    TContractTable tct = gEqmFactory.getCurrentContractEntry();
    TClientEntry tce = null;
    if (tct != null)
    {
      try
      {
        tce = (TClientEntry)tct.clt.element();
      }
      catch (NoSuchElementException e)
      {
        DbgLog.log("getCaller", "contract does not have a client list");
      }
    }
    return tce != null ? tce.cln : null;
  }  
  public TClientEntry[] getClientList()
  {
    TContractTable tct = gEqmFactory.getCurrentContractEntry();
    return tct != null ? ((TClientEntry[])tct.clt.toArray(new TClientEntry[0])) : null;
  }
  protected int clearCASAlarmList()
  {
    String ctx = getPrimaryContext();
    String exp = getExportName();
    String eqm = getLocalName();
    if (ctx == null || exp == null || eqm == null) return TErrorList.invalid_name;
    if (eqm.compareTo(TSrvEntry.SRVEQM_NAME) == 0) return 0;
    String devName = "/" + ctx + "/CAS/" + exp;
    int cc = TErrorList.address_unknown;
    String fec = null;
    try
    {
      TLink lnk = new TLink(devName,"REMOVEALARMS",null,null,TAccess.CA_WRITE);
      cc = lnk.execute(200,true);
      fec = lnk.srvAddr.fecName;
      lnk.close();
    }
    catch (Exception e)
    { // a non-existent server runtime exception is often normal here !
      // just continue (in many cases, there is NO CAS in the corresponding context!)     
    }
    if (cc == 0)
    { // CAS exists in this context and is monitoring this FEC
      casReadRequired = true;
      casName = fec;
      TFecLog.log("clear alarm list at " + devName + "(" + casName + ")");
    }
    else
    {
      TFecLog.log("this server not monitored by a central alarm server");
    }
    return cc;
  }
  /**
   * Initializer used by constructors.
   *
   * @param name Module name.
   * @param eqpFunction The TEquipmentFunction to attach to the equipment module.
   */
  private void initialize(String name, TEquipmentFunction eqpFunction)
  {
    gEqmFactory = TEquipmentModuleFactory.getInstance();
    if (name != null)
    {
      moduleName = name.length() > TStrings.EQM_NAME_SHORTSIZE ? name.substring(0, TStrings.EQM_NAME_SHORTSIZE) : name;
    }
    else
    {
      moduleName = "";
    }
    if (eqpFunction != null)
    {
      this.eqpFcn = eqpFunction;
      this.eqpFcn.setEquipmentModule(this);
    }
    registerStockProperties();
    getRegisteredUsersFromFile(gRegisteredUsersList,gRegisteredGroupsList,null,null);
    if (getRegisteredNetsFromFile(gRegisteredNetsList,null,null))
    {
      if (gRegisteredNetsList.size() == 0) gRejectAllNets = true;
    }  
    getRegisteredAlarmDefinitionsFromFile();
    gEqmFactory.setPendingEqm(this);
    getExportInformationFromFile();
    gEqmFactory.addEquipmentModule(this);
    gEqmFactory.setPendingEqm(null);
    if (gEqmFactory.isInitialized())
    { // this is being called late (after the normal system start)
      if (!hasInitialized)
      { // have not yet initialized -> call startup routine myself !
        startup();
      }
    }
    hasInitialized = true;
  }
  private static final int PRE_DUMP_WINDOW = 300 * 1000;
  private long lastHistoryCycleTime = 0;
  public void setLastHistoryCycleTime(long time) { lastHistoryCycleTime = time; }
  private int historyCycleInterval = 1000; // milliseconds
  private boolean hstManifestDumped = false;
  private static final String hstmfHdr = "INDEX, LOCAL_NAME, PROPERTY, DEVICE, DATA_LENGTH, FORMAT, HEARTBEAT, POLLING_RATE, ARCHIVE_RATE, TOLERANCE, SHORT_DEPTH, LONG_DEPTH";
  private String getStrToleranceForManifest(float aTol, float pTol)
  {
    if (pTol > 0) return ""+pTol*100+"%";
    return ""+aTol;
  }
  public void dumpHistoryManifest()
  {
    int nHistoryRecords = gLclHstList.size();
    THistoryRecord hst = null;
    THistorySpecification spc = null;
    TInitializer initializer = TInitializerFactory.getInstance().getInitializer();
    String eqm = getLocalName();
    String filename = initializer.getFecHome()+File.separator+eqm;
    String nl = System.getProperty("line.separator");
    try
    {
      new File(filename).mkdirs();
      filename += File.separator+"hstmf.csv";
      FileWriter fw = new FileWriter(filename);
      String ln = hstmfHdr + nl;
      fw.write(ln);
      for (int i=0; i<nHistoryRecords; i++)
      {
        hst = (THistoryRecord)gLclHstList.get(i);
        if (hst.getEqm() != this) continue;
        spc = hst.getHspec();
        ln = ""+hst.getRcdIdx()+", "+eqm+", "+hst.getPrp()+", "+hst.getDev()+
             ", "+hst.getArraySize()+", "+ TFormat.toString(hst.getFmt())+
             ", "+spc.getHeartbeat()+", "+spc.getPollngRate()+", "+spc.getArchiveRate()+
             ", "+getStrToleranceForManifest(spc.getAbsoluteTolerance(),spc.getPercentTolerance())+
             ", "+spc.getDepthShort()+", "+spc.getDepthLong()+nl;
        fw.write(ln);   
      }   
      fw.close();
      MsgLog.log("dumpHistoryManifest", "wrote history manifest for <"+eqm+">",0,null,0);
    }
    catch (Exception e)
    {
      MsgLog.log("dumpHistoryManifest", "cannot write history manifest!",TErrorList.file_error,e,0);
    };
  }
  public void setHistoryCycleInterval(int cycleInterval)
  {
    historyCycleInterval = cycleInterval;
    TFecLog.log("THistoryRecord: set history cycle interval from "+historyCycleInterval+" to "+cycleInterval+" msec");
  }
  public int getHistoryCycleInterval() { return historyCycleInterval; }
  /**
   * Inserts local history data into the local history ring buffer.
   *
   * A server can insert data into the local history ring buffer directly
   * with this call. The inserted data behave in exactly the same manner
   * as if added via a call the the designated equipment module from the
   * local history subsystem.
   *
   * The information provided in the call MUST correspond to a registered
   * local history element or the call will fail. The server developer
   * can ensure that history data are inserted ONLY via this call
   * (and not via the local history subsystem) by trapping the CA_HIST
   * big in the access flag within the equipment module dispatch and
   * returning an error condition such as 'not_implemented'.
   *
   * @param property is the requested property for which a history record is being kept.
   * @param device is the device name for which a history record is being kept.
   * @param dout is aTDataType object specifying the array length, data format, and
   * timestamp and other data stamps of the history record along with the
   * accompanying data.
   *
   * @return 0 upon success.
   */
  public int appendHistoryData(String property,String device,TDataType dout)
  {
    int size = dout.dArrayLength;
    short format = dout.dFormat;
    THistoryRecord hst = getLocalHistoryRecord(property, device, size, format);
    if (hst == null) return TErrorList.un_allocated;
    if (dout.getTimeStamp() < hst.getTimestamp()) return TErrorList.out_of_sequence;
    synchronized (hst)
    { // don't concur with historyCycle
      hst.writeRecordToSTS(dout);
      if (needToArchiveRecord(hst))
      {
        hst.writeRecordToLTS(dout);
        if (hst.isPointOfInterest()) hst.writeRecordToPIF(dout);
      }
    }
    return 0;
  }
  protected void historyCycle(boolean isScheduled)
  {
    if (!hasInitialized) return;
    long thisCycleTime = System.currentTimeMillis();
    THistoryRecord hst = null;
    THistorySpecification spc = null;
    TDataType dout = null;
    TDataType din = new TDataType();
    TAccess access = new TAccess(TAccess.CA_HIST + TAccess.CA_READ);
    int cc = 0;
    boolean skip;
    if (!isScheduled && thisCycleTime-lastHistoryCycleTime < historyCycleInterval) return;
    lastHistoryCycleTime = thisCycleTime;
    int nHistoryRecords = gLclHstList.size();
    if (nHistoryRecords == 0) return;
    TEquipmentModuleFactory f = getEquipmentModuleFactory();
    f.setCurrentContractEntry(f.hstcon);
    for (int i=0; i<nHistoryRecords; i++)
    {
      hst = (THistoryRecord)gLclHstList.get(i);
      spc = hst.getHspec();
      if (spc.getDepthShort() <= 0) continue;
      if (hst.needsToFillSTS) hst.fillSTS();
      hst.setArchiveFileName(thisCycleTime); // will also remove outdated files if necessary
      skip = (thisCycleTime-hst.getTimestamp()) < spc.getPollngRate();
      if (hst.isScheduled()) skip = false;
      hst.setScheduled(false);
      if (skip) continue;
      dout = new TDataType(hst.getArraySize(),hst.getFmt());
      dout.setDataTimeStamp(TDataTime.getDataTimeStamp());
      dout.setSystemDataStamp(TEquipmentModuleFactory.getSystemStamp());
      cc = callProperty(hst.getPrp(),hst.getDev(),dout,din,access);
      if (cc != 0) continue;
      synchronized (hst)
      { // don't concur with appendHistoryData
        hst.writeRecordToSTS(dout);
        if (needToArchiveRecord(hst))
        {
          hst.writeRecordToLTS(dout);
          if (hst.isPointOfInterest()) hst.writeRecordToPIF(dout);
        }
      }
    }
    if (!hstManifestDumped &&
        lastHistoryCycleTime > getEquipmentModuleFactory().getServerStartTime() + PRE_DUMP_WINDOW)
    {
      dumpHistoryManifest();
      hstManifestDumped = true;
    }
    return;
  }
  private boolean needToArchiveRecord(THistoryRecord hst)
  {
    long ts = System.currentTimeMillis();
    THistorySpecification spc;

    if (hst == null) return false;
    if (!hst.isPastFilter()) return false;
    if((spc=hst.getHspec()) == null) return false;
    if (spc.getArchiveRate() < 0 ||
        spc.getHeartbeat() < 0   ||
        spc.getDepthLong() <= 0) return false;/* no long term storage */
    if (hst.getRngBufferSize() <= 0) return false;
    if (hst.getTimestamp() <= hst.getLastArchiveTime()) return false;/* data timestamp hasn't changed */
    if (!hst.isScheduled() &&
         ts < hst.getLastArchiveTime() + spc.getArchiveRate()) return false;
    if (ts >= hst.getLastArchiveTime() + spc.getHeartbeat()*1000) return true; /* archive no matter what */
    if (hst.isWithinTolerance()) return false;
    return true;   
  }
  private long lastAlarmCycleTime = 0;
  private long alarmCycleInterval = 500;
  protected void checkAlarmsInWatchTable()
  {
    long thisCycleTime = System.currentTimeMillis();
    if (thisCycleTime-lastAlarmCycleTime < alarmCycleInterval) return;
    lastAlarmCycleTime = thisCycleTime;
    TDataType dout = null;
    TDataType din = new TDataType();
    TAccess access = new TAccess(TAccess.CA_ALARM + TAccess.CA_READ);
    TAlarmWatchEntry awe = null;
    TAlarmDefinition adef = null;
    TAlarm alm = null;
    TDevice dev = null;
    int cc = 0, cnt = 0, siz = 0, sev = 0, devNr, code = 0, asys = 0;
    String tag = null;
    float fvals[] = null;
    int ivals[] = null;
    byte[] abytes = null;
    synchronized (gAlarmWatchList)
    {
      TEquipmentModuleFactory f = getEquipmentModuleFactory();
      f.setCurrentContractEntry(f.awtcon);
      int awlength = gAlarmWatchList.size();
      for (int i=0; i<awlength; i++)
      {
        awe = (TAlarmWatchEntry)gAlarmWatchList.get(i);
        int chk = awe.checkNormal();
        if (chk != 0)
        {
          ivals = new int[awe.getSiz()];
          dout = new TDataType(ivals);
          cc = callProperty(awe.getPrp(),awe.getDev(),dout,din,access);
        }
        else
        {
          fvals = new float[awe.getSiz()];
          dout = new TDataType(fvals);
          cc = callProperty(awe.getPrp(),awe.getDev(),dout,din,access);
        }
        if (cc != 0) continue;
        boolean hasAlarm = false;
        devNr = deviceList.getDeviceNumber(awe.getDev());
        cnt = 0; siz = awe.getSiz();
        for (int j=0; j<siz; j++)
        {
          if (awe.getAtyp() == TArrayType.AT_CHANNEL) devNr = j;
          if (awe.getAtyp() != TArrayType.AT_WAVEFORM && siz == deviceList.getNumberOfDevices())
            devNr = j; /* assume array of devices */
          dev = deviceList.getDevice(devNr);
          hasAlarm = false;
          if (ivals != null)
          {   
            int nval = awe.getNormal();
            if ((chk == 1 && nval != (awe.getMask() & ivals[j])) ||
                (chk == -1 && nval == (awe.getMask() & ivals[j])) )
            {
             if (TEquipmentModuleFactory.debugLevel > 1)
               DbgLog.log("checkAlarmsInWatchTable", awe.getPrp()+" "+awe.getDev()+
                   " alarm state ! : "+(awe.getMask() & ivals[j])+" vs "+nval);
              code = awe.getCode();
              if (code == 0) code = TErrorList.invalid_data;
              sev = awe.getSev();
              asys = awe.getAsys();
              hasAlarm = true;
            }
            abytes = Swap.LongToBytes(ivals[j]);
          }
          else if (fvals != null)
          {
            if (fvals[j] > awe.getHi())
            {
              if (TEquipmentModuleFactory.debugLevel > 1)
                DbgLog.log("checkAlarmsInWatchTable", awe.getPrp()+" "+awe.getDev()+
                    " alarm state ! : "+fvals[j]+" > "+awe.getHi());
              code = awe.getCode();
              if (code == 0) code = awe.getCodeHigh();
              if (code == 0) code = TErrorList.value_too_high;
              sev = awe.getSev();
              asys = awe.getAsys();
              tag = awe.getTagHigh();
              hasAlarm = true;
            }
            else if (fvals[j] > awe.getHiwarn())
            {
              if (TEquipmentModuleFactory.debugLevel > 1)
                DbgLog.log("checkAlarmsInWatchTable", awe.getPrp()+" "+awe.getDev()+
                    " warn state ! : "+fvals[j]+" > "+awe.getHiwarn());
              code = awe.getCode();
              if (code == 0) code = awe.getCodeHigh();
              if (code == 0) code = TErrorList.warn_too_high;         
              sev = awe.getSev()-2;
              asys = awe.getAsys();
              tag = awe.getTagHigh();
              hasAlarm = true;
            }
            else if (fvals[j] < awe.getLo())
            {
              if (TEquipmentModuleFactory.debugLevel > 1)
                DbgLog.log("checkAlarmsInWatchTable", awe.getPrp()+" "+awe.getDev()+
                    " alarm state ! : "+fvals[j]+" < "+awe.getLo());
              code = awe.getCode();
              if (code == 0) code = awe.getCodeLow();
              if (code == 0) code = TErrorList.value_too_low;         
              sev = awe.getSev();
              asys = awe.getAsys();
              tag = awe.getTagLow();
              hasAlarm = true;
            }
            else if (fvals[j] < awe.getLowarn())
            {
              if (TEquipmentModuleFactory.debugLevel > 1)
                DbgLog.log("checkAlarmsInWatchTable", awe.getPrp()+" "+awe.getDev()+
                    " warn state ! : "+fvals[j]+" < "+awe.getLowarn());
              code = awe.getCode();
              if (code == 0) code = awe.getCodeLow();
              if (code == 0) code = TErrorList.warn_too_low;         
              sev = awe.getSev()-2;
              asys = awe.getAsys();
              tag = awe.getTagLow();
              hasAlarm = true;
            }
            abytes = Swap.Float(fvals[j]);
          }
          if (!hasAlarm)
          { // run through clearAlarm()
            dev.clearAlarm(TErrorList.invalid_data,awe);
            dev.clearAlarm(TErrorList.value_too_high,awe);
            dev.clearAlarm(TErrorList.value_too_low,awe);
            dev.clearAlarm(TErrorList.warn_too_high,awe);
            dev.clearAlarm(TErrorList.warn_too_low,awe);
            if (awe.getCode() > 0) dev.clearAlarm(awe.getCode(),awe);
            if (awe.getCodeHigh() > 0) dev.clearAlarm(awe.getCodeHigh(),awe);
            if (awe.getCodeLow() > 0) dev.clearAlarm(awe.getCodeLow(),awe);
            continue;
          }
          if (code == 0) continue;
          if (!awe.isPastFilter()) continue;
          cnt = awe.getCnt() + 1;
          awe.setCnt(cnt);
          if (cnt > awe.getCntThreshold() && dev != null)
          {
            alm = dev.setAlarm(code,abytes,awe);
            if (alm != null)
            {
              adef = alm.getAlmDef();
              if (sev != 0) adef.setAlarmSeverity(sev);
              if (asys != 0) adef.setAlarmSystem(asys);
              if (tag != null && tag.length() > 0) adef.setAlarmTag(tag);
            }
          }
        }
        /* only clear the watch table counter when a complete pass has no alarms */
        if (cnt == 0) awe.setCnt(0);
      }
    }
  }
  private void updateAlarmDynSet()
  {
    long ts = System.currentTimeMillis();
    int tss = (int)(ts/1000);
    int tsu = (int)(ts%1000)*1000;
    if (deviceList.getNumberOfAlarms() <= TAlarm.getAlmCollapseWindow())
    { // no need to collapse
      if (almDynSet.starttime == 0) return;
      if ((almDynSet.descriptor & TAlarmDescriptor.TERMINATE) == TAlarmDescriptor.TERMINATE)
        return;
      almDynSet.descriptor = TAlarmDescriptor.TERMINATE;
      almDynSet.timestamp = tss;
      almDynSet.timestampUSec = tsu;
      return;
    }
    if (almDynSet.descriptor == 0 ||
        (almDynSet.descriptor & TAlarmDescriptor.TERMINATE) == TAlarmDescriptor.TERMINATE)
    { // either never set or last set to terminated -> this is now NEW !
      almDynSet.descriptor = TAlarmDescriptor.NEW;
      almDynSet.timestamp = tss;
      almDynSet.timestampUSec = tsu;
      almDynSet.starttime = almDynSet.timestamp;
      almDynSet.starttimeUSec = almDynSet.timestampUSec;
      return;
    }
    if (almDynSet.timestamp +  TAlarm.getAlmHeartbeatWindow() < tss)
    { //  set the heartbeat
      almDynSet.descriptor = TAlarmDescriptor.HEARTBEAT;
      almDynSet.timestamp = tss;
      almDynSet.timestampUSec = tsu;
      return;
    }   
  }
  protected void update()
  {
    if (isIdle) return;
    accessLock.checkAccessLocks();
    checkAlarmsInWatchTable();
    deviceList.refreshAlarms();
    updateAlarmDynSet();
    historyCycle(false);
  }
  /**
   * Registers the equipment module's stock properties
   */
  private int prpQueryCall(String devName, TDataType dout, TDataType din, TAccess devAccess)
  { // use in calls for "PROPS" or "PROPERTIES"
    int cc = 0;
    TDevice dev = getDeviceList().getDevice(devName);
    LinkedList<String> lst = null;
    boolean checkWildCard = (devName.compareTo("*") != 0 && devName.indexOf("*") != -1);
   
    if (dev != null && (lst=dev.getPropertyList()) != null)
    {
      String[] slst = new String[lst.size()];
      lst.toArray(slst);
      if (checkWildCard) slst = StringToName.matchStringsInArray(slst,devName);
      if (dout.getFormat() == TFormat.CF_USTRING)
      {
        USTRING[] ustr = new USTRING[slst.length];
        char[] cha;
        StringBuffer sb = new StringBuffer(80);
        TExportProperty prp;
        for (int i = 0; i<slst.length; i++)
        {
          ustr[i] = new USTRING();
          if ((prp = propertyList.getFirstProperty(slst[i])) == null) continue;
          cha = prp.getName().toCharArray();
          sb.insert(0,cha);
          for (int c=cha.length; c<32; c++) sb.insert(c,(char)0);
          cha = prp.getDescription().getText().toCharArray();
          sb.insert(32,cha);
          ustr[i].setValues(prp.getOutputSize(),0,0,prp.getOutputFormat(),new String(sb));
        }
        cc = dout.putData(ustr);
      }
      else
      {
        cc = dout.putData(StringToName.stringArrayToName64(slst));
      }
      if (dout.getArrayLength() > slst.length) dout.setArrayLength(slst.length);
    }
    else
    {
      String[] slst = new String[propertyList.countUniqueProperties()];
      propertyList.getPropertyNames().toArray(slst);
      if (checkWildCard) slst = StringToName.matchStringsInArray(slst,devName);
      if (dout.getFormat() == TFormat.CF_USTRING)
      {
        USTRING[] ustr = new USTRING[slst.length];
        char[] cha;
        StringBuffer sb = new StringBuffer(80);
        TExportProperty prp;
        for (int i = 0; i<slst.length; i++)
        {
          ustr[i] = new USTRING();
          if ((prp = propertyList.getFirstProperty(slst[i])) == null) continue;
          cha = prp.getName().toCharArray();
          sb.insert(0,cha);
          for (int c=cha.length; c<32; c++) sb.insert(c,(char)0);
          cha = prp.getDescription().getText().toCharArray();
          sb.insert(32,cha);
          ustr[i].setValues(prp.getOutputSize(),0,0,prp.getOutputFormat(),new String(sb));
        }
        cc = dout.putData(ustr);
      }
      else
      {
        cc = dout.putData(StringToName.stringArrayToName64(slst));
      }
      if (dout.getArrayLength() > slst.length) dout.setArrayLength(slst.length);
    }
    if (cc == 0)
    {
      if (propertyList.isDeviceOriented())
        cc = TErrorList.has_query_function | TErrorList.CE_SENDDATA;
    }
    return cc;     
  }
  private int getMetaPrpsCallFilter(TDataType din)
  {
    int filter = 0x7fffffff;
    if (din != null && TFormat.isSimpleString(din.dFormat))
    {
      String tgt = din.toString();
      if (tgt.startsWith("."))
      {
        filter = 0;
        String[] fltrs = tgt.split(",");
        for (String s : fltrs)
        {
          if (s.compareToIgnoreCase(".DESC") == 0 || s.compareToIgnoreCase(".DSC") == 0)
            filter |= TMetaProperties.DSC_FLTR;
          if (s.compareToIgnoreCase(".HIST") == 0 || s.compareToIgnoreCase(".HST") == 0)
            filter |= TMetaProperties.HST_FLTR;
          if (s.compareToIgnoreCase(".EGU") == 0) filter |= TMetaProperties.EGU_FLTR;
          if (s.compareToIgnoreCase(".XEGU") == 0) filter |= TMetaProperties.XEGU_FLTR;
          if (s.compareToIgnoreCase(".MAX") == 0) filter |= TMetaProperties.MAX_FLTR;
          if (s.compareToIgnoreCase(".XMAX") == 0) filter |= TMetaProperties.XMAX_FLTR;
          if (s.compareToIgnoreCase(".ONLINE") == 0) filter |= TMetaProperties.ONL_FLTR;
        }
      }
    }
    if (filter == 0) filter = 0x7fffffff;
    return filter;
  }
  private int metaprpQueryCall(String devName, TDataType dout, TDataType din, TAccess devAccess)
  { // use in calls for "METAPROPERTIES"
    int cc = 0, filter = getMetaPrpsCallFilter(din);
    TDevice dev = getDeviceList().getDevice(devName);
    LinkedList<String> lst = null;
   
    if (dev != null && (lst=dev.getPropertyList()) != null)
    {
      String[] slst = new String[lst.size()];
      lst.toArray(slst);
      LinkedList<TExportProperty> plst = new LinkedList<TExportProperty>();
      LinkedList<TExportProperty> thesePrps;
      for (int i = 0; i<slst.length; i++)
      {
        thesePrps = propertyList.getFilledMetaProperties(slst[i],filter);
        if (thesePrps.size() > 0) plst.addAll(thesePrps);
      }
      int size = plst.size();
      if (dout.getFormat() == TFormat.CF_USTRING)
      {
        USTRING[] ustr = new USTRING[size];
        char[] cha;
        StringBuffer sb = new StringBuffer(80);
        TExportProperty prp;
        for (int i = 0; i<size; i++)
        {
          ustr[i] = new USTRING();
          if ((prp = plst.get(i)) == null) continue;
          cha = prp.getName().toCharArray();
          sb.insert(0,cha);
          for (int c=cha.length; c<32; c++) sb.insert(c,(char)0);
          cha = prp.getDescription().getText().toCharArray();
          sb.insert(32,cha);
          ustr[i].setValues(prp.getOutputSize(),0,0,prp.getOutputFormat(),new String(sb));
        }
        cc = dout.putData(ustr);
      }
      else
      {
        slst = new String[size];
        for (int i=0; i<size; i++) slst[i] = plst.get(i).getName();
        cc = dout.putData(StringToName.stringArrayToName64(slst));
      }
      if (dout.getArrayLength() > slst.length) dout.setArrayLength(slst.length);
    }
    else
    {
      String[] slst = new String[propertyList.countUniqueProperties()];
      propertyList.getPropertyNames().toArray(slst);
      LinkedList<TExportProperty> plst = new LinkedList<TExportProperty>();
      LinkedList<TExportProperty> thesePrps;
      for (int i = 0; i<slst.length; i++)
      {
        thesePrps = propertyList.getFilledMetaProperties(slst[i],filter);
        if (thesePrps.size() > 0) plst.addAll(thesePrps);
      }
      int size = plst.size();
      if (dout.getFormat() == TFormat.CF_USTRING)
      {
        USTRING[] ustr = new USTRING[size];
        char[] cha;
        StringBuffer sb = new StringBuffer(80);
        TExportProperty prp;
        for (int i = 0; i<size; i++)
        {
          ustr[i] = new USTRING();
          if ((prp = plst.get(i)) == null) continue;
          cha = prp.getName().toCharArray();
          sb.insert(0,cha);
          for (int c=cha.length; c<32; c++) sb.insert(c,(char)0);
          cha = prp.getDescription().getText().toCharArray();
          sb.insert(32,cha);
          ustr[i].setValues(prp.getOutputSize(),0,0,prp.getOutputFormat(),new String(sb));
        }
        cc = dout.putData(ustr);
      }
      else
      {
        slst = new String[size];
        for (int i=0; i<size; i++) slst[i] = plst.get(i).getName();
        cc = dout.putData(StringToName.stringArrayToName64(slst));
      }
      if (dout.getArrayLength() > slst.length) dout.setArrayLength(slst.length);
    }
    if (cc == 0)
    {
      if (propertyList.isDeviceOriented())
        cc = TErrorList.has_query_function | TErrorList.CE_SENDDATA;
    }
    return cc;     
  }
  private int hstCall(String devName, TDataType dout, TDataType din, TAccess devAccess)
  {
    String tgt = null;
    if (din != null)
    { // input given ...
      switch (din.getFormat())
      {
        case TFormat.CF_TEXT:
        case TFormat.CF_NAME16:
        case TFormat.CF_NAME32:
        case TFormat.CF_NAME64:
          char[] tgtc = new char[64];
          din.getData(tgtc);
          tgt = new String(tgtc).trim();
          break;
      }
    }
    THistoryRecord[] hsts = (THistoryRecord[])gLclHstList.toArray(new THistoryRecord[0]);
    NAME64[] nams = null;
    THistoryRecordStruct[] hrs = null;
    if (dout.getFormat() == TFormat.CF_STRUCT)
    {
      hrs = new THistoryRecordStruct[hsts.length];
    }
    else
    {
      nams = new NAME64[hsts.length];
    }
    int n = 0;
    String p;
    for (int i=0; i<hsts.length; i++)
    {
      p = hsts[i].getPrp();
      if (tgt != null && p.compareToIgnoreCase(tgt) != 0) continue;
      if (hrs != null)
      {
        hrs[n++] = new THistoryRecordStruct(hsts[i].getDev(),hsts[i].getPrp(),hsts[i].getArraySize(),hsts[i].getFmt(),hsts[i].getHspec());
      }
      else
      {
        nams[n++] = new NAME64(p);
      }
    }
    return hrs != null ? dout.putData(hrs,n,0) : dout.putData(nams,n,0);
 
  private boolean stockPropertiesRegistered = false;
  void registerStockProperties()
  {
    if (stockPropertiesRegistered) return;
    final TEquipmentModule thisEqm = this;
    if (gEqmFactory == null) gEqmFactory = TEquipmentModuleFactory.getInstance();
    stockList = new TPropertyList();
    // TODO Separate handlers for overloaded properties
    stockList.addProperty(TStockProperties.SRVCOMMANDS, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return getTEqmFactory().callWrAccessTableInfo(moduleName,devName, dout, din, devAccess);
      }
    });
   
    TPropertyHandler propertiesStructHandler = new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        if (dout != null)
        {
          switch (dout.dFormat)
          { // TODO: tidy this copy-and-past job up !
            case TFormat.CF_STRUCT:
              if (dout.getTag().compareTo("PRPQSr4") == 0 || (dout.getArrayLength() % TPropertyQuery.sizeInBytes) == 0)
              {
                if (din != null)
                { // looking for a specific property ...
                  LinkedList<TExportProperty> propInstances;
                  String propertyName = din.toString();
                  propertyName = propertyName.replace('\n', (char) 0).trim();
                  propInstances = propertyList.getEqualProperties(propertyName);
                  if ((propInstances == null) || (propInstances.size() == 0))
                      return TErrorList.not_exported;
                  // fill in the targeted property info
                  THistoryRecord lhr = null;
                  TPropertyQuery[] prpq = new TPropertyQuery[propInstances.size()];
                  byte[] tba = new byte[prpq.length * TPropertyQuery.sizeInBytes];
                  Iterator<TExportProperty> it = propInstances.iterator();
                  for (int i = 0; (it.hasNext()) && (i < prpq.length); i++)
                  {
                    prpq[i] = new TPropertyQuery((TExportProperty) it.next(), propInstances.size() - 1);
                    if ((lhr=getLocalHistoryRecord(prpq[i].prpName,"#0")) != null)
                    {
                      prpq[i].prpHistoryDepthLong = (short)lhr.getHspec().getDepthLong();
                      prpq[i].prpHistoryDepthShort = (short)lhr.getHspec().getDepthShort();
                    }
                    System.arraycopy(prpq[i].toByteArray(), 0, tba, i * TPropertyQuery.sizeInBytes,
                        TPropertyQuery.sizeInBytes);
                  }
                  return dout.putData(tba, "PRPQSr4");
                }
                else
                { // want the whole list ...
                  String[] slst = new String[propertyList.countUniqueProperties()];
                  propertyList.getPropertyNames().toArray(slst);
                  TPropertyQuery[] prpq = new TPropertyQuery[slst.length];
                  if (slst.length == 0) break;
                  byte[] tba = new byte[slst.length * TPropertyQuery.sizeInBytes];
                  TExportProperty prp;
                  for (int i=0; i<slst.length; i++)
                  {
                    prp = propertyList.getFirstProperty(slst[i]);
                    prpq[i] = new TPropertyQuery(prp,1);
                    System.arraycopy(prpq[i].toByteArray(), 0, tba, i * TPropertyQuery.sizeInBytes,
                        TPropertyQuery.sizeInBytes);
                  }
                  return dout.putData(tba, "PRPQSr4");
                }               
              }
              else if (dout.getTag().compareTo("XPQS") == 0 || (dout.getArrayLength() % XPropertyQuery.sizeInBytes) == 0)
              {
                if (din != null)
                { // looking for a specific property ...
                  LinkedList<TExportProperty> propInstances;
                  String propertyName = din.toString();
                  propertyName = propertyName.replace('\n', (char) 0).trim();
                  propInstances = propertyList.getEqualProperties(propertyName);
                  if ((propInstances == null) || (propInstances.size() == 0))
                      return TErrorList.not_exported;
                  // fill in the targeted property info
                  THistoryRecord lhr = null;
                  XPropertyQuery[] xpq = new XPropertyQuery[propInstances.size()];
                  byte[] tba = new byte[xpq.length * XPropertyQuery.sizeInBytes];
                  Iterator<TExportProperty> it = propInstances.iterator();
                  for (int i = 0; (it.hasNext()) && (i < xpq.length); i++)
                  {
                    xpq[i] = new XPropertyQuery((TExportProperty) it.next(), propInstances.size() - 1);
                    if ((lhr=getLocalHistoryRecord(xpq[i].prpName,"#0")) != null)
                    {
                      xpq[i].prpHistoryDepthLong = (short)lhr.getHspec().getDepthLong();
                      xpq[i].prpHistoryDepthShort = (short)lhr.getHspec().getDepthShort();
                    }
                    System.arraycopy(xpq[i].toByteArray(), 0, tba, i * XPropertyQuery.sizeInBytes,
                        XPropertyQuery.sizeInBytes);
                  }
                  return dout.putData(tba, "XPQS");
                }
                else
                { // want the whole list ...
                  String[] slst = new String[propertyList.countUniqueProperties()];
                  propertyList.getPropertyNames().toArray(slst);
                  XPropertyQuery[] xpq = new XPropertyQuery[slst.length];
                  if (slst.length == 0) break;
                  byte[] tba = new byte[slst.length * XPropertyQuery.sizeInBytes];
                  TExportProperty prp;
                  for (int i=0; i<slst.length; i++)
                  {
                    prp = propertyList.getFirstProperty(slst[i]);
                    xpq[i] = new XPropertyQuery(prp,1);
                    System.arraycopy(xpq[i].toByteArray(), 0, tba, i * XPropertyQuery.sizeInBytes,
                        XPropertyQuery.sizeInBytes);
                  }
                  return dout.putData(tba, "XPQS");
                }
              }
              else if (dout.getTag().compareTo("PQS") == 0 || (dout.getArrayLength() % PropertyQuery.sizeInBytes) == 0)
              { // legacy request ...
                if (din != null)
                { // looking for a specific property ...
                  LinkedList<TExportProperty> propInstances;
                  String propertyName = din.toString();
                  propertyName = propertyName.replace('\n', (char) 0).trim();
                  propInstances = propertyList.getEqualProperties(propertyName);
                  if ((propInstances == null) || (propInstances.size() == 0))
                      return TErrorList.not_exported;
                  // fill in the targeted property info
                  PropertyQuery[] pqs = new PropertyQuery[1];
                  TExportProperty prp = propertyList.getFirstProperty(propertyName);
                  byte[] tba = new byte[PropertyQuery.sizeInBytes];
                  pqs[0] = new PropertyQuery();
                  pqs[0].name = propertyName;
                  pqs[0].prpDesc = prp.getDescription().getText();
                  pqs[0].prpAccess = (byte)prp.getAccessMode();
                  pqs[0].prpFormat = (byte)(prp.getOutputFormat()%512);
                  pqs[0].prpSize = (short)prp.getOutputSize();
                  System.arraycopy(pqs[0].toByteArray(), 0, tba, 0, PropertyQuery.sizeInBytes);
                  return dout.putData(tba, "PQS");
                }
                else
                { // want the whole list ...
                  String[] slst = new String[propertyList.countUniqueProperties()];
                  propertyList.getPropertyNames().toArray(slst);
                  if (slst.length == 0) break;
                  PropertyQuery[] pqs = new PropertyQuery[slst.length];
                  byte[] tba = new byte[slst.length * PropertyQuery.sizeInBytes];
                  TExportProperty prp;
                  for (int i=0; i<slst.length; i++)
                  {
                    pqs[i] = new PropertyQuery();
                    pqs[i].name = slst[i];
                    prp = propertyList.getFirstProperty(slst[i]);
                    pqs[i].prpDesc = prp.getDescription().getText();
                    pqs[i].prpAccess = (byte)prp.getAccessMode();
                    pqs[i].prpFormat = (byte)(prp.getOutputFormat()%512);
                    pqs[i].prpSize = (short)prp.getOutputSize();
                    System.arraycopy(pqs[i].toByteArray(), 0, tba, i * PropertyQuery.sizeInBytes,
                        PropertyQuery.sizeInBytes);
                  }
                  return dout.putData(tba, "PQS");               
                }
              }
          }
        }
        /**/
        return TErrorList.illegal_format;
      }
    };

    TPropertyHandler metapropertiesStructHandler = new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        if (dout != null)
        {
          int filter = getMetaPrpsCallFilter(din);
          switch (dout.dFormat)
          { // TODO: tidy this copy-and-past job up !
            case TFormat.CF_STRUCT:
              LinkedList<TExportProperty> propInstances;
              if (dout.getTag().compareTo("PRPQSr4") == 0 || (dout.getArrayLength() % TPropertyQuery.sizeInBytes) == 0)
              {
                if (din != null && filter != 0x7fffffff)
                { // looking for a specific property ...
                  String propertyName = din.toString();
                  propertyName = propertyName.replace('\n', (char) 0).trim();
                  propInstances = propertyList.getFilledMetaProperties(propertyName,filter);
                  if ((propInstances == null) || (propInstances.size() == 0))
                      return TErrorList.not_exported;
                  // fill in the targeted property info
                  TPropertyQuery[] prpq = new TPropertyQuery[propInstances.size()];
                  byte[] tba = new byte[prpq.length * TPropertyQuery.sizeInBytes];
                  Iterator<TExportProperty> it = propInstances.iterator();
                  for (int i = 0; (it.hasNext()) && (i < prpq.length); i++)
                  {
                    prpq[i] = new TPropertyQuery((TExportProperty) it.next(), propInstances.size() - 1);
                    System.arraycopy(prpq[i].toByteArray(), 0, tba, i * TPropertyQuery.sizeInBytes,
                        TPropertyQuery.sizeInBytes);
                  }
                  return dout.putData(tba, "PRPQSr4");
                }
                else
                { // want the whole list ...
                  String[] slst = new String[propertyList.countUniqueProperties()];
                  propertyList.getPropertyNames().toArray(slst);
                  if (slst.length == 0) break;
                  propInstances = new LinkedList<TExportProperty>();
                  LinkedList<TExportProperty> theseInstances;
                  for (int i=0; i<slst.length; i++)
                  {
                    theseInstances = propertyList.getFilledMetaProperties(slst[i],filter);
                    if (theseInstances.size() > 0) propInstances.addAll(theseInstances);
                  }
                  int size = propInstances.size();
                  TExportProperty prp;
                  TPropertyQuery[] prpq = new TPropertyQuery[size];
                  byte[] tba = new byte[size * TPropertyQuery.sizeInBytes];
                  for (int i=0; i<size; i++)
                  {
                    prp = propInstances.get(i);
                    prpq[i] = new TPropertyQuery(prp,1);
                    System.arraycopy(prpq[i].toByteArray(), 0, tba, i * TPropertyQuery.sizeInBytes,
                        TPropertyQuery.sizeInBytes);
                  }
                  return dout.putData(tba, "PRPQSr4");
                }               
              }
              else if (dout.getTag().compareTo("PQS") == 0 || (dout.getArrayLength() % PropertyQuery.sizeInBytes) == 0)
              { // legacy request ...
                if (din != null && filter != 0x7fffffff)
                { // looking for a specific property ...
                  String propertyName = din.toString();
                  propertyName = propertyName.replace('\n', (char) 0).trim();
                  propInstances = propertyList.getFilledMetaProperties(propertyName,filter);
                  if ((propInstances == null) || (propInstances.size() == 0))
                      return TErrorList.not_exported;
                  // fill in the targeted property info
                  PropertyQuery[] pqs = new PropertyQuery[1];
                  TExportProperty prp = propertyList.getFirstProperty(propertyName);
                  byte[] tba = new byte[PropertyQuery.sizeInBytes];
                  pqs[0] = new PropertyQuery();
                  pqs[0].name = propertyName;
                  pqs[0].prpDesc = prp.getDescription().getText();
                  pqs[0].prpAccess = (byte)prp.getAccessMode();
                  pqs[0].prpFormat = (byte)(prp.getOutputFormat()%512);
                  pqs[0].prpSize = (short)prp.getOutputSize();
                  System.arraycopy(pqs[0].toByteArray(), 0, tba, 0, PropertyQuery.sizeInBytes);
                  return dout.putData(tba, "PQS");
                }
                else
                { // want the whole list ...
                  String[] slst = new String[propertyList.countUniqueProperties()];
                  propertyList.getPropertyNames().toArray(slst);
                  if (slst.length == 0) break;
                  propInstances = new LinkedList<TExportProperty>();
                  LinkedList<TExportProperty> theseInstances;
                  for (int i=0; i<slst.length; i++)
                  {
                    theseInstances = propertyList.getFilledMetaProperties(slst[i],filter);
                    if (theseInstances.size() > 0) propInstances.addAll(theseInstances);
                  }
                  int size = propInstances.size();
                  PropertyQuery[] pqs = new PropertyQuery[size];
                  byte[] tba = new byte[size * PropertyQuery.sizeInBytes];
                  TExportProperty prp;
                  for (int i=0; i<size; i++)
                  {
                    prp = propInstances.get(i);
                    pqs[i] = new PropertyQuery();
                    pqs[i].name = prp.getName();
                    pqs[i].prpDesc = prp.getDescription().getText();
                    pqs[i].prpAccess = (byte)prp.getAccessMode();
                    pqs[i].prpFormat = (byte)(prp.getOutputFormat()%512);
                    pqs[i].prpSize = (short)prp.getOutputSize();
                    System.arraycopy(pqs[i].toByteArray(), 0, tba, i * PropertyQuery.sizeInBytes,
                        PropertyQuery.sizeInBytes);
                  }
                  return dout.putData(tba, "PQS");               
                }
              }
          }
        }
        /**/
        return TErrorList.illegal_format;
      }
    };
   
    TPropertyHandler alarmsStructHandler = new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        if (dout == null) return TErrorList.dimension_error;
        if (dout.dFormat != TFormat.CF_STRUCT) return TErrorList.illegal_format;
        int[] amrb = new int[4];
        if (din != null)
        {
          switch (din.dFormat)
          {
            case TFormat.CF_STRUCT:
              if (din.dArrayLength != 16) return TErrorList.illegal_format;
              byte[] bdata = din.getDataBuffer();
              byte[] ival = new byte[4];
              for (int i=0; i<3; i++)
              {
                System.arraycopy(bdata,i*4,ival,0,4);
                amrb[i] = Swap.Long(ival);
              }
              break;
            case TFormat.CF_LONG:
              din.getData(amrb);
              break;
            default:
              return TErrorList.illegal_format;
          }
        }
        else
        {
          amrb[0] = 0; // start time
          amrb[1] = (int)(System.currentTimeMillis()/1000); // stop time
          amrb[2] = 0; // minimum severity
          amrb[3] = 0; // as yet unused ...
        }
        boolean collapse = false;
        int dstart, dstop;
        int astart = amrb[0];
        int astop = amrb[1];
        int asev = amrb[2];
        if (devName == null || devName.length() == 0 || devName.startsWith("#") || devName.contains("*"))
        { // currently any of these is a signal that the caller want's ALL alarms
          int nalms = deviceList.getNumberOfAlarms();
          if (nalms > TAlarm.getAlmCollapseWindow()) collapse = true;
          dstart = 0; dstop = deviceList.getNumberOfDevices();
        }
        else
        {
          dstart = deviceList.getDeviceNumber(devName);
          dstop = dstart + 1;
        }
        TAlarm[] alms = null;
        TAlarmMessage[] ams = null;
        int buflength = 0;
        //byte[] tba = null;
        int code = TErrorList.max_alarms_exceeded, na = 0;
        if (collapse)
        { // collapse to a single message
          na = buflength = 1;
          ams = new TAlarmMessage[buflength];
          String atag = "more than "+TAlarm.getAlmCollapseWindow()+" total alarms";
          short[] tna = new short[32];
          int itna = 1;
          tna[0] = (short)deviceList.getNumberOfAlarms();
          TDevice[] tdevs = deviceList.getDeviceList();
          TAlarm[] as;
          int n = 0;
          for (TDevice td : tdevs)
          { // scan thru the device list
            if ((as=td.getAlarmList()) == null ||
                 as.length == 0 || as[0] == null) continue;
            for (int i=0; i<as.length; i++)
              if (isCasInClientList()) as[i].allowRemoval();
            if (code == TErrorList.max_alarms_exceeded) code = as[0].getCode();
            if (itna < 32 && code != tna[itna]) tna[itna] = (short)code;
            if (code != as[0].getCode()) break;
            n++;
          }
          if (n > TAlarm.getAlmCollapseWindow())
          { // at least this many with the same code
              code = TAlarm.isDiskSpaceAlarm(code) ? (code & 0xff) : TAlarm.getBaseCode(code);
              TAlarmDefinition ad = getAlarmDefinition(code);
              atag = ""+n+" alarms: "+ ad != null ? ad.getAlarmTag() : "<no defined alarm tag>";
          }
          TDataType dt = new TDataType(tna);
          ams[0] = new TAlarmMessage(thisEqm.getExportName(),thisEqm.getExportName(),atag,code,
                          deviceList.getHighestAlarmSeverity(null,0),almDynSet,dt);
          //tba = new byte[buflength * TAlarmMessage.sizeInBytes];
        }
        else
        {
          int ts = 0;
          buflength = deviceList.getNumberOfAlarms();
          ams = new TAlarmMessage[buflength];
          //tba = new byte[buflength * TAlarmMessage.sizeInBytes];
          TDevice[] dlst = deviceList.getDeviceList();
          String dnam = null, atag = null;
          for (int i=dstart; i<dstop; i++)
          {
            alms = dlst[i].getAlarmList();
            if ((alms == null) || (alms.length == 0)) continue;
            for (int n=0; n<alms.length && na<buflength; n++)
            {
              if ((ts=alms[n].getTimeStamp()) < astart || ts > astop) continue;
              if (alms[n].getSeverity() < asev) continue;
              dnam = dlst[i].getName();
              code = alms[n].getCode();
              atag = null;
              if (TAlarm.isLinkErrorAlarm(code))
              {
                dnam = thisEqm.getExportName();
                int lid = TAlarm.decodeLinkErrorAlarm(code);
                TLink lnk = TLinkFactory.getInstance().getLinkFromTable(lid);
                if (lnk != null) atag = "link error: "+lnk.getDeviceServer();
              }
              // TODO: if (TAlarm.isDiskSpaceAlarm(code)) dnam = getDiskName();
              ams[na] = new TAlarmMessage(thisEqm,dnam,atag,alms[n]);
              if (TAlarm.isHiddenAlarm(alms[n].getCode()))
              { // flagged as hidden
                ams[na].setAlarmSystem(TAlarm.hideAlarmSystem(ams[na].getAlarmSystem()));
              }
              //System.arraycopy(ams[na].toByteArray(), 0, tba, (na)*TAlarmMessage.sizeInBytes, TAlarmMessage.sizeInBytes);
              // CAS is configured and has now read the alarm => can remove it !
              if (casReadRequired && isCasInClientList()) alms[n].allowRemoval();
              na++;
            }
          }
        }
        byte[] tba = new byte[buflength * TAlarmMessage.sizeInBytes];
        if (dout.getTag().compareTo("AMSr4") == 0)
        { // modern
          for (int i=0; i<buflength && i<na; i++)
          {
            System.arraycopy(ams[i].toByteArray(), 0, tba, i*TAlarmMessage.sizeInBytes, TAlarmMessage.sizeInBytes);
          }
          if (na < dout.getArrayLength()) dout.setArrayLength(na);
          return dout.putData(tba, "AMSr4");
        }
        else
        // is legacy support necessary ?
         
        }
        return TErrorList.invalid_structure_tag;
      }
    };
    TPropertyHandler alarmDefsStructHandler = new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        TAlarmDefinition[] adef = (TAlarmDefinition[])alarmDefinitionList.values().toArray(new TAlarmDefinition[0]);
        int almdefsize = alarmDefinitionList.size();
        if (devAccess.isWrite())
        {
          if (din == null) return TErrorList.dimension_error;
          if (din.dFormat != TFormat.CF_STRUCT) return TErrorList.illegal_format;
          if (din.getTag().compareTo("ADSr4") != 0) return TErrorList.invalid_structure_tag;
          TAlarmDefinition[] inpt = new TAlarmDefinition[1];
          inpt[0] = new TAlarmDefinition();
          din.getData(inpt);
          boolean found = false;
          for (int i=0; i<almdefsize; i++)
          {
            if (adef[i].getAlarmCode() == inpt[0].getAlarmCode())
            { // got it
              adef[i].setFields(inpt[0]);
              found = true;
              break;
            }
          }
          if (!found) return TErrorList.un_allocated;
          if (dout == null || dout.getArrayLength() == 0) return 0;
        }
        if (dout == null) return TErrorList.dimension_error;
        if (dout.dFormat != TFormat.CF_STRUCT) return TErrorList.illegal_format;
        int adssize = TStructRegistry.getSizeInBytes("ADSr4");
        byte[] tba = new byte[almdefsize * adssize];
        if (dout.getTag().compareTo("ADSr4") == 0)
        { // modern
          for (int i=0; i<almdefsize; i++)
          {
            System.arraycopy(adef[i].toByteArray(), 0, tba, i*adssize, adssize);
          }
          if (almdefsize < dout.getArrayLength()) dout.setArrayLength(almdefsize);
          return dout.putData(tba, "ADSr4");
        }
        else
        // is legacy support necessary ?
         
        }
        return TErrorList.invalid_structure_tag;
      }
    };
    TPropertyHandler alarmWatchStructHandler = new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        if (dout == null) return TErrorList.dimension_error;
        if (dout.dFormat != TFormat.CF_STRUCT) return TErrorList.illegal_format;
        synchronized (gAlarmWatchList)
        {
          int almwtblsize = gAlarmWatchList.size();
          int awssize = TStructRegistry.getSizeInBytes("AWSr4");
          byte[] tba = new byte[almwtblsize * awssize];
          TAlarmWatchEntry[] aws = (TAlarmWatchEntry[])gAlarmWatchList.toArray(new TAlarmWatchEntry[0])
          if (devAccess.isWrite())
          {
            if (din.getFormat() != TFormat.CF_STRUCT) return TErrorList.illegal_format;
            if (din.getTag().compareTo("AWSr4") == 0)
            {
              TAlarmWatchEntry[] awe = new TAlarmWatchEntry[1];
              awe[0] = new TAlarmWatchEntry();
              din.getData(awe);
              awe[0].setEquipmentModule(getLocalName());
              boolean isNew = false;
              for (int i=0; i<aws.length; i++)
              {
                if (aws[i].equals(awe[0]))
                {
                  aws[i].adjustFrom(awe[0]);
                  isNew = true;
                  break;
                }
              }
              if (isNew)
              { // wasn't in list
                TExportProperty p = propertyList.getFirstProperty(awe[0].getPrp());
                if (p == null) return TErrorList.illegal_property;
                if (awe[0].getSiz() <= 0 || awe[0].getSiz() > p.getOutputSize())
                {
                  awe[0].setSiz(p.getOutputSize());
                }
                awe[0].setFmt(p.getOutputFormat());
                gAlarmWatchList.add(awe[0]);
              }
            }
            else
            {
              return TErrorList.invalid_structure_tag;           
            }
          }
          if (dout.getTag().compareTo("AWSr4") == 0)
          { // modern
            for (int i=0; i<almwtblsize; i++)
            {
              System.arraycopy(aws[i].toByteArray(), 0, tba, i*awssize, awssize);
            }
            if (almwtblsize < dout.getArrayLength()) dout.setArrayLength(almwtblsize);
            return dout.putData(tba, "AWSr4");
          }
        }
        return TErrorList.invalid_structure_tag;
      }
    };
    stockList.addProperty(TStockProperties.NALARMS, new TPropertyHandler()
    {
      int nalmdefs = alarmDefinitionList.size();
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        int devnr = -1;
       
        if (!devName.contains("*") && !devName.startsWith("#"))
        {
          devnr = deviceList.getDeviceNumber(devName);
        }
        if (casName != null  && !hasCasAttached) isCasInClientList();
        int[] nalms = new int[6];
        if (devnr < 0)
        { // all of them
          nalms[0] = deviceList.getNumberOfAlarms();
          nalms[1] = deviceList.getMostRecentAlarmTimestamp(nalms,3);
          nalms[2] = deviceList.getHighestAlarmSeverity(nalms,4);
          nalms[5] = nalmdefs;
          if (nalms[0] > TAlarm.getAlmCollapseWindow())
          {
            nalms[1] = almDynSet.timestamp;
            nalms[4] = nalms[3] = nalms[0] = 1;
          }
        }
        else
        { // just the device given ...
          int mrts = 0, ts, hsv = 0, sv, nts = 0, nsv = 0;
          TDevice[] dlst = deviceList.getDeviceList();
          if (devnr >= dlst.length) return TErrorList.device_not_connected;
          TAlarm[] alms = dlst[devnr].getAlarmList();
          nalms[0] = alms.length;
          for (int i=0; i<alms.length; i++)
          {
            if ((ts=alms[i].getTimeStamp()) > mrts)
            {
              mrts = ts;
              nts = 0;
            }
            if (ts == mrts) nts++;
            if ((sv=alms[i].getSeverity()) > hsv)
            {
              hsv = sv;
              nsv = 0;
            }
            if (sv == hsv) nsv++;
          }
          nalms[1] = mrts;
          nalms[2] = hsv;
          nalms[3] = nts;
          nalms[4] = nsv;
          nalms[5] = nalmdefs;
        }
        if (dout.dArrayLength > 5) nalms[5] = getAlarmDefinitionList().size();
        return dout.putData(nalms);
      }
    });
    stockList.addProperty(TStockProperties.NALARMDEFS, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return dout.putData((short)alarmDefinitionList.size());
      }
    });
    stockList.addProperty(TStockProperties.NALMWATCH, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return dout.putData((short)gAlarmWatchList.size());
      }
    });
    stockList.addProperty(TStockProperties.ALARMS, alarmsStructHandler);
    stockList.addProperty(TStockProperties.ALARMSEXT, alarmsStructHandler);
    stockList.addProperty(TStockProperties.ALARMSEXT_STRUCTIN, alarmsStructHandler);
    stockList.addProperty(TStockProperties.ALARMDEFS, alarmDefsStructHandler);
    stockList.addProperty(TStockProperties.ALMWATCHTBL, alarmWatchStructHandler);
    stockList.addProperty(TStockProperties.NHISTORIES, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return dout.putData((short)gLclHstList.size());
      }
    });
    stockList.addProperty(TStockProperties.HISTORIES, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return hstCall(devName,dout,din,devAccess);
      }
    });
    stockList.addProperty(TStockProperties.HISTORIES_STRUCT, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return hstCall(devName,dout,din,devAccess);
      }
    });
    stockList.addProperty(TStockProperties.ADDHISTORY, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        if (devAccess.isWrite())
        {
          if (din.getFormat() != TFormat.CF_STRUCT) return TErrorList.illegal_format;
          if (din.getTag().compareTo("HRSr4") == 0)
          {
            THistoryRecordStruct[] hrs = new THistoryRecordStruct[1];
            hrs[0] = new THistoryRecordStruct();
            din.getData(hrs);
            String prp = hrs[0].getProperty();
            String dev = hrs[0].getDevice();
            THistoryRecord hr = getLocalHistoryRecord(prp,dev);
            if (hr == null)            
            { // wasn't in list
              TExportProperty p = getPropertyList().getFirstProperty(prp);
              TPropertyDescription pd = p.getDescription();
              int at = pd != null ? pd.getArrayType() : TArrayType.AT_UNKNOWN;
              int idx = TEquipmentModuleFactory.getInstance().getNextHistoryRecordIndex();
              THistorySpecification hspec = new THistorySpecification(hrs[0].getPollingRate(), hrs[0].getArchiveRate(), hrs[0].getDepthShort(), hrs[0].getDepthLong(), hrs[0].getHeartbeat(), hrs[0].getPercentTolerance(), hrs[0].getAbsoluteTolerance(), null);
              addLocalHistoryRecord(idx, dev, prp, hrs[0].getSize(), hrs[0].getFormat(),at,hspec);
              dumpHistoryManifest();
            }
            else
            { // just an edit ...
              THistorySpecification hsp = hr.getHspec();
              hsp.setAbsoluteTolerance(hrs[0].getAbsoluteTolerance());
              hsp.setPercentTolerance(hrs[0].getPercentTolerance());
              hsp.setArchiveRate(hrs[0].getArchiveRate());
              hsp.setPollngRate(hrs[0].getPollingRate());
              hsp.setDepthLong(hrs[0].getDepthLong());
              hsp.setDepthShort(hrs[0].getDepthShort());
              hsp.setHeartbeat(hrs[0].getHeartbeat());
            }
            return 0;
          }
          else
          {
            return TErrorList.invalid_structure_tag;           
          }
        }
        return TErrorList.illegal_read_write;
      }            
    });
    stockList.addProperty(TStockProperties.ADDHISTORY_STR, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        if (devAccess.isWrite())
        {
          short fmt = din.getFormat();
          if (!TFormat.isName(fmt) && fmt != TFormat.CF_CHAR) return TErrorList.illegal_format;
          NAME64[] tgt = new NAME64[1];
          int cc = din.getData(tgt);
          if (cc != 0) return cc;
          String prp = tgt[0].getName();
          TExportProperty p = getPropertyList().getFirstProperty(prp);
          if (p == null) return TErrorList.invalid_property;
          int hfmt = p.getOutputFormat();
          int hsiz = p.getOutputSize();
          int idx = gEqmFactory.getNextHistoryRecordIndex(getLocalName(),prp,devName,hfmt,hsiz);
          addLocalHistoryRecord(idx, devName, prp, hsiz, hfmt);
          dumpHistoryManifest();
          return 0;
        }
        return TErrorList.illegal_read_write;
      }            
    });
    //TODO: finish these ...
    stockList.addProperty(TStockProperties.METAPROPERTIES, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return metaprpQueryCall(devName,dout,din,devAccess);
      }
    });
    stockList.addProperty(TStockProperties.METAPROPS, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return metaprpQueryCall(devName,dout,din,devAccess);
      }
    });
    stockList.addProperty(TStockProperties.METAPROPERTIES_STRUCT, metapropertiesStructHandler);
    stockList.addProperty(TStockProperties.METAPROPS_STRUCT, metapropertiesStructHandler);
    stockList.addProperty(TStockProperties.DEVLOCATION, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        TDevice tdv = deviceList.getDevice(devName);
        if (tdv == null) return TErrorList.illegal_device;
        if (din != null && devAccess.isWrite())
        {
          int cc = 0;
          char[] newLocation = new char[64];
          if ((cc=din.getData(newLocation)) != 0) return cc;
          tdv.setLocation(new String(newLocation));         
        }       
        String dloc = tdv.getLocation();
        if (dloc == null) dloc = gEqmFactory.getFecLocation();
        if (dloc != null && dloc.startsWith("<"))
        { /* redirection character */
          String rdrstr = null, envstr;
          dloc = dloc.substring(1); dloc.trim();
          if (dloc.startsWith("$"))
          {
            int idx = dloc.indexOf('/');
            if (idx < 0) idx = dloc.length();
            envstr = dloc.substring(1, idx);
            rdrstr = System.getenv(envstr);
            if (rdrstr == null) MsgLog.log("TEquipmentModule:DEVLOCATION", "environment variable "+envstr+" not set",TErrorList.not_defined,null,0);
            dloc = dloc.substring(idx);
          }
          if (rdrstr == null) rdrstr = "";
          rdrstr += dloc;
          if (rdrstr.indexOf('[') < 0) rdrstr += "[Location]";
          return gEqmFactory.setRedirectionString(rdrstr);
        }      
        return dout.putData(dloc == null ? "" : dloc);
      }
    });   
    stockList.addProperty(TStockProperties.DEVMASK, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        TDevice tdv = deviceList.getDevice(devName);
        if (tdv == null) return TErrorList.illegal_device;
        if (din != null && devAccess.isWrite())
        {
          int cc = 0;
          int[] newMask = new int[1];
          if ((cc=din.getData(newMask)) != 0) return cc;
          tdv.setMask(newMask[0]);         
        }               
        int mask = tdv.getMask();
        return dout.putData(mask);
      }
    });   
    stockList.addProperty(TStockProperties.ZPOSITION, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        TDevice tdv = deviceList.getDevice(devName);
        if (tdv == null) return TErrorList.illegal_device;
        if (din != null && devAccess.isWrite())
        {
          int cc = 0;
          float[] newZPos = new float[1];
          if ((cc=din.getData(newZPos)) != 0) return cc;
          tdv.setZposition(newZPos[0]);         
        }               
        float p = tdv.getZposition();
        return dout.putData(p);
      }
    });   
    stockList.addProperty(TStockProperties.DEVONLINE, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        TDevice tdv = deviceList.getDevice(devName);
        if (tdv == null) return TErrorList.illegal_device;
        if (din != null && devAccess.isWrite())
        {
          int cc = 0;
          int[] newFlg = new int[1];
          if ((cc=din.getData(newFlg)) != 0) return cc;
          tdv.setOffline(newFlg[0] == 0 ? true : false);         
        }               
        boolean p = !tdv.isOffline();
        return dout.putData(p);
      }
    });   
    stockList.addProperty(TStockProperties.PROPERTIES, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return prpQueryCall(devName,dout,din,devAccess);
      }
    });
    stockList.addProperty(TStockProperties.PROPERTIES_USTRING, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return prpQueryCall(devName,dout,din,devAccess);
      }
    });
    stockList.addProperty(TStockProperties.PROPERTIES_STRUCT, propertiesStructHandler);
    stockList.addProperty(TStockProperties.PROPS, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return prpQueryCall(devName,dout,din,devAccess);
      }
    });
    stockList.addProperty(TStockProperties.PROPS_USTRING, new TPropertyHandler()
        {
          public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
          {
            return prpQueryCall(devName,dout,din,devAccess);
          }
        });
    stockList.addProperty(TStockProperties.PROPS_STRUCT, propertiesStructHandler);
    stockList.addProperty(TStockProperties.NPROPERTIES, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return dout.putData(propertyList.countUniqueProperties());
      }
    });
    // Same as NPROPERTIES
    stockList.addProperty(TStockProperties.NPROPS, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return dout.putData(propertyList.countUniqueProperties());
      }
    });
    stockList.addProperty(TStockProperties.NDEVICES, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        //return dout.putData(deviceList.size());
        return dout.putData(deviceList.getNumberOfDevices());
      }
    });
    stockList.addProperty(TStockProperties.DEVICES, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        String[] namlst;
        if (devName.compareTo("*") != 0 && devName.indexOf("*") != -1)
        { // query for a subset of device names ...
          WildcardMatch wc = new WildcardMatch(devName);
          TDevice dev;
          LinkedList<String> nl = new LinkedList<String>();
          String dn;
          int n = 0, mask = 0;
          if (din != null && din.dArrayLength == 1 && din.dFormat == TFormat.CF_INT32)
          {
            int[] msk = new int[1];
            din.getData(msk);
            mask = msk[0];
          }
          for (int i=0; i<deviceList.getNumberOfDevices(); i++)
          {
            if ((dev=deviceList.getDevice(i)) == null) continue;
            if (dev.isOffline()) continue;
            if (mask > 0 && !dev.isMaskSet(mask)) continue;
            dn = dev.getName();
            if (!wc.matches(dn)) continue;
            nl.add(dn);
            n++;
          }
          namlst = nl.toArray(new String[n]);
        }
        else
        {
          namlst = deviceList.getDeviceNameList();
        }
        int cc = dout.putData(StringToName.stringArrayToName64(namlst));
        if (cc == 0)
        {
          if (dout.getArrayLength() > namlst.length) dout.setArrayLength(namlst.length);
          if (getDeviceList().isPropertyOriented())
            cc = TErrorList.has_query_function | TErrorList.CE_SENDDATA;
        }
        return cc;
      }
    });
    stockList.addProperty(TStockProperties.DEVDESCRIPTION, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        TDevice tdv = deviceList.getDevice(devName);
        if (tdv == null) return TErrorList.illegal_device;
        String dsc = tdv.getDescription();
        return dout.putData(dsc == null ? "" : dsc);
      }
    });   
    stockList.addProperty(TStockProperties.SRVSUBSYSTEM, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        String subs = getSubsystem();
        return dout.putData(subs == null ? "" : subs);
      }
    });   
    stockList.addProperty(TStockProperties.SRVIDLE, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        int cc = 0;
        int[] idleState = new int[1];
        if (din != null && devAccess.isWrite())
        {
          if ((cc=din.getData(idleState)) != 0) return cc;
          isIdle = idleState[0] == 0 ? false : true;
        }
        if (dout != null && dout.dArrayLength > 0)
        {
          idleState[0] = isIdle ? -1 : 0;
          cc = dout.putData(idleState);
        }
        return cc;
      }
    });
    stockList.addProperty(TStockProperties.SRVINIT, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        int cc = 0;
        if (!devAccess.isWrite()) return TErrorList.illegal_read_write;
        {
          if (eqpIni == null) return TErrorList.not_implemented;
          cc = eqpIni.initialize();
        }
        return cc;
      }
    });
    stockList.addProperty(TStockProperties.SRVADDR, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        NAME32[] n32 = new NAME32[dout.dArrayLength];
        String eqm, exp;
        TEquipmentModule eq;
        int cc = 0;
        if (gEqmFactory.isServiceRequest(moduleName))
        {
          NAME32[] tgt = new NAME32[3];
          if ((cc=din.getData(tgt)) != 0) return cc;
          String ctxName = tgt[0].getName();
          //String eqmName = tgt[1].getName();
          String expName = tgt[2].getName();
          if ((eq=gEqmFactory.getEquipmentModuleFromExportName(expName)) == null)
            return TErrorList.invalid_data;
          if (eq.context.compareToIgnoreCase(ctxName) != 0) return TErrorList.invalid_data;
          eqm = eq.moduleName;
          exp = eq.exportName;
        }       
        else
        {
          eqm = moduleName;
          exp = exportName;
        }
        cc = gEqmFactory.getSrvAddress(n32,eqm,exp);
        return (short) dout.putData(n32);
      }
    });
    stockList.addProperty(TStockProperties.USERS, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        NAME16[] n16 = getRegisteredUsers();
        if (din != null && din.dArrayLength > 0)
        {
          if (!TFormat.isString(din.dFormat)) return TErrorList.illegal_format;
          char[] data = new char[TStrings.PROPERTY_NAME_SIZE];
          int cc = din.getData(data);
          if (cc != 0) return cc;
          String str = new String(data).trim();
          TExportProperty txp = getPropertyList().getFirstProperty(str);
          if (txp != null)
          { // property name given as input
            n16 = txp.getRegisteredUsers();
          }
          else
          { // maybe a device name then ...
            TDevice dev = getDeviceList().getDevice(str);
            if (dev != null) n16 = dev.getRegisteredUsers();
            else return TErrorList.invalid_keyword;
          }
        }
        if (n16 == null)
        {
          n16 = new NAME16[1];
          n16[0] = new NAME16("");
        }
        dout.dArrayLength = n16 == null ? 0 : n16.length;
        return n16 == null ? 0 : dout.putData(n16);
      }
    });
    stockList.addProperty(TStockProperties.NUSERS, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        short n = (short)gRegisteredUsersList.size();
        if (din != null && din.dArrayLength > 0)
        {
          if (!TFormat.isString(din.dFormat)) return TErrorList.illegal_format;
          char[] data = new char[TStrings.PROPERTY_NAME_SIZE];
          int cc = din.getData(data);
          if (cc != 0) return cc;
          String str = new String(data).trim();
          TExportProperty txp = getPropertyList().getFirstProperty(str);
          if (txp != null)
          { // property name given as input
            n = (short)txp.getNumberRegisteredUsers();
          }
          else
          { // maybe a device name then ...
            TDevice dev = getDeviceList().getDevice(str);
            if (dev != null) n = (short)dev.getNumberRegisteredUsers();
            else return TErrorList.invalid_keyword;
          }
        }
        return dout.putData(n);
      }
    });
    stockList.addProperty(TStockProperties.ADDUSER, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        int cc;
        if (din != null && devAccess.isWrite())
        {
          NAME16[] usr = new NAME16[1];
          if ((cc=din.getData(usr)) != 0) return cc;
          return addRegisteredUser(usr[0].getName());
        }
        return TErrorList.illegal_read_write;
      }
    });
    stockList.addProperty(TStockProperties.DELUSER, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        int cc;
        if (din != null && devAccess.isWrite())
        {
          NAME16[] usr = new NAME16[1];
          if ((cc=din.getData(usr)) != 0) return cc;
          return removeRegisteredUser(usr[0].getName());
        }
        return TErrorList.illegal_read_write;
      }
    });
    stockList.addProperty(TStockProperties.IPNETS, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        NAME16[] n16 = getRegisteredNets();
        if (din != null && din.dArrayLength > 0)
        {
          if (!TFormat.isString(din.dFormat)) return TErrorList.illegal_format;
          char[] data = new char[TStrings.PROPERTY_NAME_SIZE];
          int cc = din.getData(data);
          if (cc != 0) return cc;
          String str = new String(data).trim();
          TExportProperty txp = getPropertyList().getFirstProperty(str);
          if (txp != null)
          { // property name given as input
            n16 = txp.getRegisteredNets();
          }
          else
          { // maybe a device name then ...
            TDevice dev = getDeviceList().getDevice(str);
            if (dev != null) n16 = dev.getRegisteredNets();
            else return TErrorList.invalid_keyword;
          }
        }
        dout.dArrayLength = n16 == null ? 0 : n16.length;
        return n16 == null ? 0 : dout.putData(n16);
      }
    });
    stockList.addProperty(TStockProperties.NIPNETS, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        short n = (short)gRegisteredNetsList.size();
        if (din != null && din.dArrayLength > 0)
        {
          if (!TFormat.isString(din.dFormat)) return TErrorList.illegal_format;
          char[] data = new char[TStrings.PROPERTY_NAME_SIZE];
          int cc = din.getData(data);
          if (cc != 0) return cc;
          String str = new String(data).trim();
          TExportProperty txp = getPropertyList().getFirstProperty(str);
          if (txp != null)
          { // property name given as input
            n = (short)txp.getNumberRegisteredUsers();
          }
          else
          { // maybe a device name then ...
            TDevice dev = getDeviceList().getDevice(str);
            if (dev != null) n = (short)dev.getNumberRegisteredNets();
            else return TErrorList.invalid_keyword;
          }
        }
        return dout.putData(n);
      }
    });
    stockList.addProperty(TStockProperties.ADDIPNET, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        int cc;
        if (din != null && devAccess.isWrite())
        {
          NAME16[] ip = new NAME16[1];
          if ((cc=din.getData(ip)) != 0) return cc;
          return addRegisteredNet(ip[0].getName());
        }
        return TErrorList.illegal_read_write;
      }
    });
    stockList.addProperty(TStockProperties.DELIPNET, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        int cc;
        if (din != null && devAccess.isWrite())
        {
          NAME16[] ip = new NAME16[1];
          if ((cc=din.getData(ip)) != 0) return cc;
          return removeRegisteredNet(ip[0].getName());
        }
        return TErrorList.illegal_read_write;
      }
    });
    stockList.addProperty(TStockProperties.ACCESSLOCK, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        int cc = 0;
        if (din != null && devAccess.isWrite())
        {
          short[] svals = new short[2];
          if ((cc=din.getData(svals)) != 0) return cc;
          TClient tc = getCaller();
          if ((cc=accessLock.setAccessLock(tc,svals[0],svals[1])) != 0) return cc;
          gEqmFactory.lockToExclusiveRead(tc);
        }
        if (dout != null)
        {
          String[] s = accessLock.getAccessLockAsString();
          NAME32[] n32 = new NAME32[3];
          n32[0] = new NAME32(s[0]);
          n32[1] = new NAME32(s[1]);
          n32[2] = new NAME32(s[2]);
          cc = dout.putData(n32);
        }
        return cc;
      }
    });
    stockList.addProperty(TStockProperties.SRVSELFTEST, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        if (dout.getFormat() != TFormat.CF_TEXT) return TErrorList.illegal_format;
        String fileName = gEqmFactory.getInitializer().getFecHome() + "/" + getLocalName() + "-selftest.csv";
        if (!TFecLog.fileExists(fileName)) return TErrorList.no_such_file;
        int nlines = dout.getArrayLength()/80;
        return (short) dout.putData(TFecLog.getLines(fileName,nlines));
      }
    });
    stockPropertiesRegistered = true;
    /* TEMPLATE */
    /*
     * stockList.setPropertyHandler("?", new TPropertyHandler() { public short
     * call(String devName, TDataType dout, TDataType din, short devAccess) {
     * return (short) 0; } });
     */
  }
  public TEquipmentModule(TEquipmentFunction eqpFunction)
  {
    String name = eqpFunction.getClass().getName();
    StringTokenizer t = new StringTokenizer(name, ".$");
    String lastToken = name;
    while (t.hasMoreTokens()) lastToken = t.nextToken();
    initialize(lastToken, eqpFunction);
  }
  public TEquipmentModule(String localName, TEquipmentFunction eqpFunction)
  {
    initialize(localName, eqpFunction);
  }
  public TEquipmentModule(String localName)
  {
    initialize(localName, null);
  }
  /**
   * Principal constructor
   *
   * Use this constructor in most cases.  This will attempt to determine
   * a suitable local name for the equipment module.
   *
   */
  public TEquipmentModule()
  {
    String name = this.getClass().getName();
    StringTokenizer t = new StringTokenizer(name, ".$");
    String localName = name;
    while (t.hasMoreTokens()) localName = t.nextToken();
    initialize(localName, null);
  }
  /**
   * Assigns an equipment function.
   *
   * @param eqpFunction
   */
  public void setEquipmentFunction(TEquipmentFunction eqpFunction)
  {
    this.eqpFcn = eqpFunction;
    this.eqpFcn.setEquipmentModule(this);
  }
  class restorePrpRowHndlr implements RowHandler
  { // the Row Handler will be called when all columns have been read in
    public String devName;
    public String prpValue;
    private int devNumber = 0;
    private TExportProperty prp = null;
    private TDataType srData = null;
    public String prpValuesFile = null;
    restorePrpRowHndlr(TExportProperty property,TDataType dt)
    {
      prpValue = "0";
      devName = "";
      srData = dt;
      prp = property;
    }
    public int process(int index)
    {
      TDeviceList dlst = getDeviceList();
      if (dlst == null) return TErrorList.device_not_connected;
      int ndevs = dlst.getNumberOfDevices();
      if (ndevs == 0) return TErrorList.device_not_connected;
      if (devNumber < 0 && devNumber >= ndevs) return TErrorList.illegal_equipment_number;
      if (srData == nullreturn TErrorList.not_implemented;
      int i = index;
      int len = srData.getArrayLength();
      short fmt = srData.getFormat();
      if (devName.length() > 0)
      { // device name given
        i = getDeviceNumber(devName, prp.getName());
        if (i < 0) i = index;
        if (i > len) return TErrorList.illegal_device;
      }
      TDataType dt = null;
      int offset = i;
      try
      {
        if (prpValue.compareToIgnoreCase("<$FILE") == 0)
        { // an array property
          len /= ndevs;
          if (prpValuesFile == null) return TErrorList.file_error;
          int p = prpValuesFile.lastIndexOf('.');
          if (p < 1) return TErrorList.invalid_name;
          prpValuesFile = prpValuesFile.substring(0, p) + "." + i;
          DataInputStream dis = new DataInputStream(new FileInputStream(prpValuesFile));
          hasSrValues[i] = true;
          switch (fmt)
          {
            case TFormat.CF_BYTE:
            {
              byte[] d = new byte[len];
              for (int k=0; k<len; k++) d[k] = dis.readByte();
              dt = new TDataType(d);
              break;
            }
            case TFormat.CF_INT16:
            {
              short[] d = new short[len];
              for (int k=0; k<len; k++) d[k] = dis.readShort();
              dt = new TDataType(d);
              break;
            }
            case TFormat.CF_INT32:
            {
              int[] d = new int[len];
              for (int k=0; k<len; k++) d[k] = dis.readInt();
              dt = new TDataType(d);
              break;
            }
            case TFormat.CF_INT64:
            {
              long[] d = new long[len];
              for (int k=0; k<len; k++) d[k] = dis.readLong();
              dt = new TDataType(d);
              break;
            }
            case TFormat.CF_FLOAT:
            {
              float[] d = new float[len];
              for (int k=0; k<len; k++) d[k] = dis.readFloat();
              dt = new TDataType(d);
              break;
            }
            case TFormat.CF_DOUBLE:
            {
              double[] d = new double[len];
              for (int k=0; k<len; k++) d[k] = dis.readDouble();
              dt = new TDataType(d);
              break;
            }
            default:
              dis.close();
              return TErrorList.not_supported;
          }
          dis.close();
          offset = i * len;
        }
        else
        { // normal scalar attribute or MCA property
          dt = TDataType.toTDataType(prpValue, fmt, true);
        }               
        dt.getData(srData.getDataObject(),len,offset);
      }
      catch (FileNotFoundException e)
      { // don't break the csv iterations: this is allowed !
        hasSrValues[i] = false;
        return 0;
      }
      catch (Exception any)
      { // parsing error probably
        return TErrorList.illegal_format;
      }
      return 0;
    }
  }
  private LinkedList<String> srLst = new LinkedList<String>();
  public int restorePropertyValues(String prpName,TDataType target)
  {
    if (prpName == null || target == null) return TErrorList.invalid_parameter;
    TExportProperty prp = propertyList.getFirstProperty(prpName);
    if (prp == null) return TErrorList.illegal_property;
    TInitializer initializer = TInitializerFactory.getInstance().getInitializer();
    String fn = initializer.getFecHome();
    String filename = prpName+"-settings.csv";
    csv csvFile = null;
    if (moduleName != null) csvFile = new csv(fn+File.separator+moduleName+File.separator+filename);
    if (csvFile == null || !csvFile.fileAvailable())
      csvFile = new csv(fn+File.separator+filename);
    return restorePropertyValues(csvFile,new restorePrpRowHndlr(prp, target));
  }
  private int restorePropertyValues(csv csvFile,restorePrpRowHndlr srRows)
  {
    if (srRows == null) return TErrorList.invalid_parameter;
    // register the csv database structure:
    srRows.prpValuesFile = csvFile.getFileName();
    csvColumn[] srCols = new csvColumn[2];
    srCols[0] = new csvColumn("VALUES","",new StringFieldHandler(srRows,"prpValue"));
    srCols[1] = new csvColumn("DEVICES","",new StringFieldHandler(srRows,"devName"));
    // read the file
    int rc = csvFile.readFile(srCols,srRows);
    // close it
    csvFile.close()
    return rc;
  }
  public int savePropertyValues(String prpName,TDataType source)
  {
    return savePropertyValues(prpName,null,source);
  }
  public int savePropertyValues(String prpName,String devName,TDataType source)
  {
    if (prpName == null || source == null) return TErrorList.invalid_parameter;
    TExportProperty prp = propertyList.getFirstProperty(prpName);
    if (prp == null) return TErrorList.illegal_property;
    try
    {
      ArrayList<String>alst = prp.getDeviceList();
      String[] dlst = (alst != null) ?
          alst.toArray(new String[0]) :
          deviceList.getDeviceNameList();
      TInitializer initializer = TInitializerFactory.getInstance().getInitializer();
      String fn = initializer.getFecHome()+File.separator;
      if (moduleName != null && new File(fn+moduleName+File.separator).exists())
        fn += moduleName+File.separator;
      String filename = fn + prpName+"-settings.csv";
      String nl = System.getProperty("line.separator");
      FileWriter fw = new FileWriter(filename);
      String ln = "DEVICES, VALUES"+nl;
      fw.write(ln);
      int len = dlst != null ? dlst.length : 1;
      String dev = "";
      int devnr = getDeviceNumber(devName, prpName);
      if (devnr < 0) devnr = 0;
      Object obj = source.getDataObject();
      boolean isSrArray = source.isArrayOfPrimitives() && !prp.isMultiChannelArray();
      for (int i=0; i<len; i++)
      {
        dev = dlst != null ? dlst[i] : "#"+i;
        if (isSrArray)
        {
          ln = dev+", <$FILE"+nl;
          if (i == devnr)
          { // the targeted device
            int alen = prp.getInputSize();
            int offset = i * alen;
            int p = filename.lastIndexOf('.');
            if (p < 1) return TErrorList.invalid_name;
            String vfn = filename.substring(0, p) + "." + i;
            DataOutputStream dos = new DataOutputStream(new FileOutputStream(vfn));          
            switch (source.dFormat)
            {
              case TFormat.CF_BYTE:
                for (int k=0; k<alen; k++)
                  dos.writeByte(((byte[])obj)[k+offset]);
                break;
              case TFormat.CF_INT16:
                for (int k=0; k<alen; k++)
                  dos.writeShort(((short[])obj)[k+offset]);
                break;
              case TFormat.CF_INT32:
                for (int k=0; k<alen; k++)
                  dos.writeInt(((int[])obj)[k+offset]);
                break;
              case TFormat.CF_INT64:
                for (int k=0; k<alen; k++)
                  dos.writeLong(((long[])obj)[k+offset]);
                break;
              case TFormat.CF_FLOAT:
                for (int k=0; k<alen; k++)
                  dos.writeFloat(((float[])obj)[k+offset]);
                break;
              case TFormat.CF_DOUBLE:
                for (int k=0; k<alen; k++)
                  dos.writeDouble(((double[])obj)[k+offset]);
                break;
              default:
                dos.close();
                return TErrorList.not_supported;
            }
            dos.close();
          }
        }
        else
        {
          ln = dev+", "+TDataType.pushToCsvString(obj, i)+nl;
        }
        fw.write(ln);
      }
      fw.close();
    }
    catch (Exception e)
    {
      TFecLog.log("savePropertyValues","cannot save "+prpName+" values: "+e.getMessage());
    }
    return 0;
  }
  private boolean validateSaveRestoreProperty(TExportProperty prp)
  {
    if (prp == null || !prp.isSaveAndRestore) return false;
    try
    {
      int sizOut,sizIn,sizSr=0;
      TDataType srData = prp.getSaveRestoreData();
      if (srData != null) return true;
      if (!TAccess.isWrite((byte)prp.getAccessMode())) return false;
      if ((sizIn=prp.getInputSize()) < 1) return false;
      if ((sizOut=prp.getOutputSize()) < 1) return false;
      short fmt=prp.getOutputFormat();
      if (fmt != prp.getInputFormat()) return false;
      if (!TFormat.isAllowedSaveAndRestoreFormat(fmt)) return false;
      //if (!TFormat.isSimpleFormat(fmt)) return false;
      if (fmt == TFormat.CF_TEXT)
      { // actually 1 string = an array of chars
        fmt = TFormat.CF_STRING;
        sizSr = deviceList.getNumberOfDevices();
        if (sizSr < 1) sizSr = numDevicesFromExportsFile;
      }
      else if (sizIn == 1)
      { // property sets one at a time
        if (sizOut == 1)
        { /* attribute style */
          sizSr = deviceList.getNumberOfDevices();
        }
        else
        { /* part of a multi-channel array ? */
          if (!TArrayType.isChannel(prp.getDescription().getArrayType())) return false;
          sizSr = sizOut;
        }
      }
      else
      { // property sets multiple values : allow only if multi-channel array
        if (sizIn != sizOut) return false;
        if (TArrayType.isChannel(prp.getDescription().getArrayType()))
        {
          sizSr = sizOut;
        }
        else if (TFormat.isNumberFormat(fmt))
        { // an array of primitives -> make allocation adjustments
          int n = deviceList.getNumberOfDevices();
          ArrayList<String> pdl = prp.getDeviceList();
          if (pdl != null) n = pdl.size();
          sizSr = n * sizOut;     
        }
        else
        {
          return false;
        }
      }
      prp.setSaveRestoreData(new TDataType(sizSr,fmt));
      return true;
    }
    catch (Exception any)
    {
      return false;
    }
  }
  private boolean[] hasSrValues = null;
  protected void applyStoredPropertyValues()
  {
    if (srLst.size() == 0) return;
    Iterator<String> it = srLst.iterator();
    String prp = null, dev = "";
    TExportProperty p = null;
    TDataType srdt = null, tdt = null, ndt = new TDataType();
    int len = 0, siz = 1, cc = 0;
    TAccess wacc = new TAccess(TAccess.CA_WRITE|TAccess.CA_SAVERESTORE);
    TAccess racc = new TAccess(TAccess.CA_READ|TAccess.CA_SAVERESTORE);
    ArrayList<String>alst = null;
    String[] dlst = null;
    TEquipmentModuleFactory f = TEquipmentModuleFactory.getInstance();
    TContractTable tct = new TContractTable();
    TClientEntry tce = new TClientEntry();
    tce.cln = new TClient();
    tce.cln.userName = "INITIALIZATION";
    tct.clt.add(tce);
    while (it.hasNext())
    {
      prp = it.next();
      it.remove();
      if ((p=propertyList.getFirstProperty(prp)) == null) continue;
      if ((srdt=p.getSaveRestoreData()) == null) continue;
      alst = p.getDeviceList();
      dlst = (alst != null) ?
          alst.toArray(new String[0]) :
          deviceList.getDeviceNameList();
      siz = 1; hasSrValues = null;
      if (srdt.isArrayOfPrimitives())
      {
        siz = p.getInputSize();
        if (siz > 1) hasSrValues = new boolean[siz];
      }
      tdt = new TDataType(siz,srdt.getFormat());
      len = srdt.getArrayLength();
      if (dlst != null && dlst.length > 0 && len > dlst.length)
      { // TODO: this should always be the case :
        len = dlst.length;
      }
      if (restorePropertyValues(prp, srdt) == 0)
      { // found a save and restore file
        Object obj = srdt.getDataObject();
        for (int i=0; i<len; i++)
        {
          if (hasSrValues != null && !hasSrValues[i]) continue;
          dev = (dlst != null && dlst.length > 0) ? dlst[i] : "#"+i;
          tdt.replaceData(obj, i*siz, 0, siz);
          f.setCurrentContractEntry(tct);
          cc = callProperty(prp, dev, ndt, tdt, wacc);
          TFecLog.log("applyStoredPropertyValues","restore ("+moduleName+")/"+dev+"["+prp+"] : "+TErrorList.getErrorString(cc));
        }
      }
      else
      { // no file: get initial values
        for (int i=0; i<len; i++)
        {
          dev = (dlst != null && dlst.length > 0) ? dlst[i] : "#"+i;
          f.setCurrentContractEntry(tct);
          cc = callProperty(prp, dev, tdt, ndt, racc);
          tdt.getData(srdt.getDataObject(),siz,i*siz);
          TFecLog.log("applyStoredPropertyValues","acquire initial value ("+moduleName+")/"+dev+"["+prp+"] : "+TErrorList.getErrorString(cc));
        }       
      }
    }
    hasSrValues = null;
  }
  /**
   * Registers a property without a handler. This property has to be handled by
   * the equipment function.
   *
   * @param prpName is the property name
   * @param dout Provides output format specification
   * @param din Provides input format specification
   * @param acc Access information
   * @param prpDesc Property description, parsed into a string description
   *        of the property as well as range values and units.
   * @param prpId an assigned Property ID
   * @return
   */
  public void registerProperty(String prpName, TDataType dout, TDataType din, short acc,
      TPropertyDescription prpDesc, int prpId)
  {
    TStrings.validatePropertyName(prpName);
    if (prpDesc == null) prpDesc = new TPropertyDescription(null);
    if (prpDesc.getArrayRowLength() == 0) prpDesc.setArrayRowLength(dout.dArrayLength);
    if (dout.dArrayLength == 1) prpDesc.setArrayType(TArrayType.AT_SCALAR);
    TExportProperty prp = new TExportProperty(prpId, prpName, acc, din, dout);
    prp.setDescription(prpDesc);
    propertyList.addProperty(this, prp, null);
    if (prpName.endsWith(".NAM")) deviceList.setPropertyOriented(true);
    if ((acc & TAccess.CA_SAVERESTORE) != 0 && validateSaveRestoreProperty(prp))
    {
      if (!srLst.contains(prpName)) srLst.add(prpName);
    }
    if ((acc & TAccess.CA_HIST) != 0)
    { // add a 'quick' history
      int fmt = prp.getOutputFormat();
      int siz = prp.getOutputSize();
      int idx = gEqmFactory.getNextHistoryRecordIndex(getLocalName(),prpName,"#0",fmt,siz);
      int cc = addLocalHistoryRecord(idx, "#0", prpName, siz, fmt);
      if (cc != 0) TFecLog.log("could not add "+prpName+" to local history subsystem: "+TErrorList.getErrorString(cc));
    }   
  }
  /**
   * Registers a property and assigns a handler.
   *
   * @param prp is the TExportProperty instance to be registered
   * @param handler is the designated property dispatch handler
   *
   * @see TPropertyHandler, TExportProperty, TPropertyDescription
   * \b example
   *
   * \include eg_registerproperty.java
   */
  public void registerProperty(TExportProperty prp, TPropertyHandler handler)
  {
    if (prp == null) return;
    propertyList.addProperty(this, prp, handler);
    short acc = prp.getAccessMode();
    String prpName = prp.getName();
    if ((acc & TAccess.CA_SAVERESTORE) != 0 && validateSaveRestoreProperty(prp))
    {
      if (!srLst.contains(prpName)) srLst.add(prpName);
    }
    if ((acc & TAccess.CA_HIST) != 0)
    { // add a 'quick' history
      int fmt = prp.getOutputFormat();
      int siz = prp.getOutputSize();
      int idx = gEqmFactory.getNextHistoryRecordIndex(getLocalName(),prpName,"#0",fmt,siz);
      int cc = addLocalHistoryRecord(idx, "#0", prpName, siz, fmt);
      if (cc != 0) TFecLog.log("could not add "+prpName+" to local history subsystem: "+TErrorList.getErrorString(cc));
    }
    String aprpnam = gEqmFactory.getAliasFromName(prp.getName());
    if (aprpnam != null)
    { // register the alias as well !
      TExportProperty aprp = new TExportProperty(prp);
      aprp.setName(aprpnam);
      propertyList.addProperty(this,aprp, handler);
    }
    if (prp.getName().endsWith(".NAM")) deviceList.setPropertyOriented(true);
  }
  public int attachPropertyHandler(String prp,TPropertyHandler handler)
  {
    String aprp = gEqmFactory.getAliasFromName(prp);
    if (aprp != null) propertyList.setHandler(aprp,handler);
    return propertyList.setHandler(prp,handler);
  }
  /**
   * Adds a collection of device names to the module's device list.
   */
  // TODO Test if sequence remains unchanged
  public void registerDevices(Collection<String[]> deviceCollection)
  {
//    deviceList.addAll(deviceNames);
//    int ndevs = deviceCollection.size();
    String[] devs = (String[])deviceCollection.toArray(new String[0]);
    int ndevs = devs.length;
    for (int i=0; i<ndevs; i++)
    {
      TStrings.validateDeviceName(devs[i]);
      deviceList.addDevice(devs[i],this);
    }
  }
  public void registerDevices(String[] deviceNames)
  {
    if (deviceNames == null) return;
    for (int i=0; i<deviceNames.length; i++)
    {
      TStrings.validateDeviceName(deviceNames[i]);
      deviceList.addDevice(deviceNames[i],i,this);
    }
  }
  /**
   * Adds a device to the module's device list.
   *
   * @param device is the TDevice instance to register
   */
  public void registerDevice(TDevice device)
  {
    if (device == null) return;
    device.setEqm(this);
    deviceList.addDevice(device);
  }
  public void registerDevices(TDevice[] devices)
  {
    if (devices == null) return;
    for (int i=0; i<devices.length; i++)
    {
      devices[i].setEqm(this);
      deviceList.addDevice(devices[i]);
    }
  }
  public void registerDevices(int numberOfDevices)
  {
    if (numberOfDevices <= 0) return;
    TDevice[] devices = new TDevice[numberOfDevices];
    for (int i=0; i<numberOfDevices; i++)
    {
      devices[i] = new TDevice(i);
      devices[i].setEqm(this);
      deviceList.addDevice(devices[i]);
    }
  }
  /**
   * Adds a device name to the module's device list.
   *
   * This method also creates a TDevice instance for the device
   * name in question, which can be retrieved via a call to
   * getDevice().
   *
   * @param devName is the device name to be assigned
   *
   * @return the assigned device number
   */
  public int registerDeviceName(String devName)
  {
    TStrings.validateDeviceName(devName);
    deviceList.addDevice(devName,this);
    String adev = gEqmFactory.getAliasFromName(devName);
    if (adev != null)
    { // register the alias as well !
      deviceList.addDevice(adev,this);
    }
    return deviceList.getDeviceNumber(devName);
  }
  public int registerDeviceName(String devName,int devNumber)
  {
    TStrings.validateDeviceName(devName);
    deviceList.addDevice(devName,devNumber,this);
    String adev = gEqmFactory.getAliasFromName(devName);
    if (adev != null)
    { // register the alias as well !
      deviceList.addDevice(adev,devNumber,this);
    }
    return devNumber;
  }
  /**
   * Returns all property names in a linked list.
   *
   * @return
   */
  public LinkedList<String> getPropertyNames()
  {
    LinkedList<String> rv = new LinkedList<String>();
    rv.addAll(propertyList.getPropertyNames());
    return rv;
  }
  private void dumpList(String text,LinkedList<String> lst)
  {
    TLinkFactory.dbgPrint(text);
    for (Iterator<String> it = lst.iterator(); it.hasNext();)
    {
      TLinkFactory.dbgPrint((String) it.next());
    }
    TLinkFactory.dbgPrint("");
  }
  public void dumpProperties()
  {
    dumpList("Registered Properties for "+getLocalName()+" :",getPropertyNames());
  }
  public void dumpDevices()
  {
    dumpList("Registered Devices for "+getLocalName()+" :",getDeviceNames());   
  }
  /**
   * Returns all device names in a linked list.
   *
   * @return
   */
  public LinkedList<String> getDeviceNames()
  {
    return deviceList.getDeviceLinkedList();
  }
  /**
   * Returns true if this equipment module has registered a stock property of a
   * given name.
   *
   * @param propertyName
   * @return
   */
  public boolean hasStockProperty(String propertyName)
  {
    return stockList.hasProperty(propertyName);
  }
  public boolean hasProperty(String propertyName)
  {
    return propertyList.hasProperty(propertyName);
  }
  /**
   * Calls a stock property handler
   *
   * @param propertyName
   * @param devName
   * @param dout
   * @param din
   * @param devAccess
   * @return
   */
  public short callStockProperty(String propertyName, String devName, TDataType dout,
      TDataType din, TAccess devAccess)
  {
    return stockList.callPropertyHandler(propertyName, devName, dout, din, devAccess);
  }
  /**
   * Finds a property and calls the property handler. If no property handler is
   * installed, the equipment function is called instead.
   *
   * @param propertyName
   * @param devName
   * @param dout
   * @param din
   * @param devAccess
   * @return Return code of property handler/equipment function.
   *         TErrorList.non_existent_elem if no property handler and no
   *         equipment function could be found.
   */
  public short callProperty(String propertyName, String devName, TDataType dout, TDataType din, TAccess devAccess)
  {
    try
    {
      if (propertyList.hasProperty(propertyName))
      {
        return propertyList.callPropertyHandler(propertyName, devName, dout, din, devAccess);
      }
      else if (eqpFcn != null)
      {
        if (TEquipmentModuleFactory.debugLevel > 1)
          DbgLog.log("callProperty","call function handler for "+getLocalName()+"/"+devName+"["+propertyName+ "]");       
        return eqpFcn.call(propertyName, devName, dout, din, devAccess.toShort());
      }
      else
      {
        if (TEquipmentModuleFactory.debugLevel > 1)
          DbgLog.log("callProperty","no handler for "+getLocalName()+"/"+devName+"["+propertyName+ "]");       
        return TErrorList.illegal_property;
      }
    }
    catch (Exception e)
    {
      e.printStackTrace();
      MsgLog.log("callProperty "+propertyName,"unhandled exception "+e.toString(),TErrorList.code_failure,e,0);
      setCompletionString(e.getMessage());
      return TErrorList.code_failure+1000;
    }
  }
  public short callMetaProperty(String metaPropertyName, String devName, TDataType dout, TDataType din, TAccess devAccess)
  {
    try
    {
      if (!TMetaProperties.isMetaProperty(metaPropertyName)) return TErrorList.illegal_property;
      int metaIndex = TMetaProperties.getIndex(metaPropertyName);
      String propertyName = TMetaProperties.getTargetProperty(metaPropertyName);
      if (stockList.hasProperty(propertyName))
      { // meta info for stock property ! (only .NAM is allowed)
        if (metaIndex == TMetaProperties.NAM_ID)
        {
          String[] slst = deviceList.getDeviceNameList();
          return (short)dout.putData(StringToName.stringArrayToName16(slst));     
        }
      }
      if (!propertyList.hasProperty(propertyName)) return TErrorList.illegal_property;
      TExportProperty prp = propertyList.getFirstProperty(propertyName);
      if (prp == null) return TErrorList.illegal_property;
      TPropertyEGU egu;
      int cc = 0;
      float[] fv = new float[1];
      switch (metaIndex)
      {
        case TMetaProperties.URL_ID:
          return (short) dout.putData(prp.getDescription().getUrl());
        case TMetaProperties.DSC_ID:
          return (short) dout.putData(prp.getDescription().getText());
        case TMetaProperties.MAX_ID:
        case TMetaProperties.XMAX_ID:
          egu = metaIndex == TMetaProperties.XMAX_ID ?
              prp.getDescription().getXRange() : prp.getDescription().getYRange();
          if (devAccess.isWrite())
          {
            if (din.dArrayLength != 1) return TErrorList.dimension_error;
            if (!TFormat.isNumberFormat(din.dFormat)) return TErrorList.illegal_format;
            if ((cc=din.getData(fv)) != 0) return (short)cc;
            egu.setMaxValue(fv[0]);
            if (dout.dArrayLength == 0) return 0;
          }                            
          if (!TFormat.isNumberFormat(dout.dFormat)) return TErrorList.illegal_format;
          fv[0] = egu.getMaxValue();
          return (short) dout.putData(fv);
        case TMetaProperties.MIN_ID:
        case TMetaProperties.XMIN_ID:
          egu = metaIndex == TMetaProperties.XMIN_ID ?
              prp.getDescription().getXRange() : prp.getDescription().getYRange();
          if (devAccess.isWrite())
          {
            if (din.dArrayLength != 1) return TErrorList.dimension_error;
            if (!TFormat.isNumberFormat(din.dFormat)) return TErrorList.illegal_format;
            if ((cc=din.getData(fv)) != 0) return (short)cc;
            egu.setMinValue(fv[0]);
            if (dout.dArrayLength == 0) return 0;
          }                            
          if (!TFormat.isNumberFormat(dout.dFormat)) return TErrorList.illegal_format;
          fv[0] = egu.getMinValue();
          return (short) dout.putData(fv);
        case TMetaProperties.XEGU_ID:
        case TMetaProperties.EGU_ID: // to satisfy DOOCS calls ...
          egu = metaIndex == TMetaProperties.XEGU_ID ?
                              prp.getDescription().getXRange() :
                              prp.getDescription().getYRange();
                             
          if (devAccess.isWrite())
          {
            switch (din.dFormat)
            {
              case TFormat.CF_NAME16:
              case TFormat.CF_NAME32:
              case TFormat.CF_NAME64:
                if (din.getArrayLength() != 1) return TErrorList.dimension_error;
              case TFormat.CF_TEXT:
                char[] newUnits = new char[64];
                cc = din.getData(newUnits);
                if (cc != 0) return (short)cc;
                egu.setUnits(new String(newUnits));
                break;
              case TFormat.CF_FLOAT:
              case TFormat.CF_DOUBLE:
              case TFormat.CF_DLONG:
              case TFormat.CF_LONG:
              case TFormat.CF_SHORT:
                if (din.dArrayLength != 2) return TErrorList.dimension_error;
                float[] yrng = new float[2];
                cc = din.getData(yrng);
                if (cc != 0) return (short)cc;
                egu.setMinValue(yrng[0]);
                egu.setMaxValue(yrng[1]);
                break;
              case TFormat.CF_USTRING:
                if (din.getArrayLength() != 1) return TErrorList.dimension_error;
                USTRING[] u = new USTRING[1];
                cc = din.getData(u);
                if (cc != 0) return (short)cc;
                egu.setUnits(u[0].getString());
                egu.setMinValue(u[0].f1val);
                egu.setMaxValue(u[0].f2val);
                egu.setGraphType((short)u[0].ival);
                break;
              default:
                return TErrorList.illegal_format;
            }           
            if (dout.dArrayLength == 0) return 0;
          }                            
          switch (dout.dFormat)
          {
            case TFormat.CF_TEXT:
            case TFormat.CF_NAME16:
            case TFormat.CF_NAME32:
            case TFormat.CF_NAME64:
              return (short) dout.putData(egu.getUnits());
            case TFormat.CF_FLOAT:
            case TFormat.CF_DOUBLE:
            case TFormat.CF_DLONG:
            case TFormat.CF_LONG:
            case TFormat.CF_SHORT:
              float[] yrng = new float[2];
              yrng[0] = egu.getMinValue();
              yrng[1] = egu.getMaxValue();
              return (short) dout.putData(yrng);
            case TFormat.CF_USTRING:
              USTRING[] u = new USTRING[1];
              u[0] = new USTRING(egu.getGraphType(),
                                 egu.getMinValue(),
                                 egu.getMaxValue(),
                                 0,
                                 egu.getUnits());
              return (short) dout.putData(u);
            default:
              return TErrorList.illegal_format;
          }
        case TMetaProperties.HST_ID:
          THistoryRecord hst = null;
          int listsize = gLclHstList.size();
          int index = 0, sample = 0;
          long start = 0;
          long stop = System.currentTimeMillis()-1000;
          int sysstart = 0, sysstop = 0;
          int cycnr = TEquipmentModuleFactory.getCycleNumber();
          float lwr = 0, upr = 0;
          Object data = null;
          int dataSize = 0, inputDataSize = 0;
          boolean isCentralArchiveRequest = false;
         
          if (TMetaProperties.isCentralArchive(metaPropertyName))
          {
            isCentralArchiveRequest = true;
          }
          boolean isSnapshot = TMetaProperties.isHistorySnapshot(metaPropertyName);
          dataSize = dout.getArrayLength();
          if (dout.dFormat == TFormat.CF_HISTORY)
          { // trap this early
            int hsiz = TFormat.getCarriedFormatSize(dout.getTag());
            if (hsiz > 0) dataSize /= hsiz + TFormat.getHistoryHeaderSize();
          }
          start = stop - dataSize * 1000; // millisecond timestamps here!
          // check the time range and/or filtering criteria the caller has given us ...
          if (din != null && (inputDataSize=din.getArrayLength()) > 0)
          {
            switch (din.getFormat())
            {
              case TFormat.CF_LONG:
                int[] ival = (int[])din.getDataObject();
                switch (inputDataSize)
                {
                  case 6: sysstop = ival[5];
                    if (cycnr == 0) sysstop = 0;
                    if (sysstop > cycnr) sysstop = cycnr;
                  case 5: sysstart = ival[4];
                    if (cycnr == 0) sysstart = 0;
                  case 4: sample = ival[3];
                  case 3: index = ival[2];
                  case 2: if (ival[1] < (int)(stop/1000)) stop = ((long)ival[1])*1000;
                  case 1: start = ((long)ival[0])*1000;
                  default: break;
                }
                if (sample < 0) sample = 0;
                break;
               
              case TFormat.CF_DLONG:
                long[] lval = (long[])din.getDataObject();
                switch (inputDataSize)
                {
                  case 6: sysstop = (int)lval[5];
                    if (cycnr == 0) sysstop = 0;
                    if (sysstop > cycnr) sysstop = cycnr;
                  case 5: sysstart = (int)lval[4];
                    if (cycnr == 0) sysstart = 0;
                  case 4: sample = (int)lval[3];
                  case 3: index = (int)lval[2];
                  case 2: if (lval[1] < stop/1000) stop = (long)(lval[1]*1000);
                  case 1: start = (long)(lval[0]*1000);
                  default: break;
                }
                if (sample < 0) sample = 0;
                break;
               
              case TFormat.CF_DOUBLE:
                double[] dval = (double[])din.getDataObject();
                switch (inputDataSize)
                {
                  case 6: sysstop = (int)dval[5];
                    if (cycnr == 0) sysstop = 0;
                    if (sysstop > cycnr) sysstop = cycnr;
                  case 5: sysstart = (int)dval[4];
                    if (cycnr == 0) sysstart = 0;
                  case 4: sample = (int)dval[3];
                  case 3: index = (int)dval[2];
                  case 2: if (dval[1] < stop/1000) stop = (long)(dval[1]*1000);
                  case 1: start = (long)(dval[0]*1000);
                  default: break;
                }
                if (sample < 0) sample = 0;
                break;
              case TFormat.CF_FWINDOW:
                FWINDOW[] fw = (FWINDOW[])din.getDataObject();
                start = ((FWINDOW[])fw)[0].i1val * 1000;
                stop = ((FWINDOW[])fw)[0].i2val * 1000;
                lwr = ((FWINDOW[])fw)[0].f1val;
                upr = ((FWINDOW[])fw)[0].f2val;
                if (lwr < upr) sample = -1;
                break;
              default:
                return TErrorList.illegal_format;
            }
          }
          else
          { // no input given
            if (dataSize == 1)
            {
              start = stop;
              isSnapshot = true;
            }
          }
          if (isSnapshot)
          {
            if (start < 1) return TErrorList.argument_list_error;
            if (sysstart > 0) sysstop = sysstart; else stop = start;
            sample = 0;
            index = 0;
            lwr = upr = (float)0.0;         
          }
          // what the caller wants returned ....
          boolean literalDevice = false;
          for (int i=0; i<listsize; i++)
          { // go thru the list to see if the requested device was stored literally
            hst = (THistoryRecord)gLclHstList.get(i);
            if (hst.getPrp().compareToIgnoreCase(prp.getName()) != 0) continue;
            if (hst.getDev().compareTo(devName) != 0) continue;
            literalDevice = true;
            break;
          }
          for (int i=0; i<listsize; i++)
          {
            hst = (THistoryRecord)gLclHstList.get(i);
            if (hst.getPrp().compareToIgnoreCase(prp.getName()) == 0)
            {
              if (hst.getDev().compareTo(devName) != 0)
              { // should we look for a match in the device name ?
                if (index == 0 && !literalDevice)
                  index = getDeviceList().getDeviceNumber(devName);
                else
                  continue;
              }
              if (isCentralArchiveRequest)
              {
                String rmtsrv = hst.getRemoteHistoryServer();
                if (rmtsrv == null || rmtsrv.length() == 0)
                { // try the central archive systematics ...
                  rmtsrv = "/"+getContext()+"/HISTORY/"+devName+"["+getExportName()+"."+propertyName+"]";
                }
                return (short)gEqmFactory.setRedirectionString(rmtsrv);
              }
              // snapshots always have index = 0
              if (start == stop && dataSize > 1) index = 0;
              switch (dout.dFormat)
              {
                case TFormat.CF_BYTE:
                  data = new byte[dataSize]; break;
                case TFormat.CF_SHORT:
                  data = new short[dataSize]; break;
                case TFormat.CF_LONG:
                  data = new int[dataSize]; break;
                case TFormat.CF_DLONG:
                  data = new long[dataSize]; break;               
                case TFormat.CF_INTINT:
                  data = new INTINT[dataSize]; break;
                case TFormat.CF_FLOAT:
                  data = new float[dataSize]; break;
                case TFormat.CF_FLTINT:
                  data = new FLTINT[dataSize]; break;
                case TFormat.CF_INTFLTINT:
                  data = new INTFLTINT[dataSize]; break;
                case TFormat.CF_DOUBLE:
                  data = new double[dataSize]; break;
                case TFormat.CF_DBLDBL:
                  data = new DBLDBL[dataSize]; break;
                case TFormat.CF_DBLTIME:
                  data = new DBLTIME[dataSize]; break;
                case TFormat.CF_NAME16: 
                  data = new NAME16[dataSize]; break;
                case TFormat.CF_NAME16I: 
                  data = new NAME16I[dataSize]; break;
                case TFormat.CF_NAME32: 
                  data = new NAME32[dataSize]; break;
                case TFormat.CF_NAME32I: 
                  data = new NAME32I[dataSize]; break;
                case TFormat.CF_NAME64: 
                  data = new NAME64[dataSize]; break;
                case TFormat.CF_NAME64I: 
                  data = new NAME64I[dataSize]; break;
                case TFormat.CF_HISTORY:
                  data = new HISTORY[dataSize];
                  // create the first one so as to pass the tag along
                  short f = TFormat.getFormatCode(dout.getTag());
                  if (f == TFormat.CF_NULL) return TErrorList.illegal_format;
                  TDataType d = new TDataType(1,f);
                  ((HISTORY[])data)[0] = new HISTORY(d);
                  break;
                default:
                  data = TFormat.makeCompoundDataObjectArray(dout.dFormat, dout.getTag(), dataSize);
                  break;
              }
              int nr = hst.getDataFromLTS(index,start,stop,sysstart,sysstop,lwr,upr,data,dataSize,0,sample);
              if (nr < 0)
              {
                if (nr == -TErrorList.server_redirection)
                {
                  String rmtsrv = hst.getRemoteHistoryServer();
                  if (rmtsrv == null || rmtsrv.length() == 0) return TErrorList.not_implemented;
                  return (short)gEqmFactory.setRedirectionString(rmtsrv);
                }
                return (short)-nr;
              }
              boolean needStsData = true;
              if (nr > 0)
              {
                if (isSnapshot || nr >= dataSize - 1)
                { // found the snapshot in or filled the buffer from LTS
                  dout.putData(data);
                  dout.setDataTimeStamp(hst.getLastReadTimestamp());
                  dout.setSystemDataStamp(hst.getLastReadSystemStamp());
                  return 0;
                }
                start = hst.getLastReadTimestamp() + 1;
                if (start >= stop) needStsData = false; // we're finished
              }
              if (needStsData)
              { // still recent points to acquire
                nr = hst.getDataFromSTS(index,start,stop,sysstart,sysstop,lwr,upr,data,dataSize,nr,sample);
                if (nr < 0) return (short)-nr;
              }
              if (dataSize > 1 && nr < dataSize) dout.dArrayLength = nr;
              dout.putData(data);
              dout.setDataTimeStamp(hst.getLastReadTimestamp());
              dout.setSystemDataStamp(hst.getLastReadSystemStamp());
              return 0;
            }
          }
          return TErrorList.not_implemented;
        case TMetaProperties.NAM_ID:
          // if .NAM properties are registered they are handled with the normal property handler
          String[] slst = deviceList.getDeviceNameList();
          ArrayList<String> lst = prp.getDeviceList();
          if (lst != null && lst.size() > 0)
          {
            slst = new String[lst.size()];
            lst.toArray(slst);
          }
          else
          { // devices from registered device list ...
            boolean chkOn = false, chkMask = false;
            int idx = -1, edx = -1, mask = 0;
            if (metaPropertyName.contains(".ONLINE")) chkOn = true;
            if ((idx=metaPropertyName.indexOf(".DMASK.")) > 0)
            { // fun and games with .NAMES ...
              idx += 7;
              edx = metaPropertyName.indexOf(".NAM");
              if (edx > idx)
              {
                try
                {
                  String mstr = metaPropertyName.substring(idx, edx);
                  int radix = 10;
                  if (mstr.toLowerCase().startsWith("0x"))
                  {
                    mstr = mstr.substring(2);            
                    radix = 16;
                  }
                  mask = Integer.parseInt(mstr, radix)
                  chkMask = true;
                }
                catch (Exception ignore) {}; // then mask stays = 0
              }
            }
            if (chkOn || chkMask)
            {
              TDevice dev;
              LinkedList<String> dl = new LinkedList<String>();
              for (int i=0; i<deviceList.getNumberOfDevices(); i++)
              {
                if ((dev=deviceList.getDevice(i)) == null) continue;
                if (chkOn && dev.isOffline()) continue;
                if (chkMask && !dev.isMaskSet(mask)) continue;
                dl.add(dev.getName());
              }
              slst = dl.toArray(new String[0]);
            }
          }
          return (short)dout.putData(StringToName.stringArrayToName64(slst));
        default
          break;
      }
      return TErrorList.illegal_property;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      MsgLog.log("callProperty "+metaPropertyName,"unhandled exception "+e.toString(),TErrorList.code_failure,e,0);
      setCompletionString(e.getMessage());
      return TErrorList.code_failure + 1000;
    }
  }
  // csv file EXPORTS handlers:
  class expRowHndlr implements RowHandler
  { // the Row Handler will be called when all columns have been read in
    private TExportProperty xp;
    private TPropertyDescription prpDesc;
    private String targetLocalName = null;
    private String localName = null;
    private String expName = null;
    private int numDevsHint = 1;
    public void setLocalName(String value) { localName = value; }
    public void setExpName(String value) { expName = value; }
    public void setNumDevsHint(int value) { numDevsHint = value; }
    public int getNumDevsHist() { return numDevsHint; }
    public TExportProperty getExportProperty() { return xp; }
    public TPropertyDescription getPropertyDescription() { return prpDesc; }
    expRowHndlr(String targetName)
    {
      xp = new TExportProperty();
      prpDesc = new TPropertyDescription();
      if (targetLocalName == null) targetLocalName = targetName;
    }
    public int process(int index)
    {
      if (localName == null || localName.compareTo(moduleName) != 0) return 0;
      if (targetLocalName != null && targetLocalName.compareTo(moduleName) != 0) return 0;
      // register this one
      if (expName != null) setExportName(expName);
      if ((TAccess.CA_WRITE & xp.getAccessMode()) == TAccess.CA_WRITE)
      { // a writeable property
        if (xp.getInputFormat() == TFormat.CF_NULL &&
            xp.getOutputFormat() != TFormat.CF_NULL)
        { // probably a quick registration: supply this
          xp.setInputFormat(xp.getOutputFormat());
        }
      }
      xp.setDescription(prpDesc);
      registerProperty(xp,null);
      // allocate a new one
      xp = new TExportProperty();
      prpDesc = new TPropertyDescription();
      if (numDevsHint > numDevicesFromExportsFile)
        numDevicesFromExportsFile = numDevsHint;
      return 0;
    }
  }
  class expXnHndlr implements csvHandler
  {
    private expRowHndlr rHndlr;
    expXnHndlr(expRowHndlr rowHndlr)
    {
      rHndlr = rowHndlr;
    }
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      String nm = (strValue.length() > TStrings.EXPORT_NAME_SIZE) ? strValue.substring(0,TStrings.EXPORT_NAME_SIZE) : strValue;
      rHndlr.setExpName(nm);
      return 0;
    }
  }
  class expCtxHndlr implements csvHandler
  {
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      setContext(strValue);
      String fctx = gEqmFactory.getFecContext();
      if (fctx == null || fctx.length() == 0)
      { // make sure the general context is set
        gEqmFactory.setFecContext(strValue);
      }
      return 0;
    }
  }
  class expMstrHndlr implements csvHandler
  {
    public int process(String strValue, int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      setMaster(strValue);
      return 0;
    }
  }
  class expSlavHndlr implements csvHandler
  {
    public int process(String strValue, int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      String mstr = getMaster();
      if (mstr != null && mstr.length() > 0) setSlaveMaster(mstr,strValue);
      return 0;
    }
  }
  class expSubHndlr implements csvHandler
  {
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      setSubsystem(strValue);
      return 0;
    }
  }
  class expGrpHndlr implements csvHandler
  {
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      setGroupName(strValue);
      return 0;
    }
  }
  class expGrpPrefixHndlr implements csvHandler
  {
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      setGroupDevicePrefix(strValue);
      return 0;
    }
  }
  class expGrpPostfixHndlr implements csvHandler
  {
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      setGroupDevicePostfix(strValue);
      return 0;
    }
  }
  class expGrpIdxHndlr implements csvHandler
  {
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      setGroupIndex(Integer.valueOf(strValue).intValue());
      return 0;
    }
  }
  class expLnHndlr implements csvHandler
  {
    private expRowHndlr rHndlr;
    private String targetLocalName = null;
    expLnHndlr(expRowHndlr rowHndlr, String targetName)
    {
      if (targetLocalName == null) targetLocalName = targetName;
      rHndlr = rowHndlr;
      //localName = targetName;
    }
    public int process(String strValue,int index)
    {
      rHndlr.setLocalName(strValue);
      if (strValue == null || strValue.length() == 0) return 0;
      if (targetLocalName != null && targetLocalName.compareTo(strValue) != 0) return 0;
      String nm = (strValue.length() > TStrings.EQM_NAME_SHORTSIZE) ? strValue.substring(0,TStrings.EQM_NAME_SHORTSIZE) : strValue;
      setLocalName(nm);
      return 0;
    }
  }
  class expPrpHndlr implements csvHandler
  {
    private expRowHndlr rHndlr;
    expPrpHndlr(expRowHndlr rowHndlr) { rHndlr = rowHndlr; }
    public int process(String strValue,int index)
    {
      rHndlr.getExportProperty().setName(strValue);
      return 0;
    }
  }
  class expPrpDescHndlr implements csvHandler
  {
    private expRowHndlr rHndlr;
    expPrpDescHndlr(expRowHndlr rowHndlr) { rHndlr = rowHndlr; }
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      // parse the csv file text description according to convention
      rHndlr.getPropertyDescription().parseComplexString(strValue);
      return 0;
    }
  }
  class expPrpSizeHndlr implements csvHandler
  {
    private expRowHndlr rHndlr;
    expPrpSizeHndlr(expRowHndlr rowHndlr) { rHndlr = rowHndlr; }
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      rHndlr.getExportProperty().setOutputSize(new Integer(strValue).intValue());
      return 0;
    }
  }
  class expNumDevsHndlr implements csvHandler
  {
    private expRowHndlr rHndlr;
    expNumDevsHndlr(expRowHndlr rowHndlr) { rHndlr = rowHndlr; }
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) strValue = "10";
      rHndlr.setNumDevsHint(new Integer(strValue).intValue());
      return 0;
    }
   
  }
  class expPrpFmtHndlr implements csvHandler
  {
    private expRowHndlr rHndlr;
    expPrpFmtHndlr(expRowHndlr rowHndlr) { rHndlr = rowHndlr; }
    public int process(String strValue,int index)
    {
      // parse the format string according to convention
      int stop = strValue.indexOf(".");
      String fmtString = stop != -1 ? strValue.substring(0,stop) : strValue;
      short fmt = TFormat.getFormatCode(fmtString);
      rHndlr.getExportProperty().setOutputFormat(fmt);
      if (stop != -1)
      {
        String fss = strValue.substring(stop+1);
        if (fmt == TFormat.CF_STRUCT)
        {
          if (fss.endsWith(".CHANNEL") || fss.endsWith(".channel"))
          {
            fss = fss.substring(0, fss.length()-8);
          }
          rHndlr.getExportProperty().setOutputTag(fss);
        }
        else
        {
          rHndlr.getPropertyDescription().setArrayType(TArrayType.getType(fss));
        }
      }
      return 0;
    }
  }
  class expDbaHndlr implements csvHandler
  {
    private expRowHndlr rHndlr;
    expDbaHndlr(expRowHndlr rowHndlr) { rHndlr = rowHndlr; }
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      try
      {
        TExportProperty xp = rHndlr.getExportProperty();
        addPrpDbaItem(xp.getName(), xp.getAccessMode(), new Integer(strValue).intValue());
      }
      catch (Exception e)
      { // just dump the stack (parsing error?, xp == null ?)
        e.printStackTrace();
      }
      return 0;
    }
   
  }
  class expPrpInSizeHndlr implements csvHandler
  {
    private expRowHndlr rHndlr;
    expPrpInSizeHndlr(expRowHndlr rowHndlr) { rHndlr = rowHndlr; }
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      rHndlr.getExportProperty().setInputSize(new Integer(strValue).intValue());
      return 0;
    }
  }
  class expPrpInFmtHndlr implements csvHandler
  {
    private expRowHndlr rHndlr;
    expPrpInFmtHndlr(expRowHndlr rowHndlr) { rHndlr = rowHndlr; }
    public int process(String strValue,int index)
    {
      int stop = strValue.indexOf(".");
      String fmtString = stop != -1 ? strValue.substring(0,stop) : strValue;
      short fmt = TFormat.getFormatCode(fmtString);
      rHndlr.getExportProperty().setInputFormat(fmt);
      if (fmt == TFormat.CF_STRUCT)
      {
        String tag = strValue.substring(stop+1);
        if (tag.endsWith(".CHANNEL") || tag.endsWith(".channel"))
        {
          tag = tag.substring(0, tag.length()-8);
        }
        rHndlr.getExportProperty().setInputTag(tag);
      }
      return 0;
    }
  }
  class expPrpIdHndlr implements csvHandler
  {
    private expRowHndlr rHndlr;
    expPrpIdHndlr(expRowHndlr rowHndlr) { rHndlr = rowHndlr; }
    public int process(String strValue,int index)
    {
      int idx = index;
      try
      {
        if (strValue != null && strValue.length() > 0)
          idx = new Integer(strValue).intValue();
      }
      catch (NumberFormatException e)
      {
        idx = index;
      }
      rHndlr.getExportProperty().setId(idx);
      return 0;
    }
  }
  class expPrpAccessHndlr implements csvHandler
  {
    private expRowHndlr rHndlr;
    expPrpAccessHndlr(expRowHndlr rowHndlr) { rHndlr = rowHndlr; }
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      // parse access mode according to convention
      String accessString = strValue.trim().toUpperCase();
      short ta = 0;
      int idx = -1;
      TExportProperty txp = rHndlr.getExportProperty();
      if (txp == null) return TErrorList.code_failure;
      if ((idx=accessString.indexOf("XREAD")) >= 0)
      { // remove this from the accessString for further comparisons
        txp.hasExclusiveRead = true;
        String s = accessString.substring(0, idx);
        s += accessString.substring(idx+5, accessString.length());
        accessString = s;
      }
      if (accessString.indexOf("WRITE") >= 0) ta |=  TAccess.CA_WRITE;
      if (accessString.indexOf("READ") >= 0) ta |=  TAccess.CA_READ;
      if (accessString.indexOf("NETWORK") >= 0) ta |=  TAccess.CA_READ|TAccess.CA_NETWORK;
      if (accessString.indexOf("ASYNC") >= 0)
      {
        ta &= ~(TAccess.CA_WRITE);
        ta |=  TAccess.CA_READ|TAccess.CA_NOSYNC;     
      }
      if (accessString.indexOf("SAVERESTORE") >= 0) ta |=  TAccess.CA_SAVERESTORE;
      if (accessString.indexOf("HIST") >= 0) ta |=  TAccess.CA_HIST;
      if (accessString.indexOf("FORCEOUTPUT") >= 0) ta |=  TAccess.CA_FORCEOUTPUT;
      txp.setAccessMode(ta);
      if (txp.hasExclusiveRead && !TAccess.isRead((byte)ta))
      { // force read calls thru the security layer for this property !
        txp.hasUnlockedExclusiveRead = true;
      }
      return 0;
    }
  }
  class expPrpRdrHndlr implements csvHandler
  {
    private expRowHndlr rHndlr;
    expPrpRdrHndlr(expRowHndlr rowHndlr) { rHndlr = rowHndlr; }
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      // TODO: parse the csv file redirection string according to convention
      rHndlr.getPropertyDescription().setRedirection(strValue);
      return 0;
    }
  }
  class expMaxValueHndlr implements csvHandler
  {
    private expRowHndlr rHndlr;
    private boolean isXaxis = false;
    expMaxValueHndlr(expRowHndlr rowHndlr,boolean xaxis) {rHndlr = rowHndlr; isXaxis = xaxis;}
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      TExportProperty txp = rHndlr.getExportProperty();
      if (txp == null) return TErrorList.code_failure;
      if (isXaxis)
        txp.setMaximumXValue(Float.valueOf(strValue).floatValue());
      else
        txp.setMaximumValue(Float.valueOf(strValue).floatValue());
      return 0;
    }
  }
  class expMinValueHndlr implements csvHandler
  {
    private expRowHndlr rHndlr;
    private boolean isXaxis = false;
    expMinValueHndlr(expRowHndlr rowHndlr,boolean xaxis) {rHndlr = rowHndlr; isXaxis = xaxis;}
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      TExportProperty txp = rHndlr.getExportProperty();
      if (txp == null) return TErrorList.code_failure;
      if (isXaxis)
        txp.setMinimumXValue(Float.valueOf(strValue).floatValue());
      else
        txp.setMinimumValue(Float.valueOf(strValue).floatValue());
      return 0;
    }
  }
  class expUnitsHndlr implements csvHandler
  {
    private expRowHndlr rHndlr;
    private boolean isXaxis = false;
    expUnitsHndlr(expRowHndlr rowHndlr,boolean xaxis) {rHndlr = rowHndlr; isXaxis = xaxis;}
    public int process(String strValue,int index)
    {
      if (strValue == null) return 0;
      TExportProperty txp = rHndlr.getExportProperty();
      if (txp == null) return TErrorList.code_failure;
      if (isXaxis)
        txp.setXUnits(strValue);
      else
        txp.setUnits(strValue);
      return 0;
    }
  }
  // TODO: call this from some strategic location
  // (currently tested inside of SineEquipmentModule)
  public int getExportInformationFromFile()
  {
    return getExportInformationFromFile(moduleName);
  }
  public int getExportInformationFromFile(String eqmName)
  {
    getEquipmentModuleFactory().getFecXmlDocument();
    FecCfg cfg = gEqmFactory.getFecXmlCfg();
    if (cfg != null)
    { // has parsed a fec.xml config file ...
      LinkedList<EqmCfg> el = cfg.getEqmList();
      LinkedList<String> dv = null;
      float max, min;
      String grp, ctx, sub, exp, mstr, slav;
      String fctx = gEqmFactory.getFecContext();
      for( EqmCfg ec : el )
      {
        if (ec.getName().compareTo(eqmName) == 0)
        {
          LinkedList<PropertyCfg> pl = ec.getPropertyList();
          for (PropertyCfg pc : pl)
          {
            TExportProperty xp = new TExportProperty(
              pc.getId().getValue(),pc.getName(),pc.getDescription(),
              pc.getSizeOut().getValue(),(short)pc.getDTypeOut().getValue(),pc.getDTagOut(),
              pc.getSizeIn().getValue(),(short)pc.getDTypeIn().getValue(),pc.getDTagIn(),
              (short)pc.getAccess().getValue());
            if (pc.getDeadband().getValue() > 0)
              xp.setAccessDeadband(pc.getDeadband().getValue());
            xp.getDescription().setArrayType((short)pc.getDArrayTypeOut());
            max = pc.getXmax().getValue();
            min = pc.getXmin().getValue();
            if (min > max) TFecLog.log(eqmName,"x min > x max for property "+pc.getName());
            xp.getDescription().setXRange(new TPropertyEGU(pc.getXEgu(),min,max));
            max = pc.getMax().getValue();
            min = pc.getMin().getValue();
            if (min > max) TFecLog.log(eqmName,"min > max for property "+pc.getName());
            xp.getDescription().setYRange(new TPropertyEGU(pc.getEgu(),min,max));
            // TODO:
            //expp.getDescription().setArrayNumRows(pc.getNumRows().getValue());
            xp.getDescription().setRedirection(pc.getRedirection());
            xp.hasExclusiveRead = pc.hasExclusiveRead;
            xp.hasUnlockedExclusiveRead = pc.hasUnlockedExclusiveRead;
            xp.isEnforceOutput = pc.isEnforceOutput;
            registerProperty(xp,null);
           
            dv = pc.getDevices();
            if (dv != null && dv.size() > 0)
            {
              int nr = 0;
              ArrayList<String> lst = new ArrayList<String>(dv.size());
              for (String d : dv)
              {
                lst.add(nr++,d);
              }
              xp.setDeviceList(lst);
              getDeviceList().setPropertyOriented(true);
            }
          }
          grp = ec.getGroup();
          if (grp != null && grp.length() > 0)
          {
            setGroupName(grp);
            if (ec.getGroupIndex().isValid())
              setGroupIndex(ec.getGroupIndex().getValue());
            setGroupDevicePrefix(ec.getGroupDevicePrefix());
            setGroupDevicePostfix(ec.getGroupDevicePostfix());
          }
          exp = ec.getServer();
          if (exp != null && exp.length() > 0) setExportName(exp);
          ctx = ec.getContext();
          if (ctx == null || ctx.length() == 0)
          { // not specified for the eqm : use the default
            ctx = cfg.getContext();
            if (ctx == null || ctx.length() == 0) ctx = fctx; // hope for the best
          }
          if (fctx == null || fctx.length() == 0)
          { // make sure this is set
            gEqmFactory.setFecContext(ctx);
            fctx = ctx;
          }
          if (ctx != null && ctx.length() > 0) setContext(ctx);
          sub = ec.getSubsystem();
          if (sub == null || sub.length() == 0)
          { // not specified for the eqm : use the default
            sub = cfg.getSubsystem();
          }
          if (sub != null && sub.length() > 0) setSubsystem(sub);
          mstr = ec.getMaster();
          if (mstr != null && mstr.length() > 0)
          {
            setMaster(mstr);
            slav = ec.getSlaveMaster();
            if (slav != null && slav.length() > 0) setSlaveMaster(mstr, slav);
          }
          break;
        }
      }   
      return 0;
    }
    // no fec.xml : look for exports.csv ...
    TInitializer initializer = TInitializerFactory.getInstance().getInitializer();
    // register the csv database structure:
    csvColumn[] expCols = new csvColumn[26];
    expRowHndlr expRows = new expRowHndlr(getLocalName());
    expCols[0] = new csvColumn("EXPORT_NAME","",new expXnHndlr(expRows));
    expCols[1] = new csvColumn("LOCAL_NAME","",new expLnHndlr(expRows,getLocalName()));
    expCols[2] = new csvColumn("PROPERTY","",new expPrpHndlr(expRows));
    expCols[3] = new csvColumn("DESCRIPTION","",new expPrpDescHndlr(expRows));
    expCols[4] = new csvColumn("PROPERTY_SIZE","",new expPrpSizeHndlr(expRows));
    expCols[5] = new csvColumn("FORMAT","",new expPrpFmtHndlr(expRows));
    expCols[6] = new csvColumn("PROPERTY_INSIZE","",new expPrpInSizeHndlr(expRows));
    expCols[7] = new csvColumn("INFORMAT","",new expPrpInFmtHndlr(expRows));
    expCols[8] = new csvColumn("PROPERTY_ID","",new expPrpIdHndlr(expRows));
    expCols[9] = new csvColumn("ACCESS","READ",new expPrpAccessHndlr(expRows));
    expCols[10] = new csvColumn("REDIRECTION","",new expPrpRdrHndlr(expRows));
    expCols[11] = new csvColumn("GROUP","",new expGrpHndlr());
    expCols[12] = new csvColumn("GROUP_INDEX","",new expGrpIdxHndlr());
    expCols[12] = new csvColumn("GROUP_DEVICE_PREFIX","",new expGrpPrefixHndlr());
    expCols[13] = new csvColumn("GROUP_DEVICE_POSTFIX","",new expGrpPostfixHndlr());
    expCols[14] = new csvColumn("CONTEXT","",new expCtxHndlr());
    expCols[15] = new csvColumn("SUBSYSTEM","",new expCtxHndlr());
    expCols[16] = new csvColumn("MASTER","",new expMstrHndlr());
    expCols[17] = new csvColumn("SLAVE_MASTER","",new expSlavHndlr());
    expCols[18] = new csvColumn("NUM_DEVICES","",new expNumDevsHndlr(expRows));
    expCols[19] = new csvColumn("MAX_VALUE","0",new expMaxValueHndlr(expRows,false));
    expCols[20] = new csvColumn("MIN_VALUE","0",new expMinValueHndlr(expRows,false));
    expCols[21] = new csvColumn("UNITS","",new expUnitsHndlr(expRows,false));
    expCols[22] = new csvColumn("XMAX_VALUE","0",new expMaxValueHndlr(expRows,true));
    expCols[23] = new csvColumn("XMIN_VALUE","0",new expMinValueHndlr(expRows,true));
    expCols[24] = new csvColumn("XUNITS","",new expUnitsHndlr(expRows,true));
    expCols[25] = new csvColumn("ACCESS_DEADBAND","",new expDbaHndlr(expRows));
    // open it
    csv expFile = new csv(initializer.getExportsResource(eqmName));
    // read it
    int rc = expFile.readFile(expCols,expRows);
    // close it
    expFile.close()
    if (rc != TErrorList.no_such_file)
      TFecLog.log("get registered exports and properties from exports.csv : " + TErrorList.errorString[rc]);
    return 0;
  }
  // csv file USERS handlers
  class usrNameHndlr implements csvHandler
  {
    LinkedList<String> usrsLst = null;
    public usrNameHndlr(LinkedList<String> lst)
    {
      usrsLst = lst;
    }
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      addRegisteredUser(usrsLst,strValue);
      return 0;
    }
  }
  private boolean scanForUsersCalled = false;
  private boolean scanForUsers = false;
  private boolean scanForNetsCalled = false;
  private boolean scanForNets = false;
  public boolean scanForUsersFiles()
  {
    if (scanForUsersCalled) return scanForUsers;
    String fh = TInitializerFactory.getInstance().getInitializer().getFecHome();
    File dir = new File(fh);
    if (dir != null && dir.list() != null)
    {
      for (String f : dir.list())
      {
        if (f.endsWith("-users.csv"))
        {
          scanForUsers = true;
          break;
        }
      }
    }
    if (!scanForUsers)
    {
      dir = new File(fh+File.separator+moduleName);
      if (dir != null && dir.list() != null)
      {
        for (String f : dir.list())
        {
          if (f.endsWith("-users.csv"))
          {
            scanForUsers = true;
            break;
          }
        }
      }
    }
    scanForUsersCalled = true;
    return scanForUsers;
  }
  public boolean scanForNetsFiles()
  {
    if (scanForNetsCalled) return scanForNets;
    String fh = TInitializerFactory.getInstance().getInitializer().getFecHome();
    File dir = new File(fh);
    if (dir != null && dir.list() != null)
    {
      for (String f : dir.list())
      {
        if (f.endsWith("-ipnets.csv"))
        {
          scanForNets = true;
          break;
        }
      }
    }
    if (!scanForNets)
    {
      dir = new File(fh+File.separator+moduleName);
      if (dir != null && dir.list() != null)
      {
        for (String f : dir.list())
        {
          if (f.endsWith("-ipnets.csv"))
          {
            scanForNets = true;
            break;
          }
        }
      }
    }
    scanForNetsCalled = true;
    return scanForNets;
  }
  public void addGroupToACL(String grp)
  {
    if (gEqmFactory.gKnownGroupsList.containsKey(grp)) return;
    ACLGroup g = new ACLGroup();
    if (getRegisteredGroupMembersFromFile(g.members, grp))
    {
      gEqmFactory.gKnownGroupsList.put(grp, g);
    }
  }
 
  public boolean getRegisteredStringSetFromXml(LinkedList<String> lst,String set,String prp,String dev)
  {
    FecCfg cfg = gEqmFactory.getFecXmlCfg();
    String eqmName = getLocalName();
    if (cfg == null) return false;
    LinkedList<EqmCfg> el = cfg.getEqmList();
    if (el == null) return false;
    for( EqmCfg ec : el )
    { // run thru the list
      if (ec.getName().compareTo(eqmName) == 0)
      { // found the right EQM
        if (dev != null && dev.length() > 0)
        { // device specified
          LinkedList<DeviceCfg> dl = ec.getDeviceList();
          for (DeviceCfg dc : dl)
          { // find the device
            if (dc.getName().compareToIgnoreCase(dev) != 0) continue;
            LinkedList<NameCfgList> nl = dc.getNameCfgList();
            for (NameCfgList nc : nl)
            { // look for users tag
              if (nc.getName().compareToIgnoreCase(set) == 0)
              { // assign the names list
                lst.addAll(nc.getMembers());
                return true;
              }
            }
          }
          return false;
        }
        if (prp != null && prp.length() > 0)
        { // device specified
          LinkedList<PropertyCfg> pl = ec.getPropertyList();
          for (PropertyCfg pc : pl)
          { // find the device
            if (pc.getName().compareToIgnoreCase(prp) != 0) continue;
            LinkedList<NameCfgList> nl = pc.getNameCfgList();
            for (NameCfgList nc : nl)
            { // look for users tag
              if (nc.getName().compareToIgnoreCase(set) == 0)
              { // assign the names list
                lst.addAll(nc.getMembers());
                return true;
              }
            }
          }         
          return false;
        }       
        LinkedList<NameCfgList> nl = ec.getNameCfgList();
        for (NameCfgList nc : nl)
        { // yes ! find it
          if (nc.getName().compareToIgnoreCase(set) == 0)
          { // assign the names list
            lst.addAll(nc.getMembers());
            return true;
          }
        }
        return false;
      }
    }
    return false;
  }
  public boolean getRegisteredUsersFromCsv(LinkedList<String> usrsLst,LinkedList<String> grpsLst,String tag)
  {
    if (tag != null && tag.length() > 0 && !scanForUsersFiles()) return false;
    TInitializer initializer = TInitializerFactory.getInstance().getInitializer();
    // register the csv database structure:
    csvColumn[] usrCols = new csvColumn[1];
    //usrRowHndlr usrRows = new usrRowHndlr(getLocalName());
    usrCols[0] = new csvColumn("USER_NAME","",new usrNameHndlr(usrsLst));
    // open it
    csv usrFile = new csv(initializer.getUsersResource(moduleName,tag));
    // read it
    int rc = usrFile.readFile(usrCols);
    // close it
    usrFile.close();
    if (rc == 0) return true;
    if (rc != TErrorList.no_such_file)
    {
      if (tag == null) tag = getLocalName();
      TFecLog.log("get registered users for "+tag+" from users.csv : " + TErrorList.errorString[rc]);
    }
    return false;
  }
  public boolean getRegisteredUsersFromFile(LinkedList<String> usrsLst,LinkedList<String> grpsLst,String prp,String dev)
  {
    boolean rc = false;
    String tag = prp != null ? prp : dev;
    String fnam;
    if (gEqmFactory.getFecXmlCfg() != null)
    {
      rc = getRegisteredStringSetFromXml(usrsLst,"USERS_ALLOWED",prp,dev);
      fnam = "fec.xml";
    }
    else
    {
      rc = getRegisteredUsersFromCsv(usrsLst,grpsLst,tag);
      fnam = "users.csv";
    }
    if (tag == null) tag = getLocalName();
    if (rc && usrsLst != null)
    {
      TFecLog.log("get registered users for "+tag+" from "+fnam);
      Iterator<String> it = usrsLst.iterator();
      String u;
      String fn =
        TInitializerFactory.getInstance().getInitializer().getFecHome() +
        File.separator + moduleName;
      while (it.hasNext())
      {
        u = it.next();
        if (u.startsWith("<"))
        { // a group !
          int p1 = u.indexOf(':');
          int p2 = u.indexOf('>');
          if (p1 < 0)
          { // not found -> then start at '<'
            p1 = 0;
          }
          else
          { // call external process to dump the members to file
            try
            {
              String cmdstr = "group2csv "+u.subSequence(1, p2)+" "+fn;
              Runtime rt = Runtime.getRuntime();
              Process rtp = rt.exec(cmdstr);
              rtp.waitFor();
            }
            catch (Exception ignore) {}; // we did our best ...
          }
          if (p2 < 0) p2 = u.length();
          u = u.substring(p1+1,p2); // ignore the source for now
          if (u != null && u.length() > 0)
          {
            addGroupToACL(u);
            if (grpsLst != null) grpsLst.add(u);
          }
          it.remove(); // not a 'user'
        }
      }
    }
    return rc;
 
  /**
   * @deprecated
   * @param usrsLst
   * @param tag
   * @return
   */
  public boolean getRegisteredUsersFromFile(LinkedList<String> usrsLst,String tag)
  {
    return false;
  }
  /**
   * @deprecated
   * @param usrsLst
   * @param grpsLst
   * @param tag
   * @return
   */
  public boolean getRegisteredUsersFromFile(LinkedList<String> usrsLst,LinkedList<String> grpsLst,String tag)
  {
    return false;
  }
  public boolean getRegisteredGroupMembersFromFile(LinkedList<String> grpsLst,String grp)
  {
    if (grp == null || grp.length()== 0) return false;
    TInitializer initializer = TInitializerFactory.getInstance().getInitializer();
    // register the csv database structure:
    csvColumn[] usrCols = new csvColumn[1];
    //usrRowHndlr usrRows = new usrRowHndlr(getLocalName());
    usrCols[0] = new csvColumn("USER_NAME","",new usrNameHndlr(grpsLst));
    // open it
    csv grpFile = new csv(initializer.getGroupsResource(moduleName,grp));
    // read it
    int rc = grpFile.readFile(usrCols);
    // close it
    grpFile.close();
    if (rc == 0) return true;
    if (rc != TErrorList.no_such_file)
      TFecLog.log("get registered groups from users.csv : " + TErrorList.errorString[rc]);
    return false;
  }
// csv file IPNETS handlers
  class subnetNameHndlr implements csvHandler
  {
    LinkedList<String> netLst = null;
    public subnetNameHndlr(LinkedList<String> lst)
    {
      netLst = lst;
    }
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      addRegisteredNet(netLst,strValue);
      return 0;
    }
  }
  /**
   * @deprecated
   * @param lst
   * @param tag
   * @return
   */
  public boolean getRegisteredNetsFromFile(LinkedList<String> lst,String tag)
  {
    return getRegisteredNetsFromCsvFile(lst,tag);
  }
  public boolean getRegisteredNetsFromCsvFile(LinkedList<String> lst,String tag)
  {
    if (tag != null && tag.length() > 0 && !scanForNetsFiles()) return false;
    TInitializer initializer = TInitializerFactory.getInstance().getInitializer();
    // register the csv database structure:
    csvColumn[] netsCols = new csvColumn[1];
    //usrRowHndlr usrRows = new usrRowHndlr(getLocalName());
    netsCols[0] = new csvColumn("SUBNET","",new subnetNameHndlr(lst));
    // open it
    csv netsFile = new csv(initializer.getNetsResource(moduleName,tag));
    // read it
    int rc = netsFile.readFile(netsCols);
    // close it
    netsFile.close();
    if (rc == 0) return true;
    if (rc != TErrorList.no_such_file)
    {
      if  (tag == null) tag = moduleName;
      TFecLog.log("get registered ip nets for "+tag+" from ipnets.csv : " + TErrorList.errorString[rc]);
    }
    return false;
  }
 
  public boolean getRegisteredNetsFromFile(LinkedList<String> lst,String prp,String dev)
  {
    boolean rc = false;
    String tag = prp != null ? prp : dev;
    String fnam;
    if (gEqmFactory.getFecXmlCfg() != null)
    {
      rc = getRegisteredStringSetFromXml(lst,"NETS_ALLOWED",prp,dev);
      fnam = "fec.xml";
    }
    else
    {
      rc = getRegisteredNetsFromCsvFile(lst,tag);
      fnam = "users.csv";
    }
    if (tag == null) tag = getLocalName();
    if (rc) TFecLog.log("get registered users for "+tag+" from "+fnam);
    return rc;
   
  }
  // csv file <EQM>-alarms handlers:
  class almDefRowHndlr implements RowHandler
  { // the Row Handler will be called when all columns have been read in
    private TAlarmDefinition adef;
    public String tag; // 16 char
    public int code;   // 4 bytes
    public int mask;   // 4 bytes
    public short severity; // 2 bytes;
    public short dataFormat; // 1 byte
    public int dataSize; // 1 byte
    public String text;      // 40 chars
    public String deviceText; // 38 chars
    public int system;   // 2 bytes;
    public String dataText; // 40 chars
    public String url; // 40 chars
    public TAlarmDefinition getAlarmDefinition() { return adef; }
    almDefRowHndlr()
    {
      tag = "";
      text = "";
      deviceText = "";
      dataText = "";
      url = "";
    }
    public int process(int index)
    {
      // make a new one
      adef = new TAlarmDefinition(tag,text,deviceText,code,severity,system,
          mask,dataFormat,dataSize,dataText,url);
      // add this to the list
      addAlarmDefinition(code,adef);
      return 0;
    }
  }
  public int getRegisteredAlarmDefinitionsFromFile()
  {
    FecCfg cfg = gEqmFactory.getFecXmlCfg();
    String eqmName = getLocalName();
    if (cfg != null)
    { // has parsed a fec.xml config file ...
      LinkedList<EqmCfg> el = cfg.getEqmList();
      for( EqmCfg ec : el )
      {
        if (ec.getName().compareTo(eqmName) == 0)
        {         
          LinkedList<AlarmDefinitionCfg> al = ec.getAlarmDefinitions();
          for (AlarmDefinitionCfg ac : al)
          {
            addAlarmDefinition(ac.getAlarmCode().getValue(),
                new TAlarmDefinition(ac.getTag(),ac.getAlarmText(),ac.getDataText(),ac.getAlarmCode().getValue(),ac.getSeverity().getValue(),ac.getAlarmSystem().getValue()));
          }
          break;
        }
      }   
      return 0;
    }
    // no fec.xml : look for alarms.csv ...
    TInitializer initializer = TInitializerFactory.getInstance().getInitializer();
    // register the csv database structure:
    csvColumn[] almCols = new csvColumn[11];
    almDefRowHndlr almRows = new almDefRowHndlr();
    almCols[0] = new csvColumn("ALARM_TAG","",new StringFieldHandler(almRows,"tag"));
    almCols[1] = new csvColumn("ALARM_CODE","",new IntFieldHandler(almRows,"code"));
    almCols[2] = new csvColumn("ALARM_MASK","",new IntFieldHandler(almRows,"mask"));
    almCols[3] = new csvColumn("SEVERITY","",new ShortFieldHandler(almRows,"severity"));
    almCols[4] = new csvColumn("DATA_FORMAT","",new FormatFieldHandler(almRows,"dataFormat"));
    almCols[5] = new csvColumn("DATA_ARRAYSIZE","",new IntFieldHandler(almRows,"dataSize"));
    almCols[6] = new csvColumn("ALARM_TEXT","READ",new StringFieldHandler(almRows,"text"));
    almCols[7] = new csvColumn("DEVICE_TEXT","",new StringFieldHandler(almRows,"deviceText"));
    almCols[8] = new csvColumn("DATA_TEXT","",new StringFieldHandler(almRows,"dataText"));
    almCols[9] = new csvColumn("URL","",new StringFieldHandler(almRows,"url"));
    almCols[10] = new csvColumn("ALARM_SYSTEM","",new IntFieldHandler(almRows,"system"));
    // open it
    csv almDefsFile = new csv(initializer.getAlmDefinitionResource(getLocalName()));
    // read it
    int rc = almDefsFile.readFile(almCols,almRows);
    // close it
    almDefsFile.close()
    if (rc != TErrorList.no_such_file)
      TFecLog.log("get alarm definitions from " + getLocalName() + "/alarms.csv : " + TErrorList.errorString[rc]);
    return 0;
  }
  // csv file <EQM>-devices.csv and <property>-names.csv handlers:
  class devNameRowHndlr implements RowHandler
  { // the Row Handler will be called when all columns have been read in
    public String deviceName;
    public String deviceNameAlt;
    public int deviceNumber;
    public int deviceNumberAlt;
    public int deviceMask;
    public int deviceOffline;
    public float deviceZPosition;
    private boolean isPrpOriented = false;
    public String deviceRedirection;
    public String deviceDescription;
    public String deviceLocation;
    private TExportProperty prp = null;
    public String prpListFile = null;
    devNameRowHndlr()
    {
      deviceNumber = 0;
      deviceName = "#0";
    }
    devNameRowHndlr(TExportProperty property)
    {
      deviceNumber = 0;
      deviceName = "#0";
      deviceRedirection = null;
      deviceDescription = null;
      prp = property;
      isPrpOriented = prp == null ? false : true;
    }
    public int process(int index)
    {
      TDeviceList dlst = getDeviceList();
      if (dlst == null) return TErrorList.device_not_connected;
      int ndevs = dlst.getNumberOfDevices();
      if (ndevs == 0) return TErrorList.device_not_connected;
      if (deviceNumber < 0 && deviceNumber >= ndevs) return TErrorList.illegal_equipment_number;
      if (!isPrpOriented)
      {
        TDevice tdv = dlst.getDevice(deviceNumber);
        if (tdv == null) return TErrorList.device_not_connected;
        String oldName = tdv.getName();
        if (deviceName.compareTo(oldName) != 0)
        {
          dlst.renameDevice(oldName,deviceName);
        }
        if (deviceRedirection != null && deviceRedirection.length() > 0)
        {
          tdv.setRedirection(deviceRedirection);
          deviceRedirection = null; // reset this !
        }
        if (deviceDescription != null && deviceDescription.length() > 0)
        {
          tdv.setDescription(deviceDescription);
          deviceDescription = null; // reset this !
        }
        if (prpListFile != null && prpListFile.length() > 0)
        {
          getRegisteredPropertyListFromFile(tdv,prpListFile);
          propertyList.setDeviceOriented(true);
          prpListFile = null; // reset this !
        }
        if (deviceLocation != null && deviceLocation.length() > 0)
        {
          tdv.setLocation(deviceLocation);
        }
        if (deviceMask != 0) tdv.setMask(deviceMask);
        if (deviceOffline != 0) tdv.setOffline(true);
        if (deviceZPosition != 0) tdv.setZposition(deviceZPosition);
      }
      else
      {
        if (prp != null)
        {
          int prplen = prp.getOutputSize();
          ArrayList<String> lst = prp.getDeviceList();
          if (lst == null)
          {
            lst = new ArrayList<String>(prplen);
            prp.setDeviceList(lst)
          }
          if (deviceName.length() == 0)
          {
            deviceName = deviceNameAlt;
            if (deviceNumber == 0) deviceNumber = deviceNumberAlt;
          }
          if (deviceNumber < prplen) lst.add(deviceNumber,deviceName);
          dlst.setPropertyOriented(true);
        }
      }
      return 0;
    }
  }
  public int getRegisteredDevicesFromFile()
  {
    FecCfg cfg = gEqmFactory.getFecXmlCfg();
    String eqmName = getLocalName();
    if (cfg != null)
    { // has parsed a fec.xml config file ...
      LinkedList<EqmCfg> el = cfg.getEqmList();
      String deviceRedirection;
      String deviceDescription;
      String prpSet;
      for( EqmCfg ec : el )
      {
        if (ec.getName().compareTo(eqmName) == 0)
        {   
          LinkedList<NameCfgList> nl = ec.getNameCfgList();
          TDeviceList dlst = getDeviceList();
          if (dlst == null) return TErrorList.device_not_connected;
          int ndevs = dlst.getNumberOfDevices();
          if (ndevs == 0) return TErrorList.device_not_connected;
          LinkedList<DeviceCfg> dl = ec.getDeviceList();
          for (DeviceCfg dc : dl)
          {
            TDevice tdv = dlst.getDevice(dc.getNumber().getValue());
            if (tdv == null) return TErrorList.device_not_connected;
            prpSet = dc.getPropertySet();
            if (prpSet != null && prpSet.length() > 0)
            { // is there a device-specific property list ?
              for (NameCfgList nc : nl)
              { // yes ! find it
                if (nc.getName().compareToIgnoreCase(prpSet) == 0)
                { // assign the names list
                  tdv.setPropertyList(nc.getMembers());
                }
              }
              propertyList.setDeviceOriented(true);
            }
            String oldName = tdv.getName();
            if (dc.getName().compareTo(oldName) != 0)
            {
              dlst.renameDevice(oldName,dc.getName());
            }
            deviceRedirection = dc.getRedirection();
            if (deviceRedirection != null && deviceRedirection.length() > 0)
            {
              tdv.setRedirection(deviceRedirection);
              deviceRedirection = null; // reset this !
            }
            deviceDescription = dc.getDescription();
            if (deviceDescription != null && deviceDescription.length() > 0)
            {
              tdv.setDescription(deviceDescription);
              deviceDescription = null; // reset this !
            }
          }
          break;
        }
      }   
      return 0;
    }
    // no fec.xml : look for devices.csv ...
    TInitializer initializer = TInitializerFactory.getInstance().getInitializer();
    csv devNamesFile = new csv(initializer.getDevicesResource(getLocalName()));
    int rc = devNamesFile == null ?
        TErrorList.file_error :
        getRegisteredDevicesFromFile(devNamesFile,new devNameRowHndlr());
    if (rc != TErrorList.no_such_file)
      TFecLog.log("get device names from " + getLocalName() + "-devices.csv : " + TErrorList.errorString[rc]);
    return rc;
  }
  public int getRegisteredNamesFromPropertyFile(String propertyName)
  {
    TInitializer initializer = TInitializerFactory.getInstance().getInitializer();
    TExportProperty prp = propertyList.getFirstProperty(propertyName);
    String devFileName = propertyName + "-names.csv";
    csv devNamesFile = new csv(initializer.getFecHome()+File.separator+getLocalName()+File.separator+devFileName);
    int rc = devNamesFile == null ?
        TErrorList.file_error :
        getRegisteredDevicesFromFile(devNamesFile,new devNameRowHndlr(prp));
    if (rc == TErrorList.no_such_file)
    {
      devNamesFile = new csv(initializer.getFecHome()+File.separator+devFileName);
      rc = devNamesFile == null ?
          TErrorList.file_error :
          getRegisteredDevicesFromFile(devNamesFile,new devNameRowHndlr(prp));
    }
    if (rc != TErrorList.no_such_file)
      TFecLog.log("get device names from " + devFileName + " : " + TErrorList.errorString[rc]);
    return rc;
  }
  public int getRegisteredDevicesFromFile(csv csvFile,devNameRowHndlr devRows)
  {
    // register the csv database structure:
    csvColumn[] devCols = new csvColumn[11];
    devCols[0] = new csvColumn("DEVICE_NUMBER","",new IntFieldHandler(devRows,"deviceNumber"));
    devCols[1] = new csvColumn("DEVICE_NAME","",new StringFieldHandler(devRows,"deviceName"));
    devCols[2] = new csvColumn("MODULE_NUMBER","",new IntFieldHandler(devRows,"deviceNumberAlt")); // alias for DEVICE_NAME
    devCols[3] = new csvColumn("MODULE_NAME","",new StringFieldHandler(devRows,"deviceNameAlt"));
    devCols[4] = new csvColumn("REDIRECTION","",new StringFieldHandler(devRows,"deviceRedirection"));
    devCols[5] = new csvColumn("PROPERTY_LIST","",new StringFieldHandler(devRows,"prpListFile"));
    devCols[6] = new csvColumn("DEVICE_DESCRIPTION","",new StringFieldHandler(devRows,"deviceDescription"));
    devCols[7] = new csvColumn("DEVICE_LOCATION","",new StringFieldHandler(devRows,"deviceLocation"));
    devCols[8] = new csvColumn("DEVICE_MASK","0",new IntFieldHandler(devRows,"deviceMask"));
    devCols[9] = new csvColumn("DEVICE_OFFLINE","0",new IntFieldHandler(devRows,"deviceOffline"));
    devCols[10] = new csvColumn("DEVICE_ZPOS","0",new FloatFieldHandler(devRows,"deviceZPosition"));
    // read the file
    int rc = csvFile.readFile(devCols,devRows);
    // close it
    csvFile.close()
    return rc;
  }
  class devPrpLstHndlr implements csvHandler
  {
    protected LinkedList<String> prpLst = null;
    public int process(String strValue,int index)
    {
      if (strValue == null || strValue.length() == 0) return 0;
      if (prpLst == null) prpLst = new LinkedList<String>();
      prpLst.add(strValue);
      return 0;
    }
  }
  public int getRegisteredPropertyListFromFile(TDevice device, String filename)
  {
    TInitializer initializer = TInitializerFactory.getInstance().getInitializer();
    String fn = initializer.getFecHome();
    csv csvFile = null;
    if (moduleName != null) csvFile = new csv(fn+File.separator+moduleName+File.separator+filename);
    if (csvFile == null || !csvFile.fileAvailable())
      csvFile = new csv(fn+File.separator+filename);
    devPrpLstHndlr namhdlr = new devPrpLstHndlr();
    csvColumn[] devCols = new csvColumn[1];
    devCols[0] = new csvColumn("PROPERTY_NAME","",namhdlr);
    // read the file
    int rc = csvFile.readFile(devCols,null);
    // close it
    csvFile.close();
    if (rc == 0) device.setPropertyList(namhdlr.prpLst);
    return rc;
  }
  public boolean isDeviceSetLocal(String devName, String propertyName)
  {
    TExportProperty prp = propertyList.getFirstProperty(propertyName);
    WildcardMatch wc = new WildcardMatch(devName);   
    String tgt;
    TDevice[] dlst = getDeviceList().getDeviceList();
    for (int i=0; i<dlst.length; i++)
    {
      if ((tgt=dlst[i].getRedirection()) != null)
      {
        if (wc.matches(tgt)) return false;
      }
    }   
    if ((tgt=prp.getDescription().getRedirection()) != null)
    {
      if (wc.matches(tgt)) return false;     
    }
    return true; /* appears to be local so-far */   
  }
  public String getSubsystem()
  {
    return subsystem;
  }
  /**
   * Sets the subsystem to which the device server should belong
   *
   * @param subsystem is the desired subsystem.
   */
  public void setSubsystem(String subsystem)
  {
    TStrings.validateSubSystemName(subsystem);
    this.subsystem = subsystem;
  }
  /**
   * An equipment module can schedule a property or set of properties for immediate
   * execution and delivery 
   *
   *  @param devPropertyList A single string argument containing one or more
   *  properties to be scheduled for immediate execution.  This should be a
   *  single string.  If more than one property is to be scheduled, then the
   *  property list string should be a set of properties separated by commas or
   *  blanks
   * 
   * 
   * @author duval
   */
  public int scheduleProperty(String devPropertyList)
  {
    if (devPropertyList == null) return TErrorList.argument_list_error;
    String[] prps  = null;
    String tok = " "; // try a space first
    if (!devPropertyList.contains(tok)) tok = ";";
    if (!devPropertyList.contains(tok)) tok = ",";
    if ((prps = devPropertyList.split(tok)) == null) return TErrorList.code_failure;
    for (int i=0; i<prps.length; i++)
    {
      if (propertyList.hasProperty(prps[i])) gEqmFactory.systemScheduleProperty(this,prps[i].trim());
    }
    return 0;
  }
  /**
   * Sends (schedules) a 'reset_mca_property' signal to any listening client
   *
   * A server with registered multi-channel array properties can inform any attached
   * clients that the mutli-channel array configuration has changed (e.g. array elements
   * have been added, removed, or otherwise edited) by using this call.
   * Any listening clients with multi-channel array links will then return to the original
   * client startup conditions and 're-learn' the new array indexing now in place.
   *
   * @param devPropertyList is (comma separated) list of properties which are to be reset at the client
   * side.
   * 
   * \note This routine will call scheduleProperty() to assure immediate notification
   * at the client side.
   *
   * @return 0 upon success, otherwise a TINE return code.
   */
  public int resetMultiChannelProperty(String devPropertyList)
  {
    if (devPropertyList == null) return TErrorList.argument_list_error;
    String[] prps  = null;
    String tok = " "; // try a space first
    if (!devPropertyList.contains(tok)) tok = ";";
    if (!devPropertyList.contains(tok)) tok = ",";
    if ((prps = devPropertyList.split(tok)) == null) return TErrorList.code_failure;
    LinkedList<TExportProperty> plst;
    Iterator<TExportProperty> it;
    long ts = System.currentTimeMillis();
    for (int i=0; i<prps.length; i++)
    {
      if ((plst=propertyList.getEqualProperties(prps[i].trim())) == null) break;
      it = plst.iterator();
      while (it.hasNext()) it.next().setMcaValidFloor(ts);
    }
    TEquipmentModuleFactory.getInstance().flushContractTable();
    return 0;
  }
  ConcurrentLinkedQueue<TEquipmentBackgroundTask> bkgTasks = new ConcurrentLinkedQueue<TEquipmentBackgroundTask>();
  public int addEquipmentBackgroundTask(TEquipmentBackgroundTask tbkg)
  {
    if (tbkg == null) return TErrorList.invalid_parameter;
    bkgTasks.add(tbkg);
    return getEquipmentModuleFactory().addEquipmentBackgroundTask(tbkg);
  }
  private void assignContextAndSubsystem()
  {
    TEquipmentModuleFactory f = getEquipmentModuleFactory();
    if (context == null || context.length() == 0)
    { // fill this in if it hasn't been set ...
      context = f.getFecContext();
    }
    String subsys = System.getenv(getLocalName()+"_SUBSYSTEM");
    if (subsys != null && subsys.length() > 0) subsystem = subsys;
    if (subsystem == null || subsystem.length() == 0)
    { // fill this in if it hasn't been set ...
      subsystem = f.getFecSubsystem();
    }  
  }
  public int startup()
  {
    int rc = 0;
    getRegisteredDevicesFromFile();
    assignContextAndSubsystem();
    clearCASAlarmList();
    if (getInitializationRoutine() != null)
    {
      try
      {
        rc = getInitializationRoutine().initialize();
        TFecLog.log(getLocalName()+" initialization routine called : "+TErrorList.getErrorString(rc));
      }
      catch (Exception e)
      {
        TFecLog.log(getLocalName()+" initialization routine called : unhandled exception "+e.toString());         
      }
    }
    // is there a general alarm watch file ?
    gEqmFactory.getAlarmWatchListFromFile(getLocalName());
    // is there a local history file ?
    gEqmFactory.getHistoryListFromFile(getLocalName());
    return rc;
  }
  public int shutdown()
  {
    int rc = 0;
    if (!hasInitialized) return TErrorList.not_initialized;
    hasInitialized = false;
    if (getExitRoutine() != null)
    { // there is a registered exit routine !
      try
      {
        rc = getExitRoutine().terminate();
        TFecLog.log(getLocalName()+" exit routined called : "+TErrorList.getErrorString(rc));
      }
      catch (Exception e)
      {
        TFecLog.log(getLocalName()+" exit routined called : unhandled exception "+e.toString());       
      }
    }
    alarmDefinitionList.clear();
    removeAllGCastNets();
    removeAllRegisteredNets();
    removeAllRegisteredUsers();
    return rc;
  }
  /**
   * Registers a cycle trigger callback dispatch function
   *
   * If a CYCLER is running in a server's context, then the server will receive 'Cycle Number' events
   * scheduled by the designated CYCLER server.
   * The cycle number will make use of the 'System Data Stamp' to tag all data sets obtained from the
   * server.  A server can also register a trigger function dispatch routine (or routines) to be called when a 'Cycle
   * Number' event occurs.  The dispatch routines will be called prior to setting the 'System Stamp' to the new
   * Cycle Number, which will be set following the execution of all dispatch routines.  Optionally, the server can
   * provide a property (or list of properties) to be scheduled following the dispatch execution.  This will ensure
   * that such properties will be called immediately following dispatch execution AND contain the most recent Cycle Number
   * as the 'System Data Stamp'.
   *
   * @param trigger is a reference to the dispatch handler to be called when a new cycle number arrives.
   *
   * @return 0 upon success or TErrorList.already_assigned
   *
   * \b Example:
   * @include eg_cycleTrigger.java
   */
  public int registerCycleTrigger(TCycleTrigger trigger)
  {
    return getEquipmentModuleFactory().registerCycleTrigger(trigger);
  }
  /**
   * Sets the trigger cycler key to the value given
   *
   * By default a CYCLER is a server called "CYCLER" in the same context as the
   * initializing device server and produces a keyword (property) called
   * "CycleNumber".  This gives rise to the Cycle Key:
   * "/\<context\>/CYCLER/CycleNumber". If it is known a priori, that the
   * CYCLER exists in another context or uses another server name or property
   * name, the cycle key can be set via this API call or via environment
   * (set TINE_CYCLE_KEY=\<key\>).
   *
   * @param key is the desired CYCLER trigger key
   *
   * @see registerCycleTrigger
   */
  public void setCycleNumberKey(String key)
  {
    getEquipmentModuleFactory().setCyclerNumberKey(key);
  }
  /**
   * Gets the trigger cycler key
   *
   * By default a CYCLER is a server called "CYCLER" in the same context as the
   * initializing device server and produces a keyword (property) called
   * "CycleNumber".  This gives rise to the Cycle Key:
   * "/\<context\>/CYCLER/CycleNumber". This default value can also be
   * set via the environment (set TINE_CYCLE_KEY) or via API.
   *
   * @return the current setting of the cycler trigger key.
   *
   * @see setCycleNumberKey, registerCycleTrigger
   */
  public String getCycleNumberKey()
  {
    return getEquipmentModuleFactory().getCycleNumberKey();
  }
  /**
   * Unregisters a previously registered cycle trigger callback dispatch function
   *
   * If a cycle trigger event dispatch routine is no longer required, it can be unregistered using this
   * routine.
   *
   * @param trigger is a reference to the dispatch handler to be removed
   *
   * @return 0 upon success or return TErrorList.un_allocated
   */
  public int unregisterCycleTrigger(TCycleTrigger trigger)
  {
    return getEquipmentModuleFactory().unregisterCycleTrigger(trigger);
 
  private TStateChangeTrigger scTrg = null; // a registered state-change trigger function
  /**
   * Establishes a state-change trigger callback for this equipment module.
   *
   * If a GLOBLAS server is running and providing state information, then any
   * detected state change will be passed to the callback function provided.
   * The callback must implement the TStateChangeTrigger class.  It will receive
   * both the current state and previous state (as Strings).
   *
   * @param stateChangeTrigger is a reference to the callback instance.
   */
  public void setStateChangeTrigger(TStateChangeTrigger stateChangeTrigger)
  {
    setStateChangeTrigger(stateChangeTrigger,null);
  }
  /**
   * Establishes a state-change trigger callback for this equipment module.
   *
   * If a GLOBLAS server is running and providing state information, then any
   * detected state change will be passed to the callback function provided.
   * The callback must implement the TStateChangeTrigger class.  It will receive
   * both the current state and previous state (as Strings).
   *
   * @param stateChangeTrigger is a reference to the callback instance.
   * @param stateChangeKey is the GLOBALS keyword providing the declared state
   * for the context associated with this equipment module.  If 'null' then the
   * default keyword "DeclaredState" is assumed.
   */
  public void setStateChangeTrigger(TStateChangeTrigger stateChangeTrigger,String stateChangeKey)
  {
    scTrg =  stateChangeTrigger;
    if (stateChangeKey != null && stateChangeKey.trim().length() != 0) scKey = stateChangeKey;
    if (scTrg != null)
    {
      registerStateChangeCallback(scTrg);
    }
  }
  public TStateChangeTrigger getStateChangeTrigger() { return scTrg; }
  private TStateChange scMonitor = null;
  private String scKey = "DeclaredState"; // default key
  private int registerStateChangeCallback(TStateChangeTrigger stateChangeTrigger)
  {
    scMonitor = new TStateChange(getPrimaryContext(),scKey);
    return scMonitor == null ? TErrorList.invalid_keyword : scMonitor.getTriggerStatus();
  }
}
TOP

Related Classes of de.desy.tine.server.equipment.TEquipmentModule$usrNameHndlr

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.