/* * @(#)CDROutputStream_1_2.java 1.12 04/03/01 * * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package com.sun.corba.se.impl.encoding; import org.omg.CORBA.BAD_PARAM; import org.omg.CORBA.INTERNAL; import org.omg.CORBA.CompletionStatus; import com.sun.corba.se.spi.ior.iiop.GIOPVersion; import com.sun.corba.se.impl.encoding.CodeSetConversion; import com.sun.corba.se.impl.orbutil.ORBConstants; public class CDROutputStream_1_2 extends CDROutputStream_1_1 { // There's a situation with chunking with fragmentation // in which the alignment for a primitive value is needed // to fill fragment N, but the primitive won't fit so // must go into fragment N + 1. The behavior is the same // as that for specialChunks. // // Unfortunately, given the current code, we can't reuse // specialChunk. If you wrap each of the following // write calls with handleSpecialChunkBegin/End, you // will lose your state because the primitive calls will // change the variables, etc. // // All of the CDR code should be rewritten moving chunking // to a different level, perhaps in the buffer managers. // We want to move to a compositional model rather than // using inheritance. // // Note that in the grow case, chunks are _NOT_ closed // at grow points, now. // // **** NOTE **** // Since we will not support valuetypes with GIOP 1.1, that // also means we do not support chunking there. // protected boolean primitiveAcrossFragmentedChunk = false; // Used in chunking. Here's how this works: // // When chunking and writing an array of primitives, a string, or a // wstring, _AND_ it won't fit in the buffer do the following. (As // you can see, this is a very "special" chunk.) // // 1. Write the length of the chunk including the array length // 2. Set specialChunk to true // 3 applies to ALL chunking: // 3. In grow, if we need to fragment and specialChunk is false // a) call end_block // b) fragment // Now back to the array only case: // [write the data] // 4. if specialChunk is true // a) Close the chunk // b) Set specialChunk to false protected boolean specialChunk = false; // Indicates whether the header should be padded. In GIOP 1.2 and above, the // body must be aligned on a 8-octet boundary, and so the header needs to be // padded appropriately. However, if there is no body to a request or reply // message, there is no need to pad the header, in the unfragmented case. private boolean headerPadding; protected void handleSpecialChunkBegin(int requiredSize) { // If we're chunking and the item won't fit in the buffer if (inBlock && requiredSize + bbwi.position() > bbwi.buflen) { // Duplicating some code from end_block. Compute // and write the total chunk length. int oldSize = bbwi.position(); bbwi.position(blockSizeIndex - 4); //write_long(oldSize - blockSizeIndex); writeLongWithoutAlign((oldSize - blockSizeIndex) + requiredSize); bbwi.position(oldSize); // Set the special flag so we don't end the chunk when // we fragment specialChunk = true; } } protected void handleSpecialChunkEnd() { // If we're in a chunk and the item spanned fragments if (inBlock && specialChunk) { // This is unnecessary, but I just want to show that // we're done with the current chunk. (the end_block // call is inappropriate here) inBlock = false; blockSizeIndex = -1; blockSizePosition = -1; // Start a new chunk since we fragmented during the item. // Thus, no one can go back to add more to the chunk length start_block(); // Now turn off the flag so we go back to the normal // behavior of closing a chunk when we fragment and // reopening afterwards. specialChunk = false; } } // Called after writing primitives private void checkPrimitiveAcrossFragmentedChunk() { if (primitiveAcrossFragmentedChunk) { primitiveAcrossFragmentedChunk = false; inBlock = false; // It would be nice to have a StreamPosition // abstraction if we could avoid allocation // overhead. blockSizeIndex = -1; blockSizePosition = -1; // Start a new chunk start_block(); } } public void write_octet(byte x) { super.write_octet(x); checkPrimitiveAcrossFragmentedChunk(); } public void write_short(short x) { super.write_short(x); checkPrimitiveAcrossFragmentedChunk(); } public void write_long(int x) { super.write_long(x); checkPrimitiveAcrossFragmentedChunk(); } public void write_longlong(long x) { super.write_longlong(x); checkPrimitiveAcrossFragmentedChunk(); } // Called by RequestMessage_1_2 or ReplyMessage_1_2 classes only. void setHeaderPadding(boolean headerPadding) { this.headerPadding = headerPadding; } protected void alignAndReserve(int align, int n) { // headerPadding bit is set by the write operation of RequestMessage_1_2 // or ReplyMessage_1_2 classes. When set, the very first body write // operation (from the stub code) would trigger an alignAndReserve // method call, that would in turn add the appropriate header padding, // such that the body is aligned on a 8-octet boundary. The padding // is required for GIOP versions 1.2 and above, only if body is present. if (headerPadding == true) { headerPadding = false; alignOnBoundary(ORBConstants.GIOP_12_MSG_BODY_ALIGNMENT); } // In GIOP 1.2, we always end fragments at our // fragment size, which is an "evenly divisible // 8 byte boundary" (aka divisible by 16). A fragment can // end with appropriate alignment padding, but no padding // is needed with respect to the next GIOP fragment // header since it ends on an 8 byte boundary. bbwi.position(bbwi.position() + computeAlignment(align)); if (bbwi.position() + n > bbwi.buflen) grow(align, n); } protected void grow(int align, int n) { // Save the current size for possible post-fragmentation calculation int oldSize = bbwi.position(); // See notes where specialChunk is defined, as well as the // above notes for primitiveAcrossFragmentedChunk. // // If we're writing a primitive and chunking, we need to update // the chunk length to include the length of the primitive (unless // this complexity is handled by specialChunk). // // Note that this is wasted processing in the grow case, but that // we don't actually close the chunk in that case. boolean handleChunk = (inBlock && !specialChunk); if (handleChunk) { int oldIndex = bbwi.position(); bbwi.position(blockSizeIndex - 4); writeLongWithoutAlign((oldIndex - blockSizeIndex) + n); bbwi.position(oldIndex); } bbwi.needed = n; bufferManagerWrite.overflow(bbwi); // At this point, if we fragmented, we should have a ByteBufferWithInfo // with the fragment header already marshalled. The buflen and position // should be updated accordingly, and the fragmented flag should be set. // Note that fragmented is only true in the streaming and collect cases. if (bbwi.fragmented) { // Clear the flag bbwi.fragmented = false; // Update fragmentOffset so indirections work properly. // At this point, oldSize is the entire length of the // previous buffer. bbwi.position() is the length of the // fragment header of this buffer. fragmentOffset += (oldSize - bbwi.position()); // We just fragmented, and need to signal that we should // start a new chunk after writing the primitive. if (handleChunk) primitiveAcrossFragmentedChunk = true; } } public GIOPVersion getGIOPVersion() { return GIOPVersion.V1_2; } public void write_wchar(char x) { // In GIOP 1.2, a wchar is encoded as an unsigned octet length // followed by the octets of the converted wchar. This is good, // but it causes problems with our chunking code. We don't // want that octet to get put in a different chunk at the end // of the previous fragment. // // Ensure that this won't happen by overriding write_wchar_array // and doing our own handleSpecialChunkBegin/End here. CodeSetConversion.CTBConverter converter = getWCharConverter(); converter.convert(x); handleSpecialChunkBegin(1 + converter.getNumBytes()); write_octet((byte)converter.getNumBytes()); byte[] result = converter.getBytes(); // Write the bytes without messing with chunking // See CDROutputStream_1_0 internalWriteOctetArray(result, 0, converter.getNumBytes()); handleSpecialChunkEnd(); } public void write_wchar_array(char[] value, int offset, int length) { if (value == null) { throw wrapper.nullParam(CompletionStatus.COMPLETED_MAYBE); } CodeSetConversion.CTBConverter converter = getWCharConverter(); // Unfortunately, because of chunking, we have to convert the // entire char[] to a byte[] array first so we can know how // many bytes we're writing ahead of time. You can't split // an array of primitives into multiple chunks. int totalNumBytes = 0; // Remember that every wchar starts with an octet telling // its length. The buffer size is an upper bound estimate. int maxLength = (int)Math.ceil(converter.getMaxBytesPerChar() * length); byte[] buffer = new byte[maxLength + length]; for (int i = 0; i < length; i++) { // Convert one wchar converter.convert(value[offset + i]); // Make sure to add the octet length buffer[totalNumBytes++] = (byte)converter.getNumBytes(); // Copy it into our buffer System.arraycopy(converter.getBytes(), 0, buffer, totalNumBytes, converter.getNumBytes()); totalNumBytes += converter.getNumBytes(); } // Now that we know the total length, we can deal with chunking. // Note that we don't have to worry about alignment since they're // just octets. handleSpecialChunkBegin(totalNumBytes); // Must use totalNumBytes rather than buffer.length since the // buffer.length is only the upper bound estimate. internalWriteOctetArray(buffer, 0, totalNumBytes); handleSpecialChunkEnd(); } public void write_wstring(String value) { if (value == null) { throw wrapper.nullParam(CompletionStatus.COMPLETED_MAYBE); } // In GIOP 1.2, wstrings are not terminated by a null. The // length is the number of octets in the converted format. // A zero length string is represented with the 4 byte length // value of 0. if (value.length() == 0) { write_long(0); return; } CodeSetConversion.CTBConverter converter = getWCharConverter(); converter.convert(value); handleSpecialChunkBegin(computeAlignment(4) + 4 + converter.getNumBytes()); write_long(converter.getNumBytes()); // Write the octet array without tampering with chunking internalWriteOctetArray(converter.getBytes(), 0, converter.getNumBytes()); handleSpecialChunkEnd(); } }