Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   package org.jboss.remoting.transport.socket;
   
   import  org.jboss.logging.Logger;
  import  org.jboss.util.propertyeditor.PropertyEditors;
  
  import java.net.Socket;
  import java.util.HashMap;
  import java.util.Map;
  //import java.util.concurrent.Semaphore;
  //import java.util.concurrent.TimeUnit;
  import  EDU.oswego.cs.dl.util.concurrent.Semaphore;

SocketClientInvoker uses Sockets to remotely connect to the a remote ServerInvoker, which must be a SocketServerInvoker.

Author(s):
Jeff Haynie
Tom Elrod
Ovidiu Feodorov
Version:
$Revision: 6181 $
  
  public class MicroSocketClientInvoker extends RemoteClientInvoker
  {
     // Constants ------------------------------------------------------------------------------------
  
     private static final Logger log = Logger.getLogger(MicroSocketClientInvoker.class);

   
Can be either true or false and will indicate if client socket should have TCP_NODELAY turned on or off. TCP_NODELAY is for a specific purpose; to disable the Nagle buffering algorithm. It should only be set for applications that send frequent small bursts of information without getting an immediate response; where timely delivery of data is required (the canonical example is mouse movements). The default is false.
  
     public static final String TCP_NODELAY_FLAG = "enableTcpNoDelay";

   
The client side maximum number of threads. The default is MAX_POOL_SIZE.
  
     public static final String MAX_POOL_SIZE_FLAG = "clientMaxPoolSize";

   
Specifies the fully qualified class name for the custom SocketWrapper implementation to use on the client. Note, will need to make sure this is marked as a client parameter (using the 'isParam' attribute). Making this change will not affect the marshaller/unmarshaller that is used, which may also be a requirement.
  
     public static final String CLIENT_SOCKET_CLASS_FLAG = "clientSocketClass";
   
   
Configuration key for determining if an InterruptedException should be rethrown or wrapped in a RuntimeException.
  
     public static final String WRAP_INTERRUPTED_EXCEPTION = "wrapInterruptedException";
  
   
Key for setting socket write timeout
  
     public static final String WRITE_TIMEOUT = "writeTimeout";
   
   
Default value for enable TCP nodelay. Value is false.
  
     public static final boolean TCP_NODELAY_DEFAULT = false;

   
Default maximum number of times a invocation will be made when it gets a SocketException. Default is 3.
  
     public static final int MAX_CALL_RETRIES = 3;

   
Default maximum number of socket connections allowed at any point in time. Default is 50.
  
     public static final int MAX_POOL_SIZE = 50;
  
 
    // Static ---------------------------------------------------------------------------------------
 
    private static boolean trace = .isTraceEnabled();

   
Used for debugging (tracing) connections leaks
 
    static int counter = 0;
 
    protected static final Map connectionPools = new HashMap();
    
    protected static final Map semaphores = new HashMap();
 
    // Performance measurements
    public static long getSocketTime = 0;
    public static long readTime = 0;
    public static long writeTime = 0;
    public static long serializeTime = 0;
    public static long deserializeTime = 0;
    
    private static final String patternString = "^.*(?:connection.*reset|connection.*closed|connection.*abort|broken.*pipe|connection.*shutdown).*$";
    private static final Pattern RETRIABLE_ERROR_MESSAGE = Pattern.compile(.);
   
   
Close all sockets in a specific pool.
 
    public static void clearPool(LinkedList thepool)
    {
       try
       {
          if (thepool == null)
          {
             return;
          }
          synchronized (thepool)
          {
             int size = thepool.size();
             for (int i = 0; i < sizei++)
             {
                SocketWrapper socketWrapper = (SocketWrapper)thepool.removeFirst();
                try
                {
                   socketWrapper.close();
                   socketWrapper = null;
                }
                catch (Exception ignored)
                {
                }
             }
          }
       }
       catch (Exception ex)
       {
          .debug("Failure"ex);
       }
    }

   
Close all sockets in all pools.
 
    public static void clearPools()
    {
       synchronized ()
       {
          for(Iterator i = .keySet().iterator(); i.hasNext();)
          {
             ServerAddress sa = (ServerAddressi.next();
 
             if () { .trace("clearing pool for " + sa); }
             clearPool((LinkedList.get(sa));
             i.remove();
          }
          .clear();
       }
    }
 
    // Attributes -----------------------------------------------------------------------------------
 
    private boolean reuseAddress;
 
    protected InetAddress addr;
    protected int port;
 
    // flag being set on true by a disconnect request. If trying to create a connection goes on in a
    // loop and a disconnect request arrives, this flag will be used to sent this information into
    // the connect loop
    private volatile boolean bailOut;

   
Indicates if will check the socket connection when getting from pool by sending byte over the connection to validate is still good.
 
    protected boolean shouldCheckConnection;

   
If the TcpNoDelay option should be used on the socket.
 
    protected boolean enableTcpNoDelay;
 
    protected String clientSocketClassName;
    protected Class clientSocketClass;
    protected int numberOfCallRetries;
    protected int maxPoolSize;

   
Pool for this invoker. This is shared between all instances of proxies attached to a specific invoker.
 
    protected LinkedList pool;
    
    //Semaphore is also shared between all proxies - must 1-1 correspondence between pool and semaphore
    protected Semaphore semaphore;
   

   
connection information
 
    protected ServerAddress address;
 
    //public long usedPooled;
    public Object usedPoolLock;
    
    protected boolean wrapInterruptedException = false;
   
   
If true, an IOException with message such as "Connection reset by peer: socket write error" will be treated like a SocketException.
 
    protected boolean generalizeSocketException;
    
    protected int writeTimeout = -1;
 
    // Constructors ---------------------------------------------------------------------------------
 
    {
       this(locatornull);
    }
 
    public MicroSocketClientInvoker(InvokerLocator locatorMap configuration)
    {
       super(locatorconfiguration);
 
        = null;
        = true;
        = false;
        = null;
        = null;
        = ;
      // usedPooled = 0;
     //  usedPoolLock = new Object();
 
       try
       {
          setup();
       }
       catch (Exception ex)
       {
          .error("Error setting up " + thisex);
          throw new RuntimeException(ex.getMessage(), ex);
       }
 
       .debug(this + " constructed");
    }
 
    // Public ---------------------------------------------------------------------------------------
 
   
Indicates if will check socket connection when returning from pool by sending byte to the server. Default value will be false.
 
    public boolean checkingConnection()
    {
       return ;
    }

   
Returns if newly created sockets will have SO_REUSEADDR enabled. Default is for this to be true.
 
    public boolean getReuseAddress()
    {
       return ;
    }

   
Sets if newly created socket should have SO_REUSEADDR enable. Default is true.
 
    public void setReuseAddress(boolean reuse)
    {
        = reuse;
    }
 
    public int getWriteTimeout()
    {
       return ;
    }
 
    public void setWriteTimeout(int writeTimeout)
    {
       this. = writeTimeout;
    }
   
   
Get the generalizeSocketException.

Returns:
the generalizeSocketException.
 
    
    public synchronized boolean isGeneralizeSocketException()
    {
       return ;
    }

   
Set the generalizeSocketException.

Parameters:
generalizeSocketException The generalizeSocketException to set.
 
    
    public synchronized void setGeneralizeSocketException(boolean generalizeSocketException)
    {
       this. = generalizeSocketException;
    }
 
    public synchronized void disconnect()
    {
       .debug(this + " disconnecting ...");
        = true;
       super.disconnect();
    }
 
    public void flushConnectionPool()
    {
       synchronized ()
       {
          while ( != null && .size() > 0)
          {
             SocketWrapper socketWrapper = (SocketWrapper).removeFirst();
             try
             {
                socketWrapper.close();
             }
             catch (IOException e)
             {
                .debug("Failed to close socket wrapper"e);
             }
          }
       }
    }

   
Sets the number of times an invocation will retry based on getting SocketException.
 
    public void setNumberOfCallRetries(int numberOfCallRetries)
    {
       if (numberOfCallRetries < 1)
       {
          this. = ;
       }
       else
       {
          this. = numberOfCallRetries;
       }
    }
 
    public int getNumberOfCallRetries()
    {
       return ;
    }
 
    public boolean isWrapInterruptedException()
    {
       return ;
    }
 
    public void setWrapInterruptedException(boolean wrapInterruptedException)
    {
       this. = wrapInterruptedException;
    }

   
The name of of the server.
 
    public String getServerHostName() throws Exception
    {
       return .;
    }
    
    public int getNumberOfUsedConnections()
    {
       if ( == null)
          return 0;
       
       return  - (int.permits();
    }
    
    public int getNumberOfAvailableConnections()
    {
       if ( == null)
          return 0;
       
       return (int.permits();
    }
 
    // Package protected ----------------------------------------------------------------------------
 
    // Protected ------------------------------------------------------------------------------------
 
    protected void setup() throws Exception
    {
        = InetAddress.getByName(.getHost());
        = .getPort();
 
       Properties props = new Properties();
       props.putAll();
       PropertyEditors.mapJavaBeanProperties(thispropsfalse);
 
       configureParameters();
       
        = createServerAddress();
    }
 
    protected void configureParameters()
    {
       Map params = ;
 
       if (params == null)
       {
          return;
       }
 
       // look for enableTcpNoDelay param
       Object val = params.get();
       if (val != null)
       {
          try
          {
              = Boolean.valueOf((String)val).booleanValue();
             .debug(this + " setting enableTcpNoDelay to " + );
          }
          catch (Exception e)
          {
             .warn(this + " could not convert " +  + " value of " +
                      val + " to a boolean value.");
          }
       }
 
       // look for maxPoolSize param
       val = params.get();
       if (val != null)
       {
          try
          {
              = Integer.valueOf((String)val).intValue();
             .debug(this + " setting maxPoolSize to " + );
          }
          catch (Exception e)
          {
             .warn(this + " could not convert " +  + " value of " +
                      val + " to a int value");
          }
       }
 
       // look for client socket class name
       val = params.get();
       if (val != null)
       {
          String value = (String)val;
          if (value.length() > 0)
          {
              = value;
             .debug(this + " setting client socket wrapper class name to " + );
          }
       }
 
       val = params.get(.);
       if (val != null && ((String)val).length() > 0)
       {
          String value = (Stringval;
           = Boolean.valueOf(value).booleanValue();
          .debug(this + " setting shouldCheckConnection to " + );
       }
       else if (Version.getDefaultVersion() == .)
       {
           = true;
          .debug(this + " setting shouldCheckConnection to " + );
       }
       
       // look for writeTimeout param
       val = params.get();
       if (val != null)
       {
          try
          {
              = Integer.valueOf((String)val).intValue();
             .debug(this + " setting writeTimeout to " + );
          }
          catch (Exception e)
          {
             .warn(this + " could not convert " +  + " value of " +
                      val + " to an int value");
          }
       }
    }
 
    {
       return new ServerAddress(.getHostAddress(), , -1, );
    }
 
    protected void finalize() throws Throwable
    {
       disconnect();
       super.finalize();
    }
 
    protected synchronized void handleConnect() throws ConnectionFailedException
    {
       initPool();
    }
 
    protected synchronized void handleDisconnect()
    {
       clearPools();
       clearPool();
    }

   
Each implementation of the remote client invoker should have a default data type that is used in the case it is not specified in the invoker locator URI.
 
    protected String getDefaultDataType()
    {
       return .;
    }
 
    protected Object transport(String sessionIDObject invocationMap metadata,
                               Marshaller marshallerUnMarshaller unmarshaller)
    {
       long start = System.currentTimeMillis();
       SocketWrapper socketWrapper = null;
       Object response = null;
       boolean oneway = false;
 
       // tempTimeout < 0 will indicate there is no per invocation timeout.
       int tempTimeout = -1;
       int savedTimeout = -1;
 
       if(metadata != null)
       {
          // check to see if is one way invocation and return after writing invocation if is
          Object val = metadata.get(....);
          if(val != null && val instanceof String && Boolean.valueOf((String)val).booleanValue())
          {
             oneway = true;
          }
 
          // look for temporary timeout values
          String tempTimeoutString = (Stringmetadata.get(.);
          {
             if (tempTimeoutString != null)
             {
                try
                {
                   tempTimeout = Integer.valueOf(tempTimeoutString).intValue();
                   .debug(this + " setting timeout to " + tempTimeout + " for this invocation");
                }
                catch (Exception e)
                {
                   .warn(this + " could not convert " + . + " value of " +
                            tempTimeoutString + " to an integer value.");
                }
             }
          }
       }
 
       int retryCount = 0;
       SocketException sockEx = null;
 
       for (; retryCount < retryCount++)
       {
          if (.trace(this + " retryCount: " + retryCount);
          // timeLeft < 0 will indicate that there is no per invocation timeout.
          int timeLeft = -1;
          if (0 < tempTimeout)
          {
             // If a per invocation timeout has been set, the time spent retrying
             // should count toward the elapsed time.
             timeLeft = (int) (tempTimeout - (System.currentTimeMillis() - start));
             if (timeLeft <= 0)
                break;
          }
 
          try
          {
             socketWrapper = getConnection(marshallerunmarshallertimeLeft);
          }
          catch (SocketException e)
          {
             .release();
             if (.trace(this + " released semaphore: " + .permits());
             .debug(this + " got " + e + ": " + e.getMessage());
             if (retryCount <  - 1)
             {
                continue;
             }
             throw new CannotConnectException(
                   "Can not get connection to server. Problem establishing " +
                   "socket connection for " + e);
             
          }
          catch (IOException e)
          {  
             .release();
             if (.trace(this + " released semaphore: " + .permits());
             .debug(this + " got " + e + ": " + e.getMessage());
             if (retryCount <  - 1 && isGeneralizeSocketException() 
                   && e.getMessage() != null && .matcher(e.getMessage()).matches())
             {
                continue;
             }
             throw new CannotConnectException(
                "Can not get connection to server. Problem establishing " +
                "socket connection for " + e);
          }
          catch (Exception e)
          {
 //            if (bailOut)
 //               return null;
             .release();
             if (.trace(this + " released semaphore: " + .permits());
             if (e instanceof InterruptedException && isWrapInterruptedException())
                throw new RuntimeException(e);
             throw new CannotConnectException(
                "Can not get connection to server. Problem establishing " +
                "socket connection for " + e);
          }
 
          if (tempTimeout >= 0)
          {
             savedTimeout = socketWrapper.getTimeout();
             socketWrapper.setTimeout((int) (tempTimeout - (System.currentTimeMillis() - start)));
          }
 
          long end = System.currentTimeMillis() - start;
           += end;
 
          try
          {
             int version = Version.getDefaultVersion();
             boolean performVersioning = Version.performVersioning();
 
             OutputStream outputStream = socketWrapper.getOutputStream();
 
             if (performVersioning)
             {
                writeVersion(outputStreamversion);
             }
 
             //TODO: -TME so this is messed up as now ties remoting versioning to using a marshaller type
             versionedWrite(outputStreammarshallerinvocationversion);
 
             end = System.currentTimeMillis() - start;
              += end;
             start = System.currentTimeMillis();
 
             if (oneway)
             {
                if() { .trace(this + " sent oneway invocation, so not waiting for response, returning null"); }
             }
             else
             {
                InputStream inputStream = socketWrapper.getInputStream();
                if (performVersioning)
                {
                   version = readVersion(inputStream);
                   if (version == -1)
                   {
                      throw new SocketException("end of file");
                   }
                   if (version == .)
                   {
                      .debug("Received version 254: treating as end of file");
                      throw new SocketException("end of file");
                   }
                }
 
                response = versionedRead(inputStreamunmarshallerversion);
             }
 
             end = System.currentTimeMillis() - start;
              += end;
 
             // Note that resetting the timeout value after closing the socket results
             // in an exception, so the reset is not done in a finally clause.  However,
             // if a catch clause is ever added that does not close the socket, care
             // must be taken to reset the timeout in that case.
             if (tempTimeout >= 0)
             {
                socketWrapper.setTimeout(savedTimeout);
             }
          }
          catch (SocketException sex)
          {
             handleSocketException(sexsocketWrapperretryCount);
             sockEx = sex;
             continue;
          }
          catch (IOException e)
          {
             if (isGeneralizeSocketException() && e.getMessage() != null && .matcher(e.getMessage()).matches())
             {
                handleSocketException(esocketWrapperretryCount);
                sockEx = new SocketException(e.getMessage());
                continue;
             }
             else
             {
                return handleOtherException(esocketWrapper);
             }
          }
          catch (Exception ex)
          {
             return handleOtherException(exsocketWrapper);
          }
 
          // call worked, so no need to retry
          break;
       }
 
       // need to check if ran out of retries
       if (retryCount >= )
       {
          handleException(sockExsocketWrapper);
       }
 
       // Put socket back in pool for reuse
       synchronized ()
       {
          if (.size() < )
          {
             .add(socketWrapper);
             if () { .trace(this + " returned " + socketWrapper + " to pool"); }
          }
          else
          {
             if () { .trace(this + "'s pool is full, will close the connection"); }
             try
             {
                socketWrapper.close();
             }
             catch (Exception ignored)
             {
             }
          }         
          .release();
          if (.trace(this + " released semaphore: " + .permits());
       }
 
       if ( && !oneway) { .trace(this + " received response " + response);  }
       return response;
    }
 
 
    protected void handleSocketException(Exception sexSocketWrapper socketWrapper, Semaphore semaphoreint retryCount)
    {
       .debug(this + " got SocketException " + sex);
 
       try
       {
          semaphore.release();
          if (.trace(this + " released semaphore: " + semaphore.permits());
          socketWrapper.close();            
       }
       catch (Exception ex)
       {
          if () { .trace(this + " couldn't successfully close its socketWrapper"ex); }
       }

      
About to run out of retries and pool may be full of timed out sockets, so want to flush the pool and try with fresh socket as a last effort.
 
       if (retryCount == ( - 2))
       {
          flushConnectionPool();
       }
    }
    
    protected Object handleOtherException(Exception ex, Semaphore semaphoreSocketWrapper socketWrapper)
    {
       .debug(this + " got exception " + ex);
 
       try
       {
          semaphore.release();
          if (.trace(this + " released semaphore: " + semaphore.permits());
          socketWrapper.close();
       }
       catch (Exception ignored)
       {
       }
       return handleException(exsocketWrapper);
    }
    
    protected Object handleException(Exception exSocketWrapper socketWrapper)
    {
       .error(this + " got marshalling exception, exiting ..."ex);
       
       if (ex instanceof ClassNotFoundException)
       {
          //TODO: -TME Add better exception handling for class not found exception
          .error("Error loading classes from remote call result."ex);
          throw (ClassNotFoundException)ex;
       }
 
       if (ex instanceof InterruptedException && isWrapInterruptedException())
       {
          .debug(thisex);
          throw new RuntimeException(ex);
       }
       
       throw new MarshalException(
          "Failed to communicate. Problem during marshalling/unmarshalling."ex);
    }
 
    protected void initPool()
    {
       synchronized ()
       {
           = (LinkedList).get();
           = (Semaphore).get();
          if ( == null)
          {
              = new LinkedList();
             .put();
             .debug("Creating semaphore with size " + );
              = new Semaphore();
             .put();
             
             if ()
             {
                synchronized ()
                {
                   .trace(this + " added new pool (" +  + ") as " + );
                }
             }
          }
          else
          {
             if ()
             {
                synchronized ()
                {
                   .trace(this + " using pool (" +  + ") already defined for " + );
                }
             }
          }
       }
    }
    
    protected SocketWrapper getConnection(Marshaller marshaller,
                                             UnMarshaller unmarshaller,
                                             int timeAllowed)
       throws Exception
    {
       long start = System.currentTimeMillis();
       long timeToWait = (timeAllowed > 0) ? timeAllowed : 30000;
       boolean timedout = !.attempt(timeToWait);
       if (.trace(this + " obtained semaphore: " + .permits());
       
       if (timedout)
       {
          throw new IllegalStateException("Timeout waiting for a free socket");
       }
       
       SocketWrapper pooled = null;
       
       synchronized ()
       {
          // if connection within pool, use it
          if (.size() > 0)
          {
             pooled = getPooledConnection();
             if (.trace(this + " reusing pooled connection: " + pooled);
          }
       }
       
       if (pooled == null)
       {
          //Need to create a new one  
          Socket socket = null;
 
          if () { .trace(this + " creating socket "); }
  
          // timeAllowed < 0 indicates no per invocation timeout has been set.
          int timeRemaining = -1;
          if (0 <= timeAllowed)
          {
             timeRemaining = (int) (timeAllowed - (System.currentTimeMillis() - start));
          }
          
          socket = createSocket(..timeRemaining);
          if (.trace(this + " created socket: " + socket);
 
          socket.setTcpNoDelay(.);
 
          Map metadata = getLocator().getParameters();
          if (metadata == null)
          {
             metadata = new HashMap(2);
          }
          else
          {
             metadata = new HashMap(metadata);
          }
          metadata.put(.marshaller);
          metadata.put(.unmarshaller);
          if ( > 0)
          {
             metadata.put(.new Integer());
          }
          if (timeAllowed > 0)
          {
             timeRemaining = (int) (timeAllowed - (System.currentTimeMillis() - start));
             
             if (timeRemaining <= 0)
                throw new IllegalStateException("Timeout creating a new socket");
             
             metadata.put(.new Integer(timeRemaining));
          }
          
          pooled = createClientSocket(socket.metadata);
       }
 
       return pooled;
    }
 
    protected SocketWrapper createClientSocket(Socket socketint timeoutMap metadata)
       throws Exception
    {
       if ( == null)
       {
          if( == null)
          {
              = ClassLoaderUtility.loadClass(getClass());
          }
 
          Class[] args = new Class[]{Socket.classMap.classInteger.class};
       }
 
       SocketWrapper clientSocketWrapper = null;
       clientSocketWrapper = (SocketWrapper).
          newInstance(new Object[]{socketmetadatanew Integer(timeout)});
 
       return clientSocketWrapper;
    }
 
    protected Socket createSocket(String addressint portint timeoutthrows IOException
    {
       Socket s = new Socket();
       InetSocketAddress inetAddr = new InetSocketAddress(addressport);
       s.connect(inetAddr);
       return s;
    }
 
    {
       SocketWrapper socketWrapper = null;
       while (.size() > 0)
       {
          socketWrapper = (SocketWrapper).removeFirst();
          try
          {
             if (socketWrapper != null)
             {
                if (socketWrapper instanceof OpenConnectionChecker)
                {
                   ((OpenConnectionCheckersocketWrapper).checkOpenConnection();
                }
                if ()
                {
                   socketWrapper.checkConnection();
                   return socketWrapper;
                }
                else
                {
                  return socketWrapper;
               }
            }
         }
         catch (Exception ex)
         {
            if () { .trace(this + " couldn't reuse connection from pool"); }
            try
            {
               socketWrapper.close();
            }
            catch (Exception e)
            {
               .debug("Failed to close socket wrapper"e);
            }
         }
      }
      return null;
   }
   // Private --------------------------------------------------------------------------------------
   private Object versionedRead(InputStream inputStreamUnMarshaller unmarshallerint version)
   {
      //TODO: -TME - is switch required?
      switch (version)
      {
         case .:
         case .:
         case .:
         {
            if () { .trace(this + " reading response from unmarshaller"); }
            if (unmarshaller instanceof VersionedUnMarshaller)
               return((VersionedUnMarshaller)unmarshaller).read(inputStreamnullversion);
            else
               return unmarshaller.read(inputStreamnull);
         }
         default:
         {
            throw new IOException("Can not read data for version " + version + ". " +
               "Supported versions: " + . + ", " + . + ", " + .);
         }
      }
   }
   private void versionedWrite(OutputStream outputStreamMarshaller marshaller,
                               Object invocationint versionthrows IOException
   {
      //TODO: -TME Should I worry about checking the version here?  Only one way to do it at this point
      switch (version)
      {
         case .:
         case .:
         case .:
         {
            if () { .trace(this + " writing invocation to marshaller"); }
            if (marshaller instanceof VersionedMarshaller)
               ((VersionedMarshallermarshaller).write(invocationoutputStreamversion);
            else
               marshaller.write(invocationoutputStream);
            if () { .trace(this + " done writing invocation to marshaller"); }
            return;
         }
         default:
         {
            throw new IOException("Can not write data for version " + version + ".  " +
               "Supported versions: " + . + ", " + . + ", " + .);
         }
      }
   }
   //TODO: -TME Exact same method in ServerThread
   private int readVersion(InputStream inputStreamthrows IOException
   {
      if () { .trace(this + " reading version from input stream"); }
      int version = inputStream.read();
      if () { .trace(this + " read version " + version + " from input stream"); }
      return version;
   }
   //TODO: -TME Exact same method in ServerThread
   private void writeVersion(OutputStream outputStreamint versionthrows IOException
   {
      if () { .trace(this + " writing version " + version + " on output stream"); }
      outputStream.write(version);
   }
   // Inner classes --------------------------------------------------------------------------------