Package de.desy.tine.client

Source Code of de.desy.tine.client.TLinkFactory$GlobalInfo

package de.desy.tine.client;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.NoRouteToHostException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;

import de.desy.tine.addrUtils.*;
import de.desy.tine.bitfieldUtils.*;
import de.desy.tine.console.*;
import de.desy.tine.dataUtils.*;
import de.desy.tine.definitions.*;
import de.desy.tine.endianUtils.*;
import de.desy.tine.headers.*;
import de.desy.tine.io.*;
import de.desy.tine.queryUtils.TQuery;
import de.desy.tine.server.equipment.TEquipmentModuleFactory;
import de.desy.tine.server.logger.*;
import de.desy.tine.server.properties.TMetaProperties;
import de.desy.tine.startup.*;
import de.desy.tine.structUtils.*;
import de.desy.tine.types.*;

/**
* TLinkFactory.java
*
* User interface calls to access available methods from the TLink factory
* The available public methods from the TLink factory singleton generally
* involve configuration settings affecting performance, memory (table capacities)
* and behavior.
*/
public class TLinkFactory
{
  protected void finalize() throws Throwable
  {
    MsgLog.log("TLinkFactory","link factory closing down",0,null,0);
    super.finalize();
 
  public Object ensMutex = new Object();
  public static final int TO_THRESHOLD = 10;
  public static final int TO_RETRY_THRESHOLD = 2;
  public static final long ENS_BACKOFF_THRESHOLD = 5000;
  private static TInitializer initializerInstance = TInitializerFactory.getInstance().getInitializer();
  private static boolean gSystemRunningStandAlone = false;
  public boolean isRunningStandAlone() { return gSystemRunningStandAlone; }
  TMetaProperties metaProps = TMetaProperties.getInstance();
  private static boolean gAllowNetworkAddressResolution = false;
  public void setAllowNetworkAddressResolution(boolean value) { gAllowNetworkAddressResolution = value; }
  public boolean allowNeworkAddressResolution() { return gAllowNetworkAddressResolution; }
  private static boolean isRichClient = true;
  public static boolean isRichClient() { return isRichClient; }
  public static void setRichClient(boolean value) { isRichClient = value; }
  private static LinkedList<TFilterLink> filterList = new LinkedList<TFilterLink>();
  public static void addFilterLink(TFilterLink filter)
  {
    synchronized(filterList)
    {
      if (!filterList.contains(filter)) filterList.add(filter);
    }
  }
  public static void rmvFilterLink(TFilterLink filter)
  {
    if (filter == null) return;
    synchronized(filterList)
    {
      if (!filterList.contains(filter))
      {
        filter.remove();
        filterList.remove(filter);
      }
    }   
  }
  public static void flushFilterLinks()
  {
    synchronized(filterList)
    {
      TFilterLink tfl = null;
      Iterator<TFilterLink> it = filterList.iterator();
      while (it.hasNext())
      {
        tfl = it.next();
        tfl.remove();
        it.remove();
      }
    }       
  }
  public static void dumpFilterList()
  {
    dbgPrint("\nCurrent Filter Table (local history/local alarm system):");
    synchronized (filterList)
    {
      TFilterLink tfl;
      for (int i=0; i<filterList.size(); i++)
      {
        tfl = filterList.get(i);
        dbgPrint(tfl.toString());
      }
    }
    dbgPrint("");
  }
  private static Hashtable<String,TMcaLink> mcaLst = new Hashtable<String,TMcaLink>();
  public static TMcaLink getMcaLink(String context,String server,String device,String property)
  {
    synchronized (mcaLst)
    {
      String key = "/"+context+"/"+server+"/"+device+"["+property+"]";
      return mcaLst.get(key);
    }
  }
  public static TMcaLink getMcaLinkForReuse(String context,String server,String device,String property)
  {
    synchronized (mcaLst)
    {
      String key = "/"+context+"/"+server+"/"+device+"["+property+"]";
      TMcaLink mca = mcaLst.get(key);
      if (mca != null)
      {
        if (mca.parent.removedFromMcaList > 0)
        {
          MsgLog.log("getMcaLinkForReuse","re-invigorate MCA parent "+mca.parent.getFullDeviceNameAndProperty(),0,null,1);
        }
        mca.parent.removedFromMcaList = 0;
        mca.parent.cancelledWithDependencies = false;
        mca.parent.active = true;
      }
      return mca;
    }
  }
  public static TMcaLink getMcaLink(TLink lnk)
  {
    synchronized (mcaLst)
    {
      String key = "/"+lnk.cntName+"/"+lnk.expName+"/"+lnk.devName+"["+lnk.devProperty+"]";
      return mcaLst.get(key);
    }
  }
  public static void addMcaLink(TMcaLink lnk)
  {
    String key = "/"+lnk.ctx+"/"+lnk.srv+"/"+lnk.dev+"["+lnk.prp+"]";
    synchronized (mcaLst)
    {
      if (!mcaLst.containsKey(key))
      {
        mcaLst.put(key, lnk);
        MsgLog.log("TLinkFactory", "add "+key+" to MCA list",TErrorList.property_is_mca,null,1);
      }
    }
  }
  public static void rmvMcaLink(TMcaLink lnk)
  {
    synchronized (mcaLst)
    {
      String key = "/"+lnk.ctx+"/"+lnk.srv+"/"+lnk.dev+"["+lnk.prp+"]";
      MsgLog.log("rmvMcaLink", "remove MCA link from factory",0,null,1);
      mcaLst.remove(key);
    }
  }
  public class GlobalInfo
  {
    public class GlobalRawData
    {
      byte[] dat = null;
      int len;
      short fmt;
      int dts;
      int dtsUSEC;
      int sds;
      short sts;
      GlobalRawData(byte[] data,int length,short format,int timestamp,int timestampUSEC, int systemstamp,short status)
      {
        len = length; fmt = format;
        dts = timestamp; dtsUSEC = timestampUSEC;
        sds = systemstamp; sts = status;
        int sizeInBytes = len * TFormat.formatSizeOf(fmt);
        if (dat == null) dat = new byte[sizeInBytes];
        if (data != null)
        {
          if (data.length < sizeInBytes)
          {
            MsgLog.log("GlobalInfo.GlobalRawData", "supplied data block too small",TErrorList.buffer_too_small,null,0);
            sizeInBytes = data.length;
          }
          System.arraycopy(data, 0, dat, 0, sizeInBytes);
        }
      }
      void put(byte[] data,int timestamp,int timestampUSEC, int systemstamp,short status)
      {
        if (data != null)
        {
          dts = timestamp; dtsUSEC = timestampUSEC;
          sds = systemstamp; sts = status;
          int sizeInBytes = len * TFormat.formatSizeOf(fmt);
          if (data.length < sizeInBytes)
          {
            MsgLog.log("GlobalInfo.GlobalRawData.put", "supplied data block too small",TErrorList.buffer_too_small,null,0);
            sizeInBytes = data.length;
          }
          System.arraycopy(data, 0, dat, 0, sizeInBytes);
        }
      }
    }
    public void updateDataElement(String key,byte[] data,int length,short format,int timestamp,int timestampUSEC,int systemstamp,short status)
    {
      int idx = keys.indexOf(key);
      if (idx < 0) return;
      GlobalRawData grd = blks.get(idx);
      if (grd == null) return;
      if (grd.sts == TErrorList.not_initialized)
        blks.set(idx, new GlobalRawData(data,length,format,timestamp,timestampUSEC,systemstamp,status));
      else
        grd.put(data,timestamp,timestampUSEC,systemstamp,status);
    }
    public void postDataElement(TLink lnk,String key)
    {
      int idx = keys.indexOf(key);
      GlobalRawData grd = blks.get(idx);
      if (grd != null)
      {
        TGlobalsHdr.postOutputData(lnk, grd.dat, grd.fmt, grd.len, grd.dts, grd.dtsUSEC, grd.sds, 0, grd.sts, TLink.STATUS_LOCAL);
      }
    }
    public boolean hasUpdated = false;
    private InetAddress glbAddr;
    private String ctx;
    public String getContext() { return ctx; }
    private ArrayList<String> keys = new ArrayList<String>();
    private ArrayList<GlobalRawData> blks = new ArrayList<GlobalRawData>();
    private void acquireKeywords(String context)
    {
      if (context == null) return;
      ctx = context;
      NAME64[] lst = new NAME64[100];
      TDataType tdt = new TDataType(lst);
      try
      {
        TLink lnk = new TLink("/"+ctx+"/GLOBALS","GLOBALS",tdt,null,TAccess.CA_READ);
        if (lnk.executeAndClose(500) == 0)
        {
          int siz = tdt.getCompletionLength();
          for (int i=0; i<siz; i++)
          {
            keys.add(lst[i].getName());
            blks.add(new GlobalRawData(null,0,TFormat.CF_NULL,0,0,0,TErrorList.not_initialized));
          }
        }
      }
      catch (Exception ignore)
      {              
      }
    }
    GlobalInfo(String context)
    {
      acquireKeywords(context);
      TSrvEntry tse = new TSrvEntry("GLOBALS",context);
      TFecEntry fe = tse.getFecAddr();
      glbAddr = fe != null ? fe.fecHost : null;
    }
    public InetAddress getAddress() { return glbAddr; }
    boolean isGlobalKeyword(String key)
    {
      return keys.contains(key);     
    }
    String[] getGlobalKeywords()
    {
      return keys.toArray(new String[0]);
    }
  }
  private HashMap<String,GlobalInfo> glbInfo = new HashMap<String,GlobalInfo>();
  public boolean isGlobalKeyword(String context,String keyword)
  {
    if (keyword == null) return false;
    if (context == null) context = "SITE";
    if (keyword.compareToIgnoreCase("GLOBALS") == 0) return false;
    if (keyword.compareToIgnoreCase("SYSTIME") == 0) return true;
    if (TQuery.isStockProperty(keyword)) return false;
    try
    {
      if (!glbInfo.containsKey(context))
      {
        GlobalInfo gi = new GlobalInfo(context);
        glbInfo.put(context, gi);
      }
      return glbInfo.get(context).isGlobalKeyword(keyword);
    }
    catch (Exception e)
    { // likely an invalid context
      MsgLog.log("isGlobalKeyword", "context "+context+" keyword "+keyword+" : "+e.getMessage(),TErrorList.invalid_parameter,e,1);
    }
    return false;
  }
  public GlobalInfo getGlobalsInfo(InetAddress addr)
  {
    return getGlobalsInfo(null,addr);
  }
  public GlobalInfo getGlobalsInfo(String context,InetAddress addr)
  {
    GlobalInfo gi = null;
    Iterator<GlobalInfo> itr = glbInfo.values().iterator();
    while (itr.hasNext())
    {
      gi = itr.next();
      if (context != null && gi.getContext().compareToIgnoreCase(context) != 0) continue;
      if (gi.getAddress().equals(addr)) return gi;
    }
    return null;
  }
  public class GroupCacheItem
  {
    private String ctx;
    private String grp;
    private String srv;
    private String dev;
    public GroupCacheItem(String context,String group,String server,String device)
    {
      ctx = context; grp = group; srv = server; dev = device;
    }
    public void pushItemToCache()
    {
      TSrvEntry.addServerToGroupCacheFile(ctx,grp,srv);
      TSrvEntry.addDeviceToMemberCacheFile(ctx,srv,dev);     
    }
  }
  private long lastGroupCacheAddTime = 0;
  private static LinkedList<GroupCacheItem> grpCacheLst = new LinkedList<GroupCacheItem>();
  public void addItemToGroupCache(String context,String group,String server,String device)
  {
    lastGroupCacheAddTime = System.currentTimeMillis();
    synchronized (grpCacheLst)
    {
      grpCacheLst.add(new GroupCacheItem(context, group, server, device));
    }
  }
  public void flushGroupCacheItems()
  {
    synchronized (grpCacheLst)
    {
      int siz = grpCacheLst.size();
      if (siz == 0) return;
      long delay = 1000 + 100*siz;
      if (System.currentTimeMillis() < lastGroupCacheAddTime + delay) return;
      Iterator<GroupCacheItem> itr = grpCacheLst.iterator();
      int i;
      for (i=0; i<50 && itr.hasNext(); i++)
      {
        itr.next().pushItemToCache();
        itr.remove();
      }
      MsgLog.log("TLinkFactory.flushGroupCacheItems", "flushed "+i+" group server items to local file cache",0,null,0);
    }   
  }
  public boolean ignoreListenerInitialValue = false;
  private static Hashtable<TLink, ListenerItem> LsnLnkLst = new Hashtable<TLink, ListenerItem>();
  public ListenerItem getListener(TLink lnk)
  {
    return getListener(lnk,TMode.CM_TIMER,1000);
  }
  public ListenerItem getListener(TLink lnk,int mode,int interval)
  {
    if (lnk != null)
    { // no listeners for WRITE access, queries, meta properties, etc.
      if (TAccess.isWrite((byte)lnk.devAccess)) return null;
      String prp = lnk.getProperty();
      if (TQuery.isStockProperty(prp)) return null;
      if (TMetaProperties.isMetaProperty(prp)) return null;
    }
    if (LsnLnkLst.containsKey(lnk))
    {
      return LsnLnkLst.get(lnk);
    }
    ListenerItem item = new ListenerItem(lnk,mode,interval);
    if (item != null) LsnLnkLst.put(lnk, item);
    return item;
  }
  public void removeListener(TLink lnk)
  {
    if (!LsnLnkLst.containsKey(lnk)) return;
    ListenerItem item = LsnLnkLst.get(lnk);
    if (item != null) item.clear();
    LsnLnkLst.remove(lnk);
  }
  public class ListenerItem implements TLinkCallback
  {
    private TLink lnk = null;
    public TLink getListenerLink() { return lnk; }
    private int rate = 1000;
    public int getPollingInterval() { return rate; }
    public void setPollingInterval(int interval)
    {
      if (interval < 100) interval = 100;
      rate = interval;
    }
    private int mode = TMode.CM_TIMER;
    public int getMode() { return mode; }
    public void setMode(int mode) { this.mode = mode; }
    private int status = TErrorList.not_initialized;
    public int getStatus() { return status; }
    private long lastread = System.currentTimeMillis();
    private int duration = 600;
    public void setListenerDuration(int durationInSeconds) { duration = durationInSeconds; }
    public int getListenerDuraction() { return duration; }
    private boolean isAlive = false;
    public boolean isActive() { return isAlive; }
    public void keepAlive() { lastread = System.currentTimeMillis(); }
    public void stop()
    {
      if (lnk != null)
      { //run thru the cancel procedure
        // if !blockCloseListener: leave it in the table ...
        instance.cancel(lnk, lnk.blockCloseListener);
        status = TErrorList.not_initialized;
        lnk.linkStatus = TErrorList.not_initialized;
        isAlive = false;
      }
    }
    public void restart()
    {
      stop();
      start();
    }
    public void clear()
    {
      if (lnk != null)
      {
        stop();
        lnk.terminate = true;
        lnk = null;
      }
    }
    public void start()
    {
      if (lnk == null) return;
      if (isAlive) return;
      int ststmode = mode;
      if (!ignoreListenerInitialValue) ststmode |= TMode.CM_WAIT;
      int id = lnk.attach(ststmode, this, rate);
      isAlive = id < 0 ? false : true;
      status = isAlive ? 0 : TErrorList.link_error;
    }
    public ListenerItem(TLink lnk, int mode, int interval)
    {
      if (lnk == null) return;
      if (lnk.getOutputDataObject() == null)
      {
        status = TErrorList.not_allowed;
        return;
      }
      if (interval < 100) interval = 100;
      this.lnk = lnk;
      this.rate = interval;
    }
//    @Override
    public void callback(TLink link)
    {
      if (duration < 0) return;
      long t = System.currentTimeMillis();
      if ((int)((t-lastread)/1000) > duration) stop();
    }
  }
  protected static String traceKey = null;
  public static String getTraceLinkKey() { return traceKey; }
  public static void setTraceLinkKey(String devname, String property)
  {
    if (devname == null || property == null) traceKey = null;
    traceKey = devname+"["+property+"]";
  }
  public static void setTraceLinkKey(String fullname)
  {
    if (fullname == null || fullname.length() == 0)
    {
      traceKey = null;
      return;
    }
    try
    {
      if (fullname.contains("["))
      {
        traceKey = fullname;
      }
      else
      {
        int idx = fullname.lastIndexOf("/");
        String prp = fullname.substring(idx+1);
        traceKey = fullname.substring(0, idx)+"["+prp+"]";
      }
    }
    catch (Exception any)
    {
      traceKey = null;
      return;     
    }
  }
  public static String getLinkKey(TLink lnk)
  {
    if (lnk == null) return null;
    if (lnk.isRedirected && lnk.rdrKey != null) return lnk.rdrKey;
    return "/"+lnk.cntName+"/"+lnk.expName+"/"+lnk.devName+"["+lnk.devProperty+"]";
  }
  public static boolean isRedirected(String context,String server,String device,String property)
  {
    if (context == null || server == null || device == null || property == null) return false;
    String key = "/"+context+"/"+server+"/"+device+"["+property+"]";
    return RdrLst.containsKey(key);
  }
  public class RedirectedItem
  {
    private String srcKey; // full name + [property]
    private String dstCtx;
    private String dstSrv;
    private String dstDev;
    private String dstPrp;
    public boolean destinationEquals(RedirectedItem target)
    {
      if (target == null) return false;
      String tgtCtx = target.getDstContext();
      String tgtSrv = target.getDstServer();
      String tgtDev = target.getDstDevice();
      String tgtPrp = target.getDstProperty();
      if (dstCtx != null || tgtCtx != null)
      {
        if (dstCtx == null || tgtCtx == null) return false;
        if (dstCtx.compareToIgnoreCase(tgtCtx) != 0) return false;
      }
      if (dstSrv != null || tgtSrv != null)
      {
        if (dstSrv == null || tgtSrv == null) return false;
        if (dstSrv.compareToIgnoreCase(tgtSrv) != 0) return false;
      }
      if (dstDev != null || tgtDev != null)
      {
        if (dstDev == null || tgtDev == null) return false;
        if (dstDev.compareToIgnoreCase(tgtDev) != 0) return false;
      }
      if (dstPrp != null || tgtPrp != null)
      {
        if (dstPrp == null || tgtPrp == null) return false;
        if (dstPrp.compareToIgnoreCase(tgtPrp) != 0) return false;
      }
      return true;
    }
    public RedirectedItem(TLink lnk,String context,String server,String device,String property)
    {
      srcKey = getLinkKey(lnk);
      dstCtx = context == null ? lnk.cntName : context;
      dstSrv = server == null ? lnk.expName : server;
      dstDev = device == null ? lnk.devName : device;
      dstPrp = property == null ? lnk.devProperty : property;
    }
    public RedirectedItem(String key,String context,String server,String device,String property)
    {
      srcKey = key;
      dstCtx = context;
      dstSrv = server;
      dstDev = device;
      dstPrp = property;
    }
    public String getDstContext() { return dstCtx; }
    public void setDstContext(String context) { this.dstCtx = context; }
    public String getDstDevice() { return dstDev; }
    public void setDstDevice(String device) { this.dstDev = device; }
    public String getDstProperty() { return dstPrp; }
    public void setDstProperty(String property) { this.dstPrp = property; }
    public String getDstServer() { return dstSrv; }
    public void setDstServer(String server) { this.dstSrv = server; }
    public String getSrcKey() { return srcKey; }
  }
  private static Hashtable<String, RedirectedItem> RdrLst = new Hashtable<String, RedirectedItem>();
  public void redirectLink(TLink lnk)
  {
    String key = getLinkKey(lnk);
    if (key == null) return;
    boolean trace = traceKey != null && lnk.isTraceLink();
    if (RdrLst.containsKey(key))
    {
      String meta = TMetaProperties.getMetaExtension(lnk.devProperty);
      RedirectedItem rdr = RdrLst.get(key);
      lnk.cntName = rdr.getDstContext();
      lnk.expName = rdr.getDstServer();
      lnk.devName = rdr.getDstDevice();
      lnk.devProperty = rdr.getDstProperty();
      if (meta != null && !TMetaProperties.isMetaProperty(lnk.devProperty)
          && lnk.expName.compareToIgnoreCase("HISTORY") != 0)
        lnk.devProperty += meta;
      lnk.isRedirected = true;
      lnk.rdrKey = key;
      MsgLog.log("redirectLink", "redirect "+key+" to "+lnk.getFullDeviceNameAndProperty(),0,null,1);
      if (trace) lnk.traceLink("redirectLink"," is redirected to "+lnk.getFullDeviceNameAndProperty());
      return;
    }
    // check for wildcard redirection
    int idx;
    if ((idx=key.indexOf('[')) > 0)
    { // this should always be true !
      String wckey = key.substring(0, idx)+"[*]";
      if (RdrLst.containsKey(wckey))
      { // redirect any property for the device (e.g. GENS ?)
        RedirectedItem rdr = RdrLst.get(wckey);
        lnk.cntName = rdr.getDstContext();
        lnk.expName = rdr.getDstServer();
        lnk.devName = rdr.getDstDevice();
        lnk.isRedirected = true;
        lnk.rdrKey = wckey;
        MsgLog.log("redirectLink", "redirect "+key+" to "+lnk.getFullDeviceNameAndProperty(),0,null,1);
        if (trace) lnk.traceLink("redirectLink"," is redirected to "+lnk.getFullDeviceNameAndProperty());
      }
    }
  }
  public void flushRedirectionList()
  {
    RdrLst.clear();
  }
  public static RedirectedItem getRedirectionInformation(TLink lnk)
  {
    String key = getLinkKey(lnk);
    if (key == null) return null;
    return RdrLst.get(key);
  }
  public void addLinkToRedirectionList(TLink lnk,String context,String server,String device,String property)
  {
    String key = getLinkKey(lnk);
    RdrLst.put(key, new RedirectedItem(lnk,context,server,device,property));
    lnk.rdrKey = key;
    lnk.isRedirected = true;
  }
  public void appendRedirectionList(String srcKey,String dstCtx,String dstSrv,String dstDev,String dstPrp)
  {
    RdrLst.put(srcKey, new RedirectedItem(srcKey,dstCtx,dstSrv,dstDev,dstPrp));
  }
  public static void dumpRedirectionTable()
  {
    dbgPrint("\nCurrent Redirection Table");
    Iterator<RedirectedItem> itr = RdrLst.values().iterator();
    RedirectedItem r;
    String rdrItemString;
    while (itr.hasNext())
    {
      r = itr.next();
      rdrItemString = r.getSrcKey()+" -> /"+r.getDstContext()+"/"+r.getDstServer()+"/"+r.getDstDevice()+"["+r.getDstProperty()+"]";
      dbgPrint(rdrItemString);
    }
    dbgPrint("");
 
  public static void dumpRelinkTable()
  {
    dbgPrint("\nCurrent Relink Table");
    Iterator<RelinkedItem> itr = ReLnkLst.values().iterator();
    RelinkedItem r;
    String rdrItemString;
    TDataType dt;
    while (itr.hasNext())
    {
      r = itr.next();
      dt = r.getTargetDataObject();
      if (r.sts == TErrorList.invalid_datarequest)
      {
        rdrItemString = r.getSrcKey()+" "+dt.dArrayLength+" "+TFormat.toString(dt.dFormat)+
            " -> "+r.getRelinkDataLength()+" "+TFormat.toString(r.getRelinkDataFormat());
      }
      else
      {
        TStructDescription sd;
        TBitfield bf;
        sd = r.getStructDescription();
        bf = r.getBitfield();
        if (sd != null) rdrItemString = r.getSrcKey()+" -> "+ sd.getTagName()+" structures";
        else if (bf != null) rdrItemString = r.getSrcKey()+" -> "+ bf.getTag()+" bitfields";
        else rdrItemString = r.getSrcKey()+" -> unmapped";
      }
      dbgPrint(rdrItemString);
    }
    dbgPrint("");
 
  public class RelinkedItem
  {
    private String srcKey; // full name + [property]
    private TBitfield dstBf = null;
    private TStructDescription dstSd = null;
    private String field = null;
    private byte[] dstBytes = null;
    private TDataType dstTdt = null;
    private TDataType tdt = null;
    private int len = 0;
    private short fmt = TFormat.CF_NULL;
    private int sts = 0; // reason for re-link
    public int getRelinkReason() { return sts; }
    public int getRelinkDataLength() { return len; }
    public short getRelinkDataFormat() { return fmt; }
    public RelinkedItem(TLink lnk,int len,short fmt)
    {
      srcKey = getLinkKey(lnk);
      if (lnk.dOutput.getFormat() == TFormat.CF_DEFAULT)
      {
        lnk.dOutput.dFormat = fmt;
        lnk.dOutput.dArrayLength = len;
      }
      dstTdt = lnk.dOutput;
//      tdt = new TDataType(len,fmt);
//      lnk.dOutput = tdt;
      sts = TErrorList.invalid_datarequest;
      this.len = len;
      this.fmt = fmt;
    }
    public RelinkedItem(TLink lnk,TBitfield bitfield,String field)
    {
      srcKey = getLinkKey(lnk);
      dstBf = bitfield;
      this.field = field;
      if (lnk.dOutput.dFormat == TFormat.CF_DEFAULT) lnk.dOutput.dArrayLength = 1;
      lnk.dOutput.dFormat = TFormat.getBitfieldFormat(lnk.dOutput.dFormat);
      sts = TErrorList.has_bitfield_tag;
      this.len = lnk.dOutput.dArrayLength;
      this.fmt = lnk.dOutput.dFormat;     
    }
    public TBitfield getBitfield() { return dstBf; }
    public TStructDescription getStructDescription() { return dstSd; }
    public String getSrcKey() { return srcKey; }
    public TDataType getDataObject() { return tdt; }
    public TDataType getTargetDataObject() { return dstTdt; }
    public String getField() { return field; }
    public void setDestination(TLink lnk)
    {
      if (sts == TErrorList.invalid_datarequest)
      {
        if (lnk.dOutput.getFormat() == TFormat.CF_DEFAULT)
        {
          lnk.dOutput.dArrayLength = len;
          lnk.dOutput.dFormat = fmt;
        }
        dstTdt = lnk.dOutput;
        tdt = new TDataType(len,fmt);
        lnk.dOutput = tdt;
        return;
      }
      if (lnk == null || dstSd == null || field == null) return;
      if (lnk.dOutput.getFormat() == TFormat.CF_DEFAULT)
      {
        lnk.dOutput.dFormat = dstSd.getField(field).getFormat();
        lnk.dOutput.dArrayLength = 1;
      }
      dstTdt = lnk.dOutput;
      int slen = dstSd.getRawLength();
      if (dstTdt.dArrayLength > tdt.dArrayLength/slen)
      {
        dstBytes = new byte[slen*lnk.dOutput.getArrayLength()];
        tdt = new TDataType(dstBytes,dstSd.getTagName());
      }
    }
    public RelinkedItem(TLink lnk,TStructDescription structDesc,String field)
    {
      srcKey = getLinkKey(lnk);
      dstSd = structDesc;
      this.field = field;
      if (lnk.dOutput.getFormat() == TFormat.CF_DEFAULT)
      {
        lnk.dOutput.dFormat = dstSd.getField(field).getFormat();
        lnk.dOutput.dArrayLength = 1;
      }
      dstTdt = lnk.dOutput;
      dstBytes = new byte[dstSd.getRawLength()*lnk.dOutput.getArrayLength()];
      tdt = new TDataType(dstBytes,dstSd.getTagName());
      sts = TErrorList.has_structure_tag;
      this.len = lnk.dOutput.dArrayLength;
      this.fmt = lnk.dOutput.dFormat;
    }
  }
  private static Hashtable<String, RelinkedItem> ReLnkLst = new Hashtable<String, RelinkedItem>();
  public void reLinkLink(TLink lnk)
  {
    String key = getLinkKey(lnk);
    if (key == null) return;
    boolean trace = traceKey != null && lnk.isTraceLink();
    if (ReLnkLst.containsKey(key))
    {
      RelinkedItem rlnk = ReLnkLst.get(key);
      switch (rlnk.getRelinkReason())
      {
        case TErrorList.invalid_datarequest:
          lnk.mapInvalidDataRequest();
          if (trace) lnk.traceLink("reLinkLink", "remapping invalid data request");
          break;
        case TErrorList.has_bitfield_tag:
          if (lnk.dOutput.getTag().length() > 0)
          { // already tagged -> don't relink
            return;
          }
          TBitfield bf = rlnk.getBitfield();
          if (bf != null)
          {
            lnk.mapSingleFieldToBitfield();
            if (trace) lnk.traceLink("reLinkLink", "remapping bitfield");
            return;
          }
        case TErrorList.has_structure_tag:
          if (lnk.dOutput.getTag().length() > 0)
          { // already tagged -> don't relink
            return;
          }
          TStructDescription tsd = rlnk.getStructDescription();
          if (tsd != null)
          {
            //lnk.dOutput = rlnk.getDataObject();
            if (lnk.dOutput.getTag().length() == 0) lnk.dOutput.setTag(tsd.getTagName());
            lnk.mapSingleFieldToStruct();
            if (trace) lnk.traceLink("reLinkLink", "remapping struct field");
          }
          break;
      }
    }
  }
  public void flushReLinkList()
  {
    ReLnkLst.clear();
  }
  public RelinkedItem getRelinkedItem(TLink lnk)
  {
    String key = getLinkKey(lnk);
    return ReLnkLst.get(key);
  }
  public RelinkedItem addLinkToReLinkList(TLink lnk,TBitfield bitfield,String field)
  {
    String key = getLinkKey(lnk);
    RelinkedItem item = null;
    if (ReLnkLst.containsKey(key))
    {
      item = ReLnkLst.get(key);
    }
    else
    {
      item = new RelinkedItem(lnk,bitfield,field);
      ReLnkLst.put(key, item);
    }
    lnk.relnkItem = item;
    return item;
  }
  public RelinkedItem addLinkToReLinkList(TLink lnk,TStructDescription structDesc,String field)
  {
    String key = getLinkKey(lnk);
    RelinkedItem item = null;
    if (ReLnkLst.containsKey(key))
    {
      item = ReLnkLst.get(key);
    }
    else
    {
      item = new RelinkedItem(lnk,structDesc,field);
      ReLnkLst.put(key, item);
    }
    item.setDestination(lnk);
    lnk.relnkItem = item;
    return item;
  }
  public RelinkedItem addLinkToReLinkList(TLink lnk,int len,short fmt)
  {
    String key = getLinkKey(lnk);
    RelinkedItem item = null;
    if (ReLnkLst.containsKey(key))
    {
      item = ReLnkLst.get(key);
    }
    else
    {
      item = new RelinkedItem(lnk,len,fmt);
      ReLnkLst.put(key, item);
    }
    item.setDestination(lnk);
    lnk.relnkItem = item;
    return item;
  }
  private static Hashtable<String, BlackListedItem> BlackLnkLst = new Hashtable<String, BlackListedItem>();
  public class BlackListedItem
  {
    private String srcKey; // full name + [property]
    private int status = 0;
    public BlackListedItem(TLink lnk)
    {
      srcKey = getLinkKey(lnk);
      status = lnk.linkStatus;
    }
    public int getLinkStatus() { return status; }
    public String getSrcKey() { return srcKey; }   
  }
  public void addLinkToBlackList(TLink lnk)
  {
    if (lnk.linkBlacklists++ > 2) return;
    String key = getLinkKey(lnk);
    BlackLnkLst.put(key, new BlackListedItem(lnk));
    if (lnk.linkBlacklists > 1) lnk.linkStatus = TErrorList.link_blacklisted;
  }
  public boolean isLinkBlackListed(TLink lnk)
  {
    String key = getLinkKey(lnk);
    if (key == null) return false;
    return BlackLnkLst.containsKey(key);  
  }
  /**
   * Returns the original link status which caused the link to be black listed
   *
   * @param lnk is the TLink for which the black listed status is desired
   * @return the black listed link status (or invalid_link if the link has not been blacklisted)
   */
  public int getBlackListedLinkStatus(TLink lnk)
  {
    String key = getLinkKey(lnk);
    if (key != null && BlackLnkLst.containsKey(key))
    {
      BlackListedItem bllnk = BlackLnkLst.get(key);
      return bllnk.getLinkStatus();
    }
    return TErrorList.invalid_link;
  }
  public void flushBlackList()
  {
    if (BlackLnkLst.size() == 0) return;
    if (debugLevel > 0) DbgLog.log("flushBlackList","flushing the current link black list");
    BlackLnkLst.clear();
    for (int i=0; i<numberTLinksInTable; i++)
    {
      if (linkTable[i] == null) continue;
      if (linkTable[i].linkStatus == TErrorList.link_blacklisted)
      {
        linkTable[i].linkStatus = TErrorList.link_not_open;
        linkTable[i].linkBlacklists = 0;
      }
    }
  }
  public static enum AccessLockType {
    LOCK_UNLOCKED ,
    LOCK_PREEMPTIVE,
    LOCK_PERSISTENT,
    LOCK_ABORT;
  }
  public class AccessLockListItem implements TLinkCallback
  {
    private String key;
    TLink lockLink;
    int lockType;
    int lockLinkStatus;
    long lockDuration;
    long lastSent;
    public AccessLockListItem(String context, String server, AccessLockType lockType, int lockDuration)
    {
      this(context,server,lockType,lockDuration,0);
    }
    public AccessLockListItem(String context, String server, AccessLockType lockType, int lockDuration,int lockFlags)
    {
      int lt = lockType.ordinal();
      if (lockFlags != 0) lt |= lockFlags;
      short[] lvals = new short[2];
      lvals[0] = (short)lt;
      lvals[1] = (short)lockDuration;
      TDataType din = new TDataType(lvals);
      TLink lnk = new TLink("/"+context+"/"+server,"ACCESSLOCK",null,din,TAccess.CA_WRITE|TAccess.CA_RETRY);
      key = getLinkKey(lnk);
      this.lockType = lt;
      this.lockDuration = lockDuration;
      lockLink = lnk;
    }
    public void callback(TLink link)
    {
      lockLinkStatus = link.linkStatus;
      if (lockLinkStatus != 0)
      {
        if (debugLevel > 0)
          DbgLog.log("AccessLockListItem","access lock " + key + " : " + link.linkErrString);
      }
    }
  }
  private static Hashtable<String, AccessLockListItem> LockedLnkLst = new Hashtable<String, AccessLockListItem>();
  /**
   * Acquires an access lock to the server specified.
   *
   * A client application can obtain an access lock to the given server provided the caller
   * has WRITE privileges himself.  Once the lock is obtained, no other process will be
   * allowed WRITE access regardless of any other access control lists.  A lock may be
   * preemptive (LOCK_PREEMTIVE) or persistent (LOCK_PERSISTENT).  Preemptive locks may be
   * aborted (LOCK_ABORT) by other callers with WRITE privileges. Persistent locks may not.
   *
   * @param context is the targeted context of the server
   * @param server is the targeted device server
   * @param lockType is the desired lock type: one of AccessLockType.LOCK_CANCEL,
   * AccessLockType.LOCK_PREEPMTIVE, AccessLockType.LOCK_PERSISTENT or AccessLockType.LOCK_ABORT.
   * @param lockDuration is the requested duration of the lock in seconds.  This applies only to
   * preemptive locks.  Persistent locks will ignore this parameter and continue to renew the lock
   * until the caller cancels the lock (graceful) or disappears (maximum 60 second wait).
   * @return a tine return code
   *
   * \b example:
   *
   * \include eg_AccessLock.java
   */
  public static int setAccessLock(String context, String server, AccessLockType lockType, int lockDuration)
  {
    return setAccessLock(context,server,lockType,lockDuration,0);
  }
  /**
   * Acquires an access lock to the server specified.
   *
   * A client application can obtain an access lock to the given server provided the caller
   * has WRITE privileges himself.  Once the lock is obtained, no other process will be
   * allowed WRITE access regardless of any other access control lists.  A lock may be
   * preemptive (LOCK_PREEMTIVE) or persistent (LOCK_PERSISTENT).  Preemptive locks may be
   * aborted (LOCK_ABORT) by other callers with WRITE privileges. Persistent locks may not.
   *
   * @param context is the targeted context of the server
   * @param server is the targeted device server
   * @param lockType is the desired lock type: one of AccessLockType.LOCK_CANCEL,
   * AccessLockType.LOCK_PREEPMTIVE, AccessLockType.LOCK_PERSISTENT or AccessLockType.LOCK_ABORT.
   * @param lockDuration is the requested duration of the lock in seconds.  This applies only to
   * preemptive locks.  Persistent locks will ignore this parameter and continue to renew the lock
   * until the caller cancels the lock (graceful) or disappears (maximum 60 second wait).
   * @param lockFlags can contain optional access lock flags such as LOCK_XREAD (exclusive read).
   * @return a tine return code
   */
  public static int setAccessLock(String context, String server, AccessLockType lockType, int lockDuration,int lockFlags)
  {
    AccessLockListItem all;
    String key = "/"+context+"/"+server+"/[ACCESSLOCK]";
    if (LockedLnkLst.containsKey(key))
    { // already have a lock
      all = LockedLnkLst.get(key);
    }
    else
    {
      all = TLinkFactory.getInstance().new AccessLockListItem(context,server,lockType,lockDuration,lockFlags);
      if (lockType.ordinal() > 0) LockedLnkLst.put(key, all);
    }
    int lid = all.lockLink.attach(TMode.CM_SINGLE, all, 500);  
    all.lastSent = System.currentTimeMillis();
    return lid < 0 ? -lid : 0;
  }
  /**
   * removes an access lock on the server specified.
   *
   * A client application can obtain an access lock to the given server provided the caller
   * has WRITE privileges himself.  Once the lock is obtained, no other process will be
   * allowed WRITE access regardless of any other access control lists.  A lock may be
   * preemptive (LOCK_PREEMTIVE) or persistent (LOCK_PERSISTENT).  Preemptive locks may be
   * aborted (LOCK_ABORT) by other callers with WRITE privileges. Persistent locks may not.
   * A client can free a lock he has obtained by calling this method (nominally equivalent to
   * setAccessLock() + AccessLockType.LOCK_CANCEL.
   * @param context is the targeted context of the server
   * @param server is the targeted device server
   * @return a tine return code
   *
   * \b example:
   *
   * \include eg_AccessLock.java
   */
  public static void removeAccessLock(String context, String server)
  {
    if (context != null && server != null)
    {
      String key = "/"+context+"/"+server+"/[ACCESSLOCK]";
      if (!LockedLnkLst.containsKey(key)) return;
      LockedLnkLst.remove(key);
      setAccessLock(context,server,AccessLockType.LOCK_UNLOCKED,0);
    }
    else
    {
      AccessLockListItem all;
      Enumeration<AccessLockListItem> lst = LockedLnkLst.elements();
      while (lst.hasMoreElements())
      {
        all = (AccessLockListItem) lst.nextElement();
        setAccessLock(all.lockLink.cntName,all.lockLink.expName,AccessLockType.LOCK_UNLOCKED,0);
      }
      LockedLnkLst.clear();
    }
  }
  private void checkAccessLockItems()
  {
    long t = System.currentTimeMillis();
    AccessLockListItem all;
    Enumeration<AccessLockListItem> lst = LockedLnkLst.elements();
    while (lst.hasMoreElements())
    {
      all = (AccessLockListItem) lst.nextElement();
      if (all.lockType != AccessLockType.LOCK_PERSISTENT.ordinal()) continue;
      if (all.lockLinkStatus != 0) continue;
      if (t < all.lockDuration * 1000 + all.lastSent - 5000) continue;
      all.lockLink.attach(TMode.CM_SINGLE, all, 500);  
      all.lastSent = System.currentTimeMillis();
    }
  }
  public static final int DEFAULT_PROTOCOL_LEVEL = 6;
  private static final int RENEWAL_REMINDER = 10;
  private static final int RENEWAL_URGENT = 5;
  public static final int RETRY_THRESHOLD = 2;
  private static final int TIMEOUT_GRACE_INTERVAL = 500;
  private static final int BLACKLIST_FLUSH_INTERVAL = 300;
  private static TLinkFactory instance = new TLinkFactory();
  public static boolean alwaysRetry = true;
  public void setAlwaysRetry(boolean value) { alwaysRetry = value; }
  public boolean getAlwaysRetry() { return alwaysRetry; }
  boolean active;
  public boolean terminate;
  private TEquipmentModuleFactory gEqmFactory = null;
  public TEquipmentModuleFactory getEquipmentModuleFactory()
  {
    if (gEqmFactory == null) gEqmFactory = TEquipmentModuleFactory.getInstance();
    return gEqmFactory;
 
  private static boolean autoLinkWatchdogs = true;
  private static boolean gIsRunningAsServer = false;
  public void SetRunningAsServer(boolean value) { gIsRunningAsServer = value; }
  public static boolean isRunningAsServer() { return gIsRunningAsServer; }
  private boolean autoLinkErrorAlarms = true;
  public void setAutoLinkErrorAlarms(boolean value) { autoLinkErrorAlarms = value; }
  public boolean getAutoLinkErrorAlarms() { return autoLinkErrorAlarms; }
  public void setAutoLinkWatchdogs(boolean value) { autoLinkWatchdogs = value; }
  public boolean getAutoLinkWatchdogs() { return autoLinkWatchdogs; }
  Date date = new Date();
  int time;
  private static String tineUserName = System.getProperty("user.name");
  private String doocsUserName = null;
  public void setDoocsUserName(String userName)
  {
    if (userName == null) userName = "";
    MsgLog.log("setDoocsUserName","set doocs user to "+userName,0,null,0);
    doocsUserName = new String(userName);
  }
  public String getDoocsUserName() { return doocsUserName; }
  /**
   * Gets the user name seen in all link requests from this client application.
   *
   * @return the current user name setting
   */
  public String getUserName() { return tineUserName; }
  /**
   * Sets the user name seen in all link requests to that specified.
   *
   * @param userName is the desired user name setting
   */
  public void setUserName(String userName) { tineUserName = new String(userName); }
  private TPacket atp; // general socket for asynchronous communication
  private TPacket stp; // socket for synchronous communication
  private TPacket qtp; // socket for synchronous queries
  private TPacket nmtp; // socket for async net service requests
  private TPacket gtp; // globals socket
  public TPacket getGlobalsSocket() { return gtp; }
  private TPacket amtp; // asynchronous multicast socket
  public TPacket getMulticastSocket() { return amtp; }
  private static int sckRcvBufferSize;
  private static int sckSndBufferSize;
  public static int getSckRcvBufferSize() { return sckRcvBufferSize; }
  public static void setSckBufferSize(int bufferSize)
  {
    if (bufferSize > 0x1000) sckRcvBufferSize = bufferSize;
  }
  public static int getSckSndBufferSize() { return sckSndBufferSize; }
  public static void setSckSndBufferSize(int bufferSize)
  {
    if (bufferSize > 0x1000) sckSndBufferSize = bufferSize;
  }
  private static int sckTimeToLive;
  public static int getSckTimeToLive() { return sckTimeToLive; }
  public static void setTimeToLive(int timeToLive)
  {
    if (timeToLive > 1) sckTimeToLive = timeToLive;
  }
  TPHdr pHdr; // producer-header for incoming link entries
  TGlobalsHdr glbHdr;
  public static int debugLevel;
  private int totalLinkTimeouts = 0;
  private int totalConnectionArrivals = 0;
  private boolean useConnectedSockets = false;
  public boolean isUseConnectedSockets()
  {
    return useConnectedSockets;
  }
  public void setUseConnectedSockets(boolean useConnectedSockets)
  {
    this.useConnectedSockets = useConnectedSockets;
  }
  public int getTotalLinkTimeouts() { return totalLinkTimeouts; }
  public int getTotalConnectionArrivals() { return totalConnectionArrivals; }
  static private int numberTLinksInTable = 1; // assume the ENS is there ...
  public int getNumberTLinksInTable() { return numberTLinksInTable;}
  // is this significantly quicker to have a static link table rather than a linked list ?
  // probably not => TODO: convert this to a linked list
  static private int maximumNumberTLinks = 1024;
  static private int HEARTBEAT = 60000; // inactive seconds allowed for REFRESH links
  static public int getMaximumNumberOfLinks()
  {
    return maximumNumberTLinks;
  }
  static public int setMaximumNumberOfLinks(int numberOfLinks)
  {
    if (numberOfLinks > 10) maximumNumberTLinks = numberOfLinks;
    linkTable = Arrays.copyOf(linkTable, maximumNumberTLinks);
    return maximumNumberTLinks;
  }
  private TLinkHook tLinkHook = null;
  public void setTLinkHook(TLinkHook hook) { tLinkHook = hook; }
  public TLinkHook getTLinkHook() { return tLinkHook; }
  private static final TLink tNullLink = new TLink();
  protected static LinkedList<TLink> siblings = new LinkedList<TLink>(); // used by common ENS links
  protected static TLink[] linkTable = new TLink[maximumNumberTLinks];
  public TLink[] getLinkTable()
  {
    return linkTable;
  }
  public TLink getLinkFromTable(int linkId)
  {
    try
    {
      return linkTable[linkId];
    }
    catch (Exception any)
    {
      return null;
    }
  }
  public static void dbgPrint(String msg)
  {
    if (gIsRunningAsServer)
    {
      TEquipmentModuleFactory.dbgPrint(msg);
      return;
    }
    if (msg == null) msg = "";
    System.out.println(msg);
  }
  public static void dumpStats()
  {
    if (gIsRunningAsServer)
    {
      TEquipmentModuleFactory.dumpStats();
    }
    else
    {
      dbgPrint("not running as a server!");
    }
  }
  public static void dumpUserLists()
  {
    if (gIsRunningAsServer)
    {
      TEquipmentModuleFactory.dumpUserLists();
    }
    else
    {
      dbgPrint("not running as a server!");
    }
  }
  public static void dumpNetsLists()
  {
    if (gIsRunningAsServer)
    {
      TEquipmentModuleFactory.dumpNetsLists();
    }
    else
    {
      dbgPrint("not running as a server!");
    }
  }
  public static void dumpProperties(String eqmName)
  {
    if (gIsRunningAsServer)
    {
      TEquipmentModuleFactory.dumpProperties(eqmName);
    }
    else
    {
      dbgPrint("not running as a server!");
    }   
  }
  public static void dumpDevices(String eqmName)
  {
    if (gIsRunningAsServer)
    {
      TEquipmentModuleFactory.dumpDevices(eqmName);
    }
    else
    {
      dbgPrint("not running as a server!");
    }   
  }
  public static void dumpDeadbands(String eqmName)
  {
    if (gIsRunningAsServer)
    {
      TEquipmentModuleFactory.dumpDeadbands(eqmName);
    }
    else
    {
      dbgPrint("not running as a server!");
    }       
  }
  public static void dumpSettings()
  {
    dbgPrint("\nCurrent System Settings");
    TInitializer ti = TInitializerFactory.getInstance().getInitializer();
    dbgPrint("JVM Settings         :");
    dbgPrint("JVM max  heap size   : "+Runtime.getRuntime().maxMemory());
    dbgPrint("JVM cur. heap size   : "+Runtime.getRuntime().totalMemory());
    dbgPrint("JVM cur. free memory : "+Runtime.getRuntime().freeMemory());
    if (gIsRunningAsServer)
    {
      dbgPrint("Server Settings      :");
      dbgPrint("System Cycle interval: 10 msec");
      dbgPrint("Min Polling interval : "+TEquipmentModuleFactory.getMinimumPollingInterval()+" msec");
      dbgPrint("Req ack. on change   : "+(TEquipmentModuleFactory.gRequireAcknowledgments ? "yes" : "no"));
      dbgPrint("Retard cont. removal : "+(TEquipmentModuleFactory.gRetardSingleContractRemoval ? "yes" : "no"));
      dbgPrint("Server Burst Limit   : "+TEquipmentModuleFactory.getBurstLimit()+" packets");
      dbgPrint("Contract renewal len : "+TEquipmentModuleFactory.getRenewalLength()+" items");
      dbgPrint("Burst Cycle Delay    : "+TEquipmentModuleFactory.getCycleDelay()+" msec");
      dbgPrint("Server Packet MTU    : "+ti.getSrvPacketMtu()+" bytes");
      dbgPrint("Server Recv Buffers  : "+ti.getSrvRcvBufferSize()+" bytes");
    }
    dbgPrint("Client Settings      :");
    dbgPrint("Connect. tbl capacity: "+maximumNumberTLinks+" items");
    dbgPrint("Globals  tbl capacity: "+maximumNumberGlobals+" items");
    dbgPrint("Client Recv Buffers  : "+ti.getClnRcvBufferSize()+" bytes");
    dbgPrint("use watchdog links   : "+(autoLinkWatchdogs ? "yes" : "no"));
    dbgPrint("retry on timeout     : "+(alwaysRetry ? "yes" : "no"));   
  }
  public static void dumpContractTable()
  {
    if (gIsRunningAsServer)
    {
      TEquipmentModuleFactory.dumpContractTable();
    }
    else
    {
      dbgPrint("Not running as server !");
    }
  }
  public static void dumpClientTable()
  {
    if (gIsRunningAsServer)
    {
      TEquipmentModuleFactory.dumpClientTable();
    }
    else
    {
      dbgPrint("Not running as server !");
    }   
  }
  public static void dumpGlobals()
  {
    TLink glb;
    String gs;
    InetAddress ia;
    TSrvEntry tse;
    TFecEntry tfe;
    dbgPrint("\nCurrent Globals Table");
    for (int i=0; i<numberTLinksInTable; i++)
    {
      if ((glb=linkTable[i]) == null) continue;
      if (glb.sub.mode != TMode.CM_GLOBAL) continue;
      gs = glb.toString();
      if (glb.dOutput != null)
      {
        glb.dOutput.setArrayDelimiter(" ");
        gs += "((value : "+glb.dOutput.toString()+")";
      }
      dbgPrint(gs);
      tse = glb.srvAddr;
      tfe = tse != null ? tse.fecAddr : null;
      ia = tfe != null ? tfe.fecHost : null;
      gs = "    multicast source: "+(tse != null ? glb.getFullDeviceName()+" [FEC: "+ tse.getFecName()+"]" : "unknown");
      if (ia != null) gs += " "+ia.getHostAddress();
      dbgPrint(gs);
      ia = glb.getMulticastGroup();
      gs = "    multicast group: "+(ia != null ? ia.getHostAddress() : "unknown");
      dbgPrint(gs);
      gs = "    last update: "+ (glb.sub != null ? TDataTime.toString(glb.dOutput.getDataTimeStamp()) : "unknown");
      dbgPrint(gs);
    }  
    dbgPrint("");
  }
  public static void dumpLinkEntry(int i)
  {
    if (i < 0 || i >= numberTLinksInTable)
    {
      dbgPrint("link "+i+" is not a link table entry!");
      return;
    }
    if (linkTable[i] == null || linkTable[i] == tNullLink)
    {
      dbgPrint("link "+i+" is no longer available!");
      return;
    }
    TLink lnk = linkTable[i];
    dbgPrint("link ["+i+"]: "+lnk.toString());
    dbgPrint("\tdata input: "+(lnk.dInput == null ? "(null)" : lnk.dInput.getObjectInfo()));
    dbgPrint("\tdata output: "+(lnk.dOutput == null ? "(null)" : lnk.dOutput.getObjectInfo()));
    dbgPrint("\tdata access: "+TAccess.toString(lnk.devAccess));
    dbgPrint("\ttransport mode:"+ (lnk.sub == null ? "(null)" : TMode.toString(lnk.sub.mode)));
    dbgPrint("\tis active: "+lnk.isActive());
    dbgPrint("\tis parent: "+lnk.hasDependencies());
    dbgPrint("\tis bound: "+lnk.isBound());
    dbgPrint("\tis mca element: "+(lnk.getMcaIndex() > 0));
    dbgPrint("\tis mca parent: "+lnk.isMcaParent());
    dbgPrint("\tis redirected: "+lnk.isRedirected);
    dbgPrint("\tis wildcard link: "+lnk.isWildcardLink);
    dbgPrint("\tis grouped: "+lnk.isGrouped());
    dbgPrint("\thas notified once: "+lnk.hasNotifiedOnce);
    dbgPrint("\thas obtained status: "+lnk.hasObtainedStatus);
    dbgPrint("\tlast notification: "+TDataTime.toString(lnk.lastLinkNotification)+"; utc: "+lnk.lastLinkNotification);
    dbgPrint("\tlast suppressed notification: "+TDataTime.toString(lnk.lastLinkSuppressedNotification)+"; utc: "+lnk.lastLinkSuppressedNotification);
  }
  public static void dumpLinkTable()
  {
    dbgPrint("\nCurrent Connection Table");
    String linkItemString;
    for (int i=0; i<numberTLinksInTable; i++)
    {
      if (linkTable[i] == null || linkTable[i] == tNullLink) continue;
      linkItemString = "["+i+"] "+linkTable[i].toString();
      if (linkTable[i].dOutput != null)
      {
        if (linkTable[i].dOutput.dCompletionLength == 1)
        {
          linkTable[i].dOutput.setArrayDelimiter(" ");
          linkItemString += "((value : "+linkTable[i].dOutput.toString()+")";
        }
        else
        {
          linkItemString += "("+linkTable[i].dOutput.dCompletionLength+" values read)";
        }
      }
      dbgPrint(linkItemString);
    }
    dbgPrint("");
  }
  public static void dumpLinkAddresses()
  {
    dbgPrint("\nConnection Table Active Addresses");
    String as;
    InetAddress ia;
    TSrvEntry tse;
    TFecEntry tfe;
    for (int i=0; i<numberTLinksInTable; i++)
    {
      if (linkTable[i] == null || linkTable[i] == tNullLink) continue;
      if (!linkTable[i].active) continue;
      as = "["+i+"] "+linkTable[i].getFullDeviceNameAndProperty();
      tse = linkTable[i].srvAddr;
      tfe = tse != null ? tse.fecAddr : null;
      ia = tfe != null ? tfe.fecHost : null;
      as += " : "+(tse != null ? tse.getEqmName()+"@"+ tse.getFecName()+"]" : "unknown");
      dbgPrint(as);
      if (ia != null)
      {
        as = "    host: "+ia.getHostAddress();
        dbgPrint(as);
        as = "    port offset: "+tfe.fecPortOffset;
        dbgPrint(as);
      }
      if ((ia=linkTable[i].getMulticastGroup()) != null)
      {
        as = "    multicast group: "+ia.getHostAddress();
        dbgPrint(as);       
      }
      if (tfe != null)
      {
        as = "    protocol: "+tfe.getTineProtocol();
        dbgPrint(as);
      }
      if (linkTable[i].sub != null)
      {
        as = "    mode: "+TMode.toString(linkTable[i].sub.mode);
        dbgPrint(as);
        as = "    last update: "+TDataTime.toString(linkTable[i].sub.linkLastTime);
        dbgPrint(as);
      }
      if (linkTable[i].linkStatus != 0)
      {
        as = "    status: " +linkTable[i].getLastError();
        dbgPrint(as);
      }
    }
    dbgPrint("");
  }
  public static void dumpModules()
  {
    if (gIsRunningAsServer)
    {
      TEquipmentModuleFactory.dumpModules();
    }
    else
    {
      dbgPrint("Not running as server !");
    }   
  }
  public int getLinkTableId(TLink lnk)
  {
    for (int i=0; i<numberTLinksInTable; i++)
    {     
      if (linkTable[i] == lnk) return i;
    }
    return -1;
  }
  public static final boolean isEnsCall(String context,String server)
  {
    if (server == null) return false;
    boolean ctxOk = false;
    if (context == null || context.length() == 0)
    {
      ctxOk = true;
    }
    else
    {
      if (context.compareTo("DEFAULT") == 0 ||
          context.compareToIgnoreCase("SITE") == 0 ||
          context.compareToIgnoreCase("SERVICE") == 0)
      {
        ctxOk = true;
      }
    }
    if (!ctxOk) return false;
    if (server.equalsIgnoreCase("ENS")) return true;
    if (server.startsWith("ENS#")) return true;
    return false;
  }
  public static final int adjustLinkTableRemove = 0;
  public static final int adjustLinkTableAdd = 1;
  public static final int adjustLinkTableReplace = 2;
  private static Object lnkTblObject = new Object();
  public static int adjustLinkTable(TLink lnk,int direction)
  { synchronized(lnkTblObject) {
    int i;
    if (lnk == null) return -TErrorList.argument_list_error;
    switch (direction)
    {
      case adjustLinkTableRemove:
        if (lnk == null || (i = lnk.linkId) < 0) return -TErrorList.argument_list_error;
        if (i == 0) return 0; // never remove the ENS link
        if (linkTable[i] == null) return 0;
        if (debugLevel > 1) DbgLog.log("adjustLinkTable","removing link " + linkTable[i]);
        linkTable[i].terminate = false;
        linkTable[i] = null;
        while (numberTLinksInTable > 1 && linkTable[numberTLinksInTable-1] == null)
        {
          if (debugLevel > 1) DbgLog.log("adjustLinkTable","decrement number of entries in link table");
          numberTLinksInTable--;
        }
        if (lnk.twcl != null && lnk.twcl.parent != null)
        { // if part of a wildcard link, signal the watchdog to get rid of the parent
          lnk.twcl.parent.terminate = true;
       
        break;
      case adjustLinkTableAdd:
        String srv = lnk.getDeviceServer();
        String ctx = lnk.getContext();
        if (isEnsCall(ctx, srv))
        {
          if (numberTLinksInTable == 0)
          {
            if (debugLevel > 1) DbgLog.log("adjustLinkTable","add ENS entry to link table");
            numberTLinksInTable++;
          }
          return 0;
        }
        int freeslot = 0;
        for (i = 1; i < numberTLinksInTable && (linkTable[i] !=  lnk || linkTable[i] == tNullLink); i++)
        {
          if (freeslot == 0 && linkTable[i] == null) freeslot = i;
        }
        if (i < numberTLinksInTable)
        {
          linkTable[i].terminate = false;
          return i; // already in table
        }
        if (freeslot > 0)
        {
          linkTable[freeslot] = lnk;
          lnk.linkId = freeslot;
          return freeslot;
        }
        if (numberTLinksInTable == maximumNumberTLinks)
        {
          if (gIsRunningAsServer)
          {
            TFecLog.log("link "+lnk.getFullDeviceNameAndProperty()+" cannot be added to link table");
            TFecLog.log("link table capacity "+TLinkFactory.maximumNumberTLinks+" has been saturated");
          }
          return -TErrorList.resources_exhausted;
        }
        if (debugLevel > 1) DbgLog.log("adjustLinkTable","increment number of entries in link table");
        lnk.linkId = i;
        linkTable[i] = lnk;
        numberTLinksInTable++;
        return i;
      case adjustLinkTableReplace:
        if (lnk == null || (i = lnk.linkId) < 0) return -TErrorList.argument_list_error;
        if (i == 0) return 0; // never replace the ENS link
        if (debugLevel > 1) DbgLog.log("adjustLinkTable","replace link " + linkTable[i]);
        linkTable[i] = lnk;
        break;
      default:
        break;
    }
    return 0;
  } }
  public int getNumberActiveLinks()
  {
    int n = 0;
    for (int i=1; i<numberTLinksInTable; i++)
      if (linkTable[i] != null && linkTable[i].isActive()) n++;
    return n;   
  }
  public TLink[] getActiveLinks()
  {
    int n = 0;
    for (int i=1; i<numberTLinksInTable; i++)
      if (linkTable[i] != null && linkTable[i].isActive()) n++;
    if (n == 0) return null;
    TLink[] tbl = new TLink[n];
    n = 0;
    for (int i=1; i<numberTLinksInTable; i++)
    {     
      if (linkTable[i] != null && linkTable[i].isActive())
        tbl[n++] = linkTable[i];
      if (n >= tbl.length) break;
    }
    return tbl;
  }
  boolean hasDeferredLinks = false;
  public boolean hasDeferredLinks()
  {
    if (hasDeferredLinks) return true;
    for (int i = 0; i < numberTLinksInTable; i++)
    {
      if (linkTable[i] != null && linkTable[i] != tNullLink)
      {
        if (linkTable[i].delayEstablishLink) return true;
      }     
    }
    hasDeferredLinks(false);
    return false;
  }
  public void hasDeferredLinks(boolean value) { hasDeferredLinks = value; }
  boolean isInsideCallback = false;
  private LinkedList<TWildcardLink> wcList = null;
  // java 1.5 or higher !!!! ->
  private Hashtable<String,TWatchdogLink> wdList = null;
  public boolean hasWatchdogLink(String key)
  {
    if (wdList == null) return false;
    return wdList.contains(key.toUpperCase());
  }
  public TWatchdogLink getWatchdogLink(String key)
  {
    if (wdList == null) return null;
    return wdList.get(key.toUpperCase());
  }
  public void addWatchdogLink(String key,TWatchdogLink lnk)
  {
    if (wdList == null) wdList = new Hashtable<String, TWatchdogLink>();
    if (key != null && lnk != null) wdList.put(key.toUpperCase(), lnk);
  }
  public void rmvWatchdogLink(String key)
  {
    if (wdList == null) wdList = new Hashtable<String, TWatchdogLink>();
    if (key != null) wdList.remove(key.toUpperCase());
  }
  private static Hashtable<Object,TLinkGroup> grpList = null;
  public TLinkGroup getGroup(TLinkCallback cb)
  {
    TLinkGroup grp = null;
    if (cb == null) return null;
    if (grpList == null) grpList = new Hashtable<Object,TLinkGroup>();
    if (grpList.containsKey(cb)) return (TLinkGroup)grpList.get(cb);
    if (debugLevel > 0) DbgLog.log("getGroup","adding new group to group table");
    grp = new TLinkGroup();
    grpList.put(cb,grp);
    return grp;
  }
  public TLinkGroup getGroup(TCallback cb)
  {
    TLinkGroup grp = null;
    if (cb == null) return null;
    if (grpList == null) grpList = new Hashtable<Object,TLinkGroup>();
    if (grpList.containsKey(cb)) return (TLinkGroup)grpList.get(cb);
    grp = new TLinkGroup();
    grpList.put(cb,grp);
    return grp;
  }
  public static void dumpGroups()
  {
    if (grpList == null || grpList.size() == 0)
    {
      dbgPrint("no link groups active");
      return;
    }
    TLinkGroup grp = null;
    Iterator<TLinkGroup> itr = grpList.values().iterator();
    while (itr.hasNext())
    {
      grp = itr.next();
      dbgPrint("\n *** link group "+grp.getGroupHead().getFullDeviceNameAndProperty()+" contains : ***");
      dbgPrint(grp.toString());
    }
  }
  public static void resetGroups()
  {
    if (grpList == null || grpList.size() == 0)
    {
      dbgPrint("no link groups to reset!");
      return;
    }
    TLinkGroup grp = null;
    Iterator<TLinkGroup> itr = grpList.values().iterator();
    while (itr.hasNext())
    {
      grp = itr.next();
      dbgPrint("reset link group "+grp.getGroupHead().getFullDeviceNameAndProperty());
      grp.reset();
    }
  }
  public TWildcardLink getWildcardLink()
  {
    TWildcardLink wc = null;
    if (wcList != null)
    {
      for (int i=0; i<wcList.size(); i++)
      {
        wc = (TWildcardLink)wcList.get(i);
        if (wc.parent == null) return wc;
      }
    }
    else
    {
      wcList = new LinkedList<TWildcardLink>();
    }
    wc = new TWildcardLink();
    wcList.add(wc);
    return wc;
  }
  public int getWildcardLinkId(TWildcardLink wc)
  {
    if (wc == null ||  wcList == null) return -1;
    for (int i=0; i<wcList.size(); i++)
    {
      if ((TWildcardLink)wcList.get(i) == wc) return i;
    }
    return -1;
  }
  public void rmvWildcardLink(TWildcardLink wc)
  {
    if (wc == null ||  wcList == null) return;
    for (int i=0; i<wcList.size(); i++)
    {
      if ((TWildcardLink)wcList.get(i) == wc)
      {
        wc.links = null;
        wc.list = null;
        wc.status = null;
        wc.parent = null;
        wc.tcb = null;
        wc.tlcb = null;
      }
    }   
  }
  private TFactoryThread atfThrd;
  public TFactoryThread getAsynchronousLinkThread() { return atfThrd; }
  private TFactoryThread stfThrd;
  public TFactoryThread getSynchronousLinkThread() { return stfThrd; }
  private TFactoryThread qtfThrd;
  public TFactoryThread getQueryLinkThread() { return qtfThrd; }
  private TFactoryGlobalsThread gtfThrd;
  public TFactoryGlobalsThread getGlobalsLinkThread() { return gtfThrd; }
  private TFactoryThread mtfThrd;
  public TFactoryThread getMulticastLinkThread() { return mtfThrd; }
  private TFactoryThread ntfThrd;
  public TFactoryThread getNetcastLinkThread() { return ntfThrd; }
  final TFactoryWatchdogThread tfwdThrd = new TFactoryWatchdogThread();
  public static synchronized void watchdogCycle()
  {
    if (instance != null &&
        instance.tfwdThrd.isWaiting())
    { // in a wait state, but we need it now !
      instance.tfwdThrd.interrupt();
    }
  }
  //static final int maximumNumberTBuckets = 10;
  static final int maximumNumberGlobals = 25;
  private LinkedList<TLinkBucket> bucketList = new LinkedList<TLinkBucket>();
  //private TLinkBucket[] bucketTable = new TLinkBucket[maximumNumberTBuckets];
  private TInitializer initializer = TInitializerFactory.getInstance().getInitializer();
  public TInitializer getInitializer()
  {
    return initializer;
  }
  public void startGlobalsListener()
  {
    if (gtp == null)
    { // socket not yet created
      gtp = new TPacket(initializer.getGCastPort());
    }
    if (gtfThrd == null)
    {
      gtfThrd = new TFactoryGlobalsThread(gtp);
      gtfThrd.start();
    }   
  }
  public void startMulticastListener(int rcvBufferSize,int timeToLive)
  {
    if (amtp == null)
    { // socket not yet created
      amtp = new TPacket(initializer.getMCastPort(),rcvBufferSize,timeToLive);
    }
    if (mtfThrd == null)
    {
      mtfThrd = new TFactoryThread(amtp,"async mcast",true);
      mtfThrd.start();   
    }
  }
  public class TLinkBucket extends TBucket
  {
    private int transport = TTransport.TCP;
    private int buffersize = TPacket.MAX_DATAGRAM_SIZE;
    private int port;
    public TLinkBucket(TLink lnk// index in link table
    {
      int index = lnk.linkId;
      if (index < 0 || index >= TLinkFactory.getMaximumNumberOfLinks()) return;
      if (linkTable[index].getBucket() != null) return; // already have a bucket
      TBucketThread tfBckThrd = getTBucketThread(index);
      if (tfBckThrd != null
      { // a thread is already attached to this link
        TLinkBucket tb = tfBckThrd.getBucket();
        setSocket(tb.getSocket());
        setOutputStream(tb.getOutputStream());
        setInputStream(tb.getInputStream());
        return;
      }
      // this is a new one ...
      if (TMode.isStream(lnk.sub.mode))
      {
        transport = TTransport.STREAM;
        buffersize = lnk.dOutput.getDataSize();
        if (buffersize < TPacket.MAX_DATAGRAM_SIZE) buffersize = TPacket.MAX_DATAGRAM_SIZE;
        port =  getInitializer().getStreamPort();
      }
      else
      {
        port = getInitializer().getTCPPort();
      }
      port += linkTable[index].srvAddr.fecAddr.fecPortOffset;
      if (initBucket(linkTable[index].srvAddr.fecAddr.fecHost,port,
            new TBucketThread(this,transport),buffersize) == 0)
      {
        putTLinkBucket(this);
        activate();
      }
    }
    public int getActiveLinks()
    {
      int n = 0;
      for (int i=0; i<numberTLinksInTable; i++)
      {
        if (linkTable[i] == null) continue;
        if (!linkTable[i].active) continue;
        if (linkTable[i].tb != this) continue;
        n++;
      }
      return n;
    }
    public int getTransport()
    {
      return transport;
    }
  }
  public class TBucketThread extends Thread
  {
    TLinkBucket tb;
    protected boolean isWaiting = false;
    protected int transport = TTransport.TCP;
    private byte[] payload = new byte[TPacket.MAX_DATAGRAM_SIZE];
    public TLinkBucket getBucket() { return tb; }
    private Socket sck = null;
    public TBucketThread(TLinkBucket tBucket,int type)
    {
      tb = tBucket;
      if ((type & (TTransport.STREAM|TTransport.TCP)) == 0) type = TTransport.TCP;
      transport = type;
      tb.active = true;
      String s = null;
      sck = tb.getSocket();
      if (sck != null) s = new String("tcp port " + sck.getLocalPort());
      else s = new String("tcp unbound socket ?");
      this.setName("Link Factory " + s);
    }
    public synchronized void run()
    {
      byte[] msgsizb = new byte[4];
      int nread, nleft, n, payloadptr, payloadsize, sizeptr=0;
      MsgLog.log("TBucketThread","Link Factory Thread " + getName() + " started ...",0,null,1);
      try
      {
        InputStream is = tb.getInputStream();
        while (tb.active)
        {
          if (terminate) break;
          if (debugLevel > 2) DbgLog.log("TBucketThread","Waiting for steam data ...");
          isWaiting = true;
          if ((nread = is.read(payload, 0, TPacket.MAX_DATAGRAM_SIZE)) == -1)
          {
            tb.isDeactivating = true;
            MsgLog.log("TBucketThread","read stream at EOF -> close socket !",0,null,0);
            break;
          }
          tb.touch();
          totalConnectionArrivals++;
          nleft = nread;
          payloadptr = 0;
          while (nleft > 0)
          {
            if (tb.getBucketPointer() == 0)
            { // at the beginning
              if (transport == TTransport.STREAM)
              {
                int count = nleft < 4 ? nleft : 4 - sizeptr;
                System.arraycopy(payload, payloadptr, msgsizb, sizeptr, count);
                sizeptr += count;
                if (sizeptr >= 4
                {// reset
                  sizeptr = 0;
                }
                else
                { // only a couple of bytes have dribbled in ...
                  break;
                }
                payloadsize = Swap.Long(new DataInputStream(new ByteArrayInputStream(msgsizb)).readInt());
                if (payloadsize > tb.getCapacity())
                {
                  tb.setCapacity(payloadsize+TPacket.MAX_DATAGRAM_SIZE);
                }
              }
              else
              {
                int count = nleft < 2 ? nleft : 2 - sizeptr;
                System.arraycopy(payload, payloadptr, msgsizb, sizeptr, count);
                sizeptr += count;
                if (sizeptr >= 2
                {// reset
                  sizeptr = 0;
                }
                else
                { // only a couple of bytes have dribbled in ...
                  break;
                }
                //System.arraycopy(payload, payloadptr, msgsizb, 0, 2);
                payloadsize = Swap.Short(new DataInputStream(new ByteArrayInputStream(msgsizb)).readShort());
              }
              tb.setBucketSize(payloadsize); // set bucket size for this payload
            }
            int ptr = tb.getBucketPointer();
            int siz = tb.getBucketSize();
            byte[] buf = tb.getBucketBuffer();
            n = siz - ptr; // remaining bucket buffer space
            if (n < 0 || payloadptr < 0 || ptr < 0)
            {
              MsgLog.log("TBucketThread", "invalid copy parameters: payload ptr "+payloadptr+", bucket pointer "+ptr+", length "+n,TErrorList.tcp_socket_error,null,1);
              ptr = 0;
              nleft = 0;
            }
            else if (nleft >= n) // at least enough
            { // and we've filled up the bucket !
              if (ptr + n > buf.length)
              { // this shouldn't happen, but it seems to ?
                MsgLog.log("TBucketThread", "copy "+n+" bytes at "+ptr+" would overflow buffer of "+buf.length+" bytes",TErrorList.tcp_socket_error,null,1);
                nleft = n =  buf.length - ptr;
              }
              if (payloadptr + n > payload.length)
              { // this shouldn't happen, but it seems to ?
                MsgLog.log("TBucketThread", "copy "+n+" bytes from "+payloadptr+" is past buffer length of "+payload.length+" bytes",TErrorList.tcp_socket_error,null,1);
                nleft = n =  payload.length - payloadptr;               
              }
              System.arraycopy(payload, payloadptr, buf, ptr, n);
              InterpretIncomingData(transport,buf, siz, tb.getBucketEndpoint(), tb.getBucketPort(), false);
              payloadptr += n; // move the read buffer pointer along
              nleft -= n; // decrement what's remaining to be read
              ptr = 0; // reset the bucket pointer
            }
            else
            {
              System.arraycopy(payload, payloadptr, buf, ptr, nleft);
              ptr += nleft;
              nleft = 0; // and we're out of the loop !
            }
            tb.setBucketPointer(ptr);
            if (payloadptr >= TPacket.MAX_DATAGRAM_SIZE && nleft != 0)
            { // should not be possible, but it's happened !
              MsgLog.log("TBucketThread","payload pointer "+payloadptr+" past end; last set: "+n+" bytes",TErrorList.code_failure,null,0);
              ptr = 0;
              nleft = 0;
            }
          }
        }
        tb.getSocket().close();
      }
      catch (IOException e)
      { // most likely an IOException ...
        if (sck != null && !sck.isClosed())
          MsgLog.log("TBucketThread", e.toString(), TErrorList.io_error, e, 1);
      }
      catch (Exception e)
      {
        e.printStackTrace();
        MsgLog.log("TBucketThread", e.toString(), TErrorList.code_failure, e, 0);
      }
      finally
      {
        removeTLinkBucket(tb,false);
      }
    }
  }
  public TBucketThread getTBucketThread(int index)
  {
    if (index < 0 || index >= numberTLinksInTable) return null;
    TFecEntry fec = linkTable[index].srvAddr.fecAddr;
    TLinkBucket tlb;
    synchronized (bucketList)
    {
      Iterator<TLinkBucket> it = bucketList.iterator();
      while (it.hasNext())
      {
        tlb = it.next();
        if (tlb.getTransport() == TTransport.TCP &&
            tlb.getBucketPort() != initializer.getTCPPort()+fec.fecPortOffset)
          continue;
        if (tlb.getTransport() == TTransport.STREAM &&
            tlb.getBucketPort() != initializer.getStreamPort()+fec.fecPortOffset)
          continue;
        if (tlb.getBucketEndpoint() != fec.fecHost) continue;
        return (TBucketThread)tlb.getBucketThread();
      }
    }
    return null;
  }
  public int putTLinkBucket(TLinkBucket tb)
  {
    synchronized (bucketList)
    {
      bucketList.add(tb);
    }
    return 0;
  }
  private void removeIdleTLinkBuckets(long timeStamp)
  {
    synchronized (bucketList)
    {
      Iterator<TLinkBucket> tlbitr = bucketList.iterator();
      TLinkBucket tlb;
      Socket sck;
      try
      {
        while (tlbitr.hasNext())
        {
          tlb = tlbitr.next();
          if (tlb.getActiveLinks() == 0 &&
              timeStamp > tlb.getTimeLastActive() + TBucket.ALLOWED_IDLE_TIME)
          {
            tlb.setBucketPort(0);
            sck = tlb.getSocket();
            tlb.isDeactivating = false;
            tlbitr.remove();
            sck.shutdownInput();
            sck.shutdownOutput();
            sck.close();
            MsgLog.log("removeIdleTLinkBuckets", "remove idle tcp connection to "+tlb.getBucketEndpoint().getHostAddress(),0,null,1);
          }
        }
      }
      catch (IOException e)
      {
        MsgLog.log("removeIdleTLinkBuckets"," IOException : " + e.toString(),TErrorList.io_error,e,1);
      }
      catch (Exception e)
      {
        e.printStackTrace();
        MsgLog.log("removeIdleTLinkBuckets",e.toString(),TErrorList.code_failure,e,1);     
      }
    }
  }
  private int removeTLinkBucket(TLinkBucket tb,boolean removeLinks)
  {
    if (tb == null) return -1;
    for (int i = 0; i < numberTLinksInTable; i++)
    {
      if (linkTable[i] == null || linkTable[i].tb != tb) continue;
      linkTable[i].tb = null;
      if (removeLinks)
      {
        linkTable[i].active = false;
        linkTable[i].terminate = true;
      }
    }
    tb.setBucketPort(0);
    synchronized (bucketList)
    {
      bucketList.remove(tb);
    }
    try
    {
      Socket sck = tb.getSocket();
      if (sck != null && !sck.isClosed())
      {
        sck.shutdownInput();
        sck.shutdownOutput();
        sck.close();
      }
    }
    catch (IOException e)
    {
      MsgLog.log("removeBucketThread"," IOException : " + e.toString(),TErrorList.io_error,e,1);
    }
    catch (Exception e)
    {
      e.printStackTrace();
      MsgLog.log("removeBucketThread",e.toString(),TErrorList.code_failure,e,1);     
    }
    finally
    {
      tb.isDeactivating = false;
    }
    return 0;
  }
  public class TLinkFactoryShutdown extends Thread
  {
    public synchronized void run()
    {
      DbgLog.log("TLinkFactoryShutdown","shutting down link factory");
      this.setName("Link Factory shutdown");
      for (int i = 0; i < numberTLinksInTable; i++)
      {
        if (linkTable[i] != null && linkTable[i] != tNullLink)
        {
          linkTable[i].close();
        }
      }
    }   
  }
  public class TFactoryWatchdogThread extends Thread
  {
    private boolean isWaiting = false;
    public boolean isWaiting() { return isWaiting; }
    private ArrayList<TLink> deferredLinks = new ArrayList<TLink>();
    private TFecEntry deferredTarget = null;
    private void checkLink(TLink lnk,long ltime)
    {
      long deltatime, gracePeriod, tcpIdlePeriod;
      time = (int) (ltime / 1000); // time in seconds
      int syncLinkTimeouts = 0;
      int to_ceiling;
      boolean linkHasStatus = false;
      boolean suppressNotify = false;
      boolean isGlobalLink = false;
      boolean mcgShuffle = false;
      boolean linkRemovalAllowed = false;
      boolean trace = TLinkFactory.traceKey != null && lnk.isTraceLink();
      try
      {
        if (lnk.delayEstablishLink)
        {
          if (trace) lnk.traceLink("checkLink", "collect delayed link establishment");
          lnk.delayEstablishLink = false;
          if (lnk.canSendPacked())
          {
            if (deferredTarget == null) deferredTarget =  lnk.srvAddr.fecAddr;
            if (deferredTarget !=  lnk.srvAddr.fecAddr)
            { // oops!  going to a different FEC, wait another round ...
              lnk.delayEstablishLink(true);
              if (trace) lnk.traceLink("checkLink", "wrong target server -> continue delay");
              return;
            }
            lnk.needsToSendLinkRequest = true;
            deferredLinks.add(lnk);
          }
          else
          {
            if (trace) lnk.traceLink("checkLink", "prepare to send link renewal request");
            int sts = lnk.linkStatus;
            if (lnk.isRenewal()) lnk.hasRenewed = true;
            sendLinkRequest(lnk);
            lnk.setIsRenewal(false);
            lnk.linkStatus = sts;
          }
          return;
        }
        if (lnk.needsToStartLinkWatchdog)
        { // constructor will add itself to the factory hash table
          lnk.needsToStartLinkWatchdog = false;
          new TWatchdogLink(lnk);
        }
        linkRemovalAllowed = (lnk.sub != null && lnk.sub.starttime < time && (lnk.sub.linkLastTime/1000) < time);
        if (lnk.terminate == true && lnk.linkId > 0 && linkRemovalAllowed)
        { // marked for termination and removal
          if (trace) lnk.traceLink("checkLink", "remove terminated link");
          if (debugLevel > 1)
          {
            DbgLog.log("TFactoryWatchdogThread","remove terminated link " + lnk.linkId + " /" + lnk.cntName + "/"
                + lnk.expName + "/" + lnk.devName + " " + lnk.devProperty);
          }
          synchronized (lnk)
          { // wake up the calling thread
            lnk.notifyAll();
          }
          removeTLink(lnk);
        }
        if (lnk.removedFromMcaList > 0)
        { // wait a good 5 seconds before actually removing an MCA parent
          if (time > lnk.removedFromMcaList + 4) synchronized (mcaLst)
          { // re-check the removal flag ...
            if (lnk.removedFromMcaList > 0)
            {
              if (trace) lnk.traceLink("checkLink", "remove terminated MCA parent");
              unlinkMcaParent(lnk);
            }
          }
        }
        if (lnk.linkStatus != TErrorList.property_is_mca &&
            lnk.getMcaIndex() > 0)
        { // don't check the mca links (unless a cancel is needed)
          if (!lnk.active)
          {
            if (trace) lnk.traceLink("checkLink", "close inactive MCA element link");
            lnk.close();
          }
          return;
        }
        if (!lnk.active)
        {
          if (trace) lnk.traceLink("checkLink", "link is not active");
          return;
        }
        if (lnk.sub == null)
        {
          if (trace) lnk.traceLink("checkLink", "link subscription is null !");
          return; // what's the point ?
        }
        if (lnk.linkStatus == TErrorList.has_bitfield_tag &&
            lnk.mapSingleFieldToBitfield() != 0) return;
        if (lnk.linkStatus == TErrorList.has_structure_tag &&
            lnk.mapSingleFieldToStruct() != 0) return;
        if (lnk.linkStatus == TErrorList.property_is_mca)
        {
          lnk.linkStatusSource = TLink.STATUS_LOCAL;
          lnk.linkStatus = 0;
          if (trace) lnk.traceLink("checkLink", "trap property_is_mca status");
          return;
        }
        if (TErrorList.isCoercive(lnk.linkStatus))
        { // need to re-submit the link
          short mod = lnk.sub.mode;
          TLink xlnk = getExistingLink(lnk, lnk.dOutput, lnk.dInput);
          if (xlnk != null)
          { // join in  ...
            if (trace) lnk.traceLink("checkLink", "coerced link depends on link "+xlnk.linkId);
            linkTable[lnk.linkId] null;
            lnk.bindToParentLink(xlnk,lnk.dOutput,lnk.dInput);
            lnk.devAccess = TMode.CM_REGISTER;
            //if (lnk.sub != null) lnk.sub.mode = TMode.CM_REGISTER;
            if (deferredLinks.contains(lnk)) deferredLinks.remove(lnk);
            if (lnk.tlcb == null && lnk.tcb == null) lnk.needsWakeUpCall = true;
            if (xlnk.hasObtainedStatus)
            { // then handle this here quickly
              lnk.getOutputDataObject().pushBytes(xlnk.getOutputDataObject().getDataBuffer());
              lnk.linkStatus = xlnk.linkStatus;
              lnk.linkErrString = xlnk.linkErrString;
              cannotNotifyFromWatchdogThread = true;
              fireCallbackEvent("TLinkFactory.Watchdog", lnk);
              cannotNotifyFromWatchdogThread = false;
            }
          }
          else
          {
            if (trace) lnk.traceLink("checkLink", "resubmitting coerced link "+lnk.linkId);
            lnk.con = new TContract(lnk);
            lnk.sub = new TSubscription(lnk.con, lnk); // going out ...
            lnk.sub.mode = mod;
            sendLinkRequest(lnk);
            lnk.linkStatus = 0;
            lnk.linkStatusSource = TLink.STATUS_LOCAL;
          }
          return;
        }
        if (lnk.linkStatus == TErrorList.illegal_protocol)
        { // need to re-submit the link
          if (lnk.getTineProtocol() == 6)
          { // legacy server end-point
            lnk.setTineProtocol(5);
            short mod = lnk.sub.mode;
            lnk.sub = new TSubscription(lnk.con, lnk); // going out ...
            lnk.sub.mode = mod;
            sendLinkRequest(lnk);
            lnk.linkStatus = 0;
            lnk.linkStatusSource = TLink.STATUS_LOCAL;
            lnk.sub.linkLastTime = ltime;
            return;
          }
          lnk.sub.mode = TMode.CM_CANCEL;
          MsgLog.log("TFactoryWatchdogThread","link " + lnk.linkId + " protocol level " + lnk.getTineProtocol() + " is invalid",TErrorList.illegal_protocol,null,1);
        }
        if (lnk.linkStatus == TErrorList.reacquire_address)
        {
          lnk.lastEnsAddressRequest = ltime;
          lnk.srvAddr.getAddressFromENS(lnk.expName, lnk.cntName);
          sendLinkRequest(lnk);
          return;
        }
        if (lnk.linkInvalidCount > TO_THRESHOLD*3 &&
            lnk.lastEnsAddressRequest < ltime + ENS_BACKOFF_THRESHOLD)
        { // non_existent_elem could imply a 'moved' server !
          lnk.lastEnsAddressRequest = ltime;
          lnk.linkInvalidCount = 0;
          lnk.srvAddr.getAddressFromENS(lnk.expName, lnk.cntName);
        }
        if (TMode.isStream(lnk.sub.mode)) return; // streams don't timeout here!
        // the link is still active ...
        linkHasStatus = false;
        short baseMode = (short) (lnk.sub.mode & 0x00ff);
        tcpIdlePeriod = 3000;
        deltatime = ltime - lnk.sub.linkLastTime;
        gracePeriod = TIMEOUT_GRACE_INTERVAL; //lnk.devTimeout < 1000 ? 100 : 1000;
        if (deltatime > gracePeriod && lnk.isLinkReassignment)
          lnk.isLinkReassignment = false;
        if (TMode.hasLongDeadband(baseMode) && lnk.isAlive())
          gracePeriod += HEARTBEAT;
        isGlobalLink = baseMode == TMode.CM_GLOBAL;
        to_ceiling = lnk.devTimeout;
        if (lnk.isGrouped())
        { // belongs to a group: if synchronized find the best timeout ceiling
          TLinkGroup g = lnk.getGroup();
          if (g.getUpdateInterval() > 0 &&
              g.getSynchronizationLevel() == TLinkGroup.GRP_SYNC_INSYNC)
            to_ceiling = lnk.getGroup().getUpdateInterval();
        }
        if (deltatime > (long) (to_ceiling + gracePeriod) && TMode.canTimeOut(baseMode))
        { // link has timed out ...
          if (!lnk.canTimeout)
          { // is inside 'interpretIncomingData'
            lnk.sub.linkLastTime = ltime;
            if (trace) lnk.traceLink("checkLink", "(inside intrpretIncomingData: suppress timeout link "+lnk.linkId);
            return;
          }
          linkHasStatus = true;
          lnk.hasObtainedStatus = true;
          if (lnk.linkStatus == -1)
          { // a pending link has not come in within the alloted time
            lnk.lastLinkStatus = lnk.linkStatus = TErrorList.link_timeout;
            lnk.linkStatusSource = TLink.STATUS_LOCAL;
            if (trace) lnk.traceLink("checkLink", "pending link continues to timeout: link "+lnk.linkId);
            return;
          }
          if (lnk.srvAddr.isENSCall(lnk.expName,lnk.cntName) &&
              siblings.isEmpty() &&
              !lnk.cannotNotifyFromWatchdogThread)
          {
            TSrvEntry.toggleENS();
            lnk.srvAddr.getAddress(lnk.devName,lnk.expName,lnk.cntName);
          }
          if (autoLinkErrorAlarms && isRunningAsServer() && lnk.canSetAlarms)
          { // set some relevant alarm
            if (lnk.isInAlarmState ||
                lnk.linkTimeouts > RETRY_THRESHOLD ||
                baseMode == TMode.CM_SINGLE)
            {
              getEquipmentModuleFactory().setFecLinkErrorAlarm(lnk,baseMode);
              lnk.isInAlarmState = true;
            }
          }
          totalLinkTimeouts++;
          lnk.linkTimeouts++;
          suppressNotify = (lnk.retryOnTimeoutError && lnk.linkTimeouts < RETRY_THRESHOLD) ? true : false;
          if (debugLevel > 0)
          {
            DbgLog.log("TFactoryWatchdogThread","link " + lnk.linkId + " link timeout " + lnk.devTimeout
                + " exceeded : " + ltime + " vs " + lnk.sub.linkLastTime + " timeout counter : "
                + lnk.linkTimeouts);
            if (suppressNotify)
            {
              if (trace) lnk.traceLink("checkLink", "suppressing timeout notification: link "+lnk.linkId);
              DbgLog.log("TFactoryWatchdogThread","suppressing link timeout notification");
            }
          }
          lnk.notifyPending = false;
          mcgShuffle = false;
          if (lnk.linkTimeouts > TO_THRESHOLD*3 &&
              lnk.lastEnsAddressRequest < ltime + ENS_BACKOFF_THRESHOLD)
          {
            if (trace) lnk.traceLink("checkLink", "reaquire address for link "+lnk.linkId);
            lnk.lastEnsAddressRequest = ltime;
            lnk.linkTimeouts = 0; // reset it
            lnk.srvAddr.getAddressFromENS(lnk.expName, lnk.cntName);
            if (isGlobalLink) mcgShuffle = true;
          }
          if (lnk.linkStatus != TErrorList.link_blacklisted)
          { // set the link status
            lnk.linkStatus = TErrorList.link_not_open;
            lnk.linkStatusSource = TLink.STATUS_LOCAL;
          }
          else
          { // a blacklisted link keeps his status code!
            linkHasStatus = false;
          }
          if (!isGlobalLink)
          { // only do this if this is not a globals link !
            synchronized (pHdr) { pHdr.lnkCounter = 0; }
          }
          if (lnk.isGrouped())
          {
            if (!lnk.getGroup().canNotify(lnk)) suppressNotify = true;
          }
          if (lnk.isInsideCallback)
          {
            if (trace) lnk.traceLink("checkLink", "timed out link is inside callback !"+lnk.linkId);
            return;
          }
          if (!lnk.getCriticalSection()) return;
          if (lnk.linkTimeouts >  TO_RETRY_THRESHOLD)
          { // is there an error value ?
            fillinIncomingDataWithErrValue(lnk);
          }
          if (!suppressNotify && lnk.isGrouped())
          {
            lnk.notifyPending = false;
            if (!lnk.getGroup().canNotify(lnk))
            {
              suppressNotify = true;
            }
            else
            {
              if (debugLevel > 1)
                DbgLog.log("TFactoryWatchdogThread","all members of group have updated");
            }
          }
          // TODO: the logic below looks like it can be cleaned up a bit ...
          if (!suppressNotify && lnk.linkStatusLastNotification != lnk.linkStatus)
          {
            String msg;
            if (baseMode > TMode.CM_SINGLE)
              msg = "link status changed from "+TErrorList.getErrorString(lnk.linkStatusLastNotification)+" to "+TErrorList.getErrorString(lnk.linkStatus);
            else
              msg = TErrorList.getErrorString(lnk.linkStatus);
            MsgLog.log("TFactoryWatchdogThread",
                lnk.getFullDeviceNameAndProperty()+" "+msg,lnk.linkStatus,null,1);
            lnk.linkStatusLastNotification = lnk.linkStatus;
          }
          if (!suppressNotify && baseMode > TMode.CM_SINGLE) synchronized (lnk)
          {
            lnk.needsNotification = true;
            if (lnk.terminate == false)
            {
              allowSynchronousLinks = false;
              fecEntryWithTimeout = lnk.srvAddr.getFecAddr();
              cannotNotifyFromWatchdogThread = true;
              //fireCallbackEvent("TFactoryWatchdogThread",lnk);
              fireCallbackEventCheckDependencies("TFactoryWatchdogThread",lnk);
              cannotNotifyFromWatchdogThread = false;
              fecEntryWithTimeout = null;
              allowSynchronousLinks = true;
              lnk.needsNotification = false;
            }
            lnk.notifyAll(); // wake up the calling thread
            lnk.sub.linkLastTime = ltime;
          }
          lnk.freeCriticalSection();
          if (isGlobalLink)
          { // missing globals: try to take corrective action
            if (lnk.expName.compareToIgnoreCase("CYCLER") == 0)
            { // don't let missing cyclers report errors in the link table
              lnk.linkStatus = 0;
              lnk.linkStatusSource = TLink.STATUS_LOCAL;
            }
            if (lnk.srvAddr == null ||
                lnk.srvAddr.fecAddr == null ||
                lnk.srvAddr.fecAddr.fecHost == null)
              return;
            // globals timing out: check the address occasionally
            InetAddress mygrp = lnk.getMulticastGroup();
            String[] mcaddr = initializer.getGCastAddress().split("\\.");
            String[] ipaddr = lnk.srvAddr.fecAddr.fecHost.getHostAddress().split("\\.");
            String ip = mcaddr[0] + "." + mcaddr[1] + "." + ipaddr[2] + "." + ipaddr[3];
            InetAddress tgtgrp = InetAddress.getByName(ip);
            if (mygrp != null && !mygrp.equals(tgtgrp))
            { // new multicast address !
              if (debugLevel > 0)
                DbgLog.log("TFactoryWatchdogThread",lnk.getFullDeviceNameAndProperty()+" globals multicast group changed from "
                    + mygrp.toString()+" to "+tgtgrp.toString()+" "+TDataTime.toString(ltime));
              mcgShuffle = true;
            }
            if (mcgShuffle)
            {
              detachMulticastGroup(lnk.isGlobalsLink,mygrp);
              lnk.setMulticastGroup(null);
              if (numLinksInMulticastGroup(tgtgrp) == 0)
              {
                getGlobalsSocket().getSocket().joinGroup(tgtgrp);
              }
              lnk.setMulticastGroup(tgtgrp);
            }
            return;
          }
          if (baseMode > TMode.CM_SINGLE)
          {
            lnk.sub.mode |= TMode.CM_RETRY;
            lnk.sub.linkLastTime = ltime;
            if (trace) lnk.traceLink("checkLink", "resubmit link request for presistent link "+lnk.linkId);
            sendLinkRequest(lnk);
          }
          else if (baseMode == TMode.CM_SINGLE)
          {
            if (suppressNotify)
            {
              lnk.sub.mode |= TMode.CM_RETRY;
              lnk.sub.linkLastTime = ltime;
              if (trace) lnk.traceLink("checkLink", "resubmit link request for single link "+lnk.linkId);
              sendLinkRequest(lnk);
            }
            else if (lnk.getCriticalSection())
            {
              lnk.needsNotification = true;
              synchronized (lnk)
              {
                if (lnk.terminate == false)
                {
                  allowSynchronousLinks = false;
                  fecEntryWithTimeout = lnk.srvAddr.getFecAddr();
                  cannotNotifyFromWatchdogThread = true;
                  fireCallbackEvent("TFactoryWatchdogThread",lnk);
                  cannotNotifyFromWatchdogThread = false;
                  fecEntryWithTimeout = null;
                  allowSynchronousLinks = true;
                  lnk.needsNotification = false;
                }
                if (trace) lnk.traceLink("checkLink", "cancel link "+lnk.linkId);
                if (debugLevel > 1)
                  DbgLog.log("TFactoryWatchdogThread"," cancel single link " + lnk.linkId + " from watchdog" + " "
                      + TDataTime.toString(ltime));
                if ((lnk.sub.mode & TMode.CM_CONNECT) == TMode.CM_CONNECT)
                {
                  tcpIdlePeriod = 0;
                  lnk.sub.mode = (short) (TMode.CM_CANCEL | TMode.CM_CONNECT);
                }
                else
                {
                  lnk.sub.mode = TMode.CM_CANCEL; // turn off the link
                }
                lnk.active = false; lnk.notifyPending = false;
                syncLinkTimeouts++;
                if ((lnk.linkTimeouts > TO_THRESHOLD || syncLinkTimeouts > TO_THRESHOLD) &&
                    lnk.lastEnsAddressRequest < ltime + ENS_BACKOFF_THRESHOLD)
                {
                  lnk.linkTimeouts = 0;
                  syncLinkTimeouts = 0;
                  lnk.lastEnsAddressRequest = ltime;
                  lnk.cannotNotifyFromWatchdogThread = true;
                  lnk.srvAddr.getAddressFromENS(lnk.expName, lnk.cntName);
                  lnk.cannotNotifyFromWatchdogThread = false;
                  if (trace) lnk.traceLink("checkLink", "reacquire address for link "+lnk.linkId);
                }
                if (lnk.removeOnClose)
                {
                  if (trace) lnk.traceLink("checkLink", "mark link as terminated for link "+lnk.linkId);
                  if (debugLevel > 1) DbgLog.log("TFactoryWatchdogThread","mark link " + lnk.linkId + " for termination");
                  lnk.terminate = true; // flush the table next time through
                }
                lnk.notifyAll(); // wake up the calling thread
              }
              lnk.freeCriticalSection();
            }
          }
        }
        if (deltatime > (long) (lnk.devTimeout + tcpIdlePeriod)
            && lnk.sub.mode == (TMode.CM_CANCEL | TMode.CM_CONNECT)
            && lnk.linkBlacklists == 0 && lnk.tb != null)
        {
          if (((TLinkBucket)lnk.tb).getActiveLinks() == 0)
            removeTLinkBucket((TLinkBucket)lnk.tb,true);
        }
        if (lnk.sub.mode == TMode.CM_CANCEL && !lnk.active && lnk.linkStatus != -1)
        {
          if (debugLevel > 1) DbgLog.log("TFactoryWatchdogThread","watchdog ignoring cancelled link " + lnk.linkId);
          //lnk.terminate = true;
        }
        if (debugLevel > 3 || (debugLevel > 2 && linkHasStatus))
        {
          DbgLog.log("TFactoryWatchdogThread","link : " + "/" + lnk.cntName + "/" + lnk.expName + "/" + lnk.devName
              + " " + lnk.devProperty + " : linkStatus " + lnk.linkStatus + " Link active : "
              + lnk.active + " terminate : " + lnk.terminate + " " + TDataTime.toString(ltime));
        }             
      }
      catch (Exception e)
      {
        e.printStackTrace();
        MsgLog.log("TFactoryWatchdogThread", e.toString(), TErrorList.code_failure,e, 1);       
      }
    }
    public synchronized void run()
    {
      this.setName("Link Factory watchdog");
      MsgLog.log("TFactoryWatchdogThread","Thread " + getName() + " started ...",0,null,1);
      TLink lnk;
      long ltime;
      ltime = System.currentTimeMillis();
      time = (int) (ltime / 1000); // time in seconds
      int lasttime = time;
      while (active)
      {
        if (terminate) break;
        try
        {
          if (!hasDeferredLinks())
          {
            isWaiting = true;
            try { wait(100); } catch (InterruptedException fallthru) {}
          }
          isWaiting = false;
          ltime = System.currentTimeMillis();
          time = (int) (ltime / 1000);
          if (lasttime != time)
          { // do things in here once per second
            if ((lasttime % BLACKLIST_FLUSH_INTERVAL) == 0)
            { // get the black listed links a second chance every now and then
              flushBlackList();
            }
            if (LockedLnkLst.size() > 0) checkAccessLockItems();
            flushGroupCacheItems();
            lasttime = time;
          }
          // below happens at 10 Hz ...
          removeIdleTLinkBuckets(ltime);
          deferredLinks.clear();
          deferredTarget = null;
          fecEntryWithTimeout = null;
          synchronized (TLinkFactory.siblings)
          {
            for (int i=0; i<TLinkFactory.siblings.size(); i++)
            {
              checkLink(TLinkFactory.siblings.get(i),ltime);
            }
          }
          for (int i = 0; i < numberTLinksInTable; i++)
          {
            if (linkTable[i] != null && linkTable[i] != tNullLink)
            {
              lnk = linkTable[i];
              if (lnk.linkId != i && lnk.boundTo != null)
              {
                DbgLog.log("TFactoryWatchdogThread","link table id does not match entry index !");
              }
              checkLink(lnk,ltime);
            }
          }
          if (deferredLinks.size() > 0)
          {
            TLink[] lnks = deferredLinks.toArray(new TLink[0]);
            while (sendLinkRequest(lnks) > 0);
          }
          hasDeferredLinks = false;
        }
        catch (Exception e)
        {
          if (!active)
          { // program terminating ?
            MsgLog.log("TFactoryWatchdogThread", "TLinkFactory no longer active", TErrorList.not_running, e, 0);
            return;
          }
          e.printStackTrace();
          MsgLog.log("TFactoryWatchdogThread", e.toString(), TErrorList.code_failure,e, 1);
        }
      }
      removeAccessLock(null, null);
    }
  }
  private int NotifyDeferredCallbacks()
  {
    TLink lnk;
    for (int i = 0; i < numberTLinksInTable; i++)
    {
      if (linkTable[i] != null && linkTable[i] != tNullLink)
      {
        lnk = linkTable[i];
        if (lnk.needsNotification)
        {
          synchronized (lnk)
          {
            if (lnk.terminate == false)
            {
              fireCallbackEvent("NotifyDeferredCallbacks", lnk);
            }
            lnk.needsNotification = false;
          }
          lnk.notifyAll(); // wake up the calling thread
        }
      }
    }
    return 0;
  }
  public int fillinIncomingData(TLink lnk)
  {
    String key = lnk.cntName == null ? null : "/"+lnk.cntName+"/"+lnk.expName;
    int cc = fillinIncomingData(lnk.dOutput,key);
    if (cc == 0 && lnk.relnkItem != null)
    { // remap the contents ?
      if (lnk.relnkItem.getRelinkReason() == TErrorList.invalid_datarequest)
      {
        RelinkedItem rli = lnk.relnkItem;
        TDataType dt = rli.getTargetDataObject();
        lnk.dOutput.getData(); // fill in the link's data object
        int len = lnk.dOutput.dCompletionLength;
        cc = rli.tdt.getData(dt.getDataObject()); // map to destination
        if (len < dt.dArrayLength) dt.dCompletionLength = len;
        dt.setDataTimeStamp(lnk.dOutput.dTimestamp);
        dt.setUserDataStamp(lnk.dOutput.usrDataStamp);
        dt.setSystemDataStamp(lnk.dOutput.sysDataStamp);
      }
      else if (lnk.dOutput.getFormat() == TFormat.CF_STRUCT)
      {
        try
        {
          RelinkedItem rli = lnk.relnkItem;
          TStructDescription sd = rli.getStructDescription();
          if (sd == null) cc = TErrorList.invalid_structure_tag;
          TStructDescription.Field f = sd.getField(rli.getField());
          if (f == null) cc = TErrorList.invalid_field;
          int len = lnk.dOutput.dCompletionLength;
          int siz = f.getArraySize() * TFormat.formatSizeOf(f.getFormat());
          int ssiz = sd.getSize();
          int soff = f.getAddress();
          byte[] src = (byte[])lnk.dOutput.getDataObject();
          byte[] dst = new byte[len * siz];
          TDataType dt = rli.getTargetDataObject();
          for (int i=0; i<len; i++)
          {
            System.arraycopy(src, soff + i*ssiz, dst, i*siz, siz)
          }
          dt.setDataTimeStamp(lnk.dOutput.dTimestamp);
          dt.setUserDataStamp(lnk.dOutput.usrDataStamp);
          dt.setSystemDataStamp(lnk.dOutput.sysDataStamp);
          dt.pushBytes(dst);
          dt.setStructureKey(key);
          cc = dt.getData();
        }
        catch (Exception ignore)
        { // just forward the completion code
          lnk.canTimeout = true;
        };
      }
    }
    return cc;
  }
  public int fillinIncomingData(TDataType dtype)
  {
    return fillinIncomingData(dtype,null);
  }
  private int fillinIncomingData(TDataType dtype,String key)
  {
    Object hDataObject = dtype.getDataObject();
    String bff = null;
    switch (dtype.getFormat())
    {
      case TFormat.CF_STRING:
        dtype.getData((String[]) hDataObject);
        break;
      case TFormat.CF_KEYVALUE:
        dtype.getData((KEYVALUE[]) hDataObject);
        break;
      case TFormat.CF_STRUCT:
        Object hStructObject = dtype.getStructObject();
        TTaggedStructure ts = null;
        boolean hasExtSpace = false;
        if (hStructObject instanceof TTaggedStructure)
        {
          ts = (TTaggedStructure)hStructObject;
          hasExtSpace = ts.hasExtendedSpace();
        }
        else if (hStructObject instanceof TTaggedStructure[])
        {
          ts = ((TTaggedStructure[])hStructObject)[0];
          hasExtSpace = ts.hasExtendedSpace();
        }
        if (ts == null)
        {
          TStructDescription sd = TStructRegistry.get(dtype.getTag(),key);
          hasExtSpace = (sd != null && sd.hasExtendedSpace());
        }
        if (dtype.getStructureKey() == null) dtype.setStructureKey(key);
        //if (hasExtSpace) hDataObject = new byte[dtype.bytesin];
        byte[] hBytes = hasExtSpace ? new byte[dtype.bytesin] : (byte[])hDataObject;
        if (hBytes != null) dtype.getData((byte[])hBytes);
        if (hStructObject instanceof TTaggedStructure)
        {
          ((TTaggedStructure)hStructObject).toStruct(hBytes);
        }
        else if (hStructObject instanceof TTaggedStructure[])
        {
          TTaggedStructure[] tts = (TTaggedStructure[])hStructObject;
          int dsiz = tts[0].getSizeInBytes();
          for (int i = 0; i < tts.length; i++)
          {
            tts[i].toStruct(hBytes, i * dsiz, dsiz);
          }
        }
        if (hBytes != hDataObject)
        {
          System.arraycopy(hBytes, 0, hDataObject, 0, ((byte[])hDataObject).length);
        }
        break;
      case TFormat.CF_BITFIELD8:
      case TFormat.CF_BITFIELD16:
      case TFormat.CF_BITFIELD32:
      case TFormat.CF_BITFIELD64:
        bff = dtype.getField();
      default:
        if (hDataObject != null) dtype.getData(hDataObject);
        if (bff != null) dtype.applyBitField();
        break;
    }
    return 0;
  }
  public int fillinIncomingDataWithErrValue(TLink lnk)
  {
    if (lnk.linkStatus == 0) return 0;
    if (lnk.useErrObject)
    {
      lnk.dOutput.dataCopy(lnk.dError);
      return 0;
    }
    if (lnk.useErrValue)
    {
      lnk.dOutput.dataFill(lnk.linkErrValue);
      if (lnk.linkErrString != null) lnk.dOutput.dataFill(lnk.linkErrString);
    }
    return 0;
  }
  protected TFecEntry fecEntryWithTimeout = null;
  protected boolean allowSynchronousLinks = true;
  protected boolean cannotNotifyFromWatchdogThread = false;
  public void fireCallbackEvent(String msg,TLink lnk)
  {
    boolean trace = traceKey != null && lnk.isTraceLink();
    if (trace)
    {
      lnk.traceLink("fireCallbackEvent", "is ready to fire callback (status "+lnk.linkStatus+")");
    }
    if (lnk.linkStatus < 0)
    {
      if (lnk.boundTo != null)
      { // some crazy jddd re-shuffling
        if (lnk.boundTo.linkStatus < 0 && lnk.boundTo.lastLinkStatus < 0) return;
        lnk.lastLinkStatus = lnk.boundTo.lastLinkStatus;         
      }
      else
      {
        if (lnk.lastLinkStatus < 0) return;
        lnk.linkStatus = lnk.lastLinkStatus;
      }
    }
    if (!lnk.isWithinTolerance())
    {
      if (trace) lnk.traceLink("fireCallbackEvent", "needs to notify !");
      lnk.isInsideCallback = true;
      lnk.lastLinkNotification = System.currentTimeMillis();
      if (debugLevel > 3)
        DbgLog.log("TLinkFactory.fireCallbackEvent","fire callback for link "+lnk.getFullDeviceName()+"["+lnk.getProperty()+"] (id "+lnk.linkId+")");
      try
      {
        if (lnk.tcb != null)
          lnk.tcb.callback(lnk.callbackId,lnk.linkStatus);
        else if (lnk.tlcb != null)
          lnk.tlcb.callback(lnk);
        else if (lnk.needsWakeUpCall)
          lnk.wakeUpCall();
      }
      catch (Throwable e)
      {
        e.printStackTrace();
        MsgLog.log(msg,"unhandled exception " + e.toString() + " inside link callback",TErrorList.runtime_error,e,0);
      }
      lnk.isInsideCallback = false;
      if (lnk.linkStatus == TErrorList.reset_mca_property && !lnk.isMcaParent())
      { // force a re-issue of the link following the callback
        if (trace) lnk.traceLink("fireCallbackEvent", "reset MCA information !");
        MsgLog.log("TMcaLink.callback","received reset mca property signal for "+
            lnk.getFullDeviceNameAndProperty(),lnk.linkStatus, null,1);
        lnk.sub.starttime = (int)(System.currentTimeMillis()/1000);
        lnk.linkCounter = RENEWAL_REMINDER-1;
        lnk.sub.linkLastTime = 0;
      }
    }
    else
    {
      if (trace) lnk.traceLink("fireCallbackEvent", "suppress callback information");
      lnk.lastLinkSuppressedNotification = System.currentTimeMillis();
      if (debugLevel > 3)
        DbgLog.log("TLinkFactory.fireCallbackEvent","suppress callback for link "+lnk.getFullDeviceName()+"["+lnk.getProperty()+"] (id "+lnk.linkId+")");     
    }
    lnk.hasNotifiedOnce = true;
    lnk.hasObtainedStatus = true;
    lnk.isLinkReassignment = false;
    if (TErrorList.getErrorCode(lnk.linkStatus) == TErrorList.information_static)
    { // no reason to keep this link open
      if (lnk.sub.mode != TMode.CM_REGISTER) lnk.close();
    }
  }
  public void fireCallbackEventCheckDependencies(String msg,TLink lnk)
  {
    boolean trace = traceKey != null && lnk.isTraceLink();
    if (trace) lnk.traceLink("fireCallbackEventCheckDependencies", "is ready to fire callback");
    if (lnk.hasDependencies())
    { // chained dependencies ? -> do them first
      TLink xlnk;
      TDataType xlnkData;
      LinkedList<TLink> xlst = lnk.getDependencies();
      for (int k=0; k < xlst.size(); k++)
      {
        xlnk = (TLink)xlst.get(k);
        if (xlnk == null || xlnk.sub == null) continue;
        if ((xlnkData=xlnk.getOutputDataObject()) != null)
        {
          xlnkData.dataCopy(lnk.dOutput);
          xlnkData.setDataTimeStamp(lnk.dOutput.getDataTimeStamp());
        }                         
        xlnk.sub.linkLastTime = lnk.sub.linkLastTime;
        xlnk.linkStatus = lnk.linkStatus;
        xlnk.linkStatusSource = lnk.linkStatusSource;
        fireCallbackEvent(msg,xlnk);
      }
    }
    if (!lnk.isCancelledWithDependencies())
    {
      fireCallbackEvent(msg,lnk);
    }       
  
  }
  private int InterpretIncomingData(int transport,byte[] data, int length, InetAddress addr, int port, boolean deferCallbacks)
  {
    TLink lnk;
    boolean hasData;
    int n = 0;
    TLink[] lnks = null;
    synchronized (pHdr)
    { // this will run through everything that has come in and fill in the blanks
      // also checking return code vs server_redirection, etc. ...
      lnks = pHdr.toStruct(addr, port, data, length, transport);
    }
    if (lnks == null) return 0;
    Thread.yield();
    long ltime = System.currentTimeMillis();
    if (debugLevel > 1)
      DbgLog.log("InterpretIncomingData","recv " + length + " bytes "+lnks.length+" contracts");
    try
    {
      for (int i = 0; i < lnks.length; i++)
      { // this is what just came in ...
        lnk = lnks[i];
        if (debugLevel > 1)
        {
          String dbgstr = "link " + lnk.linkId + "(" +
             "/"+lnk.cntName+"/"+lnk.expName+"/"+lnk.devName +
             "["+lnk.devProperty+"] : " +
             (lnk.active ? "active" : "inactive") +
             (lnk.linkStale ? " stale" : " not stale" +
             " -> " + lnk.dOutput.blksin + " blks in from " +
             lnk.dOutput.numblks + " <" + lnk.linkStatus + ">");
          DbgLog.log("InterpretIncomingData",dbgstr);
        }
        if (!lnk.active)
        {
          if (TMode.getBaseMode(lnk.sub.mode) == TMode.CM_CANCEL)
          { // not active should always mean that the mode = CM_CANCEL
            if (lnk.linkStatus != TErrorList.invalid_link &&
                pHdr.lnkStarttime != TFecEntry.BCAST_ID &&
                pHdr.lnkCounter > TContract.CTR_RENEWAL)
            { // not in a client-server 'cancel' loop and not a multicast subscription
              sendLinkRequest(lnk);
            }
          }
          continue;
        }
        if (!lnk.linkStale) continue;
        lnk.linkStale = false;
        lnk.linkPeer = addr;
        // use the phs.subId to get the incoming link id !
        if (lnk.dOutput.blksin == lnk.dOutput.numblks)
        { // ready to deal with !
          n++;
          lnk.sub.linkLastTime = ltime;
          if (lnk.adjustDefaultValues)
          { // adjust contract upon first completion
            int fmtsize = TFormat.formatSizeOf(lnk.dOutput.dFormat);
            int dretsize = lnk.dOutput.bytesin;
            if (lnk.linkStatus != 0) dretsize -= TDataType.RPCERR_SIZE;
            // TODO: CF_STRING ?
            if (dretsize > 0 && fmtsize > 0)
            {
              dretsize -= TFormat.getFormatHeaderSize(lnk.dOutput.dFormat);
              lnk.dOutput.dArrayLength = dretsize / fmtsize;
            }
            lnk.adjustDefaultValues = false;
          }
          lnk.notifyPending = false;
          if (lnk.linkStatus == TErrorList.get_subscription_id) continue;
          if (lnk.linkStatus == 0 || (lnk.linkStatus & TErrorList.CE_SENDDATA) == TErrorList.CE_SENDDATA)
            hasData = true;
          else
            hasData = false;
          if (hasData)
          {
            if (debugLevel > 1)
              DbgLog.log("InterpretIncomingData","call getData for link " + lnk.linkId + " format "
                  + TFormat.toString(lnk.dOutput.getFormat()) + " "
                  + TDataTime.toString(System.currentTimeMillis()));
            // do something with it (events ?) ...
            lnk.dOutput.isDataObjectInSync = false;
            fillinIncomingData(lnk);
          }
          else
          {
            fillinIncomingDataWithErrValue(lnk);
          }
          lnk.hasObtainedStatus = true;
          if (lnk.isGrouped())
          {
            TLinkGroup grp = lnk.getGroup();
            //if (grp.getNumberPending() > 0) continue;
            //grp.reset();
            if (!grp.canNotify(lnk)) continue;
            if (debugLevel > 1) DbgLog.log("InterpretIncomingData","all members of group have updated");
          }
          if (lnk.linkStatus >= 0)
          {
            if (lnk.linkStatus == TErrorList.illegal_property ||
                lnk.linkStatus == TErrorList.non_existent ||
                lnk.linkStatus == TErrorList.non_existent_elem ||
                lnk.linkStatus == TErrorList.non_existent_fec )
            {
              boolean blacklistIt = !lnk.isWildcardLink;
              if (lnk.linkStatus == TErrorList.non_existent_elem &&
                  lnk.linkTimeouts > 0) blacklistIt = false;
              if (blacklistIt)
              {
                addLinkToBlackList(lnk);
                if (debugLevel > 0)
                  DbgLog.log("InterpretIncomingData","add link " + lnk.getFullDeviceNameAndProperty() + " to black list");
              }
              else
              {
                if (debugLevel > 0)
                  DbgLog.log("InterpretIncomingData","link " + lnk.getFullDeviceNameAndProperty() + " returned " + TErrorList.getErrorString(lnk.linkStatus));
              }
            }
            else
            { // reset this counter
              lnk.linkBlacklists = 0;
            }
            lnk.notifyPending = true;
            if (lnk.dOutput.timestamp == 0)
            {
              lnk.dOutput.timestamp = (int) (ltime / 1000);
              lnk.dOutput.timestampMSEC = (int) (ltime % 1000);
              lnk.dOutput.timestampUSEC = lnk.dOutput.timestampMSEC * 1000;
            }
            lnk.linkTimeouts = 0; // reset timeout counter
            // if caller "cancel()s" then terminate is set to true (should be polite and tell the server).
            // if the link automatically terminates (due to SINGLE -> CANCEL, etc.
            // then the TMode is set to CM_CANCEL (don't need to tell the server)
            if (lnk.sub.mode == TMode.CM_CANCEL) lnk.active = false;
            if (lnk.isInAlarmState)
            {
              lnk.isInAlarmState = false;
              if (autoLinkErrorAlarms && isRunningAsServer())
              { // remove any link error alarms
                getEquipmentModuleFactory().clearFecLinkErrorAlarm(lnk);
              }                         
            }
            // set notification flag:
            if (lnk.linkStatusLastNotification != lnk.linkStatus)
            {
              MsgLog.log("InterpretIncomingData",
                  lnk.getFullDeviceName()+"["+lnk.getProperty()+"] link status changed from "+
                  TErrorList.getErrorString(lnk.linkStatusLastNotification)+" to "+TErrorList.getErrorString(lnk.linkStatus),
                  lnk.linkStatus,null,1);
              lnk.linkStatusLastNotification = lnk.linkStatus;
              if (lnk.linkStatus == 0)
              { // is now successful again !
                lnk.hasNotifiedOnce = false;
              }
            }
            lnk.needsNotification = true;           
            lnk.dOutput.bytesin = 0; // reset here !
            if (!lnk.getCriticalSection()) continue;
            synchronized (lnk)
            {
              try
              {
                if (lnk.active == true)
                {
                  if (!lnk.hasNotifiedOnce)
                  { // first round of callbacks ...
                    if (autoLinkWatchdogs && TWatchdogLink.isWatchableLink(lnk))
                    {
                      lnk.needsToStartLinkWatchdog = true;
                    }
                  }
                  if (!deferCallbacks || lnk.isWildcardLink)
                  {
                    isInsideCallback = true;
                    if (lnk.hasDependencies() && !lnk.isMcaParent())
                    { // chained dependencies (but not MCA links) ? -> do them first
                      Hashtable<TLink,Thread> tht = new Hashtable<TLink,Thread>();
                      Thread th;
                      TLink xlnk;
                      TDataType xlnkData;
                      LinkedList<TLink> xlst = lnk.getDependencies();
                      for (int k=0; xlst != null && k < xlst.size(); k++)
                      {
                        xlnk = (TLink)xlst.get(k);
                        if (xlnk == null || xlnk.sub == null) continue;
                        if ((xlnkData=xlnk.getOutputDataObject()) != null)
                        {
                          xlnkData.dataCopy(lnk.dOutput);
                          String key = lnk.cntName == null ? null : "/"+lnk.cntName+"/"+lnk.expName;
                          xlnkData.setStructureKey(key);
                          xlnkData.getData(); // Karol, 03.12.2009
                          xlnkData.setDataTimeStamp(lnk.dOutput.getDataTimeStamp());
                        }                         
                        xlnk.sub.linkLastTime = lnk.sub.linkLastTime;
                        xlnk.linkStatus = lnk.linkStatus;
                        fireCallbackEvent("InterpretIncomingData",xlnk);
                        if ((th=xlnk.getThread()) != lnk.getThread())
                        { // if dependent links belong to different threads than the parent !
                          if (!tht.containsValue(th)) tht.put(xlnk, th);
                        }
                      }
                      if (tht.size() > 0)
                      { // synchronous calls depend on being 'notified' to stop waiting!
                        for (TLink k : tht.keySet()) k.wakeUpCall();
                      }
                    }
                    if (!lnk.isCancelledWithDependencies())
                    {
                      fireCallbackEvent("InterpretIncomingData",lnk);
                    }
                    isInsideCallback = false;
                  }
                  lnk.needsNotification = false;
                }
                else
                {
                  lnk.sub.mode = TMode.CM_CANCEL;
                }
                // wake up the calling thread
                if (!lnk.isWildcardLink) lnk.notifyAll();
              }
              catch (Throwable e)
              {
                e.printStackTrace();
                MsgLog.log("InterpretIncomingData","unexpected exception",TErrorList.code_failure,e,0);
              }
            }
            if ((lnk.devAccess & TAccess.CA_SYNC) != 0 && TMode.getBaseMode(lnk.sub.mode) == TMode.CM_SINGLE)
            {
              if (debugLevel > 1)
                DbgLog.log("InterpretIncomingData","cancel single link ("+lnk.linkId+") "+lnk.getFullDeviceNameAndProperty());
              if ((lnk.sub.mode & TMode.CM_CONNECT) == TMode.CM_CONNECT)
              {
                lnk.sub.mode = (short) (TMode.CM_CANCEL | TMode.CM_CONNECT);
              }
              else
              {
                lnk.sub.mode = TMode.CM_CANCEL; // turn off the link
                lnk.active = false;
              }
              if (lnk.removeOnClose)
              { // don't cause link removal here !
                if (debugLevel > 1)
                  DbgLog.log("InterpretIncomingData","mark link ("+lnk.linkId+") "+lnk.getFullDeviceNameAndProperty()+ " for termination");
                lnk.terminate = true; // let the watchdog thread flush the table
              }
            }
            if (lnk.sub.mode == TMode.CM_SINGLE)
            { // async single link (not connected)
              if (debugLevel > 1)
                DbgLog.log("InterpretIncomingData","de-activate single link ("+lnk.linkId+") "+lnk.getFullDeviceNameAndProperty());
              if (!lnk.keepActive) lnk.active = false;
              lnk.keepActive = false;
            }
            lnk.freeCriticalSection();
          }
          // need to re-establish the link ?
          if (debugLevel > 2)
            DbgLog.log("InterpretIncomingData","link ("+lnk.linkId+") "+lnk.getFullDeviceNameAndProperty()+" Link counter: " + lnk.linkCounter + " at " + ltime);
          checkLinkRenewalCondition(lnk);
        }
      }
    }
    catch (Exception e)
    {
      e.printStackTrace();
      MsgLog.log("InterpretIncomingData","processing error",TErrorList.code_failure,e,0);
    }
    for (int i = 0; i < lnks.length; i++) lnks[i].canTimeout = true;
    return 0;
  }
  private boolean delayLinkRenewals = true;
  public void setDelayLinkRenewals(boolean value)
  {
    delayLinkRenewals = value;
  }
  public boolean getDelayLinkRenewals()
  {
    return delayLinkRenewals;
  }
  public void checkLinkRenewalCondition(TLink lnk)
  { // reset counter if < RENEWAL_REMINDER !!!!
    if (!lnk.active) return;
    int renewalMultiplier = TSubscription.getRenewalMultiplier(lnk.renewalMultiplier, lnk.sub.pollingInterval);
    if (lnk.sub.isLegacy && renewalMultiplier > 4) renewalMultiplier = 4;
    if (TMode.getBaseMode(lnk.sub.mode) > TMode.CM_SINGLE &&
        lnk.linkCounter > 0 &&
        lnk.linkCounter < renewalMultiplier*RENEWAL_REMINDER &&
        lnk.linkStatus != TErrorList.link_blacklisted)
    { // a persistent link !
      if (!lnk.hasRenewed || lnk.linkCounter < renewalMultiplier*RENEWAL_URGENT)
      {
        if (delayLinkRenewals &&
            !lnk.needsToStartLinkWatchdog &&
            lnk.linkCounter > RENEWAL_URGENT)
        {
          if (debugLevel > 1)
            DbgLog.log("InterpretIncomingData","post link renewal ("+lnk.linkId+") "+lnk.getFullDeviceNameAndProperty());
          lnk.delayEstablishLink(true);
          lnk.setIsRenewal(true);
          return;
        }
        lnk.hasRenewed = true;
        int sts = lnk.linkStatus;
        if (debugLevel > 1)
          DbgLog.log("InterpretIncomingData","send link renewal ("+lnk.linkId+") "+lnk.getFullDeviceNameAndProperty());
        sendLinkRequest(lnk);
        lnk.linkStatus = sts; // P.D. 6.4.08 : smooth things out regarding the link status
      }
    }
    else
    {
      lnk.hasRenewed = false;
    }   
  }
  private int InterpretIncomingGlobalsData(InetAddress gaddr, byte[] data, int length, boolean deferCallbacks)
  {
    TLink glb;
    TLink[] lnks = null;
    synchronized (glbHdr)
    {
      lnks = glbHdr.toStruct(gaddr, data, length);
    }
    if (lnks == null) return 0;
    if (debugLevel > 1) DbgLog.log("InterpretIncomingGlobalsData","glb recv " + length + " bytes, "+lnks.length+" global links");
    for (int i = 0; i < lnks.length; i++)
    {
      glb = lnks[i];
      if (glb.sub.mode == TMode.CM_GLOBAL)
      {
        glb.sub.linkLastTime = System.currentTimeMillis();
        glb.notifyPending = false;
        if (debugLevel > 2) DbgLog.log("InterpretIncomingGlobalsData","call getData for format " + glb.dOutput.getFormat());
        if (glb.linkStatus >= 0 && glb.notifyPending == false)
        {
          glb.notifyPending = true;
          //glb.dOutput.timestamp = time;  <<< this was assigned in glbHdr.toStruct() !
          glb.linkTimeouts = 0; // reset timeout counter
          // if caller "cancel()s" then terminate is set to true
          // if the link automatically terminates (due to SINGLE -> CANCEL,etc.
          // then the TMode is set to CM_CANCEL (don't need to tell the server)
          if (glb.linkStatusLastNotification != glb.linkStatus)
          {
            MsgLog.log("InterpretIncomingGlobalsData",
                glb.getFullDeviceName()+"["+glb.getProperty()+"] link status changed from "+
                TErrorList.getErrorString(glb.linkStatusLastNotification)+" to "+TErrorList.getErrorString(glb.linkStatus),
                glb.linkStatus,null,1);
            glb.linkStatusLastNotification = glb.linkStatus;
          }
          if (glb.isInAlarmState)
          {
            glb.isInAlarmState = false;
            if (autoLinkErrorAlarms && isRunningAsServer())
            { // remove any link error alarms
              getEquipmentModuleFactory().clearFecLinkErrorAlarm(glb);
            }                         
          }
          glb.needsNotification = true;
          synchronized (glb)
          {
            try
            {
              if (glb.terminate == false)
              {
                isInsideCallback = true;
                if (glb.hasDependencies())
                { // chained dependencies ? -> do them first
                  TLink xlnk;
                  TDataType xlnkData;
                  LinkedList<TLink> xlst = glb.getDependencies();
                  for (int k=0; k < xlst.size(); k++)
                  {
                    xlnk = (TLink)xlst.get(k);
                    if ((xlnkData=xlnk.getOutputDataObject()) != null)
                    {
                      xlnkData.dataCopy(glb.dOutput);
                      xlnkData.setDataTimeStamp(glb.dOutput.getDataTimeStamp());
                    }                         
                    xlnk.sub.linkLastTime = glb.sub.linkLastTime;
                    xlnk.linkStatus = glb.linkStatus;
                    fireCallbackEvent("InterpretIncomingGlobalsData", xlnk);
                  }
                }
                if (!glb.isCancelledWithDependencies())
                {
                  fireCallbackEvent("InterpretIncomingGlobalsData", glb);
                }
                isInsideCallback = false;
              }               
              glb.notifyAll(); // wake up the calling thread
            }
            catch (Exception e)
            {
              e.printStackTrace();
              MsgLog.log("InterpretIncomingGlobalsData","unhandled execption"+e.toString(),TErrorList.runtime_error,e,0);
            }
          }
        }
      }
      glb.hasObtainedStatus = true;
    }
    return 0;
  }
  public class TFactoryThread extends Thread
  {
    TPacket tp;
    boolean isWaiting = false;
    boolean canCallback = false;
    String tag = null;
    TFactoryThread(TPacket tPacket,String tag,boolean canFireCallback)
    {
      this.tag = tag;
      tp = tPacket;
      canCallback = canFireCallback;
      String s = null;
      MulticastSocket sck;
      if ((sck=tp.getSocket()) != null)
      {
        String dsc = tp.isMulticastListener() ? "(multicast listener) " : "";
        s = new String(" datagram port " + dsc + sck.getLocalPort());
      }
      else s = new String(" unbound socket ?");
      this.setName("Link Factory "+tag+s);
      //this.setPriority(MAX_PRIORITY);
    }
    public synchronized void run()
    {
      MsgLog.log("TFactoryThread","Link Factory Thread " + getName() + " started ...",0,null,1);
      MulticastSocket sck = tp.getSocket();
      if (sck == null)
      {
        MsgLog.log("TFactoryThread", "unable to obtain "+tag+" socket!",TErrorList.code_failure,null,0);
        return;
      }
      while (active)
      {
        if (terminate) break;
        if (debugLevel > 1) DbgLog.log("TFactoryThread","Waiting for data ...");
        try
        {
          isWaiting = true;
          tp.dpIn.setLength(TPacket.MAX_DATAGRAM_SIZE); // reset the receive buffer length
          if (sck != null)
          {
            sck.receive(tp.dpIn);
          }
          totalConnectionArrivals++;
          isWaiting = false;
          // got it, now interpret it ...
          if (debugLevel > 1) DbgLog.log("TFactoryThread","examine " + tp.dpIn.getLength() + " bytes");
          InterpretIncomingData(TTransport.UDP,tp.dpIn.getData(), tp.dpIn.getLength(), tp.dpIn.getAddress(),tp.dpIn.getPort(),!canCallback);
        }
        catch (IOException e)
        {
          MsgLog.log("TFactoryThread",e.toString(),TErrorList.io_error,e,1);
        }
        catch (Exception e)
        {
          if (debugLevel > 0) e.printStackTrace();         
          MsgLog.log("TFactoryThread",e.toString(),TErrorList.code_failure,e,0);
        }
      }
    }
  }
  public class TFactoryGlobalsThread extends Thread
  {
    TPacket tp;
    boolean isWaiting = false;
    TFactoryGlobalsThread(TPacket tPacket)
    {
      tp = tPacket;
      String s = null;
      MulticastSocket sck = tp.getSocket();
      if (sck != null) s = new String("globals port (multicast listener) " + sck.getLocalPort());
      else s = new String("globals unbound socket ?");
      this.setName("Link Factory " + s);
    }
    public synchronized void run()
    {
      MsgLog.log("TFactoryGlobalsThread","Link Factory Thread " + getName() + " started ...",0,null,1);
      MulticastSocket sck = tp.getSocket();
      if (sck == null)
      {
        MsgLog.log("TFactoryGlobalsThread", "unable to obtain globals socket!",TErrorList.code_failure,null,0);
        return;
      }
      while (active)
      {
        if (terminate) break;
        if (debugLevel > 1) DbgLog.log("TFactoryGlobalsThread","Waiting for globals data ...");
        try
        {
          isWaiting = true;
          tp.dpIn.setLength(TTransport.UDP_BUFFER_SIZE); // reset the receive buffer length
          sck.receive(tp.dpIn);
          isWaiting = false;
          // got it, now interpret it ...
          InterpretIncomingGlobalsData(tp.dpIn.getAddress(), tp.dpIn.getData(), tp.dpIn.getLength(), false);
        }
        catch (IOException e)
        {
          if (debugLevel > 1) DbgLog.log("TFactoryGlobalsThread", "IO exception: "+e.getMessage());
        }
        catch (Exception e)
        {
          e.printStackTrace();
          MsgLog.log("TFactoryGlobalsThread", "unhandled exception "+e.toString(),TErrorList.code_failure,e,0);
        }
      }
    }
  }
  /**
   * TLinkFactory constructor comment.
   */
  private TLinkFactory()
  {
    initFactory();
  }
  private static boolean factoryHasInitialzed = false;
  private void initFactory()
  {
    if (factoryHasInitialzed) return;
    MsgLog.log("initFactory","TLink factory initializing ...",0,null,1);
    //System.setSecurityManager(null);
    // get the socket settings here ...
    sckRcvBufferSize = initializerInstance.getClnRcvBufferSize();
    sckSndBufferSize = initializerInstance.getClnSndBufferSize();
    sckTimeToLive = initializerInstance.getSckTimeToLive();
    atp = new TPacket(0,sckRcvBufferSize,sckSndBufferSize,sckTimeToLive);
    stp = new TPacket(0,sckRcvBufferSize,sckSndBufferSize,sckTimeToLive);
    qtp = new TPacket();
    nmtp = new TPacket();
    pHdr = new TPHdr(this);
    glbHdr = new TGlobalsHdr(this);
    stfThrd = new TFactoryThread(stp,"sync",false);
    qtfThrd = new TFactoryThread(qtp,"query",false);
    atfThrd = new TFactoryThread(atp,"async",true);
    gtfThrd = null;
    active = true;
    stfThrd.start();
    qtfThrd.start();
    atfThrd.start();
    tfwdThrd.setPriority(Thread.MIN_PRIORITY);
    tfwdThrd.start();
    String dbglvlStr;
    if ((dbglvlStr = System.getProperty("debug.level")) != null)
    {
      try { debugLevel = Integer.parseInt(dbglvlStr);} catch (Exception ignore) {}
    }
    String env = System.getProperty("tine.transport");
    if (env == null) env = System.getenv("TINE_TRANSPORT");
    if (env != null)
    {
      if (env.substring(0,3).compareToIgnoreCase("TCP") == 0)
      {
        useConnectedSockets = true;
      }
    }
    env = System.getProperty("tine.");
    if (env == null) env = System.getenv("TINE_STANDALONE");
    if (env != null)
    {
      gSystemRunningStandAlone = (env.compareToIgnoreCase("TRUE") == 0);
      initializer.setENSAddress(null);
      MsgLog.log("TLink Factory","is running in stand-alone mode: "+gSystemRunningStandAlone,0,null,0);
    }
    env = System.getProperty("tine.networkaddress.resolution");
    if (env == null) env = System.getenv("TINE_NETWORKADDRESS_RESOLUTION");
    if (env != null)
    {
      setAllowNetworkAddressResolution((env.compareToIgnoreCase("TRUE") == 0));
      MsgLog.log("TLink Factory","allow network address resolution: "+allowNeworkAddressResolution(),0,null,0);
    }
    env = System.getProperty("tine.delay.link.renewal");
    if (env == null) env = System.getenv("TINE_DELAY_LINK_RENEWAL");
    if (env != null)
    {
      setDelayLinkRenewals((env.compareToIgnoreCase("TRUE") == 0));
      MsgLog.log("TLink Factory","delay link renewals: "+getDelayLinkRenewals(),0,null,0);
    }
    env = System.getProperty("doocs.name");
    if (env == null) env = System.getProperty("doocs.user");
    if (env == null) env = System.getenv("DOOCS_USER");
    if (env != null) setDoocsUserName(env);
    Runtime.getRuntime().addShutdownHook(factoryShutdownHook);
    boolean irc = initializerInstance.isRichClient();
    if (irc != isRichClient) setRichClient(irc);
    factoryHasInitialzed = true;
  }
  Thread factoryShutdownHook = new TLinkFactoryShutdown();
  /**
   * unpins and closes any background globals links that were
   * started due to synchronous calls to a known globals server.
   */
  public void closeBackgroundGlobalsLinks()
  {
    for (int i = 1; i < numberTLinksInTable; i++)
    {
      if (linkTable[i] == null) continue;
      if (linkTable[i] == tNullLink) continue;
      if (linkTable[i].isGlobalsLinkPinned)
      {
        linkTable[i].isGlobalsLinkPinned = false;
        cancel(linkTable[i],true);
      }
    }   
   
  }
  /*
   * cancel() terminates an active link
   *
   * @param lnk is the link to cancel
   */
  public int cancel(TLink lnk)
  {
    return cancel(lnk,true);
  }
  private void unlinkMcaParent(TLink lnk)
  { // already synchronized on mcaLst ...
    lnk.removedFromMcaList = 0;
    mcaLst.remove(lnk.getFullDeviceNameAndProperty());
    if (lnk.boundTo != null)
    { // the mca parent is a bound link -> remove the dependency and leave ...
      lnk.boundTo.rmvDependency(lnk.boundTo);
      lnk.boundTo = null;
      return;
    }
    MsgLog.log("unlinkMcaParent", "closing MCA parent link "+lnk.getFullDeviceNameAndProperty(),0,null,1);
    cancel(lnk,true);   
  }
  /*
   * cancel() terminates an active link
   *
   * @param lnk is the link to cancel
   * @param removeFromTable determines whether the link is to be
   * removed from the link table or not.  If not, the link remains
   * in the table as a place holder.
   */
  public int cancel(TLink lnk,boolean removeFromTable)
  { synchronized(lnkTblObject) {
    boolean trace = traceKey != null && lnk.isTraceLink();
    if (trace) lnk.traceLink("TLinkFactory.cancel", "closing link (remove from table : "+removeFromTable+")");
    if (lnk == null || lnk.sub == null)
    { // never allocated
      if (lnk != null) lnk.active = false;
      return 0;
    }
    lnk.keepActive = false;
    if (autoLinkErrorAlarms && isRunningAsServer())
    { // remove any link error alarms
      getEquipmentModuleFactory().clearFecLinkErrorAlarm(lnk);
    }
    if (lnk.isGlobalsLinkPinned)
    { // keep alive
      if (trace) lnk.traceLink("TLinkFactory.cancel", "globals link is pinned!");
      return 0;
    }
    InetAddress g;
    if ((g=lnk.getMulticastGroup()) != null)
    { // canceling a multicast link
      if (numLinksInMulticastGroup(g) == 1)
      { // last one in the group !
        detachMulticastGroup(lnk.isGlobalsLink,g);
        if (trace) lnk.traceLink("TLinkFactory.cancel", "detach from multicast group");
      }
      lnk.setMulticastGroup(null);
      if (lnk.isGlobalsLink) lnk.active = false;
    }
    TLink thisLnk = lnk;
    int mcaidx = lnk.getMcaIndex();
    if (mcaidx > 0)
    { // this is an mca link element
      if (trace) lnk.traceLink("TLinkFactory.cancel", "is an MCA element");
      TLink parent = lnk.getBoundLink();
      TMcaLink mca = parent == null ? null : getMcaLink(parent.cntName,parent.expName,parent.devName,parent.devProperty);
      if (mca != null)
      { // remove this dependent link
        if (!lnk.isInsideCallback && !parent.isInsideCallback)
        {
          if (trace) lnk.traceLink("TLinkFactory.cancel", "remove from parent "+parent.getFullDeviceNameAndProperty());
          mca.remove(lnk);
          lnk.setMcaIndex(0);
          lnk.setMcaDevice(null);
          lnk.setBoundLink(null);
        }
        lnk.getSubscription().mode = TMode.CM_CANCEL;
      }
      else
      {
        if (trace) lnk.traceLink("TLinkFactory.cancel", "bound mca link without parent !");
        MsgLog.log("TLinkFactory.cancel", "bound mca link without parent",TErrorList.link_error,null,0);
        return TErrorList.link_error;
      }
      lnk.active = false;
      lnk.terminate = removeFromTable;
      if (mca.lnks.size() == 0)
      { // last one -> remove the mca link
        parent.removedFromMcaList = (int)(System.currentTimeMillis()/1000);
        MsgLog.log("TLinkFactory.cancel", "mark mca parent "+parent.getFullDeviceNameAndProperty()+" for removal",0,null,1);
        if (trace) lnk.traceLink("TLinkFactory.cancel", "remove MCA parent "+parent.getFullDeviceNameAndProperty());
        return 0;
      }     
      else
      { // just return
        return 0;
      }
    }
    if (lnk.isBound())
    { // this one is just bound to another link (don't cancel for real: don't send CM_CANCEL)
      lnk.active = false;
      thisLnk = lnk.getBoundLink();
      thisLnk.rmvDependency(lnk);
      if (trace) lnk.traceLink("TLinkFactory.cancel", "link is bound to "+thisLnk.getFullDeviceNameAndProperty());
      if (!thisLnk.hasDependencies())
      { // that was the last dependency, was the link canceled with dependencies ?
        if (trace) lnk.traceLink("TLinkFactory.cancel", "last dependent link -> cancel "+thisLnk.getFullDeviceNameAndProperty());
        if (!thisLnk.isCancelledWithDependencies()) return 0; // still active
      }
      else
      {
        return 0;
      }
    }
    if (thisLnk.hasDependencies())
    { // primary link canceled, but there are still some hangers on ...
      if (trace) lnk.traceLink("TLinkFactory.cancel", "primary link still has dependencies!");
      thisLnk.cancelledWithDependencies = true;
      return 0;
    }
    if (thisLnk.isWildcardLink && thisLnk.twcl != null)
    { // closing a wildcard link
      if (trace) lnk.traceLink("TLinkFactory.cancel", "wild card link!");
      boolean earlyExit = false;
      TWildcardLink wc = thisLnk.twcl;
      thisLnk.twcl = null;
      wc.list = null;
      if (wc.length > 0)
      { // not a single server link
        earlyExit = true;
        for (int i=0; i<wc.length; i++)
        { // this will re-enter this routine -> null out the twcl reference first!
          wc.links[i].close();
        }
        wc.length = 0;
        thisLnk.active = false;
        thisLnk.terminate = removeFromTable;
        thisLnk.notifyPending = false;
      }
      rmvWildcardLink(wc);
      if (earlyExit) return 0;
    }
    if (thisLnk.sub == null) return 0;
    boolean isInformationStatic = (TErrorList.getErrorCode(thisLnk.linkStatus) == TErrorList.information_static);
    if (isInformationStatic && thisLnk.isInsideCallback)
    { // callback has called close() !
      thisLnk.linkStatus = 0; // avoid calling the post-callback .close()
      isInformationStatic = false; // don't need special tricks below
    }
    short bmode = TMode.getBaseMode(thisLnk.sub.mode);
    if (TMode.canClose(bmode))
    {
      if (trace) lnk.traceLink("TLinkFactory.cancel", "link is allowed to close");
      MsgLog.log("cancel","cancel link ("+thisLnk.linkId+") "+thisLnk.getFullDeviceNameAndProperty()+" mode was "
          + TMode.toString(thisLnk.sub.mode),0,null,1);
      if ((thisLnk.sub.mode & TMode.CM_CONNECT) == TMode.CM_CONNECT)
      {
        thisLnk.sub.mode = TMode.CM_CANCEL | TMode.CM_CONNECT;
      }
      else
      {
        thisLnk.sub.mode = TMode.CM_CANCEL; // turn off the link
      }
      if (thisLnk.active && thisLnk.linkStatus != TErrorList.link_timeout)
      { // if the server is down then no need to tell him we're closing the link
        if (thisLnk.canSendPacked() && !isInformationStatic) thisLnk.delayEstablishLink(true);
        sendLinkRequest(thisLnk);
        if (trace) lnk.traceLink("TLinkFactory.cancel", "notify server of closing action");
      }
    }
    if (isInformationStatic)
    {
      thisLnk.removeOnClose = (thisLnk.sub.mode == TMode.CM_REGISTER);
      thisLnk.sub.mode = TMode.CM_REGISTER;
    }
    thisLnk.active = false;
    if (thisLnk.linkId == 0)
    {
      thisLnk.removeOnClose = false; // just in case
      synchronized (siblings)
      {
        if (siblings.contains(thisLnk))
        {
          MsgLog.log("TLinkFactory.cancel", "remove ENS sibling",0,null,1);
          siblings.remove(thisLnk);
          if (trace) lnk.traceLink("TLinkFactory.cancel", "remove ENS sibling");
        }
      }
    }
    else
    {
      thisLnk.terminate = removeFromTable;
      if (trace) lnk.traceLink("TLinkFactory.cancel", "mark for removal");
    }
    thisLnk.notifyPending = false;
    return 0;
  } }
  public TLink findLinkWithOutputDataType(TDataType dout)
  {
    for (int i = 1; i < numberTLinksInTable; i++)
    {
      if (linkTable[i] == null) continue;
      if (linkTable[i] == tNullLink) continue;
      if (!linkTable[i].active) continue;
      if (linkTable[i].dOutput == dout) return linkTable[i];
    }   
    return null;
  }
  TLink getExistingLink(String devname, String devproperty, TDataType dout, TDataType din, short devaccess)
  {
    for (int i = 1; i < numberTLinksInTable; i++)
    {
      if (linkTable[i] == null) continue;
      if (linkTable[i] == tNullLink) continue;
      if (!linkTable[i].active) continue;
      if (linkTable[i].getFullDeviceName().compareToIgnoreCase(devname) != 0) continue;
      if (linkTable[i].getProperty().compareToIgnoreCase(devproperty) != 0) continue;
      if (TAccess.toBase(linkTable[i].devAccess) != TAccess.toBase(devaccess)) continue;
      if (!linkTable[i].getOutputDataObject().equals(false,dout)) continue;
      if (!linkTable[i].getInputDataObject().equals(true,din)) continue;
      return linkTable[i];
    }
    return null;
  }
  TLink getExistingLink(TLink thisLink, TDataType dout, TDataType din)
  {
    boolean fixdef = true;
    for (int i = 1; i < numberTLinksInTable; i++)
    {
      if (linkTable[i] == null) continue;
      if (linkTable[i] == thisLink) continue;
      if (linkTable[i] == tNullLink) continue;
      if (!linkTable[i].active) continue;
      //if (linkTable[i].boundTo != null) continue; // this one already bound
      if (linkTable[i].isRedirected || thisLink.isRedirected)
      { // one or both of them is redirected
        RedirectedItem rt = getRedirectionInformation(thisLink);
        RedirectedItem ri = getRedirectionInformation(linkTable[i]);
        if (ri == null)
        { // table entry not redirected
          String lkey = getLinkKey(linkTable[i]);
          if (lkey.compareToIgnoreCase(thisLink.rdrKey) != 0) continue;         
        }
        else if (rt == null)
        { // this link not redirected
          String lkey = getLinkKey(thisLink);
          if (lkey.compareToIgnoreCase(linkTable[i].rdrKey) != 0) continue;                   
        }
        else
        { // both redirected
          if (!rt.destinationEquals(ri)) continue;         
        }
      }
      else
      {
        if (linkTable[i].srvAddr.fecName == null || thisLink.srvAddr.fecName == null) continue;
        if (linkTable[i].srvAddr.eqmName == null || thisLink.srvAddr.eqmName == null) continue;
        if (linkTable[i].srvAddr.fecName.compareTo(thisLink.srvAddr.fecName) != 0) continue;
        if (linkTable[i].srvAddr.eqmName.compareTo(thisLink.srvAddr.eqmName) != 0) continue;
        if (linkTable[i].getDeviceName().compareToIgnoreCase(thisLink.getDeviceName()) != 0) continue;
        if (linkTable[i].getProperty().compareToIgnoreCase(thisLink.getProperty()) != 0) continue;
      }
      if (TAccess.toBase(linkTable[i].devAccess) != TAccess.toBase(thisLink.devAccess)) continue;
      if (!linkTable[i].getInputDataObject().equals(true,din)) continue;
      fixdef = !linkTable[i].isMcaParent();
      //if (linkTable[i].isMcaParent()) fixdef = false;
      if (!linkTable[i].getOutputDataObject().equals(false,fixdef,dout)) continue;
      if (linkTable[i].boundTo != null && linkTable[i].boundTo.active)
      { // try to cut to the chase concerning known MCA information
        int mcaidx = linkTable[i].getMcaIndex();
        if (mcaidx < 1) continue; // this one already bound and not an MCA element
        thisLink.setMcaIndex(mcaidx);
        thisLink.setMcaDevice(linkTable[i].getMcaDevice());
        // the 3 statements below should be covered by fixdef = true above !
        thisLink.dOutput.setArrayLength(1);
        thisLink.dOutput.dCompletionLength = 1;
        thisLink.dOutput.dFormat = linkTable[i].getOutputDataObject().dFormat;
        TMcaLink mca = TLinkFactory.getMcaLink(linkTable[i].cntName,linkTable[i].expName,linkTable[i].getMcaDevice(),linkTable[i].devProperty);
        if (mca == null) continue; // ??
        thisLink.sub = new TSubscription(linkTable[i].sub.contract, thisLink);
        thisLink.con = linkTable[i].con;
        thisLink.reqHdr = linkTable[i].reqHdr;
        thisLink.boundTo = linkTable[i].boundTo;
        thisLink.primary = linkTable[i]; // when 2 mca single element links are identical !
        thisLink.linkId = registerLink(thisLink); // must (!) put this in the link table (not an ordinary bound link)
        linkTable[thisLink.linkId] = thisLink;
        mca.add(thisLink);
        if (TLinkFactory.debugLevel > 2)
          DbgLog.log("getExistingLink", "add "+thisLink.getFullDeviceNameAndProperty()+" (id "+thisLink.linkId+")" + " to mca parent "+mca.getParent().getFullDeviceNameAndProperty()+" (id "+mca.getParent().linkId+")");
        return linkTable[i].getBoundLink(); // will be assigned in makeLink()
      }
      return linkTable[i];
    }
    return null;
  }
  /**
   * Put a new TLink in the link table. 
   * Calling this method directly bypasses various system
   * checks for cdi hooks, duplicate links, etc. 
   * A direct call is generally used internally for address queries from the ENS.
   *
   * @return TLink
   */
  public TLink simpleLink(String devname, String devproperty, TDataType dout, TDataType din, short devaccess)
  { // called only by TSrvEntry.getSrvAddrFromENS()
    int i;
    if (devname.startsWith("ENS"))
    { // given without context => internal queries
      i = 0;
      adjustLinkTable(new TLink(0, devname, devproperty, dout, din, devaccess),adjustLinkTableAdd);
    }
    else
    { // add a place holder in the link table
      if ((i=adjustLinkTable(new TLink(0, devname, devproperty, dout, din, devaccess),adjustLinkTableAdd)) < 0)
         return null;
    }
    // re-assign the place-holder ...
    if (debugLevel > 1) DbgLog.log("simpleLink","creating TLink " + linkTable[i] + " (" + i + ")");
    if (debugLevel > 0) linkTable[i].setDebugLevel(debugLevel);
    if (i == 0) linkTable[0].removeOnClose = false; // here it is again !
    return linkTable[i];
  }
  private void detachMulticastGroup(boolean isGlobalLink,InetAddress g)
  {
    if (g == null) return;
    if (g.equals(initializer.getMCastAddress()) ||
        g.equals(initializer.getGCastAddress()))
      return; // stay joined to the canonical groups
    try
    {
      TPacket tp = isGlobalLink ? getGlobalsSocket() : getMulticastSocket();
      tp.getSocket().leaveGroup(g);
    }
    catch (IOException e)
    {
      MsgLog.log("detachMulticastGroup","could not detach from multicast group : " + e.getMessage(),TErrorList.io_error,e,0);
    }
  }
  private int numLinksInMulticastGroup(InetAddress g)
  {
    int n = 0;
    InetAddress mcg;
    for (int i=0; i<numberTLinksInTable; i++)
    {
      if (linkTable[i] == null ||
          (mcg=linkTable[i].getMulticastGroup()) == null) continue;
      if (mcg.equals(g)) n++;
    }
    return n;
  }
  private ByteArrayOutputStream sndLnkByteStream = null;
  private int sendLinkBytesToPeer(TLink lnk,ByteArrayOutputStream bs) throws IOException
  {
    boolean isSynchronous = ((lnk.devAccess & TAccess.CA_SYNC) != 0);
    boolean isQuery = lnk.isQueryLink();
    boolean isServiceRequest = ((lnk.sub.contract.eqmName.compareTo(TSrvEntry.SRVEQM_NAME)) == 0);
    TFecEntry srv = lnk.srvAddr.fecAddr;
    if (srv == null)
    {
      if (debugLevel > 0) DbgLog.log("sendLinkRequest","establish link : can't resolve server address");
      lnk.active = false;
      return TErrorList.non_existent_fec;
    }
    if (TMode.isConnected(lnk.sub.mode))
    {
      if (lnk.tb == null) lnk.tb = new TLinkBucket(lnk);
      if (lnk.tb.isDeactivating) return TErrorList.tcp_connect_error;
      if (lnk.tb.getSocket() != null)
      {
        OutputStream os = lnk.tb.getOutputStream();
        os.write(bs.toByteArray());
        os.flush();
      }
      else
      {
        lnk.tb = null;
        return TErrorList.tcp_connect_error;
      }
    }
    else if (isSynchronous && isQuery)
    {
      qtp.dpOut = new DatagramPacket(bs.toByteArray(), bs.size(), srv.fecHost, initializer.getSrvPort()
          + srv.fecPortOffset);
      qtp.getSocket().send(qtp.dpOut);         
    }
    else if (isSynchronous)
    {
      if (isServiceRequest)
      {
        stp.dpOut = new DatagramPacket(bs.toByteArray(), bs.size(), srv.fecHost, initializer.getNetCastPort());           
      }
      else
      {
        stp.dpOut = new DatagramPacket(bs.toByteArray(), bs.size(), srv.fecHost, initializer.getSrvPort()
            + srv.fecPortOffset);
      }
      stp.getSocket().send(stp.dpOut);
    }
    else
    {
      atp.dpOut = new DatagramPacket(bs.toByteArray(), bs.size(), srv.fecHost, initializer.getSrvPort()
          + srv.fecPortOffset);
      atp.getSocket().send(atp.dpOut);
    }
    return 0;
  }
  /*
   * sends just this link request (no packing)
   * @param lnk the TLink to send
   * @return link status
   */
  int sendLinkRequest(TLink lnk)
  { synchronized(lnkTblObject) {
    if (lnk == null || lnk == tNullLink)
    {
      return TErrorList.non_existent;
    }
    boolean trace = TLinkFactory.traceKey != null && lnk.isTraceLink();
    if (trace)
    {
      lnk.traceLink("sendLinkRequest", "sending link request");
    }
    if (lnk.linkStatus == TErrorList.link_blacklisted)
    {
      lnk.sub.linkLastTime = System.currentTimeMillis();
      if (trace) lnk.traceLink("sendLinkRequest", "link is blacklisted");
      return TErrorList.link_blacklisted;
    }
    if (lnk.delayEstablishLink)
    {
      if (trace) lnk.traceLink("sendLinkRequest", "delay link request (pack collection)");
      return 0;
    }
    if (lnk.sub.contract == null || lnk.sub.contract.eqmName == null)
    {
      if (trace) lnk.traceLink("sendLinkRequest", "subscription contains null entries !");
      return TErrorList.link_error;
    }
    if (lnk.linkId < 0)
    {
      lnk.linkStatus = TErrorList.out_of_client_memory;
      if (trace) lnk.traceLink("sendLinkRequest", "no valid link id !");
      return TErrorList.out_of_client_memory;
    }
    if (lnk.active && lnk.linkStatus == -1)
    { // this link is already being established
      if (trace) lnk.traceLink("sendLinkRequest", "link is already being established !");
      return 0;
    }
    lnk.lastLinkStatus = lnk.linkStatus;
    if (lnk.lastLinkStatus == TErrorList.not_signalled) lnk.lastLinkStatus = 0;
    if ((lnk.sub.mode & TMode.CM_GLOBAL) == TMode.CM_GLOBAL)
    { // a globals link !
      if (trace) lnk.traceLink("sendLinkRequest", "link is a globals link !");
      lnk.isGlobalsLink = true; // definitely turn this on !
      if (lnk.dOutput == null) return TErrorList.invalid_parameter;
      if (getGlobalsLinkThread() == null) startGlobalsListener();
      if (lnk.srvAddr.fecAddr != null)
      { // the target globals server has a known address => join his multicast group
        try
        {
          String[] mcaddr = initializer.getGCastAddress().split("\\.");
          String[] ipaddr = lnk.srvAddr.fecAddr.fecHost.getHostAddress().split("\\.");
          String ip = mcaddr[0] + "." + mcaddr[1] + "." + ipaddr[2] + "." + ipaddr[3];
          InetAddress mcastGrp = InetAddress.getByName(ip);
          if (numLinksInMulticastGroup(mcastGrp) == 0)
          {
            getGlobalsSocket().getSocket().joinGroup(mcastGrp);
          }
          lnk.setMulticastGroup(mcastGrp);
        }
        catch (Exception e)
        {
          e.printStackTrace();
          MsgLog.log("sendLinkRequest","exception " + e.toString(),TErrorList.code_failure,e,1);
          return TErrorList.code_failure;
        }
      }
      lnk.linkStatus = -1; // pending
      lnk.dOutput.blksin = lnk.dOutput.bytesin = 0;
      lnk.dOutput.resetBuffersReady();
      lnk.active = true;
      if (trace) lnk.traceLink("sendLinkRequest", "request is now pending a response from server");
      return 0;
    }
    if ((lnk.sub.mode & TMode.CM_MCAST) == TMode.CM_MCAST)
    { // a network subscription !
      try
      { // join the right group
        if (mtfThrd == null) startMulticastListener(sckRcvBufferSize,sckTimeToLive);
        String[] mcaddr = initializer.getMCastAddress().split("\\.");
        String[] ipaddr = lnk.srvAddr.fecAddr.fecHost.getHostAddress().split("\\.");
        String ip = mcaddr[0] + "." + mcaddr[1] + "." + ipaddr[2] + "." + ipaddr[3];
        InetAddress mcastGrp = InetAddress.getByName(ip);
        if (numLinksInMulticastGroup(mcastGrp) == 0)
        { // no other links in this group so join !
          getMulticastSocket().getSocket().joinGroup(mcastGrp);
        }
        lnk.setMulticastGroup(mcastGrp);
      }
      catch (IOException e)
      {
        MsgLog.log("sendLinkRequest","exception " + e.toString(),TErrorList.mcast_init_error,e,0);
        return TErrorList.mcast_init_error;
      }
    }
    if (useConnectedSockets) lnk.sub.mode |= TMode.CM_CONNECT;
    byte[] hdrbytes, subbytes;
    TFecEntry srv = lnk.srvAddr.fecAddr;
    if (srv == null)
    {
      if (trace) lnk.traceLink("sendLinkRequest", "cannot resolve server address");
      if (debugLevel > 0) DbgLog.log("sendLinkRequest","establish link : cannot resolve server address");
      lnk.active = false;
      return TErrorList.non_existent_fec;
    }
    if (lnk.srvAddr.fecAddr.getTineProtocol() == 0)
    { // somehow not yet set
      lnk.srvAddr.fecAddr.setTineProtocol(TTransport.DEFAULT_PROTOCOL_LEVEL);
    }
    if (lnk.srvAddr.fecAddr.getTineProtocol() < 6 && !lnk.sub.isLegacy)
    {
      short mode = lnk.sub.mode;
      lnk.setTineProtocol(lnk.srvAddr.fecAddr.getTineProtocol());
      TContract c = lnk.sub.contract;
      c.setLegacy(lnk);
      lnk.sub = new TSubscription(c,lnk);
      lnk.sub.mode = mode;
    }
    String usr = tineUserName;
    if (lnk.sub.contract.eqmName.compareToIgnoreCase("DCSEQM") == 0)
    {
      if (doocsUserName != null && doocsUserName.length() > 0)
        usr = doocsUserName;
    }
    int hdrSize = lnk.sub.isLegacy ? TSubscription.hdrSizeInBytesLegacy : TSubscription.hdrSizeInBytes;
    int conSize = lnk.sub.isLegacy ? TContractP5.hdrSizeInBytes : TContract.hdrSizeInBytes;
    lnk.dInput.resetCounters(lnk.getTineProtocol());
    lnk.sub.resetCounters((short)lnk.dInput.blkid);
    for (int n = 0; n < lnk.dInput.numblks; n++)
    {
      subbytes = lnk.sub.toByteArray(); // fixes lnk.sub.nextDataSizeInBytes
      lnk.reqHdr.setUserName(usr);
      lnk.reqHdr.setMsgSizeInBytes((short)(conSize + hdrSize + lnk.sub.nextDataSizeInBytes));
      hdrbytes = lnk.reqHdr.toByteArray();
      if (debugLevel > 0)
        DbgLog.log("sendLinkRequest","trying to establish link " + lnk.linkId + "(" + (n + 1) + " of "
            + lnk.dInput.numblks + " " + lnk.reqHdr.getMsgSizeInBytes()+ " bytes)" + " : " + lnk.expName + " "
            + lnk.devName + " " + lnk.devProperty + " " + lnk.devTimeout + " msec");
      try
      {
        if (sndLnkByteStream == null) sndLnkByteStream = new ByteArrayOutputStream(TTransport.UDP_BUFFER_SIZE);
        ByteArrayOutputStream bs = sndLnkByteStream; bs.reset();
        DataOutputStream ds = new DataOutputStream(bs);
        ds.write(hdrbytes);
        ds.write(subbytes);
        if (lnk.sub.contract.dBuffer == null)
        {
          if (debugLevel > 0) DbgLog.log("sendLinkRequest","establish link : no address data returned");
          lnk.active = false;
          return TErrorList.non_existent_elem;
        }
        if (lnk.sub.contract.eqmName.length() == 0 || lnk.sub.msgSizeInBytes == 0)
        {
          if (debugLevel > 0) DbgLog.log("sendLinkRequest","establish link : header corrupt");
          lnk.active = false;
          return TErrorList.code_failure;
        }
        if (debugLevel > 0)
          DbgLog.log("sendLinkRequest","send to : " + lnk.srvAddr.fecAddr.fecHost.getHostAddress() + ":"
              + lnk.srvAddr.fecAddr.fecPortOffset);
        ds.write(lnk.sub.contract.toByteArray());
        // package the input data payload ...
        if (lnk.dInput.getArrayLength() > 0) ds.write(lnk.dInput.getDataBuffer(n));
        // send it out ...
        if (lnk.isInsideCallback && TMode.getBaseMode(lnk.sub.mode) == TMode.CM_SINGLE)
          lnk.sub.mode &= ~(TMode.CM_CONNECT|TMode.CM_STREAM);
        lnk.dOutput.blksin = 0;
        lnk.dOutput.resetBuffersReady();
        if (lnk.renewalMultiplier < 1) lnk.renewalMultiplier = 1;
        int cc = sendLinkBytesToPeer(lnk, bs);
        if (trace) lnk.traceLink("sendLinkRequest", "request bytes been sent to peer");
        if (cc != 0) return cc;
      }
      catch (IOException e)
      {
        if (e instanceof NoRouteToHostException)
        {
          MsgLog.log("sendLinkRequest","no route to "+lnk.srvAddr.fecAddr.fecHost.getHostAddress(),TErrorList.net_write_error,e,0);
        }
        if (TMode.isConnected(lnk.getLinkAccessMode()))
        { // connection went down at the other end
          removeTLinkBucket((TLinkBucket)lnk.tb,false);
        }
        else
        {
          e.printStackTrace();
        }
        if (debugLevel > 0) DbgLog.log("sendLinkRequest","establish link : IO Exception");       
        return TErrorList.net_write_error;
      }
      catch (Exception e)
      {
        e.printStackTrace();
        MsgLog.log("sendLinkRequest", "unhandled exception: "+e.getMessage(), TErrorList.code_failure, e, 0);    
        return TErrorList.net_write_error;
      }
    }

    if (debugLevel > 0) DbgLog.log("sendLinkRequest","establish link : success");
    if (trace) lnk.traceLink("sendLinkRequest", "request has been sent");
    lnk.active = true;
    return 0;
  } }
  private byte[] sndLnkReqBuffer = new byte[TTransport.UDP_BUFFER_SIZE];
  /*
   * only packs multiple link requests to same FEC!
   * no synchronous links; no query links; no globals links; no service links;
   * no links with multiple input blocks; no connected sockets;
   *
   * @param lnks
   * @return
   */
  int sendLinkRequest(TLink[] lnks)
  { synchronized(lnkTblObject) {
    if (lnks == null) return 0;
    TLink reflnk = null;
    TFecEntry srv = null;
    for (TLink lnk : lnks)
    { // find the first in the list that needs to go out and use it as a reference
      if (!lnk.needsToSendLinkRequest) continue;
      if (lnk.active && lnk.linkStatus == -1) continue;
      if (lnk.sub.mode == TMode.CM_REGISTER)
      {
        DbgLog.log("sendLinkRequest","mode CM_REGISTER not a valid remote access mode");
        continue;       
      }
      if (lnk.sub.contract == null || lnk.sub.contract.eqmName == null)
      {
        lnk.linkStatus = TErrorList.link_error;
        continue;
      }
      srv = lnk.srvAddr.fecAddr;
      if (srv == null)
      { // the server address is still unresolved ?!?
        if (debugLevel > 0) DbgLog.log("sendLinkRequest","establish link : can't resolve server address");
        lnk.active = false;
        lnk.linkStatus = TErrorList.non_existent_fec;
        continue;
      }
      reflnk = lnk;
      break;
    }
    if (reflnk == null) return 0;
    String usr = tineUserName;
    if (reflnk.sub.contract.eqmName.compareToIgnoreCase("DCSEQM") == 0)
    {
      if (doocsUserName != null && doocsUserName.length() > 0)
        usr = doocsUserName;
    }
    reflnk.reqHdr.setUserName(usr);
    int numLinksNotSent = 0;
    int sndLnkReqBufferPos = TReqHdr.hdrSizeInBytes;
    int consize, datsize;
    byte[] hdrbytes, subbytes, conbytes;
    TLink clnk = null;
    try
    {
      for (TLink lnk : lnks)
      {
        if (lnk.dInput.numblks > 1) continue;
        if (lnk.linkStatus == TErrorList.link_blacklisted)
        {
          lnk.sub.linkLastTime = System.currentTimeMillis();
          lnk.needsToSendLinkRequest = false;
          continue;
        }
        if (lnk.linkId < 0)
        {
          lnk.linkStatus = TErrorList.out_of_client_memory;
          lnk.needsToSendLinkRequest = false;
          continue;
        }
        if (lnk.sub.contract.dBuffer == null)
        {
          if (debugLevel > 0) DbgLog.log("sendLinkRequest","establish link : no address data returned");
          lnk.active = false;
          lnk.linkStatus = TErrorList.non_existent_elem;
          lnk.needsToSendLinkRequest = false;
          continue;
        }
        if (lnk.sub.contract.eqmName.length() == 0 || lnk.sub.msgSizeInBytes == 0)
        {
          if (debugLevel > 0) DbgLog.log("sendLinkRequest","establish link : header corrupt");
          lnk.active = false;
          lnk.linkStatus = TErrorList.code_failure;
          lnk.needsToSendLinkRequest = false;
          continue;
        }
        if (lnk.active && lnk.linkStatus == -1)
        { // this link is already being established
          lnk.needsToSendLinkRequest = false;
          continue;
        }
        if (lnk.srvAddr.fecAddr != reflnk.srvAddr.fecAddr)
        {
          if (debugLevel > 3)
            DbgLog.log("sendLinkRequest","skipping packed link "+lnk.getFullDeviceName()+" ("+TMode.toString(lnk.sub.mode)+") this pass");
          numLinksNotSent++;
          continue;
        }
        if (!lnk.getCriticalSection()) continue;
        clnk = lnk;
        lnk.needsToSendLinkRequest = false;
        lnk.delayEstablishLink(false);
        lnk.lastLinkStatus = lnk.linkStatus;
        if (lnk.lastLinkStatus == TErrorList.not_signalled) lnk.lastLinkStatus = 0;
        lnk.linkStatus = -1; // pending
        if ((lnk.sub.mode & TMode.CM_MCAST) == TMode.CM_MCAST)
        { // a network subscription !
          try
          { // join the right group
            if (mtfThrd == null) startMulticastListener(sckRcvBufferSize,sckTimeToLive);
            String[] mcaddr = initializer.getMCastAddress().split("\\.");
            String[] ipaddr = lnk.srvAddr.fecAddr.fecHost.getHostAddress().split("\\.");
            String ip = mcaddr[0] + "." + mcaddr[1] + "." + ipaddr[2] + "." + ipaddr[3];
            InetAddress mcastGrp = InetAddress.getByName(ip);
            if (numLinksInMulticastGroup(mcastGrp) == 0)
            { // no other links in this group so join !
              getMulticastSocket().getSocket().joinGroup(mcastGrp);
            }
            lnk.setMulticastGroup(mcastGrp);
          }
          catch (IOException e)
          {
            MsgLog.log("sendLinkRequest","exception " + e.toString(),TErrorList.mcast_init_error,e,0);
            lnk.linkStatus = TErrorList.mcast_init_error;
            lnk.freeCriticalSection();
            continue;
          }
        }
        // if (useConnectedSockets) lnk.sub.mode |= TMode.CM_CONNECT;
        if (lnk.srvAddr.fecAddr.getTineProtocol() == 0)
        { // somehow not yet set
          lnk.srvAddr.fecAddr.setTineProtocol(TTransport.DEFAULT_PROTOCOL_LEVEL);
        }
        if (lnk.srvAddr.fecAddr.getTineProtocol() < 6 && !lnk.sub.isLegacy)
        {
          short mode = lnk.sub.mode;
          lnk.setTineProtocol(lnk.srvAddr.fecAddr.getTineProtocol());
          TContract c = lnk.sub.contract;
          c.setLegacy(lnk);
          lnk.sub = new TSubscription(c,lnk);
          lnk.sub.mode = mode;
        }
        lnk.dInput.resetCounters(lnk.getTineProtocol());
        lnk.sub.resetCounters((short)lnk.dInput.blkid);
       
        if (debugLevel > 0)
          DbgLog.log("sendLinkRequest","trying to establish link " + lnk.linkId + "(1 of "
              + lnk.dInput.numblks + " " + lnk.reqHdr.getMsgSizeInBytes()+ " bytes)" + " : " + lnk.expName + " "
              + lnk.devName + " " + lnk.devProperty + " " + lnk.devTimeout + " msec");
        subbytes = lnk.sub.toByteArray();
        conbytes = lnk.sub.contract.toByteArray();
        datsize = lnk.dInput.getSizeInBytes();
        consize = subbytes.length + conbytes.length + datsize;
        if (sndLnkReqBufferPos + consize > TTransport.UDP_BUFFER_SIZE)
        { // send out what I've got
          reflnk.reqHdr.setMsgSizeInBytes((short)(sndLnkReqBufferPos-TReqHdr.hdrSizeInBytes));
          hdrbytes = reflnk.reqHdr.toByteArray();
          System.arraycopy(hdrbytes, 0, sndLnkReqBuffer, 0, hdrbytes.length);
          if (sndLnkByteStream == null) sndLnkByteStream = new ByteArrayOutputStream(TTransport.UDP_BUFFER_SIZE);
          ByteArrayOutputStream bs = sndLnkByteStream; bs.reset();
          DataOutputStream ds = new DataOutputStream(bs);
          ds.write(sndLnkReqBuffer);      
          atp.dpOut = new DatagramPacket(bs.toByteArray(), bs.size(), srv.fecHost, initializer.getSrvPort()
              + srv.fecPortOffset);
          atp.getSocket().send(atp.dpOut);
          sndLnkReqBufferPos = TReqHdr.hdrSizeInBytes; // reset
        }
        System.arraycopy(subbytes, 0, sndLnkReqBuffer, sndLnkReqBufferPos, subbytes.length);
        sndLnkReqBufferPos += subbytes.length;
        System.arraycopy(conbytes, 0, sndLnkReqBuffer, sndLnkReqBufferPos, conbytes.length);
        sndLnkReqBufferPos += conbytes.length;
        if (lnk.dInput.getArrayLength() > 0)
        {
          System.arraycopy(lnk.dInput.getDataBuffer(0), 0, sndLnkReqBuffer, sndLnkReqBufferPos, datsize);
          sndLnkReqBufferPos += datsize;
        }
        lnk.dOutput.blksin = lnk.dOutput.bytesin = 0;
        lnk.dOutput.resetBuffersReady();
        if (debugLevel > 0) DbgLog.log("sendLinkRequest","establish link : success");
        if (lnk.sub.mode != TMode.CM_CANCEL) lnk.active = true;
        lnk.freeCriticalSection();
      }
      clnk = null;
      if (sndLnkReqBufferPos > TReqHdr.hdrSizeInBytes)
      { // something left to send
        reflnk.reqHdr.setMsgSizeInBytes((short)(sndLnkReqBufferPos-TReqHdr.hdrSizeInBytes));
        hdrbytes = reflnk.reqHdr.toByteArray();
        System.arraycopy(hdrbytes, 0, sndLnkReqBuffer, 0, hdrbytes.length);
        if (sndLnkByteStream == null) sndLnkByteStream = new ByteArrayOutputStream(TTransport.UDP_BUFFER_SIZE);
        ByteArrayOutputStream bs = sndLnkByteStream; bs.reset();
        DataOutputStream ds = new DataOutputStream(bs);
        ds.write(sndLnkReqBuffer);      
        atp.dpOut = new DatagramPacket(bs.toByteArray(), bs.size(), srv.fecHost, initializer.getSrvPort()
            + srv.fecPortOffset);
        atp.getSocket().send(atp.dpOut);
        sndLnkReqBufferPos = TReqHdr.hdrSizeInBytes; // reset
      }
    }
    catch (IOException e)
    {
      e.printStackTrace();
      if (debugLevel > 0) DbgLog.log("sendLinkRequest","establish link : IO Exception");
      reflnk.linkStatus = TErrorList.net_write_error;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      MsgLog.log("sendLinkRequest", "unhandled exception: "+e.getMessage(), TErrorList.code_failure, e, 0);    
    }
    if (clnk != null) clnk.freeCriticalSection(); // in case we're here via an exception
    if (debugLevel > 3) DbgLog.log("sendLinkRequest","number of packed links not sent this pass : "+numLinksNotSent);
    return numLinksNotSent;
  } }
  private static final int link_not_identified = 999999999;
  public TLink findLink(InetAddress inetAddr,int port)
  {
    return findLink(link_not_identified,inetAddr,port);
  }
  public TLink findLink(int linkid, InetAddress inetAddr,int port)
  {
    InetAddress ia;
    TFecEntry fec;
    for (int i = 0; i < numberTLinksInTable; i++) /* cross reference */
    {
      if (linkTable[i] == null || linkTable[i] == tNullLink) continue;
      if (linkTable[i].getTineProtocol() < TTransport.DEFAULT_PROTOCOL_LEVEL) continue;
      if ((fec=linkTable[i].srvAddr.fecAddr) == null) continue;
      if (linkid != link_not_identified && linkTable[i].srvId != linkid) continue;
      ia = fec.fecHost;
      if (ia.getHostAddress().compareTo(inetAddr.getHostAddress()) != 0) continue;
      if (port != initializer.getSrvPort() + fec.fecPortOffset) continue;
      return linkTable[i];
    }
    return null;
  }
  public int findTLink(TLink link)
  {
    for (int i = 0; i < numberTLinksInTable; i++)
    {
      if (linkTable[i] == link) return i;
    }
    return -1;
  }
  public TLink findTLink(int linkid, int linkstarttime)
  {
    if (linkstarttime == TFecEntry.BCAST_ID) /* BCAST or MCAST contract */
    { // should not land here!
      DbgLog.log("findTLink","incoming multicast link id not expected");
      for (int i = 0; i < numberTLinksInTable; i++) /* cross reference */
      {
        if (linkTable[i] == null || linkTable[i] == tNullLink) continue;
        if (linkTable[i].srvId != linkid) continue;
        return linkTable[i];
      }
      return null;
    }
    if (linkstarttime == TFecEntry.ENS_ID)
    {
      if (linkTable[0] != null && linkTable[0].sub.id != linkid)
      {
        return null;
      }
      linkTable[0].removeOnClose = false; // how many times do I have to set this ?
      return linkTable[0];
    }
    if (debugLevel > 2)
    {
      DbgLog.log("findTLink","find incoming link id "+linkid+" start time "+
          linkstarttime+" among "+numberTLinksInTable+" registered links");
    }
    if (linkid < 0 || linkid >= numberTLinksInTable) return null;
    if (linkTable[linkid] == null || linkTable[linkid].sub == null) return null;
    if (debugLevel > 2) DbgLog.log("findTLink","link table id " + linkid +
        " : start time " + linkTable[linkid].sub.starttime);
    if (linkTable[linkid].sub.starttime != linkstarttime) return null;
    if (debugLevel > 1)
      DbgLog.log("findTLink","returning link "+linkid+" "+linkTable[linkid].getFullDeviceNameAndProperty());
    return linkTable[linkid];
  }
  public TLink findTLink(InetAddress gaddr, String context, String keyword)
  { // method used for finding globals links
    keyword = keyword.trim();
    if (debugLevel > 2)
      DbgLog.log("findTLink","looking for global link "+keyword+" in context "+context+" among "+numberTLinksInTable
          + " registered links");

    for (int i = 0; i < numberTLinksInTable; i++)
    {
      if (linkTable[i] == null || linkTable[i] == tNullLink) continue;
      if (linkTable[i].sub == null) continue;
      if (!TMode.isGlobal(linkTable[i].sub.mode))
      {
        if (debugLevel > 4) DbgLog.log("findTLink",linkTable[i].devProperty + " is not a global");
        continue;
      }
      if (gaddr != null)
      { // if given, make sure it's coming from the correct source
        if (linkTable[i].expName == null ||
            linkTable[i].srvAddr == null ||
            linkTable[i].srvAddr.fecAddr == null ||
            linkTable[i].srvAddr.fecAddr.fecHost == null) continue;
        if (!linkTable[i].srvAddr.fecAddr.fecHost.equals(gaddr)) continue;
      }
      if (context != null && linkTable[i].getContext().compareToIgnoreCase(context) != 0) continue;
      if (debugLevel > 2)
        DbgLog.log("findTLink","Compare " + keyword + " against " + linkTable[i].devProperty);
      if (linkTable[i].devProperty == null) continue;
      if (keyword.compareToIgnoreCase(linkTable[i].devProperty) == 0) // found it
      {
        if (debugLevel > 1) DbgLog.log("findTLink","returning link "+i+" : "+linkTable[i].getFullDeviceName());
        return linkTable[i];
      }
    }
    if (debugLevel > 2) DbgLog.log("findTLink",keyword+" not required by application : data discarded");
    return null;   
  }
  public TLink findTLink(InetAddress gaddr, String keyword)
  { // method used for finding globals links
    return findTLink(gaddr,null,keyword);
  }
  /**
   * Insert the method's description here. Creation date: (8/24/01 3:51:37 PM)
   *
   * @return de.desy.tine.client.TLinkFactory
   */
  public static synchronized TLinkFactory getInstance()
  {
    if (instance == null) instance = new TLinkFactory();
    return instance;
  }
  /*
   * Put a new TLink in the link table Creation date: (8/17/01 5:59:27 PM)
   *
   * @return TLink
   */
  public void activateLink(TLink link)
  {
    linkTable[link.linkId] = link;
    if (debugLevel > 0)
      DbgLog.log("activateLink","creating TLink " + linkTable[link.linkId] + " (" + link.linkId + ")");
  }
  /*
   * Put a new TLink in the link table Creation date: (8/17/01 5:59:27 PM)
   *
   * @return TLink
   */
  public int registerLink(TLink link)
  {
    int i;
    if (link.expName.startsWith("ENS") &&
        (link.cntName.startsWith("DEFAULT") || link.cntName.length() == 0))
    {
      i = adjustLinkTable(link,adjustLinkTableAdd);
    }
    else
    {
      i = adjustLinkTable(tNullLink,adjustLinkTableAdd);
    }
    return i;
  }
  /**
   * Remove a TLink from the link table Removes a TLink from the link
   * table by setting the entry explicitly to null. If the link is a TCP/IP link
   * then the termination flag is reset to false in order to give the
   * terminateBucket thread a chance to clean up the connection.
   */
  protected void removeTLink(TLink lnk)
  {
    adjustLinkTable(lnk, adjustLinkTableRemove);
  }
  /*
   * called by TEquipmentModuleFactory during a general server shutdown
   */
  public void shutdown()
  {
    MsgLog.log("TLinkFactory.shutdown", "shutting down",0,null,0);
    active = false;
    if (atp != null) atp.shutdown();
    if (stp != null) stp.shutdown();
    if (qtp != null) qtp.shutdown();
    if (nmtp != null) nmtp.shutdown();
    if (gtp != null) gtp.shutdown();
  }
  public int setDebugLevel(int level)
  {
    return setOutputDebugLevel(level);
  }
  public int getDebugLevel()
  {
    return debugLevel;
  }
  public static int setOutputDebugLevel(int level)
  {
    if (gIsRunningAsServer) TEquipmentModuleFactory.setDebugLevel(level);
    MsgLog.setDebugLevel(level);
    TLinkFactory.debugLevel = level;
    if (!java.awt.GraphicsEnvironment.isHeadless() && level >= 0)
    {
      try
      {
        TConsole tc = TConsole.getInstance();
        if (!TCommandList.isExecuting() && !tc.isShowing())
        {
          if (level > 0) tc.show();
        }
        else
        {
          tc.setDebugLevel(level);
        }
      }
      catch (Exception e)
      { // maybe there's no way to make GUI components?
        e.printStackTrace();
        System.out.println("cannot instantiate the TConsole !");
      }
    }
    return TLinkFactory.debugLevel;
  }
  public static int getOutputDebugLevel()
  {
    return TLinkFactory.debugLevel;
  }
}
TOP

Related Classes of de.desy.tine.client.TLinkFactory$GlobalInfo

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.