blob: a8e7f47db0926ba1f33af81acd7f5a7d886cbede [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*
*/
package org.apache.qpid.framing;
import org.apache.mina.common.ByteBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.lang.ref.WeakReference;
/**
* A short string is a representation of an AMQ Short String
* Short strings differ from the Java String class by being limited to on ASCII characters (0-127)
* and thus can be held more effectively in a byte buffer.
*
*/
public final class AMQShortString implements CharSequence, Comparable<AMQShortString>
{
private static final byte MINUS = (byte)'-';
private static final byte ZERO = (byte) '0';
private final class TokenizerImpl implements AMQShortStringTokenizer
{
private final byte _delim;
private int _count = -1;
private int _pos = 0;
public TokenizerImpl(final byte delim)
{
_delim = delim;
}
public int countTokens()
{
if(_count == -1)
{
_count = 1 + AMQShortString.this.occurences(_delim);
}
return _count;
}
public AMQShortString nextToken()
{
if(_pos <= AMQShortString.this.length())
{
int nextDelim = AMQShortString.this.indexOf(_delim, _pos);
if(nextDelim == -1)
{
nextDelim = AMQShortString.this.length();
}
AMQShortString nextToken = AMQShortString.this.substring(_pos, nextDelim++);
_pos = nextDelim;
return nextToken;
}
else
{
return null;
}
}
public boolean hasMoreTokens()
{
return _pos <= AMQShortString.this.length();
}
}
private AMQShortString substring(final int from, final int to)
{
return new AMQShortString(_data, from+_offset, to+_offset);
}
private static final ThreadLocal<Map<AMQShortString, WeakReference<AMQShortString>>> _localInternMap =
new ThreadLocal<Map<AMQShortString, WeakReference<AMQShortString>>>()
{
protected Map<AMQShortString, WeakReference<AMQShortString>> initialValue()
{
return new WeakHashMap<AMQShortString, WeakReference<AMQShortString>>();
};
};
private static final Map<AMQShortString, WeakReference<AMQShortString>> _globalInternMap =
new WeakHashMap<AMQShortString, WeakReference<AMQShortString>>();
private static final Logger _logger = LoggerFactory.getLogger(AMQShortString.class);
private final byte[] _data;
private final int _offset;
private int _hashCode;
private String _asString = null;
private final int _length;
private static final char[] EMPTY_CHAR_ARRAY = new char[0];
public static final AMQShortString EMPTY_STRING = new AMQShortString((String)null);
public AMQShortString(byte[] data)
{
_data = data.clone();
_length = data.length;
_offset = 0;
}
public AMQShortString(byte[] data, int pos)
{
final int size = data[pos++];
final byte[] dataCopy = new byte[size];
System.arraycopy(data,pos,dataCopy,0,size);
_length = size;
_data = dataCopy;
_offset = 0;
}
public AMQShortString(String data)
{
this((data == null) ? EMPTY_CHAR_ARRAY : data.toCharArray());
_asString = data;
}
public AMQShortString(char[] data)
{
if (data == null)
{
throw new NullPointerException("Cannot create AMQShortString with null char[]");
}
final int length = data.length;
final byte[] stringBytes = new byte[length];
int hash = 0;
for (int i = 0; i < length; i++)
{
stringBytes[i] = (byte) (0xFF & data[i]);
hash = (31 * hash) + stringBytes[i];
}
_hashCode = hash;
_data = stringBytes;
_length = length;
_offset = 0;
}
public AMQShortString(CharSequence charSequence)
{
final int length = charSequence.length();
final byte[] stringBytes = new byte[length];
int hash = 0;
for (int i = 0; i < length; i++)
{
stringBytes[i] = ((byte) (0xFF & charSequence.charAt(i)));
hash = (31 * hash) + stringBytes[i];
}
_data = stringBytes;
_hashCode = hash;
_length = length;
_offset = 0;
}
private AMQShortString(ByteBuffer data, final int length)
{
if(data.isDirect() || data.isReadOnly())
{
byte[] dataBytes = new byte[length];
data.get(dataBytes);
_data = dataBytes;
_offset = 0;
}
else
{
_data = data.array();
_offset = data.arrayOffset() + data.position();
data.skip(length);
}
_length = length;
}
private AMQShortString(final byte[] data, final int from, final int to)
{
_offset = from;
_length = to - from;
_data = data;
}
public AMQShortString shrink()
{
if(_data.length != _length)
{
byte[] dataBytes = new byte[_length];
System.arraycopy(_data,_offset,dataBytes,0,_length);
return new AMQShortString(dataBytes,0,_length);
}
else
{
return this;
}
}
/**
* Get the length of the short string
* @return length of the underlying byte array
*/
public int length()
{
return _length;
}
public char charAt(int index)
{
return (char) _data[_offset + index];
}
public CharSequence subSequence(int start, int end)
{
return new CharSubSequence(start, end);
}
public int writeToByteArray(byte[] encoding, int pos)
{
final int size = length();
encoding[pos++] = (byte) size;
System.arraycopy(_data,_offset,encoding,pos,size);
return pos+size;
}
public static AMQShortString readFromByteArray(byte[] byteEncodedDestination, int pos)
{
final AMQShortString shortString = new AMQShortString(byteEncodedDestination, pos);
if(shortString.length() == 0)
{
return null;
}
else
{
return shortString;
}
}
public static AMQShortString readFromBuffer(ByteBuffer buffer)
{
final short length = buffer.getUnsigned();
if (length == 0)
{
return null;
}
else
{
return new AMQShortString(buffer, length);
}
}
public byte[] getBytes()
{
if(_offset == 0 && _length == _data.length)
{
return _data.clone();
}
else
{
byte[] data = new byte[_length];
System.arraycopy(_data,_offset,data,0,_length);
return data;
}
}
public void writeToBuffer(ByteBuffer buffer)
{
final int size = length();
//buffer.setAutoExpand(true);
buffer.put((byte) size);
buffer.put(_data, _offset, size);
}
public boolean endsWith(String s)
{
return endsWith(new AMQShortString(s));
}
public boolean endsWith(AMQShortString otherString)
{
if (otherString.length() > length())
{
return false;
}
int thisLength = length();
int otherLength = otherString.length();
for (int i = 1; i <= otherLength; i++)
{
if (charAt(thisLength - i) != otherString.charAt(otherLength - i))
{
return false;
}
}
return true;
}
public boolean startsWith(String s)
{
return startsWith(new AMQShortString(s));
}
public boolean startsWith(AMQShortString otherString)
{
if (otherString.length() > length())
{
return false;
}
for (int i = 0; i < otherString.length(); i++)
{
if (charAt(i) != otherString.charAt(i))
{
return false;
}
}
return true;
}
public boolean startsWith(CharSequence otherString)
{
if (otherString.length() > length())
{
return false;
}
for (int i = 0; i < otherString.length(); i++)
{
if (charAt(i) != otherString.charAt(i))
{
return false;
}
}
return true;
}
private final class CharSubSequence implements CharSequence
{
private final int _sequenceOffset;
private final int _end;
public CharSubSequence(final int offset, final int end)
{
_sequenceOffset = offset;
_end = end;
}
public int length()
{
return _end - _sequenceOffset;
}
public char charAt(int index)
{
return AMQShortString.this.charAt(index + _sequenceOffset);
}
public CharSequence subSequence(int start, int end)
{
return new CharSubSequence(start + _sequenceOffset, end + _sequenceOffset);
}
}
public char[] asChars()
{
final int size = length();
final char[] chars = new char[size];
for (int i = 0; i < size; i++)
{
chars[i] = (char) _data[i + _offset];
}
return chars;
}
public String asString()
{
if (_asString == null)
{
_asString = new String(asChars());
}
return _asString;
}
public boolean equals(Object o)
{
if(o instanceof AMQShortString)
{
return equals((AMQShortString)o);
}
if(o instanceof CharSequence)
{
return equals((CharSequence)o);
}
if (o == null)
{
return false;
}
if (o == this)
{
return true;
}
return false;
}
public boolean equals(final AMQShortString otherString)
{
if (otherString == this)
{
return true;
}
if (otherString == null)
{
return false;
}
final int hashCode = _hashCode;
final int otherHashCode = otherString._hashCode;
if ((hashCode != 0) && (otherHashCode != 0) && (hashCode != otherHashCode))
{
return false;
}
final int length = _length;
if(length != otherString._length)
{
return false;
}
final byte[] data = _data;
final byte[] otherData = otherString._data;
final int offset = _offset;
final int otherOffset = otherString._offset;
if(offset == 0 && otherOffset == 0 && length == data.length && length == otherData.length)
{
return Arrays.equals(data, otherData);
}
else
{
int thisIdx = offset;
int otherIdx = otherOffset;
for(int i = length; i-- != 0; )
{
if(!(data[thisIdx++] == otherData[otherIdx++]))
{
return false;
}
}
}
return true;
}
public boolean equals(CharSequence s)
{
if(s instanceof AMQShortString)
{
return equals((AMQShortString)s);
}
if (s == null)
{
return false;
}
if (s.length() != length())
{
return false;
}
for (int i = 0; i < length(); i++)
{
if (charAt(i) != s.charAt(i))
{
return false;
}
}
return true;
}
public int hashCode()
{
int hash = _hashCode;
if (hash == 0)
{
final int size = length();
for (int i = 0; i < size; i++)
{
hash = (31 * hash) + _data[i+_offset];
}
_hashCode = hash;
}
return hash;
}
public void setDirty()
{
_hashCode = 0;
}
public String toString()
{
return asString();
}
public int compareTo(AMQShortString name)
{
if (name == null)
{
return 1;
}
else
{
if (name.length() < length())
{
return -name.compareTo(this);
}
for (int i = 0; i < length(); i++)
{
final byte d = _data[i+_offset];
final byte n = name._data[i+name._offset];
if (d < n)
{
return -1;
}
if (d > n)
{
return 1;
}
}
return (length() == name.length()) ? 0 : -1;
}
}
public AMQShortStringTokenizer tokenize(byte delim)
{
return new TokenizerImpl(delim);
}
public AMQShortString intern()
{
hashCode();
Map<AMQShortString, WeakReference<AMQShortString>> localMap =
_localInternMap.get();
WeakReference<AMQShortString> ref = localMap.get(this);
AMQShortString internString;
if(ref != null)
{
internString = ref.get();
if(internString != null)
{
return internString;
}
}
synchronized(_globalInternMap)
{
ref = _globalInternMap.get(this);
if((ref == null) || ((internString = ref.get()) == null))
{
internString = shrink();
ref = new WeakReference(internString);
_globalInternMap.put(internString, ref);
}
}
localMap.put(internString, ref);
return internString;
}
private int occurences(final byte delim)
{
int count = 0;
final int end = _offset + _length;
for(int i = _offset ; i < end ; i++ )
{
if(_data[i] == delim)
{
count++;
}
}
return count;
}
private int indexOf(final byte val, final int pos)
{
for(int i = pos; i < length(); i++)
{
if(_data[_offset+i] == val)
{
return i;
}
}
return -1;
}
public static AMQShortString join(final Collection<AMQShortString> terms,
final AMQShortString delim)
{
if(terms.size() == 0)
{
return EMPTY_STRING;
}
int size = delim.length() * (terms.size() - 1);
for(AMQShortString term : terms)
{
size += term.length();
}
byte[] data = new byte[size];
int pos = 0;
final byte[] delimData = delim._data;
final int delimOffset = delim._offset;
final int delimLength = delim._length;
for(AMQShortString term : terms)
{
if(pos!=0)
{
System.arraycopy(delimData, delimOffset,data,pos, delimLength);
pos+=delimLength;
}
System.arraycopy(term._data,term._offset,data,pos,term._length);
pos+=term._length;
}
return new AMQShortString(data,0,size);
}
public int toIntValue()
{
int pos = _offset;
int val = 0;
boolean isNegative = (_data[pos] == MINUS);
if(isNegative)
{
pos++;
}
final int end = _length + _offset;
while(pos < end)
{
int digit = (int) (_data[pos++] - ZERO);
if((digit < 0) || (digit > 9))
{
throw new NumberFormatException("\""+toString()+"\" is not a valid number");
}
val = val * 10;
val += digit;
}
if(isNegative)
{
val = val * -1;
}
return val;
}
public boolean contains(final byte b)
{
final int end = _length + _offset;
for(int i = _offset; i < end; i++)
{
if(_data[i] == b)
{
return true;
}
}
return false; //To change body of created methods use File | Settings | File Templates.
}
public static void main(String args[])
{
AMQShortString s = new AMQShortString("a.b.c.d.e.f.g.h.i.j.k");
AMQShortString s2 = s.substring(2, 7);
AMQShortStringTokenizer t = s2.tokenize((byte) '.');
while(t.hasMoreTokens())
{
System.err.println(t.nextToken());
}
}
}