/* * Copyright 2009 Perforce Software Inc., All Rights Reserved. */ package com.perforce.p4java.impl.mapbased.rpc.packet; import java.io.UnsupportedEncodingException; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import com.perforce.p4java.CharsetDefs; import com.perforce.p4java.Log; import com.perforce.p4java.exception.P4JavaError; import com.perforce.p4java.exception.NullPointerError; import com.perforce.p4java.exception.ProtocolError; import com.perforce.p4java.impl.mapbased.rpc.connection.RpcConnection; import com.perforce.p4java.impl.mapbased.rpc.packet.helper.RpcPacketFieldRule; /** * Describes a value / name packet pair as marshaled * on to or off the RPC wire between Perforce clients and servers.<p> * * Format on wire (from C++ API source), in bytes: * <pre> * var<00><l1><l2><l3><l4>value<00> * </pre> * * where either or both var and value can be empty (null), * and where value can be interpreted as text (utf-8 or ascii, * depending on settings) binary bytes, and where * the four byte value length spec does not include the terminating * null byte, and is encoded as per RpcPacket.encodeInt4(). * * */ public class RpcPacketField { public static final String TRACE_PREFIX = "RpcPacketField"; /** * How many elements in each packet field. Changing this will probably * cause havoc. */ public static final int NUM_ELEMENTS = 2; /** * Which element will contain (or not) the field's name. */ public static final int NAME_FIELD = 0; /** * Which element will contain (or not) the field's value. */ public static final int VALUE_FIELD = 1; private String name = null; private Object value = null; /** * Construct a packet field from the passed-n name / value pair. * * @param name possibly-null name * @param value possibly-null value */ public RpcPacketField(String name, Object value) { this.name = name; this.value = value; } /** * Attempt to pick off a name / value field pair from the passed-in * byte buffer. Will always return a two-element object array, but either * or both of the objects may be null. Element zero is always the name (and * will always be text (a string) if it's not null; element one is the corresponding * value, and (if it's not null) will be a byte array whose interpretation must be * done by the caller in the light of what's expected in context -- it might represent * a normal string or it might actually be binary bytes.<p> * * Updates the buffer's position accordingly. Have to be careful * to use charsets with bytes and strings properly here and in the caller * to keep internationalization and localization straight. */ public static Object[] retrievePacketField(ByteBuffer buf, boolean isUnicodeServer, Charset charset) { return retrievePacketField(buf, isUnicodeServer, charset, null); } /** * Attempt to pick off a name / value field pair from the passed-in byte * buffer with an optional rule to handle the RPC packet fields. */ public static Object[] retrievePacketField(ByteBuffer buf, boolean isUnicodeServer, Charset charset, RpcPacketFieldRule fieldRule) { if (buf == null) { throw new NullPointerError( "Null byte buffer passed to RpcPacketField.retrievePacketField()"); } Object[] retVal; retVal = new Object[NUM_ELEMENTS]; retVal[NAME_FIELD] = null; retVal[VALUE_FIELD] = null; // Get name string; may be empty (i.e. first byte is null); also have // to deal with the fact that the name string may be arbitrarily large... final int INITIAL_BYTEARRAY_SIZE = 128; // bytes byte[] bytes = new byte[INITIAL_BYTEARRAY_SIZE]; int i = 0; while ((bytes[i] = buf.get()) != 0) { i++; if (i >= bytes.length) { // Resize needed... byte[] newBytes = new byte[bytes.length + INITIAL_BYTEARRAY_SIZE]; System.arraycopy(bytes, 0, newBytes, 0, bytes.length); bytes = newBytes; } } if (i > 0) { // Field names are currently always assumed to be in the default // charset, i.e. RpcConnection.NON_UNICODE_SERVER_CHARSET unless // the server is in Unicode mode. try { retVal[NAME_FIELD] = new String(bytes, 0, i, charset == null ? RpcConnection.NON_UNICODE_SERVER_CHARSET_NAME : (isUnicodeServer ? CharsetDefs.UTF8_NAME : charset.name())); } catch (UnsupportedEncodingException e) { // This should never be reached since we already have the Charset // object needed. Log.exception(e); } } // Get value string length which may be zero): int remaining = buf.remaining(); if (remaining < RpcPacket.RPC_LENGTH_FIELD_LENGTH) { throw new ProtocolError( "Insufficient bytes in buffer to retrieve text value field length"); } byte[] lengthBytes = new byte[RpcPacket.RPC_LENGTH_FIELD_LENGTH]; buf.get(lengthBytes); int valLength = RpcPacket.decodeInt4(lengthBytes); if (valLength < 0) { throw new ProtocolError( "Negative text field value length in P4JRpcTextField initializer: " + valLength); } // Get value (may be empty); at least here we know the // supposed number of the incoming bytes for the value: if (remaining < valLength) { throw new ProtocolError( "Insufficient bytes in buffer to retrieve text value field"); } byte[] valBytes = new byte[valLength]; buf.get(valBytes); String fieldName = (String) retVal[NAME_FIELD]; // Check for field rule on data conversion boolean skipConversion = false; if (fieldRule != null) { fieldRule.update(fieldName); skipConversion = fieldRule.isSkipConversion(); } if (!skipConversion && RpcPacketFieldType.getFieldType((String) retVal[NAME_FIELD]) == RpcPacketFieldType.TEXT) { // Incoming string is encoded in UTF-8 if we're talking to a Unicode server; // otherwise it's in the specified charset, or maybe some 8 bit ASCI variant). try { retVal[VALUE_FIELD] = new String(valBytes, charset == null ? RpcConnection.NON_UNICODE_SERVER_CHARSET_NAME : (isUnicodeServer ? CharsetDefs.UTF8_NAME : charset.name())); } catch (UnsupportedEncodingException e) { // This should never be reached since we already have the Charset // object needed. Log.exception(e); } } else { retVal[VALUE_FIELD] = valBytes; // If unicode is involved here (as, e.g., file contents), // it'll be converted elsewhere... } buf.get(); // Step over the terminating null return retVal; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public Object getValue() { return this.value; } public void setValue(Object value) { this.value = value; } /** * Marshal the passed-in packet fields onto a ByteBuffer. Affects * the buffer's position, etc., accordingly.<p> * @throws UnsupportedEncodingException */ public static void marshal(ByteBuffer buf, String name, String value, Charset charset) throws BufferOverflowException, UnsupportedEncodingException { marshal(buf, name, (value == null ? null : value.getBytes( charset == null ? CharsetDefs.DEFAULT.name() : charset.name()))); } /** * Marshal the passed-in packet fields onto a ByteBuffer. Affects * the buffer's position, etc., accordingly.<p> * @throws UnsupportedEncodingException */ public static void marshal(ByteBuffer buf, String name, StringBuffer value, Charset charset) throws BufferOverflowException, UnsupportedEncodingException { marshal(buf, name, (value == null ? null : value.toString().getBytes( charset == null ? CharsetDefs.DEFAULT.name() : charset.name()))); } /** * Marshal the passed-in packet fields onto a ByteBuffer. Affects * the buffer's position, etc., accordingly. */ public static void marshal(ByteBuffer buf, String name, byte[] value) throws BufferOverflowException { if (buf == null) { throw new NullPointerError( "Null byte buffer passed to RpcPacketField.marshal()"); } try { if (name != null) { buf.put(name.getBytes(CharsetDefs.DEFAULT.name())); } buf.put((byte) 0); int valLength = 0; if (value != null) { valLength = value.length; } buf.put(RpcPacket.encodeInt4(valLength)); if (value != null) { buf.put(value); } buf.put((byte) 0); } catch (BufferOverflowException boe) { // Will attempt to increase the size of the buffer in the caller // (some time in the future, at any rate...) throw boe; } catch (Throwable thr) { // Should be buffer overflow errors only; in any case, // this is really a panic... Log.error("Unexpected exception: " + thr.getLocalizedMessage()); Log.exception(thr); throw new P4JavaError(thr.getLocalizedMessage()); } } /** * Marshal the passed-in packet fields onto a ByteBuffer. Affects * the buffer's position, etc., accordingly. Since this one uses * special ByteBuffer to ByteBuffer methods, it gets its own * version rather than using the byte[] method.<p> * * Note: incoming value ByteBuffer must have been flipped ready for * sending; value's position will be updated by this method, so * if the buffer is to be reused, you must flip or reset (or whatever) * it yourself.<p> */ public static void marshal(ByteBuffer buf, String name, ByteBuffer value) throws BufferOverflowException { if (buf == null) { throw new NullPointerError( "Null byte buffer passed to RpcPacketField.marshal()"); } try { if (name != null) { buf.put(name.getBytes(CharsetDefs.DEFAULT.name())); } buf.put((byte) 0); int valLength = 0; if (value != null) { valLength = value.limit(); } buf.put(RpcPacket.encodeInt4(valLength)); if (value != null) { buf.put(value); } buf.put((byte) 0); } catch (Throwable thr) { Log.error("Unexpected exception: " + thr.getLocalizedMessage()); Log.exception(thr); throw new P4JavaError("Unexpected exception in RpcPacketField.marshal(ByteBuffer): " + thr.getLocalizedMessage(), thr); } } public static void marshal(byte[] buf, String name, byte[] value) throws BufferOverflowException { throw new P4JavaError("Called unimplemented marshall method"); } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 19903 | stuartrowe |
Branching //guest/perforce_software/p4java/... to //guest/stuartrowe/p4java/... |
||
//guest/perforce_software/p4java/r14.1/src/main/java/com/perforce/p4java/impl/mapbased/rpc/packet/RpcPacketField.java | |||||
#1 | 12541 | Matt Attaway | Initial add of the 14.1 p4java source code |