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.io;
016    
017    import com.liferay.portal.kernel.memory.SoftReferenceThreadLocal;
018    import com.liferay.portal.kernel.util.ClassLoaderPool;
019    import com.liferay.portal.kernel.util.GetterUtil;
020    
021    import java.io.IOException;
022    import java.io.ObjectOutputStream;
023    import java.io.OutputStream;
024    import java.io.Serializable;
025    
026    import java.nio.ByteBuffer;
027    
028    /**
029     * @author Shuyang Zhou
030     * @see    Deserializer
031     */
032    public class Serializer {
033    
034            public Serializer() {
035                    BufferQueue bufferQueue = bufferQueueThreadLocal.get();
036    
037                    buffer = bufferQueue.dequeue();
038            }
039    
040            public ByteBuffer toByteBuffer() {
041                    byte[] tempBuffer = new byte[index];
042    
043                    System.arraycopy(buffer, 0, tempBuffer, 0, index);
044    
045                    ByteBuffer byteBuffer = ByteBuffer.wrap(tempBuffer);
046    
047                    if (buffer.length <= THREADLOCAL_BUFFER_SIZE_LIMIT) {
048                            BufferQueue bufferQueue = bufferQueueThreadLocal.get();
049    
050                            bufferQueue.enqueue(buffer);
051                    }
052    
053                    buffer = null;
054    
055                    return byteBuffer;
056            }
057    
058            public void writeBoolean(boolean b) {
059                    BigEndianCodec.putBoolean(getBuffer(1), index++, b);
060            }
061    
062            public void writeByte(byte b) {
063                    getBuffer(1)[index++] = b;
064            }
065    
066            public void writeChar(char c) {
067                    BigEndianCodec.putChar(getBuffer(2), index, c);
068    
069                    index += 2;
070            }
071    
072            public void writeDouble(double d) {
073                    BigEndianCodec.putDouble(getBuffer(8), index, d);
074    
075                    index += 8;
076            }
077    
078            public void writeFloat(float f) {
079                    BigEndianCodec.putFloat(getBuffer(4), index, f);
080    
081                    index += 4;
082            }
083    
084            public void writeInt(int i) {
085                    BigEndianCodec.putInt(getBuffer(4), index, i);
086    
087                    index += 4;
088            }
089    
090            public void writeLong(long l) {
091                    BigEndianCodec.putLong(getBuffer(8), index, l);
092    
093                    index += 8;
094            }
095    
096            public void writeObject(Serializable serializable) {
097    
098                    // The if block is ordered by frequency for better performance
099    
100                    if (serializable == null) {
101                            writeByte(SerializationConstants.TC_NULL);
102    
103                            return;
104                    }
105                    else if (serializable instanceof Long) {
106                            writeByte(SerializationConstants.TC_LONG);
107                            writeLong((Long)serializable);
108    
109                            return;
110                    }
111                    else if (serializable instanceof String) {
112                            writeByte(SerializationConstants.TC_STRING);
113                            writeString((String)serializable);
114    
115                            return;
116                    }
117                    else if (serializable instanceof Integer) {
118                            writeByte(SerializationConstants.TC_INTEGER);
119                            writeInt((Integer)serializable);
120    
121                            return;
122                    }
123                    else if (serializable instanceof Boolean) {
124                            writeByte(SerializationConstants.TC_BOOLEAN);
125                            writeBoolean((Boolean)serializable);
126    
127                            return;
128                    }
129                    else if (serializable instanceof Class) {
130                            Class<?> clazz = (Class<?>)serializable;
131    
132                            ClassLoader classLoader = clazz.getClassLoader();
133    
134                            String contextName = ClassLoaderPool.getContextName(classLoader);
135    
136                            writeByte(SerializationConstants.TC_CLASS);
137                            writeString(contextName);
138                            writeString(clazz.getName());
139    
140                            return;
141                    }
142                    else if (serializable instanceof Short) {
143                            writeByte(SerializationConstants.TC_SHORT);
144                            writeShort((Short)serializable);
145    
146                            return;
147                    }
148                    else if (serializable instanceof Character) {
149                            writeByte(SerializationConstants.TC_CHARACTER);
150                            writeChar((Character)serializable);
151    
152                            return;
153                    }
154                    else if (serializable instanceof Byte) {
155                            writeByte(SerializationConstants.TC_BYTE);
156                            writeByte((Byte)serializable);
157    
158                            return;
159                    }
160                    else if (serializable instanceof Double) {
161                            writeByte(SerializationConstants.TC_DOUBLE);
162                            writeDouble((Double)serializable);
163    
164                            return;
165                    }
166                    else if (serializable instanceof Float) {
167                            writeByte(SerializationConstants.TC_FLOAT);
168                            writeFloat((Float)serializable);
169    
170                            return;
171                    }
172                    else {
173                            writeByte(SerializationConstants.TC_OBJECT);
174                    }
175    
176                    try {
177                            ObjectOutputStream objectOutputStream =
178                                    new AnnotatedObjectOutputStream(new BufferOutputStream());
179    
180                            objectOutputStream.writeObject(serializable);
181    
182                            objectOutputStream.close();
183                    }
184                    catch (IOException ioe) {
185                            throw new RuntimeException(
186                                    "Unable to write ordinary serializable object " + serializable,
187                                    ioe);
188                    }
189            }
190    
191            public void writeShort(short s) {
192                    BigEndianCodec.putShort(getBuffer(2), index, s);
193    
194                    index += 2;
195            }
196    
197            public void writeString(String s) {
198                    int length = s.length();
199    
200                    boolean asciiCode = true;
201    
202                    for (int i = 0; i < length; i++) {
203                            char c = s.charAt(i);
204    
205                            if ((c == 0) || (c > 127)) {
206                                    asciiCode = false;
207                                    break;
208                            }
209                    }
210    
211                    BigEndianCodec.putBoolean(buffer, index++, asciiCode);
212    
213                    if (asciiCode) {
214                            byte[] buffer = getBuffer(length + 4);
215    
216                            BigEndianCodec.putInt(buffer, index, length);
217    
218                            index += 4;
219    
220                            for (int i = 0; i < length; i++) {
221                                    char c = s.charAt(i);
222    
223                                    buffer[index++] = (byte)c;
224                            }
225                    }
226                    else {
227                            byte[] buffer = getBuffer(length * 2 + 4);
228    
229                            BigEndianCodec.putInt(buffer, index, length);
230    
231                            index += 4;
232    
233                            for (int i = 0; i < length; i++) {
234                                    char c = s.charAt(i);
235    
236                                    BigEndianCodec.putChar(buffer, index, c);
237    
238                                    index += 2;
239                            }
240                    }
241            }
242    
243            public void writeTo(OutputStream outputStream) throws IOException {
244                    outputStream.write(buffer, 0, index);
245    
246                    if (buffer.length <= THREADLOCAL_BUFFER_SIZE_LIMIT) {
247                            BufferQueue bufferQueue = bufferQueueThreadLocal.get();
248    
249                            bufferQueue.enqueue(buffer);
250                    }
251    
252                    buffer = null;
253            }
254    
255            /**
256             * This method is final so that JIT can inline it.
257             */
258            protected final byte[] getBuffer(int ensureExtraSpace) {
259                    int minSize = index + ensureExtraSpace;
260    
261                    if (minSize < 0) {
262    
263                            // Cannot create byte[] with length longer than Integer.MAX_VALUE
264    
265                            throw new OutOfMemoryError();
266                    }
267    
268                    int oldSize = buffer.length;
269    
270                    if (minSize > oldSize) {
271                            int newSize = oldSize << 1;
272    
273                            if (newSize < minSize) {
274    
275                                    // Overflow and insufficient growth protection
276    
277                                    newSize = minSize;
278                            }
279    
280                            byte[] tempBuffer = new byte[newSize];
281    
282                            System.arraycopy(buffer, 0, tempBuffer, 0, oldSize);
283    
284                            buffer = tempBuffer;
285                    }
286    
287                    return buffer;
288            }
289    
290            protected static final ThreadLocal<BufferQueue> bufferQueueThreadLocal =
291                    new SoftReferenceThreadLocal<BufferQueue>() {
292    
293                    @Override
294                    protected BufferQueue initialValue() {
295                            return new BufferQueue();
296                    }
297    
298            };
299    
300            protected static final int THREADLOCAL_BUFFER_COUNT_LIMIT;
301    
302            protected static final int THREADLOCAL_BUFFER_COUNT_MIN = 8;
303    
304            protected static final int THREADLOCAL_BUFFER_SIZE_LIMIT;
305    
306            protected static final int THREADLOCAL_BUFFER_SIZE_MIN = 16 * 1024;
307    
308            static {
309                    int threadLocalBufferCountLimit = GetterUtil.getInteger(
310                            System.getProperty(
311                                    Serializer.class.getName() +
312                                            ".thread.local.buffer.count.limit"));
313    
314                    if (threadLocalBufferCountLimit < THREADLOCAL_BUFFER_COUNT_MIN) {
315                            threadLocalBufferCountLimit = THREADLOCAL_BUFFER_COUNT_MIN;
316                    }
317    
318                    THREADLOCAL_BUFFER_COUNT_LIMIT = threadLocalBufferCountLimit;
319    
320                    int threadLocalBufferSizeLimit = GetterUtil.getInteger(
321                            System.getProperty(
322                                    Serializer.class.getName() +
323                                            ".thread.local.buffer.size.limit"));
324    
325                    if (threadLocalBufferSizeLimit < THREADLOCAL_BUFFER_SIZE_MIN) {
326                            threadLocalBufferSizeLimit = THREADLOCAL_BUFFER_SIZE_MIN;
327                    }
328    
329                    THREADLOCAL_BUFFER_SIZE_LIMIT = threadLocalBufferSizeLimit;
330            }
331    
332            protected byte[] buffer;
333            protected int index;
334    
335            protected static class BufferNode {
336    
337                    public BufferNode(byte[] buffer) {
338                            this.buffer = buffer;
339                    }
340    
341                    protected byte[] buffer;
342                    protected BufferNode next;
343    
344            }
345    
346            protected static class BufferQueue {
347    
348                    public void enqueue(byte[] buffer) {
349                            BufferNode bufferNode = new BufferNode(buffer);
350    
351                            if (headBufferNode == null) {
352                                    headBufferNode = bufferNode;
353    
354                                    count = 1;
355    
356                                    return;
357                            }
358    
359                            BufferNode previousBufferNode = null;
360                            BufferNode currentBufferNode = headBufferNode;
361    
362                            while ((currentBufferNode != null) &&
363                                       (currentBufferNode.buffer.length >
364                                                    bufferNode.buffer.length)) {
365    
366                                    previousBufferNode = currentBufferNode;
367    
368                                    currentBufferNode = currentBufferNode.next;
369                            }
370    
371                            if (previousBufferNode == null) {
372                                    bufferNode.next = headBufferNode;
373    
374                                    headBufferNode = bufferNode;
375                            }
376                            else {
377                                    bufferNode.next = currentBufferNode;
378    
379                                    previousBufferNode.next = bufferNode;
380                            }
381    
382                            if (++count > THREADLOCAL_BUFFER_COUNT_LIMIT) {
383                                    if (previousBufferNode == null) {
384                                            previousBufferNode = headBufferNode;
385                                    }
386    
387                                    currentBufferNode = previousBufferNode.next;
388    
389                                    while (currentBufferNode.next != null) {
390                                            previousBufferNode = currentBufferNode;
391                                            currentBufferNode = currentBufferNode.next;
392                                    }
393    
394                                    // Dereference
395    
396                                    previousBufferNode.next = null;
397    
398                                    // Help GC
399    
400                                    currentBufferNode.buffer = null;
401                                    currentBufferNode.next = null;
402                            }
403                    }
404    
405                    public byte[] dequeue() {
406                            if (headBufferNode == null) {
407                                    return new byte[THREADLOCAL_BUFFER_SIZE_MIN];
408                            }
409    
410                            BufferNode bufferNode = headBufferNode;
411    
412                            headBufferNode = headBufferNode.next;
413    
414                            // Help GC
415    
416                            bufferNode.next = null;
417    
418                            return bufferNode.buffer;
419                    }
420    
421                    protected int count;
422                    protected BufferNode headBufferNode;
423    
424            }
425    
426            protected class BufferOutputStream extends OutputStream {
427    
428                    @Override
429                    public void write(byte[] bytes) {
430                            write(bytes, 0, bytes.length);
431                    }
432    
433                    @Override
434                    public void write(byte[] bytes, int offset, int length) {
435                            byte[] buffer = getBuffer(length);
436    
437                            System.arraycopy(bytes, offset, buffer, index, length);
438    
439                            index += length;
440                    }
441    
442                    @Override
443                    public void write(int b) {
444                            getBuffer(1)[index++] = (byte)b;
445                    }
446    
447            }
448    
449    }