001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.kernel.util;
016    
017    import com.liferay.portal.kernel.memory.SoftReferenceThreadLocal;
018    
019    import java.io.IOException;
020    import java.io.Serializable;
021    import java.io.Writer;
022    
023    import java.lang.reflect.Constructor;
024    
025    /**
026     * <p>
027     * See http://issues.liferay.com/browse/LPS-6072.
028     * </p>
029     *
030     * @author Shuyang Zhou
031     * @author Brian Wing Shun Chan
032     */
033    public class StringBundler implements Serializable {
034    
035            public StringBundler() {
036                    _array = new String[_DEFAULT_ARRAY_CAPACITY];
037            }
038    
039            public StringBundler(int initialCapacity) {
040                    if (initialCapacity <= 0) {
041                            initialCapacity = _DEFAULT_ARRAY_CAPACITY;
042                    }
043    
044                    _array = new String[initialCapacity];
045            }
046    
047            public StringBundler(String s) {
048                    _array = new String[_DEFAULT_ARRAY_CAPACITY];
049    
050                    _array[0] = s;
051    
052                    _arrayIndex = 1;
053            }
054    
055            public StringBundler(String[] stringArray) {
056                    this(stringArray, 0);
057            }
058    
059            public StringBundler(String[] stringArray, int extraSpace) {
060                    _array = new String[stringArray.length + extraSpace];
061    
062                    for (int i = 0; i < stringArray.length; i++) {
063                            String s = stringArray[i];
064    
065                            if ((s != null) && (s.length() > 0)) {
066                                    _array[_arrayIndex++] = s;
067                            }
068                    }
069            }
070    
071            public StringBundler append(boolean b) {
072                    if (b) {
073                            return append(_TRUE);
074                    }
075                    else {
076                            return append(_FALSE);
077                    }
078            }
079    
080            public StringBundler append(char c) {
081                    return append(String.valueOf(c));
082            }
083    
084            public StringBundler append(char[] chars) {
085                    if (chars == null) {
086                            return append("null");
087                    }
088                    else {
089                            return append(new String(chars));
090                    }
091            }
092    
093            public StringBundler append(double d) {
094                    return append(Double.toString(d));
095            }
096    
097            public StringBundler append(float f) {
098                    return append(Float.toString(f));
099            }
100    
101            public StringBundler append(int i) {
102                    return append(Integer.toString(i));
103            }
104    
105            public StringBundler append(long l) {
106                    return append(Long.toString(l));
107            }
108    
109            public StringBundler append(Object obj) {
110                    return append(String.valueOf(obj));
111            }
112    
113            public StringBundler append(String s) {
114                    if (s == null) {
115                            s = StringPool.NULL;
116                    }
117    
118                    if (s.length() == 0) {
119                            return this;
120                    }
121    
122                    if (_arrayIndex >= _array.length) {
123                            expandCapacity(_array.length * 2);
124                    }
125    
126                    _array[_arrayIndex++] = s;
127    
128                    return this;
129            }
130    
131            public StringBundler append(String[] stringArray) {
132                    if (ArrayUtil.isEmpty(stringArray)) {
133                            return this;
134                    }
135    
136                    if ((_array.length - _arrayIndex) < stringArray.length) {
137                            expandCapacity((_array.length + stringArray.length) * 2);
138                    }
139    
140                    for (int i = 0; i < stringArray.length; i++) {
141                            String s = stringArray[i];
142    
143                            if ((s != null) && (s.length() > 0)) {
144                                    _array[_arrayIndex++] = s;
145                            }
146                    }
147    
148                    return this;
149            }
150    
151            public StringBundler append(StringBundler sb) {
152                    if ((sb == null) || (sb._arrayIndex == 0)) {
153                            return this;
154                    }
155    
156                    if ((_array.length - _arrayIndex) < sb._arrayIndex) {
157                            expandCapacity((_array.length + sb._arrayIndex) * 2);
158                    }
159    
160                    System.arraycopy(sb._array, 0, _array, _arrayIndex, sb._arrayIndex);
161    
162                    _arrayIndex += sb._arrayIndex;
163    
164                    return this;
165            }
166    
167            public int capacity() {
168                    return _array.length;
169            }
170    
171            public int index() {
172                    return _arrayIndex;
173            }
174    
175            public int length() {
176                    int length = 0;
177    
178                    for (int i = 0; i < _arrayIndex; i++) {
179                            length += _array[i].length();
180                    }
181    
182                    return length;
183            }
184    
185            public void setIndex(int newIndex) {
186                    if (newIndex < 0) {
187                            throw new ArrayIndexOutOfBoundsException(newIndex);
188                    }
189    
190                    if (newIndex > _array.length) {
191                            String[] newArray = new String[newIndex];
192    
193                            System.arraycopy(_array, 0, newArray, 0, _arrayIndex);
194    
195                            _array = newArray;
196                    }
197    
198                    if (_arrayIndex < newIndex) {
199                            for (int i = _arrayIndex; i < newIndex; i++) {
200                                    _array[i] = StringPool.BLANK;
201                            }
202                    }
203    
204                    if (_arrayIndex > newIndex) {
205                            for (int i = newIndex; i < _arrayIndex; i++) {
206                                    _array[i] = null;
207                            }
208                    }
209    
210                    _arrayIndex = newIndex;
211            }
212    
213            public void setStringAt(String s, int index) {
214                    if ((index < 0) || (index >= _arrayIndex)) {
215                            throw new ArrayIndexOutOfBoundsException(index);
216                    }
217    
218                    _array[index] = s;
219            }
220    
221            public String stringAt(int index) {
222                    if ((index < 0) || (index >= _arrayIndex)) {
223                            throw new ArrayIndexOutOfBoundsException(index);
224                    }
225    
226                    return _array[index];
227            }
228    
229            @Override
230            public String toString() {
231                    return toString(true);
232            }
233    
234            public String toString(boolean unsafeCreate) {
235                    if (_arrayIndex == 0) {
236                            return StringPool.BLANK;
237                    }
238    
239                    if (_arrayIndex == 1) {
240                            return _array[0];
241                    }
242    
243                    if (_arrayIndex == 2) {
244                            return _array[0].concat(_array[1]);
245                    }
246    
247                    if (_arrayIndex == 3) {
248                            return _array[0].concat(_array[1]).concat(_array[2]);
249                    }
250    
251                    int length = 0;
252    
253                    for (int i = 0; i < _arrayIndex; i++) {
254                            length += _array[i].length();
255                    }
256    
257                    StringBuilder sb = null;
258    
259                    if ((length > _unsafeCreateLimit) && (_stringConstructor != null) &&
260                            CharBufferPool.isEnabled() && unsafeCreate) {
261    
262                            char[] charBuffer = CharBufferPool.borrow(length);
263    
264                            int offset = 0;
265    
266                            for (int i = 0; i < _arrayIndex; i++) {
267                                    String s = _array[i];
268    
269                                    s.getChars(0, s.length(), charBuffer, offset);
270    
271                                    offset += s.length();
272                            }
273    
274                            try {
275                                    return _stringConstructor.newInstance(0, length, charBuffer);
276                            }
277                            catch (Exception e) {
278                                    _stringConstructor = null;
279    
280                                    return toString(false);
281                            }
282                    }
283                    else if (length > _threadLocalBufferLimit) {
284                            sb = _stringBuilderThreadLocal.get();
285    
286                            if (sb == null) {
287                                    sb = new StringBuilder(length);
288    
289                                    _stringBuilderThreadLocal.set(sb);
290                            }
291                            else if (sb.capacity() < length) {
292                                    sb.setLength(length);
293                            }
294    
295                            sb.setLength(0);
296                    }
297                    else {
298                            sb = new StringBuilder(length);
299                    }
300    
301                    for (int i = 0; i < _arrayIndex; i++) {
302                            sb.append(_array[i]);
303                    }
304    
305                    return sb.toString();
306            }
307    
308            public void writeTo(Writer writer) throws IOException {
309                    for (int i = 0; i < _arrayIndex; i++) {
310                            writer.write(_array[i]);
311                    }
312            }
313    
314            protected void expandCapacity(int newCapacity) {
315                    String[] newArray = new String[newCapacity];
316    
317                    System.arraycopy(_array, 0, newArray, 0, _arrayIndex);
318    
319                    _array = newArray;
320            }
321    
322            private static final int _DEFAULT_ARRAY_CAPACITY = 16;
323    
324            private static final String _FALSE = "false";
325    
326            private static final int _THREADLOCAL_BUFFER_LIMIT = GetterUtil.getInteger(
327                    System.getProperty(
328                            StringBundler.class.getName() + ".threadlocal.buffer.limit"));
329    
330            private static final String _TRUE = "true";
331    
332            private static final int _UNSAFE_CREATE_LIMIT = GetterUtil.getInteger(
333                    System.getProperty(
334                            StringBundler.class.getName() + ".unsafe.create.limit"));
335    
336            private static final long serialVersionUID = 1L;
337    
338            private static ThreadLocal<StringBuilder> _stringBuilderThreadLocal;
339            private static Constructor<String> _stringConstructor;
340            private static int _threadLocalBufferLimit;
341            private static int _unsafeCreateLimit;
342    
343            static {
344                    if (_THREADLOCAL_BUFFER_LIMIT > 0) {
345                            _stringBuilderThreadLocal =
346                                    new SoftReferenceThreadLocal<StringBuilder>();
347                            _threadLocalBufferLimit = _THREADLOCAL_BUFFER_LIMIT;
348                    }
349                    else {
350                            _stringBuilderThreadLocal = null;
351                            _threadLocalBufferLimit = Integer.MAX_VALUE;
352                    }
353    
354                    if (_UNSAFE_CREATE_LIMIT > 0) {
355                            try {
356                                    _unsafeCreateLimit = _UNSAFE_CREATE_LIMIT;
357    
358                                    _stringConstructor = String.class.getDeclaredConstructor(
359                                            int.class, int.class, char[].class);
360    
361                                    _stringConstructor.setAccessible(true);
362                            }
363                            catch (Exception e) {
364                            }
365                    }
366                    else {
367                            _unsafeCreateLimit = Integer.MAX_VALUE;
368                            _stringConstructor = null;
369                    }
370            }
371    
372            private String[] _array;
373            private int _arrayIndex;
374    
375    }