001    /*
002     * Copyright 2009 Red Hat, Inc.
003     * Red Hat licenses this file to you under the Apache License, version
004     * 2.0 (the "License"); you may not use this file except in compliance
005     * with the License.  You may obtain a copy of the License at
006     *    http://www.apache.org/licenses/LICENSE-2.0
007     * Unless required by applicable law or agreed to in writing, software
008     * distributed under the License is distributed on an "AS IS" BASIS,
009     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
010     * implied.  See the License for the specific language governing
011     * permissions and limitations under the License.
012     */
013    
014    package org.hornetq.api.core;
015    
016    import java.io.Serializable;
017    import java.util.ArrayList;
018    import java.util.List;
019    
020    import org.hornetq.core.logging.Logger;
021    import org.hornetq.utils.DataConstants;
022    
023    /**
024     * A simple String class that can store all characters, and stores as simple byte[],
025     * this minimises expensive copying between String objects.
026     *
027     * This object is used heavily throughout HornetQ for performance reasons.
028     * 
029     * @author <a href="mailto:tim.fox@jboss.com">Tim Fox</a>
030     *
031     */
032    
033    public class SimpleString implements CharSequence, Serializable, Comparable<SimpleString>
034    {
035       private static final long serialVersionUID = 4204223851422244307L;
036    
037       private static final Logger log = Logger.getLogger(SimpleString.class);
038    
039       // Attributes
040       // ------------------------------------------------------------------------
041       private final byte[] data;
042    
043       private transient int hash;
044    
045       // Cache the string
046       private transient String str;
047    
048       // Static
049       // ----------------------------------------------------------------------
050    
051       /**
052        * Returns a SimpleString constructed from the <code>string</code> parameter.
053        * If <code>string</code> is <code>null</code>, the return value will be <code>null</code> too.
054        */
055       public static SimpleString toSimpleString(final String string)
056       {
057          if (string == null)
058          {
059             return null;
060          }
061          return new SimpleString(string);
062       }
063    
064       // Constructors
065       // ----------------------------------------------------------------------
066       /**
067        * creates a SimpleString from a conventional String
068        * @param string the string to transform
069        */
070       public SimpleString(final String string)
071       {
072          int len = string.length();
073    
074          data = new byte[len << 1];
075    
076          int j = 0;
077    
078          for (int i = 0; i < len; i++)
079          {
080             char c = string.charAt(i);
081    
082             byte low = (byte)(c & 0xFF); // low byte
083    
084             data[j++] = low;
085    
086             byte high = (byte)(c >> 8 & 0xFF); // high byte
087    
088             data[j++] = high;
089          }
090    
091          str = string;
092       }
093    
094       /**
095        * creates a SimpleString from a byte array
096        * @param data the byte array to use
097        */
098       public SimpleString(final byte[] data)
099       {
100          this.data = data;
101       }
102    
103       // CharSequence implementation
104       // ---------------------------------------------------------------------------
105    
106       public int length()
107       {
108          return data.length >> 1;
109       }
110    
111       public char charAt(int pos)
112       {
113          if (pos < 0 || pos >= data.length >> 1)
114          {
115             throw new IndexOutOfBoundsException();
116          }
117          pos <<= 1;
118    
119          return (char)(data[pos] | data[pos + 1] << 8);
120       }
121    
122       public CharSequence subSequence(final int start, final int end)
123       {
124          int len = data.length >> 1;
125    
126          if (end < start || start < 0 || end > len)
127          {
128             throw new IndexOutOfBoundsException();
129          }
130          else
131          {
132             int newlen = end - start << 1;
133             byte[] bytes = new byte[newlen];
134    
135             System.arraycopy(data, start << 1, bytes, 0, newlen);
136    
137             return new SimpleString(bytes);
138          }
139       }
140    
141       // Comparable implementation -------------------------------------
142    
143       public int compareTo(final SimpleString o)
144       {
145          return toString().compareTo(o.toString());
146       }
147    
148       // Public
149       // ---------------------------------------------------------------------------
150    
151       /**
152        * returns the underlying byte array of this SimpleString
153        * @return the byte array
154        */
155       public byte[] getData()
156       {
157          return data;
158       }
159    
160       /**
161        * returns true if the SimpleString parameter starts with the same data as this one. false if not.
162        * @param other the SimpelString to look for
163        * @return true if this SimpleString starts with the same data
164        */
165       public boolean startsWith(final SimpleString other)
166       {
167          byte[] otherdata = other.data;
168    
169          if (otherdata.length > data.length)
170          {
171             return false;
172          }
173    
174          for (int i = 0; i < otherdata.length; i++)
175          {
176             if (data[i] != otherdata[i])
177             {
178                return false;
179             }
180          }
181    
182          return true;
183       }
184    
185       @Override
186       public String toString()
187       {
188          if (str == null)
189          {
190             int len = data.length >> 1;
191    
192             char[] chars = new char[len];
193    
194             int j = 0;
195    
196             for (int i = 0; i < len; i++)
197             {
198                int low = data[j++] & 0xFF;
199    
200                int high = data[j++] << 8 & 0xFF00;
201    
202                chars[i] = (char)(low | high);
203             }
204    
205             str = new String(chars);
206          }
207    
208          return str;
209       }
210    
211       @Override
212       public boolean equals(final Object other)
213       {
214          if (this == other)
215          {
216             return true;
217          }
218          
219          if (other instanceof SimpleString)
220          {
221             SimpleString s = (SimpleString)other;
222    
223             if (data.length != s.data.length)
224             {
225                return false;
226             }
227    
228             for (int i = 0; i < data.length; i++)
229             {
230                if (data[i] != s.data[i])
231                {
232                   return false;
233                }
234             }
235    
236             return true;
237          }
238          else
239          {
240             return false;
241          }
242       }
243    
244       @Override
245       public int hashCode()
246       {
247          if (hash == 0)
248          {
249             int tmphash = 0;
250             for (byte element : data)
251             {
252                tmphash = (tmphash << 5) - tmphash + element; // (hash << 5) - hash is same as hash * 31
253             }
254             hash = tmphash;
255          }
256    
257          return hash;
258       }
259    
260       /**
261        * splits this SimpleString into an array of SimpleString using the char param as the delimeter.
262        *
263        * i.e. "a.b" would return "a" and "b" if . was the delimeter
264        * @param delim
265        */
266       public SimpleString[] split(final char delim)
267       {
268          if (!contains(delim))
269          {
270             return new SimpleString[] { this };
271          }
272          else
273          {
274             List<SimpleString> all = new ArrayList<SimpleString>();
275             int lasPos = 0;
276             for (int i = 0; i < data.length; i += 2)
277             {
278                byte low = (byte)(delim & 0xFF); // low byte
279                byte high = (byte)(delim >> 8 & 0xFF); // high byte
280                if (data[i] == low && data[i + 1] == high)
281                {
282                   byte[] bytes = new byte[i - lasPos];
283                   System.arraycopy(data, lasPos, bytes, 0, bytes.length);
284                   lasPos = i + 2;
285                   all.add(new SimpleString(bytes));
286                }
287             }
288             byte[] bytes = new byte[data.length - lasPos];
289             System.arraycopy(data, lasPos, bytes, 0, bytes.length);
290             all.add(new SimpleString(bytes));
291             SimpleString[] parts = new SimpleString[all.size()];
292             return all.toArray(parts);
293          }
294       }
295    
296       /**
297        * checks to see if this SimpleString contains the char parameter passed in
298        *
299        * @param c the char to check for
300        * @return true if the char is found, false otherwise.
301        */
302       public boolean contains(final char c)
303       {
304          for (int i = 0; i < data.length; i += 2)
305          {
306             byte low = (byte)(c & 0xFF); // low byte
307             byte high = (byte)(c >> 8 & 0xFF); // high byte
308             if (data[i] == low && data[i + 1] == high)
309             {
310                return true;
311             }
312          }
313          return false;
314       }
315    
316       /**
317        * concatanates a SimpleString and a String
318        *
319        * @param toAdd the String to concate with.
320        * @return the concatanated SimpleString
321        */
322       public SimpleString concat(final String toAdd)
323       {
324          return concat(new SimpleString(toAdd));
325       }
326    
327       /**
328        * concatanates 2 SimpleString's
329        *
330        * @param toAdd the SimpleString to concate with.
331        * @return the concatanated SimpleString
332        */
333       public SimpleString concat(final SimpleString toAdd)
334       {
335          byte[] bytes = new byte[data.length + toAdd.getData().length];
336          System.arraycopy(data, 0, bytes, 0, data.length);
337          System.arraycopy(toAdd.getData(), 0, bytes, data.length, toAdd.getData().length);
338          return new SimpleString(bytes);
339       }
340    
341       /**
342        * concatanates a SimpleString and a char
343        *
344        * @param c the char to concate with.
345        * @return the concatanated SimpleString
346        */
347       public SimpleString concat(final char c)
348       {
349          byte[] bytes = new byte[data.length + 2];
350          System.arraycopy(data, 0, bytes, 0, data.length);
351          bytes[data.length] = (byte)(c & 0xFF);
352          bytes[data.length + 1] = (byte)(c >> 8 & 0xFF);
353          return new SimpleString(bytes);
354       }
355    
356       /**
357        * returns the size of this SimpleString
358        * @return the size
359        */
360       public int sizeof()
361       {
362          return DataConstants.SIZE_INT + data.length;
363       }
364    
365       /**
366        * returns the size of a SimpleString
367        * @param str the SimpleString to check
368        * @return the size
369        */
370       public static int sizeofString(final SimpleString str)
371       {
372          return str.sizeof();
373       }
374    
375       /**
376        * returns the size of a SimpleString which could be null
377        * @param str the SimpleString to check
378        * @return the size
379        */
380       public static int sizeofNullableString(final SimpleString str)
381       {
382          if (str == null)
383          {
384             return 1;
385          }
386          else
387          {
388             return 1 + str.sizeof();
389          }
390       }
391    
392       /**
393        * 
394        * @param srcBegin
395        * @param srcEnd
396        * @param dst
397        * @param dstBegin
398        */
399       public void getChars(final int srcBegin, final int srcEnd, final char dst[], final int dstBegin)
400       {
401          if (srcBegin < 0)
402          {
403             throw new StringIndexOutOfBoundsException(srcBegin);
404          }
405          if (srcEnd > length())
406          {
407             throw new StringIndexOutOfBoundsException(srcEnd);
408          }
409          if (srcBegin > srcEnd)
410          {
411             throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
412          }
413    
414          int j = 0;
415    
416          for (int i = srcBegin; i < srcEnd - srcBegin; i++)
417          {
418             int low = data[j++] & 0xFF;
419    
420             int high = data[j++] << 8 & 0xFF00;
421    
422             dst[i] = (char)(low | high);
423          }
424       }
425    
426    }