jdk/src/jdk.snmp/share/classes/com/sun/jmx/snmp/daemon/SnmpSubRequestHandler.java
changeset 25859 3317bb8137f4
parent 23010 6dadb192ad81
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.snmp/share/classes/com/sun/jmx/snmp/daemon/SnmpSubRequestHandler.java	Sun Aug 17 15:54:13 2014 +0100
@@ -0,0 +1,630 @@
+/*
+ * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+package com.sun.jmx.snmp.daemon;
+
+
+
+// java import
+//
+import java.util.logging.Level;
+import java.util.Vector;
+
+// jmx imports
+//
+import static com.sun.jmx.defaults.JmxProperties.SNMP_ADAPTOR_LOGGER;
+import com.sun.jmx.snmp.SnmpPdu;
+import com.sun.jmx.snmp.SnmpVarBind;
+import com.sun.jmx.snmp.SnmpDefinitions;
+import com.sun.jmx.snmp.SnmpStatusException;
+import com.sun.jmx.snmp.SnmpEngine;
+
+// SNMP Runtime import
+//
+import com.sun.jmx.snmp.agent.SnmpMibAgent;
+import com.sun.jmx.snmp.agent.SnmpMibRequest;
+import com.sun.jmx.snmp.ThreadContext;
+import com.sun.jmx.snmp.internal.SnmpIncomingRequest;
+
+class SnmpSubRequestHandler implements SnmpDefinitions, Runnable {
+
+    protected SnmpIncomingRequest incRequest = null;
+    protected SnmpEngine engine = null;
+    /**
+     * V3 enabled Adaptor. Each Oid is added using updateRequest method.
+     */
+    protected SnmpSubRequestHandler(SnmpEngine engine,
+                                    SnmpIncomingRequest incRequest,
+                                    SnmpMibAgent agent,
+                                    SnmpPdu req) {
+        this(agent, req);
+        init(engine, incRequest);
+    }
+
+    /**
+     * V3 enabled Adaptor.
+     */
+    protected SnmpSubRequestHandler(SnmpEngine engine,
+                                    SnmpIncomingRequest incRequest,
+                                    SnmpMibAgent agent,
+                                    SnmpPdu req,
+                                    boolean nouse) {
+        this(agent, req, nouse);
+        init(engine, incRequest);
+    }
+    /**
+     * SNMP V1/V2 . To be called with updateRequest.
+     */
+    protected SnmpSubRequestHandler(SnmpMibAgent agent, SnmpPdu req) {
+        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
+            SNMP_ADAPTOR_LOGGER.logp(Level.FINER, SnmpSubRequestHandler.class.getName(),
+                "constructor", "creating instance for request " + String.valueOf(req.requestId));
+        }
+
+        version= req.version;
+        type= req.type;
+        this.agent= agent;
+
+        // We get a ref on the pdu in order to pass it to SnmpMibRequest.
+        reqPdu = req;
+
+        //Pre-allocate room for storing varbindlist and translation table.
+        //
+        int length= req.varBindList.length;
+        translation= new int[length];
+        varBind= new NonSyncVector<SnmpVarBind>(length);
+    }
+
+    /**
+     * SNMP V1/V2 The constructor initialize the subrequest with the whole varbind list contained
+     * in the original request.
+     */
+    @SuppressWarnings("unchecked")  // cast to NonSyncVector<SnmpVarBind>
+    protected SnmpSubRequestHandler(SnmpMibAgent agent,
+                                    SnmpPdu req,
+                                    boolean nouse) {
+        this(agent,req);
+
+        // The translation table is easy in this case ...
+        //
+        int max= translation.length;
+        SnmpVarBind[] list= req.varBindList;
+        for(int i=0; i < max; i++) {
+            translation[i]= i;
+            ((NonSyncVector<SnmpVarBind>)varBind).addNonSyncElement(list[i]);
+        }
+    }
+
+    SnmpMibRequest createMibRequest(Vector<SnmpVarBind> vblist,
+                                    int protocolVersion,
+                                    Object userData) {
+
+        // This is an optimization:
+        //    The SnmpMibRequest created in the check() phase is
+        //    reused in the set() phase.
+        //
+        if (type == pduSetRequestPdu && mibRequest != null)
+            return mibRequest;
+
+        //This is a request comming from an SnmpV3AdaptorServer.
+        //Full power.
+        SnmpMibRequest result = null;
+        if(incRequest != null) {
+            result = SnmpMibAgent.newMibRequest(engine,
+                                                reqPdu,
+                                                vblist,
+                                                protocolVersion,
+                                                userData,
+                                                incRequest.getPrincipal(),
+                                                incRequest.getSecurityLevel(),
+                                                incRequest.getSecurityModel(),
+                                                incRequest.getContextName(),
+                                                incRequest.getAccessContext());
+        } else {
+            result = SnmpMibAgent.newMibRequest(reqPdu,
+                                                vblist,
+                                                protocolVersion,
+                                                userData);
+        }
+        // If we're doing the check() phase, we store the SnmpMibRequest
+        // so that we can reuse it in the set() phase.
+        //
+        if (type == pduWalkRequest)
+            mibRequest = result;
+
+        return result;
+    }
+
+    void setUserData(Object userData) {
+        data = userData;
+    }
+
+    public void run() {
+
+        try {
+            final ThreadContext oldContext =
+                ThreadContext.push("SnmpUserData",data);
+            try {
+                switch(type) {
+                case pduGetRequestPdu:
+                    // Invoke a get operation
+                    //
+                    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
+                        SNMP_ADAPTOR_LOGGER.logp(Level.FINER, SnmpSubRequestHandler.class.getName(),
+                            "run", "[" + Thread.currentThread() +
+                              "]:get operation on " + agent.getMibName());
+                    }
+
+                    agent.get(createMibRequest(varBind,version,data));
+                    break;
+
+                case pduGetNextRequestPdu:
+                    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
+                        SNMP_ADAPTOR_LOGGER.logp(Level.FINER, SnmpSubRequestHandler.class.getName(),
+                            "run", "[" + Thread.currentThread() +
+                              "]:getNext operation on " + agent.getMibName());
+                    }
+                    //#ifdef DEBUG
+                    agent.getNext(createMibRequest(varBind,version,data));
+                    break;
+
+                case pduSetRequestPdu:
+                    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
+                        SNMP_ADAPTOR_LOGGER.logp(Level.FINER, SnmpSubRequestHandler.class.getName(),
+                            "run", "[" + Thread.currentThread() +
+                            "]:set operation on " + agent.getMibName());
+                    }
+                    agent.set(createMibRequest(varBind,version,data));
+                    break;
+
+                case pduWalkRequest:
+                    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
+                        SNMP_ADAPTOR_LOGGER.logp(Level.FINER, SnmpSubRequestHandler.class.getName(),
+                            "run", "[" + Thread.currentThread() +
+                            "]:check operation on " + agent.getMibName());
+                    }
+                    agent.check(createMibRequest(varBind,version,data));
+                    break;
+
+                default:
+                    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
+                        SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpSubRequestHandler.class.getName(),
+                            "run", "[" + Thread.currentThread() +
+                              "]:unknown operation (" +  type + ") on " +
+                              agent.getMibName());
+                    }
+                    errorStatus= snmpRspGenErr;
+                    errorIndex= 1;
+                    break;
+
+                }// end of switch
+
+            } finally {
+                ThreadContext.restore(oldContext);
+            }
+        } catch(SnmpStatusException x) {
+            errorStatus = x.getStatus() ;
+            errorIndex=  x.getErrorIndex();
+            if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
+                SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpSubRequestHandler.class.getName(),
+                    "run", "[" + Thread.currentThread() +
+                      "]:an Snmp error occurred during the operation", x);
+            }
+        }
+        catch(Exception x) {
+            errorStatus = SnmpDefinitions.snmpRspGenErr ;
+            if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
+                SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpSubRequestHandler.class.getName(),
+                    "run", "[" + Thread.currentThread() +
+                      "]:a generic error occurred during the operation", x);
+            }
+        }
+        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
+            SNMP_ADAPTOR_LOGGER.logp(Level.FINER, SnmpSubRequestHandler.class.getName(),
+                "run", "[" + Thread.currentThread() + "]:operation completed");
+        }
+    }
+
+    // -------------------------------------------------------------
+    //
+    // This function does a best-effort to map global error status
+    // to SNMP v1 valid global error status.
+    //
+    // An SnmpStatusException can contain either:
+    // <li> v2 local error codes (that should be stored in the varbind)</li>
+    // <li> v2 global error codes </li>
+    // <li> v1 global error codes </li>
+    //
+    // v2 local error codes (noSuchInstance, noSuchObject) are
+    // transformed in a global v1 snmpRspNoSuchName error.
+    //
+    // v2 global error codes are transformed in the following way:
+    //
+    //    If the request was a GET/GETNEXT then either
+    //         snmpRspNoSuchName or snmpRspGenErr is returned.
+    //
+    //    Otherwise:
+    //      snmpRspNoAccess, snmpRspInconsistentName
+    //               => snmpRspNoSuchName
+    //      snmpRspAuthorizationError, snmpRspNotWritable, snmpRspNoCreation
+    //               => snmpRspReadOnly  (snmpRspNoSuchName for GET/GETNEXT)
+    //      snmpRspWrong*
+    //               => snmpRspBadValue  (snmpRspNoSuchName for GET/GETNEXT)
+    //      snmpRspResourceUnavailable, snmpRspRspCommitFailed,
+    //      snmpRspUndoFailed
+    //                  => snmpRspGenErr
+    //
+    // -------------------------------------------------------------
+    //
+    static final int mapErrorStatusToV1(int errorStatus, int reqPduType) {
+        // Map v2 codes onto v1 codes
+        //
+        if (errorStatus == SnmpDefinitions.snmpRspNoError)
+            return SnmpDefinitions.snmpRspNoError;
+
+        if (errorStatus == SnmpDefinitions.snmpRspGenErr)
+            return SnmpDefinitions.snmpRspGenErr;
+
+        if (errorStatus == SnmpDefinitions.snmpRspNoSuchName)
+            return SnmpDefinitions.snmpRspNoSuchName;
+
+        if ((errorStatus == SnmpStatusException.noSuchInstance) ||
+            (errorStatus == SnmpStatusException.noSuchObject)   ||
+            (errorStatus == SnmpDefinitions.snmpRspNoAccess)    ||
+            (errorStatus == SnmpDefinitions.snmpRspInconsistentName) ||
+            (errorStatus == SnmpDefinitions.snmpRspAuthorizationError)){
+
+            return SnmpDefinitions.snmpRspNoSuchName;
+
+        } else if ((errorStatus ==
+                    SnmpDefinitions.snmpRspAuthorizationError)         ||
+                   (errorStatus == SnmpDefinitions.snmpRspNotWritable)) {
+
+            if (reqPduType == SnmpDefinitions.pduWalkRequest)
+                return SnmpDefinitions.snmpRspReadOnly;
+            else
+                return SnmpDefinitions.snmpRspNoSuchName;
+
+        } else if ((errorStatus == SnmpDefinitions.snmpRspNoCreation)) {
+
+                return SnmpDefinitions.snmpRspNoSuchName;
+
+        } else if ((errorStatus == SnmpDefinitions.snmpRspWrongType)      ||
+                   (errorStatus == SnmpDefinitions.snmpRspWrongLength)    ||
+                   (errorStatus == SnmpDefinitions.snmpRspWrongEncoding)  ||
+                   (errorStatus == SnmpDefinitions.snmpRspWrongValue)     ||
+                   (errorStatus == SnmpDefinitions.snmpRspWrongLength)    ||
+                   (errorStatus ==
+                    SnmpDefinitions.snmpRspInconsistentValue)) {
+
+            if ((reqPduType == SnmpDefinitions.pduSetRequestPdu) ||
+                (reqPduType == SnmpDefinitions.pduWalkRequest))
+                return SnmpDefinitions.snmpRspBadValue;
+            else
+                return SnmpDefinitions.snmpRspNoSuchName;
+
+        } else if ((errorStatus ==
+                    SnmpDefinitions.snmpRspResourceUnavailable) ||
+                   (errorStatus ==
+                    SnmpDefinitions.snmpRspCommitFailed)        ||
+                   (errorStatus == SnmpDefinitions.snmpRspUndoFailed)) {
+
+            return SnmpDefinitions.snmpRspGenErr;
+
+        }
+
+        // At this point we should have a V1 error code
+        //
+        if (errorStatus == SnmpDefinitions.snmpRspTooBig)
+            return SnmpDefinitions.snmpRspTooBig;
+
+        if( (errorStatus == SnmpDefinitions.snmpRspBadValue) ||
+            (errorStatus == SnmpDefinitions.snmpRspReadOnly)) {
+            if ((reqPduType == SnmpDefinitions.pduSetRequestPdu) ||
+                (reqPduType == SnmpDefinitions.pduWalkRequest))
+                return errorStatus;
+            else
+                return SnmpDefinitions.snmpRspNoSuchName;
+        }
+
+        // We have a snmpRspGenErr, or something which is not defined
+        // in RFC1905 => return a snmpRspGenErr
+        //
+        return SnmpDefinitions.snmpRspGenErr;
+
+    }
+
+    // -------------------------------------------------------------
+    //
+    // This function does a best-effort to map global error status
+    // to SNMP v2 valid global error status.
+    //
+    // An SnmpStatusException can contain either:
+    // <li> v2 local error codes (that should be stored in the varbind)</li>
+    // <li> v2 global error codes </li>
+    // <li> v1 global error codes </li>
+    //
+    // v2 local error codes (noSuchInstance, noSuchObject)
+    // should not raise this level: they should have been stored in the
+    // varbind earlier. If they, do there is nothing much we can do except
+    // to transform them into:
+    // <li> a global snmpRspGenErr (if the request is a GET/GETNEXT) </li>
+    // <li> a global snmpRspNoSuchName otherwise. </li>
+    //
+    // v2 global error codes are transformed in the following way:
+    //
+    //    If the request was a GET/GETNEXT then snmpRspGenErr is returned.
+    //    (snmpRspGenErr is the only global error that is expected to be
+    //     raised by a GET/GETNEXT request).
+    //
+    //    Otherwise the v2 code itself is returned
+    //
+    // v1 global error codes are transformed in the following way:
+    //
+    //      snmpRspNoSuchName
+    //               => snmpRspNoAccess  (snmpRspGenErr for GET/GETNEXT)
+    //      snmpRspReadOnly
+    //               => snmpRspNotWritable (snmpRspGenErr for GET/GETNEXT)
+    //      snmpRspBadValue
+    //               => snmpRspWrongValue  (snmpRspGenErr for GET/GETNEXT)
+    //
+    // -------------------------------------------------------------
+    //
+    static final int mapErrorStatusToV2(int errorStatus, int reqPduType) {
+        // Map v1 codes onto v2 codes
+        //
+        if (errorStatus == SnmpDefinitions.snmpRspNoError)
+            return SnmpDefinitions.snmpRspNoError;
+
+        if (errorStatus == SnmpDefinitions.snmpRspGenErr)
+            return SnmpDefinitions.snmpRspGenErr;
+
+        if (errorStatus == SnmpDefinitions.snmpRspTooBig)
+            return SnmpDefinitions.snmpRspTooBig;
+
+        // For get / getNext / getBulk the only global error
+        // (PDU-level) possible is genErr.
+        //
+        if ((reqPduType != SnmpDefinitions.pduSetRequestPdu) &&
+            (reqPduType != SnmpDefinitions.pduWalkRequest)) {
+            if(errorStatus == SnmpDefinitions.snmpRspAuthorizationError)
+                return errorStatus;
+            else
+                return SnmpDefinitions.snmpRspGenErr;
+        }
+
+        // Map to noSuchName
+        //      if ((errorStatus == SnmpDefinitions.snmpRspNoSuchName) ||
+        //   (errorStatus == SnmpStatusException.noSuchInstance) ||
+        //  (errorStatus == SnmpStatusException.noSuchObject))
+        //  return SnmpDefinitions.snmpRspNoSuchName;
+
+        // SnmpStatusException.noSuchInstance and
+        // SnmpStatusException.noSuchObject can't happen...
+
+        if (errorStatus == SnmpDefinitions.snmpRspNoSuchName)
+            return SnmpDefinitions.snmpRspNoAccess;
+
+        // Map to notWritable
+        if (errorStatus == SnmpDefinitions.snmpRspReadOnly)
+                return SnmpDefinitions.snmpRspNotWritable;
+
+        // Map to wrongValue
+        if (errorStatus == SnmpDefinitions.snmpRspBadValue)
+            return SnmpDefinitions.snmpRspWrongValue;
+
+        // Other valid V2 codes
+        if ((errorStatus == SnmpDefinitions.snmpRspNoAccess) ||
+            (errorStatus == SnmpDefinitions.snmpRspInconsistentName) ||
+            (errorStatus == SnmpDefinitions.snmpRspAuthorizationError) ||
+            (errorStatus == SnmpDefinitions.snmpRspNotWritable) ||
+            (errorStatus == SnmpDefinitions.snmpRspNoCreation) ||
+            (errorStatus == SnmpDefinitions.snmpRspWrongType) ||
+            (errorStatus == SnmpDefinitions.snmpRspWrongLength) ||
+            (errorStatus == SnmpDefinitions.snmpRspWrongEncoding) ||
+            (errorStatus == SnmpDefinitions.snmpRspWrongValue) ||
+            (errorStatus == SnmpDefinitions.snmpRspWrongLength) ||
+            (errorStatus == SnmpDefinitions.snmpRspInconsistentValue) ||
+            (errorStatus == SnmpDefinitions.snmpRspResourceUnavailable) ||
+            (errorStatus == SnmpDefinitions.snmpRspCommitFailed) ||
+            (errorStatus == SnmpDefinitions.snmpRspUndoFailed))
+            return errorStatus;
+
+        // Ivalid V2 code => genErr
+        return SnmpDefinitions.snmpRspGenErr;
+    }
+
+    static final int mapErrorStatus(int errorStatus,
+                                    int protocolVersion,
+                                    int reqPduType) {
+        if (errorStatus == SnmpDefinitions.snmpRspNoError)
+            return SnmpDefinitions.snmpRspNoError;
+
+        // Too bad, an error occurs ... we need to translate it ...
+        //
+        if (protocolVersion == SnmpDefinitions.snmpVersionOne)
+            return mapErrorStatusToV1(errorStatus,reqPduType);
+        if (protocolVersion == SnmpDefinitions.snmpVersionTwo ||
+            protocolVersion == SnmpDefinitions.snmpVersionThree)
+            return mapErrorStatusToV2(errorStatus,reqPduType);
+
+        return SnmpDefinitions.snmpRspGenErr;
+    }
+
+    /**
+     * The method returns the error status of the operation.
+     * The method takes into account the protocol version.
+     */
+    protected int getErrorStatus() {
+        if (errorStatus == snmpRspNoError)
+            return snmpRspNoError;
+
+        return mapErrorStatus(errorStatus,version,type);
+    }
+
+    /**
+     * The method returns the error index as a position in the var bind list.
+     * The value returned by the method corresponds to the index in the original
+     * var bind list as received by the SNMP protocol adaptor.
+     */
+    protected int getErrorIndex() {
+        if  (errorStatus == snmpRspNoError)
+            return -1;
+
+        // An error occurs. We need to be carefull because the index
+        // we are getting is a valid SNMP index (so range starts at 1).
+        // FIX ME: Shall we double-check the range here ?
+        // The response is : YES :
+        if ((errorIndex == 0) || (errorIndex == -1))
+            errorIndex = 1;
+
+        return translation[errorIndex -1];
+    }
+
+    /**
+     * The method updates the varbind list of the subrequest.
+     */
+    protected  void updateRequest(SnmpVarBind var, int pos) {
+        int size= varBind.size();
+        translation[size]= pos;
+        varBind.addElement(var);
+    }
+
+    /**
+     * The method updates a given var bind list with the result of a
+     * previsouly invoked operation.
+     * Prior to calling the method, one must make sure that the operation was
+     * successful. As such the method getErrorIndex or getErrorStatus should be
+     * called.
+     */
+    protected void updateResult(SnmpVarBind[] result) {
+
+        if (result == null) return;
+        final int max=varBind.size();
+        final int len=result.length;
+        for(int i= 0; i< max ; i++) {
+            // bugId 4641694: must check position in order to avoid
+            //       ArrayIndexOutOfBoundException
+            final int pos=translation[i];
+            if (pos < len) {
+                result[pos] =
+                    (SnmpVarBind)((NonSyncVector)varBind).elementAtNonSync(i);
+            } else {
+                if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
+                    SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpSubRequestHandler.class.getName(),
+                        "updateResult","Position `"+pos+"' is out of bound...");
+                }
+            }
+        }
+    }
+
+    private void init(SnmpEngine engine,
+                      SnmpIncomingRequest incRequest) {
+        this.incRequest = incRequest;
+        this.engine = engine;
+    }
+
+    // PRIVATE VARIABLES
+    //------------------
+
+    /**
+     * Store the protocol version to handle
+     */
+    protected int version= snmpVersionOne;
+
+    /**
+     * Store the operation type. Remember if the type is Walk, it means
+     * that we have to invoke the check method ...
+     */
+    protected int type= 0;
+
+    /**
+     * Agent directly handled by the sub-request handler.
+     */
+    protected SnmpMibAgent agent;
+
+    /**
+     * Error status.
+     */
+    protected int errorStatus= snmpRspNoError;
+
+    /**
+     * Index of error.
+     * A value of -1 means no error.
+     */
+    protected int errorIndex= -1;
+
+    /**
+     * The varbind list specific to the current sub request.
+     * The vector must contain object of type SnmpVarBind.
+     */
+    protected Vector<SnmpVarBind> varBind;
+
+    /**
+     * The array giving the index translation between the content of
+     * <VAR>varBind</VAR> and the varbind list as specified in the request.
+     */
+    protected int[] translation;
+
+    /**
+     * Contextual object allocated by the SnmpUserDataFactory.
+     **/
+    protected Object data;
+
+    /**
+     * The SnmpMibRequest that will be passed to the agent.
+     *
+     **/
+    private   SnmpMibRequest mibRequest = null;
+
+    /**
+     * The SnmpPdu that will be passed to the request.
+     *
+     **/
+    private   SnmpPdu reqPdu = null;
+
+    // All the methods of the Vector class are synchronized.
+    // Synchronization is a very expensive operation. In our case it is not always
+    // required...
+    //
+    @SuppressWarnings("serial")  // we never serialize this
+    class NonSyncVector<E> extends Vector<E> {
+
+        public NonSyncVector(int size) {
+            super(size);
+        }
+
+        final void addNonSyncElement(E obj) {
+            ensureCapacity(elementCount + 1);
+            elementData[elementCount++] = obj;
+        }
+
+        @SuppressWarnings("unchecked")  // cast to E
+        final E elementAtNonSync(int index) {
+            return (E) elementData[index];
+        }
+    };
+}