Package de.desy.tine.client

Source Code of de.desy.tine.client.TLink

package de.desy.tine.client;

import java.net.InetAddress;
import java.lang.reflect.Array;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;

import de.desy.tine.addrUtils.*;
import de.desy.tine.bitfieldUtils.TBitfield;
import de.desy.tine.bitfieldUtils.TBitfieldRegistry;
import de.desy.tine.client.TLinkFactory.RelinkedItem;
import de.desy.tine.dataUtils.*;
import de.desy.tine.definitions.*;
import de.desy.tine.exceptions.BoundToInactiveLinkException;
import de.desy.tine.exceptions.InvalidDataReferenceException;
import de.desy.tine.exceptions.UnresolvedAddressException;
import de.desy.tine.headers.*;
import de.desy.tine.io.TBucket;
import de.desy.tine.queryUtils.TQuery;
import de.desy.tine.server.logger.DbgLog;
import de.desy.tine.server.logger.MsgLog;
import de.desy.tine.server.properties.TPropertyList;
import de.desy.tine.server.properties.TStockProperties;
import de.desy.tine.structUtils.TStructDescription;
import de.desy.tine.structUtils.TStructRegistry;

/**
* TLink.java
*
* User interface calls to access tine data links.
* The methods presented here deal with linking local application data with
* remote data sources.  Primarily you will need to create a TLink object,
* passing the necessary identifying parameters of the remote server to which
* the link is to be established.  When the TLink object is instantiated,
* you will then need to retrieve the data using one of the retrieval
* methods, e.g. execute() for synchronous acquisition, or attach() for 
* asynchronous acquisition, or receive() for the acquisition of network
* globals.
*/
public class TLink // mdavid: removed 'final' statement
//public final class TLink // mdavid: commented
{

/*
* (non-Javadoc)
*
* mdavid: actually, isn't a good idea to rely on finalize,
* since the JVM doesn't guarantee that finalize is ever called on any
* object. May be use "Runtime.getRuntime().addShutdownHook(new Thread(<Runnable_Cleaner>))" instead.
*
* @see java.lang.Object#finalize()
*/
protected void finalize() throws Throwable
  {
    if (active)
    {
      MsgLog.log("finalize TLink","closing active link to /"+cntName+"/"+expName+"/"+devName+"["+devProperty+"] in finalizer",0,null,1);
      close();
    }
//    super.finalize(); mdavid: commented, unnecessary - Object has no finalize implementation
  }
  private Object criticalMutex = new Object();
  private boolean isInCriticalSection = false;
  protected boolean getCriticalSection()
  { // protect the watchdog thread and io thread from deadlocking on the link
    if (isInCriticalSection) return false;
    synchronized (criticalMutex)
    {
      isInCriticalSection = true;
    }
    return isInCriticalSection;
  }
  protected void freeCriticalSection()
  {
    if (isInCriticalSection) isInCriticalSection = false;
  }
  public String cntName;
  public String expName;
  public String devName;
  public String devProperty;
  public TDataType dOutput;
  public TDataType dInput;
  public TDataType dError;
  private static int maxOutputBufferSize = 0xffff;
  private String arrayDelimiter;
  public short devAccess;
  public int devTimeout;
  public static int defaultTimeout = 500// milliseconds
  short renewalMultiplier = 1;
  public short getRenewalMultiplier() { return renewalMultiplier; }
  public void setRenewalMultiplier(short value) { renewalMultiplier = value; }
  private boolean isRenewal = false;
  public boolean isRenewal() { return isRenewal; }
  public void setIsRenewal(boolean value) { isRenewal = value; }
  public short srvId; // for network subscriptions
  private InetAddress mcastGrp = null;
  public InetAddress getMulticastGroup() { return mcastGrp; }
  public void setMulticastGroup(InetAddress group) { mcastGrp = group; }
  private int tineProtocol = TTransport.DEFAULT_PROTOCOL_LEVEL;
  boolean active;
  boolean keepActive = false;
  protected boolean isInAlarmState = false;
  protected boolean canSetAlarms = true;
  public void setAutoLinkErrorAlarms(boolean value) { canSetAlarms = value; }
  public boolean getAutoLinkErrorAlarms() { return canSetAlarms; }
  public boolean isActive()
  {
    //if (linkStatus == -1 || linkStatus == TErrorList.not_signalled) return false; // pending (don't count it yet)
    if (linkStatus == TErrorList.not_signalled) return false; // pending (don't count it yet)
    if (sub == null) return false;
    if ((sub.mode & TMode.CM_REGISTER) == TMode.CM_REGISTER) return false;
    if (boundTo != null) return false;
    return active;
  }
  boolean terminate;
  public int removedFromMcaList = 0;
  boolean delayEstablishLink = false;
  void delayEstablishLink(boolean value)
  {
    delayEstablishLink = value;
    if (value) tf.hasDeferredLinks(true);
  }
  boolean needsNotification = false;
  boolean isInsideCallback = false;
  boolean isWildcardLink = false;
  boolean isGlobalsLink = false;
  boolean isGlobalsLinkPinned = false;
  boolean isQuery = false;
  boolean useErrValue = false;
  boolean useErrObject = false;
  boolean isRedirected = false;
  boolean needsWakeUpCall = false;
  String rdrKey = null;
  public String getRedirectionKey()
  {
    return rdrKey != null ? rdrKey : "";
  }
  RelinkedItem relnkItem = null;
  TLinkHook hook = null;
  Object hookObj = null;
  public boolean adjustDefaultValues = false;
  public boolean retryOnTimeoutError = true;
  public boolean removeOnClose = false; // was true
  public boolean hasRenewed = false;
  public boolean hasNotifiedOnce = false;
  public boolean hasObtainedStatus = false;
  public boolean isBeingWatched = false;
  public boolean needsToStartLinkWatchdog = false;
  public boolean cannotNotifyFromWatchdogThread = false;
  public boolean canTimeout = true;
  public boolean isLinkReassignment = false;
  public boolean isAlive()
  {
    if (isLinkReassignment) return true;
    if (!TErrorList.isLinkSuccess(linkStatus)) return false;
    if (linkTimeouts > 0) return false;
    return true;
  }
  public TSrvEntry srvAddr;
  public TSrvEntry getServerAddress() { return srvAddr; }
  public String toString()
  {
    String s = getFullDeviceNameAndProperty();
    String tag = " " + TMode.toString(sub.mode);
    if (isRedirected)
    {
      tag += " is redirected from "+rdrKey;
    }
    if (relnkItem != null)
    {
      String dtag = "";
      String dfmt = TFormat.toString(relnkItem.getRelinkDataFormat())+"s";
      if (relnkItem.getBitfield() != null)
      {
        dtag = relnkItem.getBitfield().getTag();
        dfmt = "bitfields";
      }
      if (relnkItem.getStructDescription() != null)
      {
        dfmt = "structures";
        dtag = relnkItem.getStructDescription().getTagName();
      }
      tag += " is relinked from "+relnkItem.getRelinkDataLength()+" "+dtag+" "+dfmt;
    }
    if (boundTo != null)
    {
      tag += " is bound to "+boundTo.getFullDeviceNameAndProperty();
      if (boundTo.mcaIndex > 0) tag += " (an MCA element)";
      else if (mcaIndex > 0) tag += " (as an MCA element)";
    }
    s += tag + " @" + sub.pollingInterval + " msec";
    s += " <"+linkStatus+"> ";   
    return s;
  }
  TContract con;
  TSubscription sub;
  public TSubscription getSubscription() { return sub; }
  TPHdr pHdr;
  TReqHdr reqHdr;
  TLink boundTo = null;
  boolean cancelledWithDependencies = false;
  private TLinkGroup grp = null;
  public boolean isGrouped() {return grp == null ? false : true; }
  public TLinkGroup getGroup() { return grp; }
  LinkedList<TLink> dependencies = null;
  public LinkedList<TLink> getDependencies() { return dependencies; }
  public boolean hasDependencies()
  {
    if (dependencies != null && dependencies.size() > 0) return true;
    return false;
  }
  public boolean isBound() { return boundTo == null ? false : true; }
  public TLink getBoundLink() { return boundTo; }
  public void setBoundLink(TLink parent) { boundTo = parent; }
  public boolean isCancelledWithDependencies() { return cancelledWithDependencies; }
  public void addDependency(TLink tlink)
  {
    if (dependencies == null) dependencies = new LinkedList<TLink>();
    dependencies.add(tlink);
//    chkDependencyListIntegrity();
  }
  public void rmvDependency(TLink tlink)
  {
    if (dependencies == null) return;
    dependencies.remove(tlink);
//    chkDependencyListIntegrity();
  }
  private void chkDependencyListIntegrity()
  {
    if (dependencies == null || dependencies.size() == 0) return;
    Iterator<TLink> it = dependencies.iterator();
    TLink lnk;
    while (it.hasNext())
    {
      lnk = it.next();
      if (!lnk.active) dependencies.remove(lnk);
    }
  }
  public static final int STATUS_LOCAL = 1;
  public static final int STATUS_REMOTE = 2;
  public int linkStatusLastNotification = 0;
  public long lastLinkNotification = 0;
  public long lastLinkSuppressedNotification = 0;
  public int linkStatus = TErrorList.not_signalled;
  protected int lastLinkStatus = TErrorList.not_signalled;
  public int linkStatusSource = STATUS_LOCAL;
  public boolean isStatusLocal() { return linkStatusSource == STATUS_LOCAL; }
  public boolean isDoocsServer()
  {
    if (sub == null || sub.contract == null) return false;
    return (sub.contract.eqmName.compareToIgnoreCase("DCSEQM") == 0);
  }
  public int linkCounter;
  public boolean linkStale;
  public double linkErrValue;
  public String linkErrString;
  public byte[] linkStatusStringBuffer = new byte[TStrings.STATUS_SIZE];
  public InetAddress linkPeer;
  TCallback tcb;
  int callbackId;
  //EventListener tlsnr;
  TLinkCallback tlcb;
  TWildcardLink twcl;
  private TDataTolerance dtf = null;
  private int mcaIndex = -1;
  private String mcaDevice = null;
  public void setMcaDevice(String dev) { mcaDevice = dev; }
  public String getMcaDevice() { return mcaDevice; }
  private Object usrObject = null;
  public void setUserObject(Object object) { usrObject = object; }
  public Object getUserObject() { return usrObject; }
  public int getMcaIndex() { return mcaIndex; }
  public void setMcaIndex(int index) { mcaIndex = index; }
  public boolean isMcaParent() { return (tlcb instanceof TMcaLink); }
  public TDataTolerance getNotificationTolerance() { return dtf; }
  /**
   * Sets the tolerance level for asynchronous event notification
   *
   * Link callback notification can be suppressed if changes in the link
   * data do not exceed the tolerance applied in this call.  The tolerance
   * can have an absolute part (in the units of the data) and/or a
   * percent part.
   *
   * @param absoluteValue is the absolute tolerance any particular data element
   * is allowed to have before an event notification is called.
   * @param percentValue is the percent tolerance any particular data element
   * is allowed to have before an event notification is called.
   */
  public void setNotificationTolerance(float absoluteValue, float percentValue)
  {
    dtf = new TDataTolerance(absoluteValue,percentValue);
  }
  public void setNotificationTolerance(double absoluteValue, double percentValue)
  {
    dtf = new TDataTolerance((float)absoluteValue,(float)percentValue);
  }
  public void setNotificationTolerance(int absoluteValue, int percentValue)
  {
    dtf = new TDataTolerance(absoluteValue,percentValue);
  }
  public boolean isWithinTolerance()
  {
    if (linkStatus > 0) return false; // error condition => always true
    if (lastLinkStatus > 0 && linkStatus == 0)
    { // back to normal conditions ...
      lastLinkStatus = 0;
      return false;
    }
    if (dtf == null) return false; // no tolerance specified => always true
    if (dOutput == null) return false; // no output data object
    if (isGrouped()) return false; // no tolerances for grouped calls
    TDataType dRef = dtf.getReference();
    if (dRef == null) // first call ?
    {
      dRef = new TDataType(dOutput.getArrayLength(),dOutput.getFormat());
      dRef.dataCopy(dOutput);
      dtf.setReference(dRef);
      return false;
    }
    if (!hasNotifiedOnce) return false;
    if (isMcaParent()) return false; // treat member links individually
    try
    {
      int len = dOutput.getArrayLength();
      if (dRef.getArrayLength() < len) len = dRef.getArrayLength();
      float abs = dtf.absolute;
      float pcn = (float)(dtf.percent/100.0);
      Object dObj = dOutput.getDataObject();
      Object rObj = dRef.getDataObject();
      if (dObj == null || rObj == null) return false;
      if (dObj.getClass().isArray())
      { // somehow jddd has made this check necessary
        int alen = Array.getLength(dObj);
        if (alen < len) len = alen;
      }
      switch (dOutput.getFormat())
      {
        case TFormat.CF_BYTE:
        {
          byte[] athis = (byte[])dObj;
          byte[] alast = (byte[])rObj;
          for (int i=0; i<len; i++)
            if (Math.abs(athis[i]-alast[i]) > abs + pcn*(float)alast[i])
            {
              dRef.dataCopy(dOutput);
              return false;
            }
          break;
        }
        case TFormat.CF_SHORT:
        {
          short[] athis = (short[])dObj;
          short[] alast = (short[])rObj;
          for (int i=0; i<len; i++)
            if (Math.abs(athis[i]-alast[i]) > abs + pcn*(float)alast[i])
            {
              dRef.dataCopy(dOutput);
              return false;
            }
          break;
        }
        case TFormat.CF_LONG:
        {
          int[] athis = (int[])dObj;
          int[] alast = (int[])rObj;
          for (int i=0; i<len; i++)
            if (Math.abs(athis[i]-alast[i]) > abs + pcn*(float)alast[i])
            {
              dRef.dataCopy(dOutput);
              return false;
            }
          break;
        }
        case TFormat.CF_FLOAT:
        {
          float[] athis = (float[])dObj;
          float[] alast = (float[])rObj;
          for (int i=0; i<len; i++)
            if (Math.abs(athis[i]-alast[i]) > abs + pcn*(float)alast[i])
            {
              dRef.dataCopy(dOutput);
              return false;
            }
          break;
        }
        case TFormat.CF_DOUBLE:
        {
          double[] athis = (double[])dObj;
          double[] alast = (double[])rObj;
          for (int i=0; i<len; i++)
            if (Math.abs(athis[i]-alast[i]) > abs + pcn*(float)alast[i])
            {
              dRef.dataCopy(dOutput);
              return false;
            }
          break;
        }      
        case TFormat.CF_DLONG:
        {
          long[] athis = (long[])dObj;
          long[] alast = (long[])rObj;
          for (int i=0; i<len; i++)
            if (Math.abs(athis[i]-alast[i]) > abs + pcn*(float)alast[i])
            {
              dRef.dataCopy(dOutput);
              return false;
            }
          break;
        }
        default:
          if (!dRef.equals(true,dOutput))
          {
            dRef.dataCopy(dOutput);
            return false;
          }
      }
      return true;
    }
    catch (Exception e)
    {
      MsgLog.log("TLink.isWithinTolerance", "unexpected exception",TErrorList.code_failure,e,1);
    }
    return false;
  }
  public int getLinkAccessMode()
  {
    if (sub != null) return sub.mode;
    return TMode.CM_CANCEL;
  }
  public int getLinkPollingInterval()
  {
    if (sub != null) return sub.pollingInterval;
    return 0;   
  }
  /**
   * Returns the current status for this link
   *
   * @return The Link status as tine error code
   */
  public int getLinkStatus()
  {
    return linkStatus < 0 ? lastLinkStatus : linkStatus;
  }
  /**
   * Returns the context of this link
   *
   * @return The Context of the current link
   */
  public String getContext()
  {
    return cntName;
  }
  /**
   * Returns the device server addressed in this link
   *
   * @return The device server addressed in the current link
   */
  public String getDeviceServer()
  {
    return expName;
  }
  public String getEquipmentModuleName()
  {
    return con != null ? con.eqmName : null;
  }
  /**
   * Returns the device (module) name addressed in this link
   *
   * @return The device (module) name addressed in the current link
   */
  public String getDeviceName()
  {
    return devName;
  }
  /**
   * Returns the device property addressed in this link
   *
   * @return The device property addressed in the current link
   */
  public String getProperty()
  {
    return devProperty;
  }
  /**
   * Returns the fully qualified device name addressed in this link
   *
   * @return The the fully qualified device name addressed in the current link
   */
  public String getFullDeviceName()
  {
//    return new String("/"+cntName+"/"+expName+"/"+devName); // mdavid: commented
    return ("/"+cntName+"/"+expName+"/"+devName); // mdavid: improved
  }
  /**
   * Returns the fully qualified device name and property addressed in this link
   *
   * @return The the fully qualified device name and property addressed in the current link
   */
  public String getFullDeviceNameAndProperty()
  {
//    return new String("/"+cntName+"/"+expName+"/"+devName+"["+devProperty+"]"); // mdavid: commented
    return (getFullDeviceName()+"["+devProperty+"]"); // mdavid: improved
  }
  /**
   * Returns the output data object as a TDataType
   *
   * @return The output data type
   */
  public TDataType getOutputDataObject()
  {
    return dOutput;
  }
  public void setOutputDataObject(TDataType dout)
  {
    if (boundTo == null)
    { // already bound ? (a jDDD specialty)
      tf.cancel(this, false);
      cancelledWithDependencies = false;
    }
    dOutput = dout;
    con = new TContract(this);
    sub = new TSubscription(con,this);
    TLink xlnk = getExistingTLink(this,dout,this.dInput);
    if (xlnk != null)
    { // will NOT add this TLink to the link table !
      int lnkId = this.linkId;
      bindToParentLink(xlnk,dout,this.dInput);
      devAccess = TMode.CM_REGISTER;
      this.linkId = lnkId; // set it back for removal from the table
      tf.removeTLink(this);
    }
    if (hasDependencies())
    { // already has dependencies ? (a jDDD specialty)
      LinkedList<TLink> xlst = getDependencies();
      TDataType dt;
      for (int k=0; k<xlst.size(); k++)
      {
        if ((xlnk = (TLink)xlst.get(k)) == null) continue;
        dt = xlnk.getOutputDataObject();
        if (dt.dFormat != TFormat.CF_DEFAULT) continue;
        int dlen = dOutput.dArrayLength;
        if (xlnk.getMcaDevice() != null) dlen = 1;
        dt.setDataObject(dlen,dOutput.dFormat);
        dt.dArrayLength = dlen;
        dt.dFormat = dOutput.dFormat;
      }
    }   
  }
  /**
   * Returns the output data input as a TDataType
   *
   * @return The input data type
   */
  public TDataType getInputDataObject()
  {
    return dInput;
  }
  public void setInputDataObject(TDataType din)
  {
    if (boundTo == null)
    { // already bound ? (a jDDD specialty)
      tf.cancel(this, false);
      cancelledWithDependencies = false;
    }
    dInput = din;
    con = new TContract(this);
    sub = new TSubscription(con,this);
    TLink xlnk = getExistingTLink(this,dOutput,this.dInput);
    if (xlnk != null)
    { // will NOT add this TLink to the link table !
      int lnkId = this.linkId;
      bindToParentLink(xlnk,dOutput,this.dInput);
      devAccess = TMode.CM_REGISTER;
      this.linkId = lnkId; // set it back for removal from the table
      tf.removeTLink(this);
    }
  }
  public void setDataObjects(TDataType dout,TDataType din)
  {
    if (boundTo == null)
    { // already bound ? (a jDDD specialty)
      tf.cancel(this, false);
      cancelledWithDependencies = false;
    }
    dOutput = dout;
    dInput = din;
    con = new TContract(this);
    sub = new TSubscription(con,this);
    TLink xlnk = getExistingTLink(this,dout,din);
    if (xlnk != null)
    { // will NOT add this TLink to the link table !
      int lnkId = this.linkId;
      bindToParentLink(xlnk,dout,din);
      devAccess = TMode.CM_REGISTER;
      this.linkId = lnkId; // set it back for removal from the table
      tf.removeTLink(this);
    }
    if (hasDependencies())
    { // already has dependencies ? (a jDDD specialty)
      LinkedList<TLink> xlst = getDependencies();
      TDataType dt;
      for (int k=0; k<xlst.size(); k++)
      {
        if ((xlnk = (TLink)xlst.get(k)) == null) continue;
        dt = xlnk.getOutputDataObject();
        if (dt.dFormat != TFormat.CF_DEFAULT) continue;
        int dlen = dOutput.dArrayLength;
        if (xlnk.getMcaDevice() != null) dlen = 1;
        dt.setDataObject(dlen,dOutput.dFormat);
        dt.dArrayLength = dlen;
        dt.dFormat = dOutput.dFormat;
      }
    }   
  }
  /**
   * The last link timestamp
   *
   * @return The last link timestamp as a UTC java long value (milliseconds
   * since 1.1.1970)
   */
  public long getLastTimeStamp()
  {
    if (dOutput == null) return (long)0;
    return (long)(dOutput.timestamp) * 1000 + (long)(dOutput.timestampUSEC/1000);
  }
  /**
   * The last link timestamp as a TINE double timestamp value
   *
   * @return The last link timestamp as a UTC java long value (seconds
   * since 1.1.1970 with decimal fractional)
   */
  public double getLastDataTimeStamp()
  {
    if (dOutput == null) return (double)0;
    return (double)(dOutput.timestamp) + ((double)(dOutput.timestampUSEC)/1000000.0);
  }
  public int getSynchronizationOffset()
  {
    if (dOutput == null) return 0;
    return dOutput.getSynchronizationOffset();
  }
  private int groupLatency = 0;
  public int getGroupLatency() { return groupLatency; }
  public void setGroupLatency(int value) { groupLatency = value; }
  /**
   * The last link status in plain text
   *
   * @return The last link status in plain text
   */
  public String getLastError()
  {
  return getError(linkStatus < 0 ? lastLinkStatus : linkStatus);
  }     
  /**
   * The specified error code in plain text
   *
   * @param code Is the tine error code for which the error text is desired
   *
   * @return The specified error code in plain text
   */
  public String getError(int code)
  {
    String msg = isStatusLocal() ? "(local) " : "";
    if (code >= 512)
      msg += new String(linkStatusStringBuffer).trim();
    else
    msg += TErrorList.getErrorString(code);
   
    return msg;
  }
  Date  date = new Date();
  /**
   * Specifies an error value to fill in associated data buffers
   * in case of link error.
   *
   * @param errValue Is the error value to use in case of link errors
   *
   * 0 if successful
   */
  public int setErrorValue(double errValue)
  {
    if (sub.mode == TMode.CM_CANCEL) return TErrorList.illegal_mode;
    linkErrValue = errValue;
    useErrValue = true;
    return 0;
  }
  /**
   * Specifies an error string to fill in associated data buffers
   * in case of link error.
   *
   * @param errString Is the string to use in case of link errors
   *
   * 0 if successful
   */
  public int setErrorValue(String errString)
  {
    if (sub.mode == TMode.CM_CANCEL) return TErrorList.illegal_mode;
    linkErrString = errString;
    useErrValue = true;
    return 0;
  }
  public int setDebugLevel(int level)
  {
    return tf.setDebugLevel(level);
  }
  public int getDebugLevel()
  {
    return tf.getDebugLevel();
  }
  public String getModuleAddressInfo()
  {
    return srvAddr.toString(expName,cntName);
  }
/* mdavid: commented, unused
  * private TLink getExistingTLink(String devname, String devproperty, TDataType dout, TDataType din, short devaccess)
  {
    return tf.getExistingLink(devname,devproperty,dout,din,devaccess);   
  }*/
  private TLink getExistingTLink(TLink lnk, TDataType dout, TDataType din)
  {
    return tf.getExistingLink(lnk,dout,din);   
  }
  private void redirectLink()
  {
    tf.redirectLink(this);
  }
  private void relinkLink()
  {
    tf.reLinkLink(this);
  }
  private boolean isBlackListed()
  {
    return tf.isLinkBlackListed(this);
  }
  // TODO:  why is this so complicated ?
  // a name can be /<context> only (a global)
  // a name can be /<context>/<server> (no device name : e.g. some stock properties)
  // a name can be /<context>/<server>/<device> (most cases)
  // a name can be <server>  (bad idea, but historically allowed) (no context, no device)
  // a name can be <server>/<device> (bad idea, but historically allowed) (no context)
  private void splitDeviceName(String fullname)
  {
    int indx = fullname.indexOf('/');
    cntName = "DEFAULT";
    devName = "";
    if (indx == 0
    { // context given
      fullname = fullname.substring(1);
      if ((indx=fullname.indexOf('/')) == -1)
      { // no server name given -> assume globals server
        cntName = fullname;
        expName = "GLOBALS";
        return;
      }
      else
      { // set the context and move along
        cntName = fullname.substring(0,indx);
        fullname = fullname.substring(indx+1);
        indx = fullname.indexOf('/');
      }
    }
    else
    { // no context specified (discouraged!)
      expName = fullname;
      if (indx > 0)
      { // device name given
        expName = fullname.substring(0,indx);
        devName = fullname.substring(indx+1);       
      }
      return;
    }
    if (indx == -1)
    { // no device name
      expName = fullname;
      devName = "";
    }
    else
    { // normal case
      expName = fullname.substring(0,indx);
      devName = fullname.substring(indx+1);
    }
  }
  private void remapStringArrayToStringBuffer(TDataType d, boolean hasInputData)
  {
    try
    {
      if (Integer.parseInt(d.getTag()) > 0) return; // already done ?
    }
    catch (NumberFormatException e) {};
    d.setTag(""+d.dArrayLength);
  }
  protected void bindToParentLink(TLink xlnk,TDataType dout,TDataType din)
  {
    if (TLinkFactory.debugLevel > 1) DbgLog.log("makeLink", this.getFullDeviceName()+" is marked as bound to "+xlnk.linkId);
    synchronized (this)
    {
      MsgLog.log("bindToParentLink", "bind dependency to "+xlnk.getFullDeviceNameAndProperty(),0,null,1);
      //sub = xlnk.sub; //<< p.d. 29.5.12 this is not only not necessary, but a bad idea
      sub = new TSubscription(xlnk.sub.contract, this);
      con = xlnk.con;
      reqHdr = xlnk.reqHdr;
      linkId = xlnk.linkId;
      if (xlnk.isWildcardLink)
      {
        isWildcardLink = true;
        twcl = xlnk.twcl;
      }
      TDataType xdin = xlnk.getInputDataObject();
      if (xdin != null && din != null && din != xdin)
      { // we have to lock the input data !
        din.isLocked = xdin.isLocked = true;
      }
      TDataType xdout = xlnk.getOutputDataObject();
      if (xdout != null && dout != null && xdout.hasBeenUpdated)
      {
        if (dout.acquireDefaultFormat) synchronized (dout)
        {
          if (mcaDevice == null)
          {
            if (dout.getDataSize() < xdout.getDataSize())
            { // another jDDD specialty
              this.linkStatus = TErrorList.buffer_too_small;
              tf.fireCallbackEvent("bindToParentLink",this);
              return;
            }
            dout.dFormat = xdout.dFormat;
            dout.dArrayLength = xdout.dArrayLength;
            dout.dCompletionLength = xdout.dCompletionLength;
          }
          else
          {
            dout.dArrayLength = 1;
            dout.dCompletionLength = 1;
          }
        }
        if (dout.dFormat != TFormat.CF_DEFAULT)
        { // if the parent doesn't know yet either don't do this
          dout.setDataObject(dout.dArrayLength,dout.dFormat);
          dout.dataCopy(xdout);
          dout.setDataTimeStamp(xdout.getDataTimeStamp());
        }
      }                         
      boundTo = xlnk;
      xlnk.addDependency(this);
    }
  }
  private TLink findLinkWithOutputDataType(TDataType dout)
  {
    return tf.findLinkWithOutputDataType(dout);
  }
  private Thread linkThread = null;
  public Thread getThread() { return linkThread; }
  public void wakeUpCall()
  {
    synchronized (this) {notifyAll();}
    if (TLinkFactory.debugLevel > 1)
      DbgLog.log("TLink.wakupCall","notify dependent link "+getFullDeviceNameAndProperty()+" on thread "+linkThread.getName());
    needsWakeUpCall = false;
  }
  private synchronized int makeLink(String devname, String devproperty, TDataType dout, TDataType din, short devaccess)
  {
    linkThread = Thread.currentThread();
    if (devproperty == null)
    {
      return TErrorList.argument_list_error;
    }
    if (devname == null)
    { // assume a SITE global is desired
      devname = "/SITE";
    }
    String fullname;
    if (hook == null) hook = tf.getTLinkHook();
    if (hook != null && (fullname=hook.getLinkName(devname)) != null)
    {
      /* mdavid: commented
      Object objOut = dout == null ? null : dout.getDataObject();
      Object objIn = din == null ? null : din.getDataObject();
      if (dout != null) dOutput = dout; else dOutput = new TDataType();
      if (din != null) dInput = din; else dInput = new TDataType();
      */
     
      // mdavid: changed
      Object objOut = null;
    if (dout != null) {
      objOut = dout.getDataObject();
      dOutput = dout;
    } else {
      dOutput = new TDataType();
    }
     
    Object objIn = null;
    if (din != null) {
      objIn = din.getDataObject();
      dInput = din;
    } else {
      dInput = new TDataType();
   
     
     
      hookObj = hook.create(fullname,devproperty,objOut,objIn);
      /* mdavid: commented
    devName = new String(fullname);
    devProperty = new String(devproperty);
      */
      devName = fullname; // mdavid: improved
      devProperty = devproperty; // mdavid: improved
     
      expName = "cdi";
      cntName = "localhost";
      return 0;
    }
//    fullname = new String(devname); // mdavid: commented
    fullname = devname; // mdavid: changed
    active = true;
    if (din != null && !din.isLocked)
    { // in case of 'late additions' ...
      din.putData();
    }
    if (dout != null) dOutput = dout; else dOutput = new TDataType();
    if (din != null) dInput = din; else dInput = new TDataType();
    if (arrayDelimiter != null) dOutput.setArrayDelimiter(arrayDelimiter);

    // splitDeviceName will assign devName, expName, cntName
    splitDeviceName(fullname);

//    devProperty = new String(devproperty); // mdavid: commented
    devProperty = devproperty; // mdavid: improved
    devAccess = devaccess;
    devTimeout = 1000;

    if (TFormat.isVariableLengthString(dOutput.dFormat))
      remapStringArrayToStringBuffer(dOutput, false);
    if (TFormat.isVariableLengthString(dInput.dFormat))
      remapStringArrayToStringBuffer(dInput, true);
    if (dOutput.dArrayLength == 0)
    { // strip the read access off if no data returned
      devAccess &= ~(TAccess.CA_READ);
      dOutput.dFormat = TFormat.CF_NULL;
    }
    TPropertyList pl = TStockProperties.getPropertyList();
    isQuery = pl != null ? TStockProperties.getPropertyList().hasProperty(devProperty) : false;
    if (isBlackListed())
    { // link parameters have been black listed -> don't send out
      if (TLinkFactory.debugLevel > 0) DbgLog.log("makeLink","link " + getFullDeviceName() + " " + getProperty() + " has been black listed");
      linkStatus = tf.getBlackListedLinkStatus(this);
      if (linkStatus == TErrorList.not_signalled) linkStatus = TErrorList.link_blacklisted;
      return linkStatus;
    }
    redirectLink();
    relinkLink();

//    isGlobalsLink = (expName.compareToIgnoreCase("GLOBALS") == 0) && !TQuery.isStockProperty(devProperty);
    isGlobalsLink = (expName.compareToIgnoreCase("GLOBALS") == 0) && tf.isGlobalKeyword(cntName, devProperty);
    if (expName.compareToIgnoreCase("CYCLER") == 0 && devProperty.contains("CycleNumber"))
    { // a very special "GLOBAL"
      isGlobalsLink = true;
    }
    srvAddr = new TSrvEntry(this);
    if (srvAddr.fecAddr == null && !isGlobalsLink)
    {
      linkStatus = TErrorList.address_unknown;
      tf.addLinkToBlackList(this);
      String msg = "Could not resolve address for "+getFullDeviceName()+"\n"+srvAddr.getResolutionHistory();
      //RuntimeException e = new RuntimeException(msg);
      UnresolvedAddressException e = new UnresolvedAddressException(msg);
      MsgLog.log("makeLink",msg,TErrorList.address_unknown,e,1);
      throw e;
    }
   
    // redirecLink() or relinkLink() may have altered the original deviceName
    //TLink xlnk = getExistingTLink(this.getFullDeviceName(),this.devProperty,dout,din,devaccess);
    TLink xlnk = getExistingTLink(this,dout,din);
    if (xlnk != null)
    { // will NOT add this TLink to the link table !
      bindToParentLink(xlnk,dout,din);
      devAccess = TMode.CM_REGISTER;
      return 0;
    }
    else
    { // a new link: does it try to use the same output data object as another ?
      TLink clnk = findLinkWithOutputDataType(dout);
      if (clnk != null)
      {
        if ((xlnk = getExistingTLink(this,dout,din)) != null)
        { // jddd panels manage to get here ...
          bindToParentLink(xlnk,dout,din);
          return 0;         
        }
        linkStatus = TErrorList.invalid_reference;
        String msg = fullname+"["+devproperty+"] is attempting to use data object from "+clnk.getFullDeviceNameAndProperty();
        MsgLog.log("makeLink",msg,TErrorList.invalid_reference,null,0);
        throw new InvalidDataReferenceException(msg);
        //return TErrorList.invalid_reference;
      }
    }
   
    linkId = tf.registerLink(this); // adds a placeholder in the link table and returns the link id
    if (linkId < 0)
    {
      linkStatus = TErrorList.resources_exhausted;
      DbgLog.log("makeLink","link table is full, please close some links and try again!");
      return linkStatus;
    }
    if (linkId == 0 && this != TLinkFactory.linkTable[0] && TLinkFactory.linkTable[0] != null)
    { // an ENS rider
      synchronized (TLinkFactory.siblings)
      {
        TLinkFactory.siblings.add(this);
        MsgLog.log("TLink.makelink", "add ENS sibling link",0,null,1);
      }
    }
    this.tineProtocol = srvAddr.isLegacy ? TTransport.DEFAULT_PROTOCOL_LEVEL-1 : TTransport.DEFAULT_PROTOCOL_LEVEL;
    con = new TContract(this);
    sub = new TSubscription(con,this); // going out ...
    reqHdr = new TReqHdr(tf.getUserName(),tineProtocol);
    String defaultTimeoutStr;
    if ((defaultTimeoutStr = System.getProperty("default.timeout")) != null)
    {
      try { defaultTimeout = Integer.parseInt(defaultTimeoutStr); } catch (Exception ignore) {};
    }
    tf.activateLink(this); // sets the placeholder to this TLink !
    return 0;
  }
  public TLink(String deviceName, String deviceProperty, TDataType dout, TDataType din, short devaccess)
  {
    makeLink(deviceName,deviceProperty,dout,din,devaccess);
  }
  /**
   * Defines a tine Data link
   *
   * This defines a tine Data Link, verifies that the requested deviceName
   * exists and returns a non-null link object if successful.  This does not
   * establish the link! You must use either
   * the execute method or the attach method to establish the link.
   * 
   * @param devname is the fully specified device name as in /&lt;context&gt;/&lt;server&gt;/&lt;device&gt;
   * or &lt;server&gt;/&lt;device&gt;
   * @param devproperty is the desired property
   * @param dout is a reference to the output data object (which will receive
   * the data from the server)
   * @param din is a reference to the input data object (which will send data
   * to the server)
   * @param devaccess is the requested control access mode (TMode.CA_READ, TMode.CA_WRITE
   * etc.)
   *
   * @return A valid TLink instance if successful or null if not
   *
   * @throws UnresolvedAddressException if fullDeviceNameAndProperty does not point to a valid address
   * (i.e. if the address pointed to is unable to be resolved by the system)
   * @throws InvalidDataReferenceException if the output data object used (dout above)
   * is already used as the output data object in another TLink. For instance,
   * TLink("/TEST/Server1/Device1","Property1",dout,null,TAccess.CA_READ) already
   * exists. An attempt to create
   * TLink("/TEST/Server1/Device1","Property2",dout,null,TAccess.CA_READ) will
   * throw this exception as 'dout' is the same object already in use in the
   *
   * \b Example:
   *
   * \include eg_TLink1.java
   *
   * @see de.desy.tine.client.TLink#attach
   * @see de.desy.tine.client.TLink#execute
   * @see de.desy.tine.dataUtils.TDataType#TDataType
   */
  public TLink(String devname, String devproperty, TDataType dout, TDataType din, int devaccess)
  {
    makeLink(devname,devproperty,dout,din,(short)devaccess);
  }
  private String[] parseFullDeviceNameAndProperty(String fullDeviceNameAndProperty)
  {
    String[] parts = new String[2];
    // parts[0] => device name; parts[1] = property
    if (fullDeviceNameAndProperty != null)
    {
      int idx = fullDeviceNameAndProperty.indexOf('[');
      if (idx > 0)
      { // of the form "/context/server/device[property]"
        int idx2 = fullDeviceNameAndProperty.indexOf(']');
        parts[0] = fullDeviceNameAndProperty.substring(0,idx);
        if (idx2 < idx) idx2 = fullDeviceNameAndProperty.length();
        parts[1] = fullDeviceNameAndProperty.substring(idx+1,idx2);       
      }
      else
      {
        idx =  fullDeviceNameAndProperty.lastIndexOf('/');
        if (idx > 0)
        { // of the form "/context/server/device/property"
          parts[0] = fullDeviceNameAndProperty.substring(0,idx);
          parts[1] = fullDeviceNameAndProperty.substring(idx+1);       
        }
        else
        { // just the property : a globals call ?
          parts[0] = null;
          parts[1] = fullDeviceNameAndProperty;
        }
      }
    }
    return parts;
  }
  /**
   * Defines a tine Data link
   *
   * This defines a tine Data Link, verifies that the requested deviceName
   * exists and returns a non-null link object if successful.  This does not
   * establish the link! You must use either
   * the execute method or the attach method to establish the link.
   * 
   * @param fullDeviceNameAndProperty is the fully specified, property-appended
   * device name as in /&lt;context&gt;/&lt;server&gt;/&lt;device&gt;/&lt;property&gt;
   * or &lt;server&gt;/&lt;device&gt;/&lt;property&gt;
   * or /&lt;context&gt;/&lt;server&gt;/&lt;device&gt;[&lt;property&gt;]
   * @param dout is a reference to the output data object (which will receive
   * the data from the server)
   * @param din is a reference to the input data object (which will send data
   * to the server)
   * @param devaccess is the requested control access mode (TMode.CA_READ, TMode.CA_WRITE
   * etc.)
   *
   * @return A valid TLink instance if successful
   *
   * @throws UnresolvedAddressException if fullDeviceNameAndProperty does not point to a valid address
   * (i.e. if the address pointed to is unable to be resolved by the system)
   * @throws InvalidDataReferenceException if the output data object used (dout above)
   * is already used as the output data object in another TLink. For instance,
   * TLink("/TEST/Server1/Device1","Property1",dout,null,TAccess.CA_READ) already
   * exists. An attempt to create
   * TLink("/TEST/Server1/Device1","Property2",dout,null,TAccess.CA_READ) will
   * throw this exception as 'dout' is the same object already in use in the
   * initial link. 
   *
   * \b Example:
   *
   * \include eg_TLink2.java
   *
   * @see de.desy.tine.client.TLink#attach
   * @see de.desy.tine.client.TLink#execute
   * @see de.desy .tine.dataUtils.TDataType#TDataType
   */
  public TLink(String fullDeviceNameAndProperty, TDataType dout, TDataType din, short devaccess)
  {
    if (fullDeviceNameAndProperty == null)
    {
      String msg = "Could not resolve address for empty field";
      UnresolvedAddressException e = new UnresolvedAddressException(msg);
      MsgLog.log("makeLink",msg,TErrorList.address_unknown,e,1);
      throw e;
    }
   
    String[] parts = parseFullDeviceNameAndProperty(fullDeviceNameAndProperty);
   
    makeLink(parts[0],parts[1],dout,din,devaccess);
  }

  /**
   * Defines a read-only tine Data link with default behavior.
   *
   * This defines a tine Data Link, verifies that the requested deviceName
   * exists and returns a non-null link object if successful.  This does not
   * establish the link! You must use either
   * the execute method or the attach method to establish the link.
   * 
   * @param devname is the fully specified device name as in /&lt;context&gt;/&lt;server&gt;/&lt;device&gt;
   * or &lt;server&gt;/&lt;device&gt;
   * @param devproperty is the desired property
   *
   * @return A valid TLink instance if successful or null if not
   *
   * @throws UnresolvedAddressException if fullDeviceNameAndProperty does not point to a valid address
   * (i.e. if the address pointed to is unable to be resolved by the system)
   * @throws InvalidDataReferenceException if the output data object used (dout above)
   * is already used as the output data object in another TLink. For instance,
   * TLink("/TEST/Server1/Device1","Property1",dout,null,TAccess.CA_READ) already
   * exists. An attempt to create
   * TLink("/TEST/Server1/Device1","Property2",dout,null,TAccess.CA_READ) will
   * throw this exception as 'dout' is the same object already in use in the
   *
   * \b Example:
   *
   * \include eg_TLink3.java
   *
   * @see de.desy.tine.client.TLink#attach
   * @see de.desy.tine.client.TLink#execute
   */
  public TLink(String deviceName, String deviceProperty)
  {
    TDataType dout = new TDataType(TLink.maxOutputBufferSize,TFormat.CF_DEFAULT);
    TDataType din = new TDataType();
    makeLink(deviceName,deviceProperty,dout,din,TAccess.CA_READ);
  }
  public static String[] splitDeviceAndPropertyFromName(String fullDeviceNameAndProperty)
  {
    String[] names = new String[2];
    int cnt = 1;
    int indx;
   
    if (fullDeviceNameAndProperty == null) return names;
    indx = fullDeviceNameAndProperty.indexOf('[');
    if (indx > 0)
    { // "check for /<context>/<server>/<device>[<property>]" form
      int stop = fullDeviceNameAndProperty.length();
      if (stop < indx+2) return names;
      /* mdavid: commented
      names[0] = new String(fullDeviceNameAndProperty.substring(0,indx));
      names[1] = new String(fullDeviceNameAndProperty.substring(indx+1,stop-1));*/  
      names[0] = fullDeviceNameAndProperty.substring(0,indx); // mdavid
      names[1] = fullDeviceNameAndProperty.substring(indx+1,stop-1); // mdavid
    }
    else
    { // "check for /<context>/<server>/<device>/<property>" form
      indx = fullDeviceNameAndProperty.indexOf('/');
      if (indx == 0) cnt = 3;
      for (int i=0; i<cnt; i++)
      {
        indx +=  fullDeviceNameAndProperty.substring(indx+1).indexOf('/') + 1;
      }   
      names[0] = fullDeviceNameAndProperty.substring(0,indx);
      names[1] = fullDeviceNameAndProperty.substring(indx+1);
    }
    return names;
  }
  /**
   * Defines a read-only tine Data link with default behavior.
   *
   * This defines a tine Data Link, verifies that the requested deviceName
   * exists and returns a non-null link object if successful.  This does not
   * establish the link! You must use either
   * the execute method or the attach method to establish the link.
   * 
   * @param fullDeviceNameAndProperty is the fully specified, property-appended
   * device name as in /&lt;context&gt;/&lt;server&gt;/&lt;device&gt;/&lt;property&gt;
   * or &lt;server&gt;/&lt;device&gt;/&lt;property&gt;
   *
   * @return A valid TLink instance if successful or null if not
   *
   * @throws UnresolvedAddressException if fullDeviceNameAndProperty does not point to a valid address
   * (i.e. if the address pointed to is unable to be resolved by the system)
   * @throws InvalidDataReferenceException if the output data object used (dout above)
   * is already used as the output data object in another TLink. For instance,
   * TLink("/TEST/Server1/Device1","Property1",dout,null,TAccess.CA_READ) already
   * exists. An attempt to create
   * TLink("/TEST/Server1/Device1","Property2",dout,null,TAccess.CA_READ) will
   * throw this exception as 'dout' is the same object already in use in the
   *
   * \b Example:
   *
   * \include eg_TLink4.java
   *
   * @see de.desy.tine.client.TLink#attach
   * @see de.desy.tine.client.TLink#execute
   */
  public TLink(String fullDeviceNameAndProperty)
  {
    if (fullDeviceNameAndProperty == null) return; // TODO: exception
    String[] names = splitDeviceAndPropertyFromName(fullDeviceNameAndProperty);
    String devname = names[0], devproperty = names[1];
    TDataType dout = new TDataType(TLink.maxOutputBufferSize,TFormat.CF_DEFAULT);
    TDataType din = new TDataType();
    makeLink(devname,devproperty,dout,din,TAccess.CA_READ);
  }

  /**
   * Defines a tine GLobal Data link
   *
   * This defines a tine Data Link and assumes that the requested parameter
   * is a network global, available on the tine multicast group.
   * This method does not establish the link! You must use either
   * the execute method or the attach method to establish the link.
   * 
   * @param globalKeyword is the desired network global parameter
   * @param dout is a reference to the output data object (which will receive
   * the network global data).
   *
   * @return A valid TLink instance if successful or null if not
   *
   * \b Example:
   *
   * \include eg_TLink5.java
   *
   * @see de.desy.tine.client.TLink#attach
   * @see de.desy.tine.client.TLink#receive
   * @see de.desy.tine.dataUtils.TDataType#TDataType
   */
  public TLink(String globalKeyword, TDataType dout)
  {
    makeLink(null,globalKeyword,dout,null,TAccess.CA_READ);
  }

  /**
   * Attaches the data sets to the created link at the default polling rate (1000 msec)
   *
   * Asynchronous. This method attaches the data sets to the created link using the calling
   * parameters provided at instantiation.  The arriving data will automatically
   * update the output data set and then fire the callback method provided.
   * If the callback is not needed, a null can be passed for this parameter.
   * 
   * @param mode is the tine control mode parameter, that is one of TMode.CM_POLL,
   * TMode.CM_REFRESH, TMode.SINGLE, etc. in possible combination with
   * TMode.CM_NETWORK or TMode.CM_CONNECT, etc.
   * @param f is the 'callback function' to be called whenever either new data
   * arrive or a change in link status occurs.
   * The callback function should instantiate a class you create which
   * implements the TCallback() class interface.
   *
   * @return A positive link handle if successful, otherwise -1.  On
   * failure the linkStatus property of TLink can be examined.
   *
   * \b Example:
   *
   * \include eg_attach1.java
   *
   * @see de.desy.tine.client.TLink#TLink
   * @see de.desy.tine.client.TLink#TLinkCallback
   * @see de.desy.tine.client.TLink#execute
   */
  public int attach(int mode,TCallback f)
  {
    return attach(mode,f,1000,-1);
  }
  public int attach(short mode,TCallback f)
  {
    return attach((int)mode,f);
  }
  /**
   * Stop thread execution until this link has been signalled.
   */
    
  public synchronized int Twait()
  {
   if (sub.id < 0) return -1;
   try
   {
    while (!this.terminate)
    {
      wait();
    }
   }
   catch (InterruptedException e) {}
   return -1;
  }           
  /**
   * Executes a synchronous link using the data sets supplied at 
   * link creation.
   *
   * Synchronous. This method executes the link using the data sets and
   * parameters provided at instantiation.  The call will block for 1000 msec
   * or until the call completes.
   * 
   * \note The TLink object created will remain resident until a call to
   * the cancel() method is made. Thus repeated calls to execute() can
   * be made without invoking separate calls to TLink().
   * 
   * @return 0 if successful, otherwise a tine return code which can be
   * interpreted using getError() or getLastError().
   *
   * \b Example:
   *
   * \include eg_execute1.java
   *
   * @see de.desy.tine.client.TLink#TLink
   * @see de.desy.tine.client.TLink#attach
   */
  public int execute()
  {
   return execute(1000);
  }                   
  /**
   * Executes a synchronous link using the data sets supplied at 
   * link creation and Closes the link upon completion.
   *
   * Synchronous. This method executes the link using the data sets and
   * parameters provided at instantiation.  The call will block for the
   * duration of the timeout specified or until the call completes.
   * When the link has completed (successfully or not) the TLink will
   * be automatically closed and hence removed from the link tables,
   * obviating the need for an additional call to closeLink().
   *
   * @return 0 if successful, otherwise a tine return code which can be
   * interpreted using getError() or getLastError().
   *
   * @see de.desy.tine.client.TLink#TLink
   * @see de.desy.tine.client.TLink#attach
   */
  public int executeAndClose()
  {
     return executeAndClose(1000);
  }                   
  /**
   * Executes a synchronous link using the data sets supplied at 
   * link creation.
   *
   * Synchronous. This method executes the link using the data sets and
   * parameters provided at instantiation.  The call will block for the
   * duration of the timeout specified or until the call completes.
   *
   * @param timeout The amount of time in milliseconds to block execution
   * until the call completes.
   * @param appendMode will be appended to the tine Control Mode, which is
   * TMode.CM_SINGLE. (e.g. TMode.CM_CONNECT).
   *  
   * \note The TLink object created will remain resident until a call to
   * the cancel() method is made. Thus repeated calls to execute() can
   * be made without invoking separate calls to TLink().
   * 
   * @return 0 if successful, otherwise a tine return code which can be
   * interpreted using getError() or getLastError().
   *
   * \b Example:
   *
   * \include eg_execute2.java
   *
   * @see de.desy.tine.client.TLink#TLink
   * @see de.desy.tine.client.TLink#attach
   */
  public int execute(int timeout, short appendMode)
  {
    boolean retry = TLinkFactory.alwaysRetry; // 1st cut
    if (!retry) retry = (devAccess & TAccess.CA_RETRY) == TAccess.CA_RETRY; // 2nd cut
    if ((devAccess & TAccess.CA_NORETRY) == TAccess.CA_NORETRY) retry = false; // takes precedence if set
    return execute(timeout,appendMode,retry);
  }                   
  /**
   * Executes a synchronous link using the data sets supplied at 
   * link creation.
   *
   * Synchronous. This method executes the link using the data sets and
   * parameters provided at instantiation.  The call will block for the
   * duration of the timeout specified or until the call completes.
   *
   * @param timeout The amount of time in milliseconds to block execution
   * until the call completes.
   * @param retryOnError if 'true' will instruct the tine engine to issue
   * an automatic retry following a connection timeout.
   *  
   * \note The TLink object created will remain resident until a call to
   * the cancel() method is made. Thus repeated calls to execute() can
   * be made without invoking separate calls to TLink().
   * 
   * @return 0 if successful, otherwise a tine return code which can be
   * interpreted using getError() or getLastError().
   *
   * \b Example:
   *
   * \include eg_execute3.java
   *
   * @see de.desy.tine.client.TLink#TLink
   * @see de.desy.tine.client.TLink#attach
   */
  public int execute(int timeout, boolean retryOnError)
  {
    return execute(timeout,(short)0,retryOnError);
  }
  /**
   * Executes a synchronous link using the data sets supplied at 
   * link creation and Closes the link upon completion.
   *
   * Synchronous. This method executes the link using the data sets and
   * parameters provided at instantiation.  The call will block for the
   * duration of the timeout specified or until the call completes.
   * When the link has completed (successfully or not) the TLink will
   * be automatically closed and hence removed from the link tables,
   * obviating the need for an additional call to closeLink().
   *
   * @param timeout The amount of time in milliseconds to block execution
   * until the call completes.
   * @param appendMode will be appended to the tine Control Mode, which is
   * TMode.CM_SINGLE. (e.g. TMode.CM_CONNECT).
   *  
   * @return 0 if successful, otherwise a tine return code which can be
   * interpreted using getError() or getLastError().
   *
   * @see de.desy.tine.client.TLink#TLink
   * @see de.desy.tine.client.TLink#attach
   */
  public int executeAndClose(int timeout, short appendMode)
  {
    boolean retry = (devAccess & TAccess.CA_RETRY) == TAccess.CA_RETRY;
    removeOnClose = true;
    int cc = execute(timeout,appendMode,retry);
    if (linkId > 0) close(); //TLinkFactory.linkTable[linkId] = null;
    if (linkId == 0)
    {
      synchronized (TLinkFactory.siblings)
      {
        if (TLinkFactory.siblings.contains(this))
        {
          MsgLog.log("TLinkFactory.cancel", "remove ENS sibling",0,null,1);
          TLinkFactory.siblings.remove(this);
        }
      }
    }
    return cc;
  }                   
  private int validLinkStatus(int status)
  {
    return status < 0 ? TErrorList.connection_timeout : status;
  }
  private boolean isListening = false;
  private int closeListener()
  {
    if (!isListening) return TErrorList.code_failure;
    if (blockCloseListener) return TErrorList.not_accepted;
    isListening = false;
    tf.removeListener(this);
    return 0;
  }
  /**
   * Executes synchronously, returning the link results.
   * Upon the initial call an asynchronous 'listener' monitor
   * link is establihes with the update interval
   * given by the 'timeout' parameter.  Subsequent calls simply
   * update the output data object with the current contents of the
   * underlying monitor link AND keep the underlying monitor link
   * active.
   *
   * If a 'dead time' interval of 5 minutes without a subsequent
   * call to executeAndListen() occurs, then monitor link is closed.
   *
   * For 'simple' applications which 'poll' for data in a timer, the
   * performance can be improved dramatically by replacing calls to the
   * 'execute()' method with 'executeAndListen().
   *
   * With this simple method, the underlying monitor will use the
   * default monitor interval of 1000 msec and the default mode
   * of TMode.CM_TIMER.
   *  
   * @see de.desy.tine.client.TLink#TLink
   * @see de.desy.tine.client.TLink#attach
   */
  public int executeAndListen()
  {
    return executeAndListen(1000,TMode.CM_TIMER);
  }
  /**
   * Executes synchronously, returning the link results.
   * Upon the initial call an asynchronous 'listener' monitor
   * link is establihes with the update interval
   * given by the 'timeout' parameter.  Subsequent calls simply
   * update the output data object with the current contents of the
   * underlying monitor link AND keep the underlying monitor link
   * active.
   *
   * If a 'dead time' interval of 5 minutes without a subsequent
   * call to executeAndListen() occurs, then monitor link is closed.
   *
   * For 'simple' applications which 'poll' for data in a timer, the
   * performance can be improved dramatically by replacing calls to the
   * 'execute()' method with 'executeAndListen().
   *
   * @param interval Is analogous to the timeout parameter in the
   * 'execute()' method family and gives the amount of time in milliseconds
   * to block execution until the initial call completes. It also establishes
   * the monitor interval for the underlying asynchronous link.
   * The underlying monitor will use TMode.CM_TIMER with this method.
   *  
   * @see de.desy.tine.client.TLink#TLink
   * @see de.desy.tine.client.TLink#attach
   */
  public int executeAndListen(int interval)
  {
    return executeAndListen(interval,TMode.CM_TIMER);
  }
  public int executeAndListen(int interval,short mode,boolean retryOnError)
  {
    return executeAndListen(interval,mode);
  }
  protected boolean blockCloseListener = false;
  /**
   * Executes synchronously, returning the link results.
   * Upon the initial call an asynchronous 'listener' monitor
   * link is establihes with the update interval
   * given by the 'timeout' parameter.  Subsequent calls simply
   * update the output data object with the current contents of the
   * underlying monitor link AND keep the underlying monitor link
   * active.
   *
   * If a 'dead time' interval of 5 minutes without a subsequent
   * call to executeAndListen() occurs, then monitor link is closed.
   *
   * For 'simple' applications which 'poll' for data in a timer, the
   * performance can be improved dramatically by replacing calls to the
   * 'execute()' method with 'executeAndListen().
   *
   * @param interval Is analogous to the timeout parameter in the
   * 'execute()' method family and gives the amount of time in milliseconds
   * to block execution until the initial call completes. It also establishes
   * the monitor interval for the underlying asynchronous link.
   * @param mode can be used to completely control the mode of the
   * underlying asynchronous monitor (default is TMode.CM_TIMER).
   *  
   * @see de.desy.tine.client.TLink#TLink
   * @see de.desy.tine.client.TLink#attach
   */
  public synchronized int executeAndListen(int interval, short mode)
  {
    short bmode = TMode.getBaseMode(mode);
    if (bmode <= TMode.CM_CANCEL || bmode == TMode.CM_REGISTER) bmode = TMode.CM_TIMER;
    TLinkFactory.ListenerItem lsnr = tf.getListener(this,bmode,interval);
    if (lsnr == null) return execute(interval);
    isListening = true;
    if (!lsnr.isActive()) lsnr.start();
    lsnr.keepAlive();
    int ls = linkStatus;
    if (ls == TErrorList.get_subscription_id) ls = 0;
    if (TLinkFactory.debugLevel > 0) DbgLog.log("executeAndListen","listener link ("+linkId+") "+getFullDeviceNameAndProperty()+
        "returned <"+linkStatus+">");
    return ls;
  }
  private boolean isThrowingBoundToInactiveLink = false;
  private boolean useAppendedAccessInConstructor = false;
  /**
   * Executes a synchronous link using the data sets supplied at 
   * link creation.
   *
   * Synchronous. This method executes the link using the data sets and
   * parameters provided at instantiation.  The call will block for the
   * duration of the timeout specified or until the call completes.
   *
   * @param timeout The amount of time in milliseconds to block execution
   * until the call completes.
   * @param appendMode will be appended to the tine Control Mode, which is
   * TMode.CM_SINGLE. (e.g. TMode.CM_CONNECT).
   * @param retryOnError if 'true' will instruct the tine engine to issue
   * an automatic retry following a connection timeout.
   *  
   * \note The TLink object created will remain resident until a call to
   * the cancel() method is made. Thus repeated calls to execute() can
   * be made without invoking separate calls to TLink().
   * 
   * @return 0 if successful, otherwise a tine return code which can be
   * interpreted using getError() or getLastError().
   *
   * \b Example:
   *
   * \include eg_execute4.java
   *
   * @see de.desy.tine.client.TLink#TLink
   * @see de.desy.tine.client.TLink#attach
   */
  //public synchronized int execute(int timeout, short appendMode,boolean retryOnError)
  public int execute(int timeout, short appendMode,boolean retryOnError)
  {
    if (isListening) return executeAndListen(timeout, appendMode, retryOnError);
    linkStatusSource = TLink.STATUS_LOCAL;
    if (this.srvAddr == null)
    { // link has probably been blacklisted
      if (linkStatus == TErrorList.link_blacklisted)
        linkStatus = tf.getBlackListedLinkStatus(this);
      return linkStatus;
    }
    if (tf.fecEntryWithTimeout == this.srvAddr.fecAddr)
    { // if fecEntryWithTimeout is non-null and equal to this address then there's no sense trying !
      if (TLinkFactory.debugLevel > 0)
        DbgLog.log("execute","execute link ("+linkId+") "+getFullDeviceName()+"["+getProperty()+"] not allowed within link watchdog callback");
      setErrorValue("link timeout");
      linkStatus = TErrorList.link_timeout;
      return TErrorList.not_allowed;
    }
    if (TLinkFactory.debugLevel > 0) DbgLog.log("execute","execute link ("+linkId+") "+getFullDeviceName()+"["+getProperty()+"]");
    if (hookObj != null) return hook.execute(hookObj,timeout);
    if (this.isBlackListed())
    {
      MsgLog.log("execute","link execution error "+getFullDeviceNameAndProperty()+" ("+linkId+") link is blacklisted",TErrorList.link_blacklisted,null,1);
      return TErrorList.link_blacklisted;
    }
    if (this.sub == null)
    {
      if (linkStatus == TErrorList.resources_exhausted)
        MsgLog.log("execute","\nLink Table ("+tf.getLinkTable().length+" elements) is full",linkStatus,null,0);
      else
        MsgLog.log("execute",getFullDeviceName()+ " ["+getProperty()+"] has a null subscription <"+linkStatus+">",linkStatus,null,0);
      return linkStatus == 0 ? TErrorList.code_failure : linkStatus;
    }
    short bmode = TMode.getBaseMode(this.sub.mode);
    if (!isBound() && TMode.canClose(bmode))
    { // synchronous link on top of a persistent link
      while (!hasObtainedStatus)
      {
        try { Thread.sleep(1); }
        catch (InterruptedException e) {}
      }
      return validLinkStatus(getLinkStatus());
    }
    if (isGlobalsLink)
    {
      if (TAccess.isWrite((byte)devAccess)) return TErrorList.illegal_read_write;
      MsgLog.log("execute", "synchronous link to "+ cntName+" global variable "+devProperty+" using multicasted value",0,null,1);
      _attach(TMode.CM_GLOBAL|TMode.CM_WAIT,null,null,1000,null,0);
      if (linkStatus == 0)
      {
        if (isBound()) this.boundTo.isGlobalsLinkPinned = true;
        else isGlobalsLinkPinned = true;
      }
      return linkStatus;
    }
    synchronized (this)
    {
      if (isBound())
      { // different TLinks to the same thing in synchronous mode :
        TLink blnk = this.boundTo;
        if (blnk.mcaDevice != null && blnk.isBound())
        { // bound to an already-'bound' mca link
          blnk = blnk.boundTo; // get the right mode !
        }
        if (blnk == null)
        { // bad news, indeed!
          if (TLinkFactory.debugLevel > 0)
          {
            DbgLog.log("execute","bound link ("+linkId+") "+getFullDeviceNameAndProperty()+
              "does not have a listening parent! ");
          }
          return TErrorList.code_failure;         
        }
        // blnk is always the MCA parent (if this is an MCA link) !
        bmode = TMode.getBaseMode(blnk.sub.mode);
        if (TMode.isActive(bmode))
        { // has been or is being used -> just copy the data ...
          while (!blnk.hasObtainedStatus)
          {
            try { Thread.sleep(1); }
            catch (InterruptedException e) {}
          }

          int idx = getMcaIndex() - 1;
          if (idx >= 0 && idx < blnk.dOutput.dArrayLength)
          {
            dOutput.dataCopy(blnk.dOutput, idx, true);
          }
          else
          { // somehow attached to an MCA link, but don't know I'm an MCA link ? (is this possible?)
            dOutput.pushBytes(this.boundTo.getOutputDataObject().getDataBuffer());
          }
          this.linkStatus = blnk.linkStatus;
          this.linkErrString = blnk.linkErrString;
          int ls = validLinkStatus(blnk.getLinkStatus());
          if (ls == TErrorList.get_subscription_id) ls = 0;
          if (TLinkFactory.debugLevel > 0)
          {
            DbgLog.log("execute","bound link ("+linkId+") "+getFullDeviceNameAndProperty()+
              "returned <"+linkStatus+">");
          }
          return ls;
        }
        // parent is not active !!
        if (!isThrowingBoundToInactiveLink)
        { // try to peek at the bound but inactive results
          short smod = blnk.sub.mode; // (REGISTER or CANCEL ?)
          int sstat = blnk.linkStatus;
          boolean sobt = blnk.hasObtainedStatus;
          boolean snot = blnk.hasNotifiedOnce;
          this.linkStatus = blnk.execute(timeout);
          this.linkErrString = blnk.linkErrString;
          blnk.sub.mode = smod; // set it back (REGISTER or CANCEL)
          blnk.linkStatus = sstat;
          blnk.hasNotifiedOnce = snot;
          blnk.hasObtainedStatus = sobt;
          return validLinkStatus(linkStatus);
        }
        // if we're here then throw the exception
        this.linkStatus = TErrorList.link_exists;
        this.linkErrString = "link is bound to an inactive link";
        throw new BoundToInactiveLinkException();
      }
      else
      { /* allow new data from the input reference under some circumstances */
        if (dInput != null && dInput.dArrayLength > 0 && !dInput.isLocked)
          dInput.putData();
      }
      int sts = 0;
      if (this.linkId < 0)
      {
        sts = this.linkStatus = TErrorList.out_of_client_memory;
        return validLinkStatus(sts);
      }
      while (!getCriticalSection()) Thread.yield();
      if (retryOnError)
      {
        if (timeout < 100) timeout = 100;
        devAccess |= TAccess.CA_RETRY; // make sure it's set
      }
      else
      {
        if (timeout < 1) timeout = 1;
        devAccess |= TAccess.CA_NORETRY; // make sure it's set
      }
      timeout = makeValidPollingInterval(timeout, bmode);
      devTimeout = timeout;
      this.sub.pollingInterval = devTimeout;
      long ltime = System.currentTimeMillis();
      this.sub.linkStartTime = ltime;
      this.sub.linkLastTime = ltime;
      if (bmode == TMode.CM_REGISTER || bmode <= TMode.CM_SINGLE)
      { // this could already be an established monitor link ...
        bmode = this.sub.mode = TMode.CM_SINGLE;
        devAccess |= TAccess.CA_SYNC;
      }
      if (appendMode == 0 &&
          useAppendedAccessInConstructor &&
          ((devAccess & TAccess.CA_CONNECT) == TAccess.CA_CONNECT))
      {
        appendMode = TMode.CM_CONNECT;
      }
      if (tf.isUseConnectedSockets() || TMode.isConnected(appendMode))
      {
        this.sub.mode |= appendMode;
        retryOnError = false; // turn this off for connected sockets
      }
      //if (TLinkFactory.alwaysRetry) retryOnError = true;
      retryOnTimeoutError = retryOnError;
      linkStatus = TErrorList.link_not_open;
      //linkStale = true;  <- don't do this here!
      freeCriticalSection();
      TWildcardLink wc = null;
      if (this.devName.indexOf("*") >= 0 &&
          !TQuery.isStockProperty(this.devProperty) &&
          !TQuery.isMetaProperty(this.devProperty))
      {
        if (!isWildcardLink)
        { // if not yet so flagged ...
          wc = tf.getWildcardLink();
          wc.parent = this;
          wc.format = this.dOutput.dFormat;
          /* reset these to the wildcard callback  */
          this.tlcb = wc.scb;
          //wait = TRUE;
          this.twcl = wc;
          this.isWildcardLink = true;
        }
      }
      if ((sts=tf.sendLinkRequest(this)) != 0) // will set the link to active
      {
        MsgLog.log("execute","can't establish link",sts,null,1);
        devAccess &= ~(TAccess.CA_SYNC);
        if (wc != null) tf.rmvWildcardLink(wc);
        return validLinkStatus(sts);
      }
    
      TLinkFactory.adjustLinkTable(this,TLinkFactory.adjustLinkTableAdd);
      long t0 = System.currentTimeMillis();
      boolean waitWithTimeout = tf.cannotNotifyFromWatchdogThread || this.cannotNotifyFromWatchdogThread;
      if (!retryOnError) waitWithTimeout = true;
      try
      {
        while (true)
        {
          active = true;
          if (waitWithTimeout)
          {
            wait(timeout);
          }
          else
          {
            wait();
          }
          if (this.twcl == null || this.twcl.canNotify) break;
        }
        if (TLinkFactory.debugLevel > 0)
        {
          DbgLog.log("execute","link ("+linkId+") "+getFullDeviceNameAndProperty()+
            "returned <"+linkStatus+"> in "+(System.currentTimeMillis()-t0)+" msec");
        }
        if (bmode == TMode.CM_SINGLE) this.active = false;
        devAccess &= ~(TAccess.CA_SYNC);
        if (linkStatus != 0 && (linkStatus & TErrorList.CE_SENDDATA) != TErrorList.CE_SENDDATA)
        {
          if (linkStatus == TErrorList.async_access_required ||
              linkStatus == TErrorList.mcast_access_required)
          {
            if ((expName.compareToIgnoreCase("GLOBALS") != 0))
            {
              blockCloseListener = true;
              if (TLinkFactory.debugLevel > 0)
                DbgLog.log("execute", "synchronous calls not allowed -> switching to listener");
              return executeAndListen(timeout, appendMode, retryOnError);
            }
            else
            {
              linkStatus = TErrorList.async_access_required;
            }
          }
          MsgLog.log("execute","link execution error "+getFullDeviceNameAndProperty()+" ("+linkId+") "+TErrorList.getErrorString(linkStatus),linkStatus,null,1);
        }
        // if (wc != null) tf.rmvWildcardLink(wc); <- happens when close()ed.
        linkStatus = validLinkStatus(linkStatus);
        return linkStatus;
      }
      catch (InterruptedException e)
      {
        linkStatus = TErrorList.net_read_error;
        MsgLog.log("execute","Interrupted io on "+getFullDeviceName()+" ("+linkId+")",linkStatus,e,1);
        //terminate = true;
      }
      if (!removeOnClose) devAccess &= ~(TAccess.CA_SYNC);
      if (linkStatus != 0 && (linkStatus & TErrorList.CE_SENDDATA) != TErrorList.CE_SENDDATA)
      {
        MsgLog.log("execute","link execution error "+getFullDeviceNameAndProperty()+" ("+linkId+") "+TErrorList.getErrorString(linkStatus),linkStatus,null,1);
      }
      linkStatus = validLinkStatus(linkStatus);
      return linkStatus;
    } // synchronized block
  }                   
  /**
   * Executes a synchronous link using the data sets supplied at 
   * link creation.
   *
   * Synchronous. This method executes the link using the data sets and
   * parameters provided at instantiation.  The call will block for the
   * duration of the timeout specified or until the call completes.
   *
   * @param timeout The amount of time in milliseconds to block execution
   * until the call completes.
   *  
   * \note The TLink object created will remain resident until a call to
   * the cancel() method is made. Thus repeated calls to execute() can
   * be made without invoking separate calls to TLink().
   * 
   * @return 0 if successful, otherwise a tine return code which can be
   * interpreted using getError() or getLastError().
   *
   * \b Example:
   *
   * \include eg_execute5.java
   *
   * @see de.desy.tine.client.TLink#TLink
   * @see de.desy.tine.client.TLink#attach
   */
  public int execute(int timeout)
  {
    return execute(timeout,(short)0);
  }                
  /**
   * Executes a synchronous link using the data sets supplied at 
   * link creation and Closes the link upon completion.
   *
   * Synchronous. This method executes the link using the data sets and
   * parameters provided at instantiation.  The call will block for the
   * duration of the timeout specified or until the call completes.
   * When the link has completed (successfully or not) the TLink will
   * be automatically closed and hence removed from the link tables,
   * obviating the need for an additional call to closeLink().
   *
   * @param timeout The amount of time in milliseconds to block execution
   * until the call completes.
   *  
   * @return 0 if successful, otherwise a tine return code which can be
   * interpreted using getError() or getLastError().
   *
   * @see de.desy.tine.client.TLink#TLink
   * @see de.desy.tine.client.TLink#attach
   */
  public int executeAndClose(int timeout)
  {
    return executeAndClose(timeout,(short)0);
  }                
 
  /**
   * Terminates an active link
   * mdavid: changed and marked as 'deprecated'
   * @deprecated use {@link #close()} instead
   */
  public int cancel()
  {
    return close();
/*  mdavid: commented 
    if (isListening) return closeListener();
    if (hookObj != null) hook.close(hookObj);
    if (dInput != null)
    {
      dInput.lockedMessage = null;
      dInput.isLocked = false;
    }
    if (isBeingWatched)
    {
      TWatchdogLink wdl = tf.getWatchdogLink("/"+cntName+"/"+expName);
      if (wdl != null) wdl.remove(this);
    }
    if (isGrouped()) grp.removeMember(this);
    return tf.cancel(this);
*/ 
  }
 
  /**
   * Terminates an active link
   *
   * Synonymous with cancel().
   * mdavid: changed
   */
  public int close()
  {
    if (isListening) return closeListener();
    if (hookObj != null) hook.close(hookObj);
    if (dInput != null)
    {
      dInput.lockedMessage = null;
      dInput.isLocked = false;
    }
    if (isBeingWatched)
    {
      TWatchdogLink wdl = tf.getWatchdogLink("/"+cntName+"/"+expName);
      if (wdl != null) wdl.remove(this);
    }
    if (isGrouped()) grp.removeMember(this);
    return tf.cancel(this)
  }
//  mdavid: commented
//  public int close()
//  {
//    return cancel();
//  }
 
  public int linkId;
  int linkBlacklists = 0;
  int linkTimeouts = 0
  long lastEnsAddressRequest = 0;
  public int linkInvalidCount = 0;
  boolean notifyPending = true;
  boolean needsToSendLinkRequest = false;
  //static TErrorList tel; 
  private TLinkFactory tf = TLinkFactory.getInstance();
  TBucket tb;
  public TBucket getBucket() { return tb; }
  public void rmvBucket() { tb = null; }
  protected int mapSingleFieldToBitfield()
  {
    TLink lnk = this;
    String tag = lnk.dOutput.getTag();
    if (tag == null || tag.length() == 0) return TErrorList.invalid_structure_tag;
    int cc = TQuery.AcquireAndRegisterBitfieldInfo(lnk.cntName, lnk.expName, tag, lnk.dOutput.dFormat);
    if (cc != 0)
    {
      lnk.linkStatus = cc;
      lnk.linkStale = true;
      return cc;
    }
    TBitfield bf = TBitfieldRegistry.getBitfield(tag);
    if (bf == null)
    {
      cc = lnk.linkStatus = TErrorList.invalid_structure_tag;
      lnk.linkStale = true;
      return cc;
    }
    boolean fieldKnown = false;
    String[] parts;
    String dev = lnk.devName;
    String prp = lnk.devProperty;
    String fld = null;
    if (!fieldKnown)
    {
      parts = lnk.devProperty.split("\\.");
      if (parts.length > 1 && bf.isField(parts[parts.length-1]))
      {
        prp = lnk.devProperty.substring(0, lnk.devProperty.indexOf(parts[parts.length-1])-1);
        fieldKnown = true;
        fld = parts[parts.length-1];
      }
    }
    if (!fieldKnown)
    {
      parts = lnk.devName.split("\\.");
      if (parts.length > 1 && bf.isField(parts[parts.length-1]))
      {
       dev = lnk.devName.substring(0, lnk.devName.indexOf(parts[parts.length-1])-1);
       fieldKnown = true;
       fld = parts[parts.length-1];
      }
    }
    if (fieldKnown)
    {
      tf.addLinkToReLinkList(lnk, bf,fld);
      lnk.devName = dev;
      lnk.devProperty = prp;
    }
    else
    { // this should not happen if every thing goes well
      cc = lnk.linkStatus = TErrorList.invalid_field;
      // TODO: find a way of calling the callback with this status
      return cc;
    }
    lnk.dOutput.dFormat = bf.getFormat();
    lnk.dOutput.setBitField(bf);
    lnk.dOutput.setField(fld);
    return 0;     
  }
  protected int mapSingleFieldToStruct()
  {
    TLink lnk = this;
    if (lnk.dOutput == null) return TErrorList.invalid_data;
    if (lnk.dOutput.getField() != null) return 0; // already mapped
    String tag = lnk.dOutput.getTag();
    if (tag == null || tag.length() == 0) return TErrorList.invalid_structure_tag;
    int cc = TQuery.AcquireAndRegisterStructInfo(lnk.cntName, lnk.expName, tag);
    if (cc != 0)
    {
      lnk.linkStatus = cc;
      lnk.linkStale = true;
      return cc;
    }
    String key = lnk.cntName == null ? null : "/"+lnk.cntName+"/"+lnk.expName;
    if (dOutput.getStructureKey() == null) dOutput.setStructureKey(key);
    TStructDescription tsd = TStructRegistry.get(tag,key);
    if (tsd == null)
    {
      cc = lnk.linkStatus = TErrorList.invalid_structure_tag;
      lnk.linkStale = true;
      return cc;
    }
    boolean fieldKnown = false;
    String[] parts;
    String prp = lnk.devProperty;
    String fld = null;
    if (!fieldKnown)
    {
      parts = lnk.devProperty.split("\\.");
      if (parts.length > 1 && tsd.hasField(parts[parts.length-1]))
      {
        prp = lnk.devProperty.substring(0, lnk.devProperty.indexOf(parts[parts.length-1])-1);
        fld = parts[parts.length-1];
        fieldKnown = true;
      }
    }
    if (fieldKnown)
    {
      RelinkedItem rli = tf.addLinkToReLinkList(lnk, tsd, fld);
      lnk.devProperty = prp;
      if (rli != null)
      {
        rli.setDestination(lnk);
        lnk.dOutput = rli.getDataObject();
      }
    }
    else
    { // this should not happen if every thing goes well
      cc = lnk.linkStatus = TErrorList.invalid_field;
      // TODO: find a way of calling the callback with this status
      return cc;
    }
    lnk.dOutput.setField(fld);
    return 0;     
  }
  protected int mapInvalidDataRequest()
  {
    TLink lnk = this;
    RelinkedItem rli = tf.getRelinkedItem(lnk);
    if (rli == null) return 0; // nothing to do
    if (lnk.dOutput == null) return TErrorList.invalid_data;
    if (lnk.dOutput.dArrayLength == rli.getRelinkDataLength() &&
        lnk.dOutput.dFormat == rli.getRelinkDataFormat())
    { // nothing to do
      return 0;
    }
    tf.addLinkToReLinkList(lnk, rli.getRelinkDataLength(), rli.getRelinkDataFormat());
    lnk.dOutput = rli.getDataObject();
    return 0;     
  }
  public boolean isQueryLink()
  {
    if (expName.startsWith("ENS") || expName.startsWith("GENS"))
      return true;
    if (devProperty.indexOf("PROPS") >= 0 ||
        devProperty.indexOf("PROPERTIES") >= 0)
      return true;
    if (devProperty.indexOf("DEVICES") >= 0)
      return true;
    return false;
  }
  public boolean canSendPacked()
  {
    if (linkStatusLastNotification == TErrorList.buffer_too_small) return false; // self-correction ?
    if (isRenewal()) return false;
    if (tf.isUseConnectedSockets()) return false;
    if (TMode.isConnected(sub.mode)) return false;
    if (isQueryLink()) return false;
    if ((devAccess & TAccess.CA_SYNC) != 0) return false;
    if (sub == null || sub.contract == null || sub.contract.eqmName == null) return false;
    if ((sub.contract.eqmName.compareTo(TSrvEntry.SRVEQM_NAME)) == 0) return false;
    if ((sub.mode & TMode.CM_GLOBAL) == TMode.CM_GLOBAL) return false;
    if (dInput != null && (dInput.numblks > 1)) return false;
    return true;
  }
  TLink() {}  
  protected TLink(int tid, String devname, String devproperty, TDataType dout, TDataType din, short devaccess)
  { // called only by TLinkFactory.createTLink() via TSrvEntry().getSrvAddrFromENS
//    String fullname = new String(devname); mdavid: commented
    active = true;
    if (dout != null) dOutput = dout; else dOutput = new TDataType();
    dInput = din;
    //tf = TLinkFactory.getInstance();

//    splitDeviceName(fullname); mdavid: commented
    splitDeviceName(devname); // mdavid: improved
   
//    devProperty = new String(devproperty); // mdavid
    devProperty = devproperty;
    devAccess = devaccess;
    devTimeout = 500;
   
    relinkLink();
   
    linkId = tid = tf.registerLink(this); // an ENS link will return 0 here
    if (TLinkFactory.debugLevel > 0) DbgLog.log("TLink","assigned link id " + linkId + " to /" + cntName + "/" + expName);
    srvAddr = new TSrvEntry(expName,cntName);
    if (srvAddr.fecAddr == null && TLinkFactory.debugLevel > 0)
      DbgLog.log("TLink","Cannot resolve Server Address for /" + cntName + "/" + expName);
    // this constructor bypasses makeLink()
    this.tineProtocol = srvAddr.isLegacy ? TTransport.DEFAULT_PROTOCOL_LEVEL-1 : TTransport.DEFAULT_PROTOCOL_LEVEL;
    con = new TContract(this);
    sub = new TSubscription(con,this); // going out ...
    String un = tf.getUserName();
    if (un == null || un.length() == 0) un = System.getProperty("user.name");
    reqHdr = new TReqHdr(un,tineProtocol);
    tf.activateLink(this);
    if (tid == 0) removeOnClose = false;
  }

/**
* Attaches the data sets to the created link at the given polling rate
*
* Asynchronous. This method attaches the data sets to the created link using the calling
* parameters provided at instantiation.  The arriving data will automatically
* update the output data set and then fire the callback method provided.
* If the callback is not needed, a null can be passed for this parameter.
* @param mode is the tine control mode parameter, that is one of TMode.CM_POLL,
* TMode.CM_REFRESH, TMode.SINGLE, etc. in possible combination with
* TMode.CM_NETWORK or TMode.CM_CONNECT, etc.
* @param f is the 'callback function' to be called whenever either new data
* arrive or a change in link status occurs.
* The callback function should instantiate a class you create which
* implements the TCallback() class interface.
* @param pollrate is the desired polling rate (at the server) in milliseconds.
*
* @return A positive link handle if successful, otherwise -1.  On
* failure the linkStatus property of TLink can be examined.
*
* \b Example:
*
* \include eg_attach2.java
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#TLinkCallback
* @see de.desy.tine.client.TLink#execute
*/
  public int attach(int mode,TCallback f,int pollrate)
  {
  return attach(mode,f,pollrate,-1);
  }
  public int attach(short mode,TCallback f,int pollrate)
  {
    return attach((int)mode,f,pollrate);
  }
  //private Object attachMutex = new Object();
/**
* Attaches the data sets to the created link at the given polling rate
* and signals the callback routine using the give notification parameter.
*
* Asynchronous. This method attaches the data sets to the created link using the calling
* parameters provided at instantiation.  The arriving data will automatically
* update the output data set and then fire the callback method provided.
* If the callback is not needed, a null can be passed for this parameter.
* @param mode is the tine control mode parameter, that is one of TMode.CM_POLL,
* TMode.CM_REFRESH, TMode.SINGLE, etc. in possible combination with
* TMode.CM_NETWORK or TMode.CM_CONNECT, etc.
* @param f is the 'callback function' to be called whenever either new data
* arrive or a change in link status occurs.
* The callback function should instantiate a class you create which
* implements the TCallback() class interface.
* @param pollInterval is the desired polling interval (at the server) in milliseconds.
* @param notificationId is the desired identifier to be passed to the
* supplied callback routine when it is fired.
*
* @return A positive link handle if successful, otherwise -1.  On
* failure the linkStatus property of TLink can be examined.
*
* \b Example:
*
* \include eg_attach3.java
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#TCallback
* @see de.desy.tine.client.TLink#execute
*/
  public synchronized int attach(int mode,TCallback f,int pollInterval,int notificationId)
  {
    return _attach(mode, null, f, pollInterval, null, notificationId);
  }
  public int attach(short mode,TCallback f,int pollrate,int notificationId)
  {
    return attach((int)mode,f,pollrate,notificationId);
  }
private int ensureGlobalsMode(int thisMode)
{
   thisMode &= ~(TMode.CM_STREAM|TMode.CM_CONNECT);
   int bmode = TMode.getBaseMode(thisMode);
   if (bmode != TMode.CM_SINGLE && bmode != TMode.CM_REGISTER)
   {
     thisMode = (thisMode&0xfff0) | TMode.CM_GLOBAL;
   }
   return thisMode;
}
private static final int minPollingInterval = 10;
public static int makeValidPollingInterval(int pollingRate, int baseMode)
{
   if (pollingRate <= 0) return 1000;
   if (baseMode <= TMode.CM_SINGLE) return pollingRate;
   if (pollingRate < minPollingInterval) return minPollingInterval;
   if (pollingRate <= 100) return pollingRate;
   if (pollingRate < 170return 100;
   if (pollingRate < 250return 200;
   if (pollingRate < 350return 300;
   if (pollingRate < 450return 400;
   if (pollingRate < 750return 500;
   if (pollingRate < 1000) return 1000;
   return (pollingRate/1000) * 1000;
}
/**
* Attaches the data sets to the created link at the given polling rate
* and signals the callback routine using a reference to the TLink instance.
*
* Asynchronous. This method attaches the data sets to the created link using the calling
* parameters provided at instantiation.  The arriving data will automatically
* update the output data set and then fire the callback method provided.
* If the callback is not needed, a null can be passed for this parameter.
* This is the preferred AttachLink method to use.  The Callback instantiates
* a TLinkCallback() interface which provides a TLink object as parameter.
* @param mode is the tine control mode parameter, that is one of TMode.CM_POLL,
* TMode.CM_REFRESH, TMode.SINGLE, etc. in possible combination with
* TMode.CM_NETWORK or TMode.CM_CONNECT, etc.
* @param f is the 'callback function' to be called whenever either new data
* arrive or a change in link status occurs.
* The callback function should instantiate a class you create which
* implements the TLinkCallback() class interface.
* @param pollrate is the desired polling rate (at the server) in milliseconds.
*
* @return A positive link handle if successful, otherwise -1.  On
* failure the linkStatus property of TLink can be examined.
*
* \b Example:
*
* \include eg_attach4.java
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#TLinkCallback
* @see de.desy.tine.client.TLink#execute
*/
  public int attach(int mode,TLinkCallback f,int pollrate)
  {
     return attach(mode,f,pollrate,null);
  }
  /**
   * Attaches the data sets to the created link at the given polling rate
   * and signals the callback routine using a reference to the TLink instance.
   *
   * Asynchronous. This method attaches the data sets to the created link using the calling
   * parameters provided at instantiation.  The arriving data will automatically
   * update the output data set and then fire the callback method provided.
   * If the callback is not needed, a null can be passed for this parameter.
   * This is the preferred AttachLink method to use.  The Callback instantiates
   * a TLinkCallback() interface which provides a TLink object as parameter.
   * 
   * @param mode is the tine control mode parameter, that is one of TMode.CM_POLL,
   * TMode.CM_REFRESH, TMode.SINGLE, etc. in possible combination with
   * TMode.CM_NETWORK or TMode.CM_CONNECT, etc.
   * @param f is the 'callback function' to be called whenever either new data
   * arrive or a change in link status occurs.
   * The callback function should instantiate a class you create which
   * implements the TLinkCallback() class interface.
   * @param pollingInterval is the desired polling rate (at the server) in milliseconds.
   * @param reference is an optional reference to any object of the caller's choosing.
   * As the callback returns a reference to the original link, this extra reference can be
   * used to quickly find a table entry position for example.
   *
   * @return A positive link handle if successful, otherwise -1.  On
   * failure the linkStatus property of TLink can be examined.
   *
   * @see de.desy.tine.client.TLink#TLink
   * @see de.desy.tine.client.TLink#TLinkCallback
   * @see de.desy.tine.client.TLink#execute
   */
  public synchronized int attach(int mode,TLinkCallback f,int pollingInterval,Object reference)
  {
    return _attach(mode, f, null, pollingInterval, reference, 0);
  }
  private int _attach(int mode,TLinkCallback tlcbf,TCallback tcbf,int pollingInterval,Object reference,int notificationId)
  {
    active = true;
    if (isInsideCallback) keepActive = true;
    linkStatusSource = TLink.STATUS_LOCAL;
    linkStatus = 0; // be optimistic !
    usrObject = reference;
    devAccess &= ~(TAccess.CA_SYNC); // attach is inherently an asychronous method!
    if (useAppendedAccessInConstructor &&
        (devAccess & TAccess.CA_CONNECT) == TAccess.CA_CONNECT)
    { // this link flagged for CM_CONNECT transport
      if ((mode & (TMode.CM_STREAM|TMode.CM_CONNECT)) == 0)
        mode |= TMode.CM_CONNECT;
    }
    short bmode = TMode.getBaseMode(mode);
    String msg = "attach link ("+linkId +") "+getFullDeviceNameAndProperty()+" : " + TMode.toString((short)mode);
    if (bmode > TMode.CM_SINGLE)
    {
      MsgLog.log("attach",msg,0,null,1);
      if (bmode == TMode.CM_DATACHANGE && dtf == null)
      {
        setNotificationTolerance(0, 0);
        MsgLog.log("attach",getFullDeviceNameAndProperty()+": DATACHANGE mode -> will suppress null-changes",0,null,1);
      }
      if (bmode == TMode.CM_EVENT)
      {
        if (pollingInterval < 30000) pollingInterval = 30000;
      }
    }
    else
      if (TLinkFactory.debugLevel > 0) DbgLog.log("attach", msg);
    if (hookObj != null)
    {
      if (tlcbf != null)
        return hook.attach(hookObj,pollingInterval,(short)mode,tlcbf,this);
      else
        return hook.attach(hookObj,pollingInterval,(short)mode,tcbf,notificationId);
    }

    tlcb = tlcbf;
    tcb = tcbf;
    if (tcb != null)
    {
      if (notificationId >= 0)
        callbackId = notificationId;
      else if (callbackId <= 0)
        callbackId = sub.id;
    }

    if (this.isBlackListed())
    {
      this.linkStatus = TErrorList.link_blacklisted;
      tf.fireCallbackEvent("attach", this);
      return -1;
    }
    if (this.sub == null || linkId < 0)
    {
      if (linkStatus == TErrorList.resources_exhausted)
        MsgLog.log("attach","\nLink Table ("+tf.getLinkTable().length+" elements) is full",linkStatus,null,0);
      else
        MsgLog.log("attach",getFullDeviceName()+ " ["+getProperty()+"] has a null subscription <"+linkStatus+">",linkStatus,null,0);
      tf.fireCallbackEvent("attach", this);
      return linkStatus == 0 ? -TErrorList.code_failure : -linkStatus;
    }
    if (isGlobalsLink) mode = ensureGlobalsMode(mode);
   
    if (dInput != null)
    { // link has input data
      if (bmode > TMode.CM_SINGLE)
      { // don't let this change from above!
        dInput.lockedMessage = "data locked by persistent link to "+getFullDeviceNameAndProperty();
        dInput.isLocked = true;
      }
      else if (bmode == TMode.CM_SINGLE)
      { // allow new data from the input reference under some circumstances
        if (dInput.dArrayLength > 0 && !dInput.isLocked) dInput.putData();
      }
    }
    pollingInterval = makeValidPollingInterval(pollingInterval, bmode);
    devTimeout = pollingInterval;
    if (this.isBound()) //synchronized (this.boundTo)
    { // this link is bound to a parent !
      if (!this.boundTo.active)
      { // the original link is no longer active !
        this.boundTo.sub.mode = (short)mode; // must reset even if a dependent link (current could be SINGLE)
        this.boundTo.sub.pollingInterval = pollingInterval;
        this.boundTo.cancelledWithDependencies = true;
        this.boundTo.active = true// now it's active again
        this.boundTo.devAccess = this.devAccess;
        long ltime = System.currentTimeMillis();
        tf.sendLinkRequest(this.boundTo);
        this.boundTo.sub.linkLastTime = ltime;
        this.boundTo.tlcb = this.tlcb;
        this.boundTo.tcb = this.tcb;
      }
      else
      { // we're a dependent link to a live parent
        boolean fireevent = false;
        TDataType bdout = this.boundTo.getOutputDataObject();
        if (bdout.getFormat() != TFormat.CF_DEFAULT)
        { // parent data is a known quantity
          TDataType dout = this.getOutputDataObject();
          synchronized (dout)
          {
            if (dout.getFormat() == TFormat.CF_DEFAULT)
            { // made it this far without knowing the type ...
              dout.dFormat = bdout.dFormat;
              dout.dArrayLength = mcaDevice != null ? 1 : bdout.dArrayLength;
              dout.setDataObject(dout.dArrayLength,bdout.dFormat);
            }
          }
          if (bdout.hasBeenUpdated)
          { // safe to copy data and fire the callback below
            linkStatus = this.boundTo.linkStatus;
            lastLinkStatus = this.boundTo.lastLinkStatus;
            dout.dataCopy(bdout);
            dout.setDataTimeStamp(bdout.getDataTimeStamp());
            dout.setDataObject(dout.dArrayLength,bdout.dFormat);
            fireevent = true;
          }
          MsgLog.log("attach","attaching "+getFullDeviceNameAndProperty()+" to active link "+boundTo.getFullDeviceNameAndProperty()+" (has been updated: "+bdout.hasBeenUpdated,0,null,2);
        }
        if (!TLinkFactory.isRichClient() || this.boundTo.mcaIndex > 0 || this.mcaIndex > 0)
        { // don't let them change horses in mid-stream ...
          pollingInterval = this.boundTo.sub.pollingInterval;
          mode = this.boundTo.sub.mode;
        }
        if (fireevent)
        { // have to fire this once here, otherwise there might be a long wait
          MsgLog.log("attach",getFullDeviceNameAndProperty()+": fire early callback",0,null,2);
          tf.fireCallbackEvent("attach", this);
          Thread.yield();
        }
        bmode = TMode.getBaseMode(this.boundTo.sub.mode);
        long ltime = System.currentTimeMillis();
        if (TMode.getBaseMode(mode) == TMode.CM_TIMER && bmode != TMode.CM_TIMER)
        { // bound link has a higher polling hierarchy !
          int pmode = (this.boundTo.sub.mode & ~(bmode)) | TMode.CM_TIMER;
          MsgLog.log("attach", this.boundTo.getFullDeviceNameAndProperty()+" adjust parent polling hierarchy from "+TMode.toString(this.boundTo.sub.mode)+" to "+TMode.toString(pmode),0,null,1);
          this.boundTo.sub.mode = (short)pmode;
          if (pollingInterval < this.boundTo.sub.pollingInterval)
          {
            MsgLog.log("attach", this.boundTo.getFullDeviceNameAndProperty()+" adjust parent polling interval to "+pollingInterval+" msec",0,null,1);
            this.boundTo.sub.pollingInterval = pollingInterval;
          }
          tf.sendLinkRequest(this.boundTo);
          if (!this.boundTo.hasNotifiedOnce) dOutput.timestamp = this.boundTo.dOutput.timestamp;
          this.boundTo.isLinkReassignment = true;
          this.boundTo.sub.linkLastTime = ltime;
        }
        else
        { // must reset even if a dependent link (current could be SINGLE)
          this.sub.mode = (short)mode;
          this.sub.pollingInterval = pollingInterval;
          if (TMode.getBaseMode(mode) > TMode.CM_SINGLE &&
              pollingInterval < this.boundTo.sub.pollingInterval)
          { // up the polling rate ...
            MsgLog.log("attach", this.boundTo.getFullDeviceNameAndProperty()+" adjust parent polling interval to "+pollingInterval+" msec",0,null,1);
            this.boundTo.sub.pollingInterval = pollingInterval;
            tf.sendLinkRequest(this.boundTo);
            if (!this.boundTo.hasNotifiedOnce) dOutput.timestamp = this.boundTo.dOutput.timestamp;
            this.boundTo.isLinkReassignment = true;
            this.boundTo.sub.linkLastTime = ltime; // don't let the watchdog thread kick in ...
          }
        }
      }
      return this.sub.id;
    }
    this.sub.mode = (short)mode;
    this.sub.pollingInterval = pollingInterval;

    if (this.devName.indexOf("*") >= 0 &&
        !TQuery.isStockProperty(this.devProperty))
    {
      if (!isWildcardLink)
      { // not yet so flagged ...
        TWildcardLink wc = tf.getWildcardLink();
        wc.tlcb = tlcbf;
        wc.tcb = tcbf;
        wc.parent = this;
        wc.format = this.dOutput.dFormat;
        /* reset these to the wildcard callback  */
        this.tlcb = wc.scb;
        this.twcl = wc;
        this.isWildcardLink = true;
        this.sub.mode &= ~(TMode.CM_GROUPED|TMode.CM_SYNCGROUP); // conflicting groups! => turn it off
      }
    }
   
    long ltime = System.currentTimeMillis();
    // TODO:  make a method setStartTime() to handle this ....
    this.sub.starttime = (int)(ltime/1000);
    this.sub.linkStartTime = ltime;
    this.sub.linkLastTime = ltime;

    if (canSendPacked()) delayEstablishLink(true);
    if (tf.sendLinkRequest(this) != 0) return -1;
    if (!TMode.isConnected(this.sub.mode) &&
         TLinkFactory.alwaysRetry) retryOnTimeoutError = true;
    if ((devAccess & TAccess.CA_NORETRY) == TAccess.CA_NORETRY)
    { // override the global settings with this
      retryOnTimeoutError = false;
    }
    if ((devAccess & TAccess.CA_RETRY) == TAccess.CA_RETRY)
    { // override the global settings with this
      retryOnTimeoutError = true;
    }
    if (TMode.isGrouped(this.sub.mode))
    {
      if (grp == null)
      {
        if (tlcbf != null)
          grp = tf.getGroup(tlcbf);
        else
          grp = tf.getGroup(tcbf);
      }
      if (grp != null)
      { // dis-allow in case getGroup returns a null
        grp.addMember(this);
        if ((this.sub.mode & TMode.CM_SYNCGROUP) == TMode.CM_SYNCGROUP)
          grp.setSynchronizationLevel(TLinkGroup.GRP_SYNC_STARTSYNC);
      }
    }
    if ((mode & TMode.CM_USE_ON_ERROR) == TMode.CM_USE_ON_ERROR)
    {
      dError = (TDataType)dOutput.clone();
      if (dError != null) useErrObject = true;
    }
    TLinkFactory.adjustLinkTable(this,TLinkFactory.adjustLinkTableAdd);
    if (TMode.isWaiting(mode))
    {
      if (TMode.isGlobal(mode) && !hasObtainedStatus && srvAddr != null && srvAddr.fecAddr != null)
      { // fill in from cached globals block
        if (TLinkFactory.debugLevel > 1)
          DbgLog.log("TLink._attach", "expedite WAIT on globals link");
        TLinkFactory.GlobalInfo gi = tf.getGlobalsInfo(srvAddr.fecAddr.fecHost);
        if (gi != null && gi.hasUpdated)
        { // try to speed things along ...
          //synchronized (this)
          {
            gi.postDataElement(this, devProperty);
            tf.fireCallbackEvent("InterpretIncomingGlobalsData", this);
          }
        }
      }
      waitForLinkCompletion();
    }
    if (TLinkFactory.debugLevel > 0) DbgLog.log("attach","link " + linkId + " is " + (removeOnClose ? "non-persistent" : "persistent"));
    return sub.id;
  }
  public int attach(short mode,TLinkCallback f,int pollrate)
  {
    return attach((int)mode,f,pollrate);
  }
  /**
   * Attaches the data sets to the global link
   * and signals the callback routine using the assigned link id.
   *
   * Asynchronous. This method attaches the data sets to the created link using the calling
   * parameters provided at instantiation.  The arriving data will automatically
   * update the output data set and then fire the callback method provided.
   * If the callback is not needed, a null can be passed for this parameter.
   * Using the receive() method assumes that the data requested are global available.
   * 
   * @param f is the 'callback function' to be called whenever either new data
   * arrive or a change in link status occurs.
   * The callback function should instantiate a class you create which
   * implements the TCallback() class interface.
   *
   * @return A positive link handle if successful, otherwise -1.  On
   * failure the linkStatus property of TLink can be examined.
   *
   * \b Example:
   *
   * \include eg_receive1.java
   *
   * @see de.desy.tine.client.TLink#TLink
   * @see de.desy.tine.client.TLink#TLinkCallback
   * @see de.desy.tine.client.TLink#receive(TCallback f,int notificationId)
   * @see de.desy.tine.client.TLink#receive(TLinkCallback f)
   */
  public synchronized int receive(TCallback f)
  {
    return attach(TMode.CM_GLOBAL,f,1000);
  }
  /**
   * Attaches the data sets to the global link
   * and signals the callback routine using a reference to the
   * given notifcation ID in the callback
   *
   * Asynchronous. This method attaches the data sets to the created link using the calling
   * parameters provided at instantiation.  The arriving data will automatically
   * update the output data set and then fire the callback method provided.
   * If the callback is not needed, a null can be passed for this parameter.
   * Using the receive() method assumes that the data requested are global available.
   * 
   * @param f is the 'callback function' to be called whenever either new data
   * arrive or a change in link status occurs.
   * The callback function should instantiate a class you create which
   * implements the TCallback() class interface.
   * @param notificationId is the desired identifier to be passed to the
   * supplied callback routine when it is fired.
   *
   * @return A positive link handle if successful, otherwise -1.  On
   * failure the linkStatus property of TLink can be examined.
   *
   * \b Example:
   *
   * \include eg_receive2.java
   *
   * @see de.desy.tine.client.TLink#TLink
   * @see de.desy.tine.client.TLink#TLinkCallback
   * @see de.desy.tine.client.TLink#receive(TCallback f)
   */
  public synchronized int receive(TCallback f,int notificationId)
  {
    return attach(TMode.CM_GLOBAL,f,1000,notificationId);
  }
  /**
   * Attaches the data sets to the global link
   * and signals the callback routine using a reference to the
   * referenced TLink object.
   *
   * Asynchronous. This method attaches the data sets to the created link using the calling
   * parameters provided at instantiation.  The arriving data will automatically
   * update the output data set and then fire the callback method provided.
   * If the callback is not needed, a null can be passed for this parameter.
   * Using the receive() method assumes that the data requested are global available.
   * This is the preferred receive method to use.  The Callback instantiates
   * a TLinkCallback() interface which provides a TLink object as parameter.
   * 
   * @param f is the 'callback function' to be called whenever either new data
   * arrive or a change in link status occurs.
   * The callback function should instantiate a class you create which
   * implements the TLinkCallback() class interface.
   *
   * @return A positive link handle if successful, otherwise -1.  On
   * failure the linkStatus property of TLink can be examined.
   *
   * \b Example:
   *
   * \include eg_receive3.java
   *
   * @see de.desy.tine.client.TLink#TLink
   * @see de.desy.tine.client.TLink#TLinkCallback
   * @see de.desy.tine.client.TLink#receive(TCallback f)
   */
  public synchronized int receive(TLinkCallback f)
  {
    return attach(TMode.CM_GLOBAL,f,1000);
  }
  /**
   * Stops thread execution until this link has completed.
   * Useful for grouped asynchronous links.
   */
  public void waitForLinkCompletion()
  {
    int attempts = 0;
    TLink lnk = this;
    int dt = this.sub.pollingInterval;
    if (dt < 100) dt = 100;
    boolean logit = false;
    int maxattemps = lnk.isGlobalsLink ? 40 : 20;
    while (!this.hasObtainedStatus)
    {
      try
      {
        Thread.sleep(100);
        if (++attempts > (dt/100))
        {
          if (this.isBound()) lnk = this.boundTo;
          if (!lnk.isGlobalsLink)
          {
            lnk.linkStatus = TErrorList.link_timeout;
            lnk.linkTimeouts++;
            lnk.sub.mode |= TMode.CM_RETRY;
            tf.sendLinkRequest(lnk);
          }
          if (attempts > maxattemps)
          { // give up
            logit = true;
            break;
          }
        }
      }
      catch (InterruptedException e)
      {
        DbgLog.log("waitForLinkCompletion"," waitForLinkCompletion : " + e);
      }
    }
    if (logit)
    {
      MsgLog.log("waitForLinkCompletion", "timed out waiting for completion",TErrorList.link_timeout,null,1);     
    }
    lnk.sub.mode &= ~(TMode.CM_WAIT);
  }
  /**
   * Gets the array delimiter for use in the toString() method
   * of associated TDataType objects.
   *
   * @return The delimiter in use in separating data
   * variables in a string dump (default ",")
   */
  public String getArrayDelimiter()
  {
    return arrayDelimiter;
  }
  /**
   * Sets the array delimiter for use in the toString() method
   * of associated TDataType objects.
   *
   * @param delimiter is the string delimiter to use in separating data
   * variables in a string dump (default ",")
   */
  public void setArrayDelimiter(String delimiter)
  {
    if (arrayDelimiter != null && arrayDelimiter.compareTo(delimiter) == 0) return;
    arrayDelimiter = new String(delimiter);
    this.dOutput.setArrayDelimiter(arrayDelimiter);
  }
  public int getTineProtocol()
  {
    return tineProtocol;
  }
  public void setTineProtocol(int protocolLevel)
  {
    tineProtocol = protocolLevel;
    if (reqHdr != null) reqHdr.setTineProtocol(protocolLevel);
    if (dInput != null) dInput.resetCounters(protocolLevel);
    if (dOutput != null && tineProtocol < TTransport.DEFAULT_PROTOCOL_LEVEL) dOutput.resetCounters(protocolLevel);
    if (sub != null && tineProtocol < TTransport.DEFAULT_PROTOCOL_LEVEL) sub.isLegacy = true;
  }
}
TOP

Related Classes of de.desy.tine.client.TLink

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.