|  | /******************************************************************************* | 
|  | * Copyright (c) 2009 Luaj.org. All rights reserved. | 
|  | * | 
|  | * Permission is hereby granted, free of charge, to any person obtaining a copy | 
|  | * of this software and associated documentation files (the "Software"), to deal | 
|  | * in the Software without restriction, including without limitation the rights | 
|  | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | 
|  | * copies of the Software, and to permit persons to whom the Software is | 
|  | * furnished to do so, subject to the following conditions: | 
|  | * | 
|  | * The above copyright notice and this permission notice shall be included in | 
|  | * all copies or substantial portions of the Software. | 
|  | * | 
|  | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 
|  | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | 
|  | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | 
|  | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 
|  | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 
|  | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | 
|  | * THE SOFTWARE. | 
|  | ******************************************************************************/ | 
|  | package org.luaj.vm3; | 
|  |  | 
|  |  | 
|  | /** | 
|  | * String buffer for use in string library methods, optimized for production | 
|  | * of StrValue instances. | 
|  | * <p> | 
|  | * The buffer can begin initially as a wrapped {@link LuaValue} | 
|  | * and only when concatenation actually occurs are the bytes first copied. | 
|  | * <p> | 
|  | * To convert back to a {@link LuaValue} again, | 
|  | * the function {@link Buffer#value()} is used. | 
|  | * @see LuaValue | 
|  | * @see LuaValue#buffer() | 
|  | * @see LuaString | 
|  | */ | 
|  | public final class Buffer { | 
|  |  | 
|  | /** Default capacity for a buffer: 64 */ | 
|  | private static final int DEFAULT_CAPACITY = 64; | 
|  |  | 
|  | /** Shared static array with no bytes */ | 
|  | private static final byte[] NOBYTES = {}; | 
|  |  | 
|  | /** Bytes in this buffer */ | 
|  | private byte[] bytes; | 
|  |  | 
|  | /** Length of this buffer */ | 
|  | private int length; | 
|  |  | 
|  | /** Offset into the byte array */ | 
|  | private int offset; | 
|  |  | 
|  | /** Value of this buffer, when not represented in bytes */ | 
|  | private LuaValue value; | 
|  |  | 
|  | /** | 
|  | * Create buffer with default capacity | 
|  | * @see #DEFAULT_CAPACITY | 
|  | */ | 
|  | public Buffer() { | 
|  | this(DEFAULT_CAPACITY); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create buffer with specified initial capacity | 
|  | * @param initialCapacity the initial capacity | 
|  | */ | 
|  | public Buffer( int initialCapacity ) { | 
|  | bytes = new byte[ initialCapacity ]; | 
|  | length = 0; | 
|  | offset = 0; | 
|  | value = null; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create buffer with specified initial value | 
|  | * @param value the initial value | 
|  | */ | 
|  | public Buffer(LuaValue value) { | 
|  | bytes = NOBYTES; | 
|  | length = offset = 0; | 
|  | this.value = value; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Get buffer contents as a {@link LuaValue} | 
|  | * @return value as a {@link LuaValue}, converting as necessary | 
|  | */ | 
|  | public LuaValue value() { | 
|  | return value != null? value: this.tostring(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set buffer contents as a {@link LuaValue} | 
|  | * @param value value to set | 
|  | */ | 
|  | public Buffer setvalue(LuaValue value) { | 
|  | bytes = NOBYTES; | 
|  | offset = length = 0; | 
|  | this.value = value; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Convert the buffer to a {@link LuaString} | 
|  | * @return the value as a {@link LuaString} | 
|  | */ | 
|  | public final LuaString tostring() { | 
|  | realloc( length, 0 ); | 
|  | return LuaString.valueOf( bytes, offset, length ); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Convert the buffer to a Java String | 
|  | * @return the value as a Java String | 
|  | */ | 
|  | public String tojstring() { | 
|  | return value().tojstring(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Convert the buffer to a Java String | 
|  | * @return the value as a Java String | 
|  | */ | 
|  | public String toString() { | 
|  | return tojstring(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Append a single byte to the buffer. | 
|  | * @return {@code this} to allow call chaining | 
|  | */ | 
|  | public final Buffer append( byte b ) { | 
|  | makeroom( 0, 1 ); | 
|  | bytes[ offset + length++ ] = b; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Append a {@link LuaValue} to the buffer. | 
|  | * @return {@code this} to allow call chaining | 
|  | */ | 
|  | public final Buffer append( LuaValue val ) { | 
|  | append( val.strvalue() ); | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Append a {@link LuaString} to the buffer. | 
|  | * @return {@code this} to allow call chaining | 
|  | */ | 
|  | public final Buffer append( LuaString str ) { | 
|  | final int n = str.m_length; | 
|  | makeroom( 0, n ); | 
|  | str.copyInto( 0, bytes, offset + length, n ); | 
|  | length += n; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Append a Java String to the buffer. | 
|  | * The Java string will be converted to bytes using the UTF8 encoding. | 
|  | * @return {@code this} to allow call chaining | 
|  | * @see LuaString#encodeToUtf8(char[], int, byte[], int) | 
|  | */ | 
|  | public final Buffer append( String str ) { | 
|  | char[] c = str.toCharArray(); | 
|  | final int n = LuaString.lengthAsUtf8( c ); | 
|  | makeroom( 0, n ); | 
|  | LuaString.encodeToUtf8( c, c.length, bytes, offset + length ); | 
|  | length += n; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** Concatenate this buffer onto a {@link LuaValue} | 
|  | * @param lhs the left-hand-side value onto which we are concatenating {@code this} | 
|  | * @return {@link Buffer} for use in call chaining. | 
|  | */ | 
|  | public Buffer concatTo(LuaValue lhs) { | 
|  | return setvalue(lhs.concat(value())); | 
|  | } | 
|  |  | 
|  | /** Concatenate this buffer onto a {@link LuaString} | 
|  | * @param lhs the left-hand-side value onto which we are concatenating {@code this} | 
|  | * @return {@link Buffer} for use in call chaining. | 
|  | */ | 
|  | public Buffer concatTo(LuaString lhs) { | 
|  | return value!=null&&!value.isstring()? setvalue(lhs.concat(value)): prepend(lhs); | 
|  | } | 
|  |  | 
|  | /** Concatenate this buffer onto a {@link LuaNumber} | 
|  | * <p> | 
|  | * The {@link LuaNumber} will be converted to a string before concatenating. | 
|  | * @param lhs the left-hand-side value onto which we are concatenating {@code this} | 
|  | * @return {@link Buffer} for use in call chaining. | 
|  | */ | 
|  | public Buffer concatTo(LuaNumber lhs) { | 
|  | return value!=null&&!value.isstring()? setvalue(lhs.concat(value)): prepend(lhs.strvalue()); | 
|  | } | 
|  |  | 
|  | /** Concatenate bytes from a {@link LuaString} onto the front of this buffer | 
|  | * @param s the left-hand-side value which we will concatenate onto the front of {@code this} | 
|  | * @return {@link Buffer} for use in call chaining. | 
|  | */ | 
|  | public Buffer prepend(LuaString s) { | 
|  | int n = s.m_length; | 
|  | makeroom( n, 0 ); | 
|  | System.arraycopy( s.m_bytes, s.m_offset, bytes, offset-n, n ); | 
|  | offset -= n; | 
|  | length += n; | 
|  | value = null; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** Ensure there is enough room before and after the bytes. | 
|  | * @param nbefore number of unused bytes which must precede the data after this completes | 
|  | * @param nafter number of unused bytes which must follow the data after this completes | 
|  | */ | 
|  | public final void makeroom( int nbefore, int nafter ) { | 
|  | if ( value != null ) { | 
|  | LuaString s = value.strvalue(); | 
|  | value = null; | 
|  | length = s.m_length; | 
|  | offset = nbefore; | 
|  | bytes = new byte[nbefore+length+nafter]; | 
|  | System.arraycopy(s.m_bytes, s.m_offset, bytes, offset, length); | 
|  | } else if ( offset+length+nafter > bytes.length || offset<nbefore ) { | 
|  | int n = nbefore+length+nafter; | 
|  | int m = n<32? 32: n<length*2? length*2: n; | 
|  | realloc( m, nbefore==0? 0: m-length-nafter ); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** Reallocate the internal storage for the buffer | 
|  | * @param newSize the size of the buffer to use | 
|  | * @param newOffset the offset to use | 
|  | */ | 
|  | private final void realloc( int newSize, int newOffset ) { | 
|  | if ( newSize != bytes.length ) { | 
|  | byte[] newBytes = new byte[ newSize ]; | 
|  | System.arraycopy( bytes, offset, newBytes, newOffset, length ); | 
|  | bytes = newBytes; | 
|  | offset = newOffset; | 
|  | } | 
|  | } | 
|  |  | 
|  | } |