// bdlbb_blobutil.h -*-C++-*- #ifndef INCLUDED_BDLBB_BLOBUTIL #define INCLUDED_BDLBB_BLOBUTIL #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide a suite of utilities for I/O operations on 'bdlbb::Blob'. // //@CLASSES: // bdlbb::BlobUtil: suite of utilities on 'bdlbb::Blob' // bdlbb::BlobUtilAsciiDumper: helper class for ascii dump of a 'blbb::Blob' // bdlbb::BlobUtilHexDumper: helper class for hex dump of a 'bdlbb::Blob' // //@SEE_ALSO: bdlbb_blob // //@DESCRIPTION: This 'struct' provides a variety of utilities for 'bdlbb::Blob' // objects, 'bdlbb::BlobUtil', such as I/O functions, comparison functions, and // streaming functions. #include <bdlscm_version.h> #include <bdlbb_blob.h> #include <bslma_allocator.h> #include <bsls_assert.h> #include <bsls_performancehint.h> #include <bsls_review.h> #include <bsl_algorithm.h> #include <bsl_cstring.h> #include <bsl_iosfwd.h> #include <bsl_utility.h> namespace BloombergLP { namespace bdlbb { // =============== // struct BlobUtil // =============== struct BlobUtil { // This 'struct' is a namespace for a collection of static methods used // for manipulating and accessing 'Blob' objects. // CLASS METHODS static void append(Blob *dest, const Blob& source, int offset, int length); // Append the specified 'length' bytes from the specified 'offset' in // the specified 'source' to the specified 'dest'. static void append(Blob *dest, const Blob& source, int offset); // Append from the specified 'offset' in the specified 'source' to the // specified 'dest'. static void append(Blob *dest, const Blob& source); // Append the specified 'source' to the specified 'dest'. static void append(Blob *dest, const char *source, int offset, int length); // Append the specified 'length' bytes starting from the specified // 'offset' from the specified 'source' address to the specified // 'dest'. The behavior of this function is undefined unless the range // '[source + offset, source + offset + length)' represents a readable // sequence of memory. static void append(Blob *dest, const char *source, int length); // Append the specified 'length' bytes starting from the specified // 'source' address to the specified 'dest'. The behavior is undefined // unless the range '[source, source + length)' is valid memory. static void appendWithCapacityBuffer(Blob *dest, BlobBuffer *buffer, const char *source, int length); // Append the specified 'length' bytes from the specified 'source' // address to the specified 'dest'. Use the existing capacity in // 'dest' first, followed by that in the 'buffer', and finally allocate // from the blob buffer factory associated with the 'dest'. Load any // unused space into the specified 'buffer'. The behavior is undefined // unless the range '[source, source + length)' represents a readable // sequence of memory. static void erase(Blob *blob, int offset, int length); // Erase the specified 'length' bytes starting at the specified // 'offset' from the specified 'blob'. The behavior is undefined // unless 'offset >= 0', 'length >= 0', and // 'offset + length <= blob->length()'. static void insert(Blob *dest, int destOffset, const Blob& source, int sourceOffset, int sourceLength); // Insert the specified 'sourceLength' bytes from the specified // 'sourceOffset' in the specified 'source' to the specified // 'destOffset' in the specified 'dest'. static void insert(Blob *dest, int destOffset, const Blob& source, int sourceOffset); // Insert from the specified 'sourceOffset' in the specified 'source' // to the specified 'destOffset' in the specified 'dest'. static void insert(Blob *dest, int destOffset, const Blob& source); // Insert the specified 'source' to the specified 'destOffset' in the // specified 'dest'. static bsl::pair<int, int> findBufferIndexAndOffset(const Blob& blob, int position); // Return a value, designated here as 'p', such that for the specified // 'blob', 'blob.buffer(p.first)' is the buffer that contains the byte // at the specified 'position' in 'blob', and 'p.second' is the offset // corresponding to 'position' within said buffer. The behavior of // this function is undefined unless '0 <= position', // '0 < blob.totalSize()', and 'position < blob.totalSize()'. Note // that (1) subsequent changes to 'blob' may invalidate the result of // this function, and (2) 'p.first' never indicates a zero-size buffer. static void copy(char *dstBuffer, const Blob& srcBlob, int position, int length); // Copy the specified 'length' bytes starting at the specified // 'position' in the specified 'srcBlob' to the specified 'dstBuffer'. // The behavior of this function is undefined unless '0 <= length', // '0 <= position', 'position <= srcBlob.totalSize() - length', and // 'dstBuffer' has room for 'length' bytes. Note that this function // does *not* set 'dstBuffer[length]' to 0. static void copy(Blob *dst, int dstOffset, const char *src, int length); // Copy into the specified 'dst' starting at the specified 'dstOffset' // the specified 'length' bytes from the specified 'src'. The behavior // is undefined unless '0 <= dstOffset', '0 <= length', // 'dst || 0 == length', 'src || 0 == length', // '!dst || dstOffset <= dst->length() - length', and 'src' refers to a // buffer with at least 'length' bytes. static void copy(Blob *dst, int dstOffset, const Blob& src, int srcOffset, int length); // Copy into the specified 'dst' starting at the specified 'dstOffset' // the specified 'length' bytes starting at the specified 'srcOffset' // in the specified 'src'. The behavior is undefined unless // '0 <= dstOffset', '0 <= srcOffset', '0 <= length', // 'dst || 0 == length', '!dst || dstOffset <= dst->length() - length', // and 'srcOffset <= src->length() - length'. static char *getContiguousRangeOrCopy(char *dstBuffer, const Blob& srcBlob, int position, int length, int alignment = 1); // Return the address of the byte at the specified 'position' in the // specified 'srcBlob', if that address is aligned to the optionally // specified 'alignment' and the specified 'length' bytes are stored // contiguously; otherwise, *copy* 'length' bytes to the specified // buffer 'dstBuffer', and return 'dstBuffer'. If alignment is not // specified, none is enforced. (An address is aligned to A if, when // converted to an integral value 'a', 'a & (A - 1)' is 0.) The // behavior of this function is undefined unless '0 < length', // '0 <= position', 'alignment' is a power of two, 'dstBuffer' is // aligned as required, 'dstBuffer' has room for 'length' bytes, and // 'position <= srcBlob.totalSize() - length'. static char *getContiguousDataBuffer(Blob *blob, int addLength, BlobBufferFactory *factory); // Obtain contiguous storage for at least the specified 'addLength' // bytes in the specified 'blob' at position 'blob->length()', and then // grow 'blob->length()' by 'addLength'. If, upon entry, such storage // does not exist in 'blob', first trim the final data buffer, if any, // and insert a new buffer obtained from the specified 'factory'. // Return a pointer to the beginning of the storage obtained. The // behavior of this function is undefined unless '0 < addLength', and // 'factory->allocate()', if called, yields a block of memory of a size // at least as large as 'addLength'. static bsl::ostream& asciiDump(bsl::ostream& stream, const Blob& source); // Write to the specified 'stream' an ascii dump of the specified // 'source', and return a reference to the modifiable 'stream'. static bsl::ostream& hexDump(bsl::ostream& stream, const Blob& source); // Write to the specified 'stream' a hexdump of the specified 'source', // and return a reference to the modifiable 'stream'. static bsl::ostream& hexDump(bsl::ostream& stream, const Blob& source, int offset, int length); // Write to the specified 'stream' a hexdump of the specified 'length' // bytes of the specified 'source' starting at the specified 'offset', // and return a reference to the modifiable 'stream'. static void padToAlignment(Blob *dest, int alignment, char fillChar = '\0'); // Append padding bytes to the specified 'dest' so that its resulting // length is an integer multiple of the specified 'alignment'. // Optionally specify 'fillChar' with which the padding is to be // filled. If 'fillChar' is not specified, a 0 byte will be used. The // behavior is undefined unless 'alignment' is a power of 2, and less // than or equal to 64. static void prependWithCapacityBuffer(Blob *dest, BlobBuffer *buffer, const char *source, int length); // Prepend the specified 'length' bytes from the specified 'source' // address to the specified 'dest'. Use the existing capacity in // 'dest' first if '0 == dest->length()', followed by that in the // 'buffer', and finally allocate from the blob buffer factory // associated with the 'dest'. Load any unused space into the // specified 'buffer'. The behavior is undefined unless the range // '[source, source + length)' represents a readable sequence of // memory. template <class STREAM> static STREAM& read(STREAM& stream, Blob *dest, int numBytes); // Read the specified 'numBytes' from the specified 'stream' and load // it into the specified 'dest', and return a reference to the // modifiable 'stream'. template <class STREAM> static STREAM& write(STREAM& stream, const Blob& source); // Write the specified 'source' to the specified 'stream', and return a // reference to the modifiable 'stream'. template <class STREAM> static int write(STREAM& stream, const Blob& source, int sourcePosition, int numBytes); // Write to the specified 'stream' the specified 'numBytes' starting at // the specified 'sourcePosition' in the specified 'source' blob. // Return 0 on success or a non-zero value otherwise. Note that this // function will fail (immediately) if the length of 'source' is less // than 'numBytes'; or if there is any error writing to 'stream'. static int compare(const Blob& a, const Blob& b); // Compare, lexicographically, the data (data length and character data // values at each index position) stored by the specified 'a' and 'b' // blobs. Return 0 if the data stored by 'a' is lexicographically // equal to the data stored by 'b', a negative value if 'a' is // lexicographically less than 'b', and a positive value if 'a' is // lexicographically greater than 'b'. static int appendBufferIfValid(Blob *dest, const BlobBuffer& buffer); // Append the specified 'buffer' after the last buffer of the specified // 'dest' if neither the resulting total size of 'dest' nor its // resulting total number of buffers exceeds 'INT_MAX'. Return 0 on // success, and a non-zero value (with no effect) otherwise. The // length of the 'dest' is unaffected. static int appendBufferIfValid(Blob *dest, bslmf::MovableRef<BlobBuffer> buffer); // Append the specified move-insertable 'buffer' after the last buffer // of the specified 'dest' if neither the resulting total size of // 'dest' nor its resulting total number of buffers exceeds 'INT_MAX'. // Return 0 on success, and a non-zero value (with no effect) // otherwise. The length of the 'dest' is unaffected. In case of // success the 'buffer' is left in a valid but unspecified state. static int appendDataBufferIfValid(Blob *dest, const BlobBuffer& buffer); // Append the specified 'buffer' after the last *data* buffer of the // specified 'dest' if neither the resulting total size of 'dest' nor // its resulting total number of buffers exceeds 'INT_MAX'. Return 0 // on success, and a non-zero value (with no effect) otherwise. The // last data buffer of the 'dest' is trimmed, if necessary. The length // of the 'dest' is incremented by the size of 'buffer'. static int appendDataBufferIfValid(Blob *dest, bslmf::MovableRef<BlobBuffer> buffer); // Append the specified move-insertable 'buffer' after the last *data* // buffer of the specified 'dest' if neither the resulting total size // of 'dest' nor its resulting total number of buffers exceeds // 'INT_MAX'. Return 0 on success, and a non-zero value (with no // effect) otherwise. The last data buffer of the 'dest' is trimmed, // if necessary. The length of the 'dest' is incremented by the size // of 'buffer'. In case of success the 'buffer' is left in a valid but // unspecified state. static int insertBufferIfValid(Blob *dest, int index, const BlobBuffer& buffer); // Insert the specified 'buffer' at the specified 'index' in the // specified 'dest' if '0 <= index <= dest->numBuffers()' and neither // the resulting total size of 'dest' nor its resulting total number of // buffers exceeds 'INT_MAX'. Return 0 on success, and a non-zero // value (with no effect) otherwise. Increment the length of the 'dest // by the size of the 'buffer' if 'buffer' is inserted *before* the // logical end of the 'dest'. The length of the 'dest' is _unchanged_ // if inserting at a position following all data buffers (e.g., // inserting into an empty blob or inserting a buffer to increase // capacity); in that case, the blob length must be changed by an // explicit call to 'setLength'. Buffers at 'index' and higher // positions (if any) are shifted up by one index position. static int insertBufferIfValid(Blob *dest, int index, bslmf::MovableRef<BlobBuffer> buffer); // Insert the specified move-insertable 'buffer' at the specified // 'index' in the specified 'dest' if // '0 <= index <= dest->numBuffers()' and neither the resulting total // size of 'dest' nor its resulting total number of buffers exceeds // 'INT_MAX'. Return 0 on success, and a non-zero value (with no // effect) otherwise. Increment the length of the 'dest by the size of // the 'buffer' if 'buffer' is inserted *before* the logical end of the // 'dest'. The length of the 'dest' is _unchanged_ if inserting at a // position following all data buffers (e.g., inserting into an empty // blob or inserting a buffer to increase capacity); in that case, the // blob length must be changed by an explicit call to 'setLength'. // Buffers at 'index' and higher positions (if any) are shifted up by // one index position. In case of success the 'buffer' is left in a // valid but unspecified state. static int prependDataBufferIfValid(Blob *dest, const BlobBuffer& buffer); // Insert the specified 'buffer' before the beginning of the specified // 'dest' if neither the resulting total size of 'dest' nor its // resulting total number of buffers exceeds 'INT_MAX'. Return 0 on // success, and a non-zero value (with no effect) otherwise. The // length of the 'dest' is incremented by the length of the prepended // buffer. static int prependDataBufferIfValid(Blob *dest, bslmf::MovableRef<BlobBuffer> buffer); // Insert the specified move-insertable 'buffer' before the beginning // of the specified 'dest' if neither the resulting total size of // 'dest' nor its resulting total number of buffers exceeds 'INT_MAX'. // Return 0 on success, and a non-zero value (with no effect) // otherwise. The length of the 'dest' is incremented by the length of // the prepended buffer. In case of success the 'buffer' is left in a // valid but unspecified state. // ---------- DEPRECATED FUNCTIONS ------------- // // DEPRECATED FUNCTIONS: basicAllocator is no longer used static void append(Blob *dest, const Blob& source, int offset, int length, bslma::Allocator *); static void append(Blob *dest, const Blob& source, int offset, bslma::Allocator *); static void append(Blob *dest, const Blob& source, bslma::Allocator *); }; // ========================== // struct BlobUtilAsciiDumper // ========================== struct BlobUtilAsciiDumper { // Utility for ascii dumping a blob to standard output streams. This class // has 'operator<<' defined for it, so it can be used, for example, in // 'ball' logs. // DATA const Blob *d_blob_p; // CREATORS explicit BlobUtilAsciiDumper(const Blob *blob); // Create an ascii dumper for the specified 'blob'. }; // FREE OPERATORS bsl::ostream& operator<<(bsl::ostream& stream, const BlobUtilAsciiDumper& rhs); // Ascii-dump the blob referenced by the specified 'rhs' to the specified // 'stream', and return a reference to the modifiable 'stream'. // ======================== // struct BlobUtilHexDumper // ======================== struct BlobUtilHexDumper { // Utility for hex dumping a blob to standard output streams. This class // has 'operator<<' defined for it, so it can be used, for example, in // 'ball' logs. // DATA const Blob *d_blob_p; int d_offset; int d_length; // CREATORS explicit BlobUtilHexDumper(const Blob *blob); // Create a hex dumper for the specified 'blob'. BlobUtilHexDumper(const Blob *blob, int offset, int length); // Create a hex dumper for the specified 'blob' that dumps the // specified 'length' bytes starting at the specified 'offset'. }; // FREE OPERATORS bsl::ostream& operator<<(bsl::ostream& stream, const BlobUtilHexDumper& rhs); // Hex-dump the blob referenced by the specified 'rhs' to the specified // 'stream', and return a reference to the modifiable 'stream'. // ============================================================================ // INLINE DEFINITIONS // ============================================================================ // --------------- // struct BlobUtil // --------------- // CLASS METHODS inline void BlobUtil::append(Blob *dest, const Blob& source, int offset) { append(dest, source, offset, source.length() - offset); } inline void BlobUtil::append(Blob *dest, const Blob& source) { append(dest, source, 0, source.length()); } inline void BlobUtil::append(Blob *dest, const Blob& source, int offset, int length, bslma::Allocator *) { return append(dest, source, offset, length); } inline void BlobUtil::append(Blob *dest, const Blob& source, int offset, bslma::Allocator *) { return append(dest, source, offset); } inline void BlobUtil::append(Blob *dest, const Blob& source, bslma::Allocator *) { return append(dest, source); } inline void BlobUtil::append(Blob *dest, const char *source, int length) { BSLS_ASSERT(0 != dest); BSLS_ASSERT(0 != source || 0 == length); if (BSLS_PERFORMANCEHINT_PREDICT_LIKELY(dest->numDataBuffers())) { const int lastDataBufIdx = dest->numDataBuffers() - 1; const BlobBuffer& lastBuf = dest->buffer(lastDataBufIdx); const int offsetInBuf = dest->lastDataBufferLength(); if (BSLS_PERFORMANCEHINT_PREDICT_LIKELY(lastBuf.size() - offsetInBuf >= length)) { dest->setLength(dest->length() + length); bsl::memcpy(lastBuf.buffer().get() + offsetInBuf, source, length); return; // RETURN } } BSLS_PERFORMANCEHINT_UNLIKELY_HINT; append(dest, source, 0, length); } inline void BlobUtil::insert(Blob *dest, int destOffset, const Blob& source, int sourceOffset) { insert(dest, destOffset, source, sourceOffset, source.length() - sourceOffset); } inline void BlobUtil::insert(Blob *dest, int destOffset, const Blob& source) { insert(dest, destOffset, source, 0, source.length()); } inline bsl::ostream& BlobUtil::hexDump(bsl::ostream& stream, const Blob& source) { return hexDump(stream, source, 0, source.length()); } inline void BlobUtil::padToAlignment(Blob *dest, int alignment, char fillChar) { BSLS_ASSERT(0 != dest); BSLS_ASSERT(static_cast<unsigned>(alignment) <= 64); const int modMask = alignment - 1; BSLS_ASSERT(0 == (alignment & modMask)); // power of 2 const int padLength = (alignment - (dest->length() & modMask)) & modMask; char padBuffer[63]; bsl::memset(padBuffer, fillChar, padLength); append(dest, padBuffer, padLength); } template <class STREAM> STREAM& BlobUtil::read(STREAM& stream, Blob *dest, int numBytes) { BSLS_ASSERT(0 != dest); dest->setLength(numBytes); for (int numBytesRemaining = numBytes, i = 0; 0 < numBytesRemaining; ++i) { BSLS_ASSERT(i < dest->numBuffers()); BlobBuffer buffer = dest->buffer(i); const int bytesToRead = numBytesRemaining < buffer.size() ? numBytesRemaining : buffer.size(); stream.getArrayInt8(buffer.data(), bytesToRead); numBytesRemaining -= bytesToRead; } return stream; } template <class STREAM> STREAM& BlobUtil::write(STREAM& stream, const Blob& source) { int numBytes = source.length(); for (int numBytesRemaining = numBytes, i = 0; 0 < numBytesRemaining; ++i) { BSLS_ASSERT(i < source.numBuffers()); BlobBuffer buffer = source.buffer(i); const int bytesToWrite = numBytesRemaining < buffer.size() ? numBytesRemaining : buffer.size(); stream.putArrayInt8(buffer.data(), bytesToWrite); numBytesRemaining -= bytesToWrite; } return stream; } template <class STREAM> int BlobUtil::write(STREAM& stream, const Blob& source, int sourcePosition, int numBytes) { BSLS_ASSERT(0 <= sourcePosition); BSLS_ASSERT(0 <= numBytes); if (sourcePosition + numBytes > source.length()) { return -1; // RETURN } if (sourcePosition == 0 && numBytes == 0) { return 0; // RETURN } int bufferIndex = 0; int bytesSkipped = 0; while (bytesSkipped + source.buffer(bufferIndex).size() <= sourcePosition) { bytesSkipped += source.buffer(bufferIndex).size(); ++bufferIndex; } int bytesRemaining = numBytes; while (0 < bytesRemaining) { const BlobBuffer& buffer = source.buffer(bufferIndex); const int startingIndex = 0 < bytesSkipped || 0 == bufferIndex ? sourcePosition - bytesSkipped : 0; const int bytesToCopy = bytesRemaining > buffer.size() - startingIndex ? buffer.size() - startingIndex : bytesRemaining; stream.putArrayInt8(buffer.data() + startingIndex, bytesToCopy); if (!stream) { return -1; // RETURN } bytesRemaining -= bytesToCopy; bytesSkipped = 0; ++bufferIndex; } BSLS_ASSERT(bytesRemaining == 0); return 0; } inline int BlobUtil::appendBufferIfValid(Blob *dest, const BlobBuffer& buffer) { BlobBuffer objectToMove(buffer); return appendBufferIfValid(dest, bslmf::MovableRefUtil::move(objectToMove)); } inline int BlobUtil::appendBufferIfValid(Blob *dest, bslmf::MovableRef<BlobBuffer> buffer) { BlobBuffer& lvalue = buffer; if (dest->totalSize() <= INT_MAX - lvalue.size() && (dest->numBuffers() < INT_MAX)) { dest->appendBuffer(bslmf::MovableRefUtil::move(buffer)); return 0; // RETURN } return -1; } inline int BlobUtil::appendDataBufferIfValid(Blob *dest, const BlobBuffer& buffer) { BlobBuffer objectToMove(buffer); return appendDataBufferIfValid(dest, bslmf::MovableRefUtil::move(objectToMove)); } inline int BlobUtil::appendDataBufferIfValid(Blob *dest, bslmf::MovableRef<BlobBuffer> buffer) { // Last data buffer can be trimmed during appending new buffer. Therefore, // the potentially allowed size of the added buffer should be adjusted // accordingly. BlobBuffer& lvalue = buffer; const int TRIMMED_SIZE = 0 == dest->numDataBuffers() ? 0 : dest->buffer(dest->numDataBuffers() - 1).size() - dest->lastDataBufferLength(); if ((dest->totalSize() - TRIMMED_SIZE <= INT_MAX - lvalue.size()) && (dest->numBuffers() < INT_MAX)) { dest->appendDataBuffer(bslmf::MovableRefUtil::move(lvalue)); return 0; // RETURN } return -1; } inline int BlobUtil::insertBufferIfValid(Blob *dest, int index, const BlobBuffer& buffer) { BlobBuffer objectToMove(buffer); return insertBufferIfValid(dest, index, bslmf::MovableRefUtil::move(objectToMove)); } inline int BlobUtil::insertBufferIfValid(Blob *dest, int index, bslmf::MovableRef<BlobBuffer> buffer) { BlobBuffer& lvalue = buffer; if (0 <= index && dest->numBuffers() >= index && (dest->totalSize() <= INT_MAX - lvalue.size()) && (dest->numBuffers() < INT_MAX)) { dest->insertBuffer(index, bslmf::MovableRefUtil::move(lvalue)); return 0; // RETURN } return -1; } inline int BlobUtil::prependDataBufferIfValid(Blob *dest, const BlobBuffer& buffer) { BlobBuffer objectToMove(buffer); return prependDataBufferIfValid(dest, bslmf::MovableRefUtil::move(objectToMove)); } inline int BlobUtil::prependDataBufferIfValid(Blob *dest, bslmf::MovableRef<BlobBuffer> buffer) { BlobBuffer& lvalue = buffer; int bufferSize = lvalue.size(); if ((dest->totalSize() <= INT_MAX - bufferSize) && (dest->numBuffers() < INT_MAX)) { dest->prependDataBuffer(bslmf::MovableRefUtil::move(lvalue)); return 0; // RETURN } return -1; } // -------------------------- // struct BlobUtilAsciiDumper // -------------------------- // CREATORS inline BlobUtilAsciiDumper::BlobUtilAsciiDumper(const Blob *blob) : d_blob_p(blob) { } } // close package namespace // FREE OPERATORS inline bsl::ostream& bdlbb::operator<<(bsl::ostream& stream, const BlobUtilAsciiDumper& rhs) { return BlobUtil::asciiDump(stream, *rhs.d_blob_p); } namespace bdlbb { // ------------------------ // struct BlobUtilHexDumper // ------------------------ // CREATORS inline BlobUtilHexDumper::BlobUtilHexDumper(const Blob *blob) : d_blob_p(blob) , d_offset(0) , d_length(blob->length()) { } inline BlobUtilHexDumper::BlobUtilHexDumper(const Blob *blob, int offset, int length) : d_blob_p(blob) , d_offset(offset) , d_length((bsl::min)(length, blob->length() - offset)) { } } // close package namespace // FREE OPERATORS inline bsl::ostream& bdlbb::operator<<(bsl::ostream& stream, const BlobUtilHexDumper& rhs) { return BlobUtil::hexDump( stream, *rhs.d_blob_p, rhs.d_offset, rhs.d_length); } } // close enterprise namespace #endif // ---------------------------------------------------------------------------- // Copyright 2018 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------