001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.directory.mavibot.btree.serializer;
021
022
023import java.io.IOException;
024import java.io.UnsupportedEncodingException;
025import java.nio.ByteBuffer;
026import java.util.Comparator;
027
028import org.apache.directory.mavibot.btree.comparator.StringComparator;
029import org.apache.directory.mavibot.btree.exception.SerializerCreationException;
030import org.apache.directory.mavibot.btree.util.Strings;
031
032
033/**
034 * The String serializer.
035 *
036 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
037 */
038public class StringSerializer extends AbstractElementSerializer<String>
039{
040    /** A static instance of a StringSerializer */
041    public static final StringSerializer INSTANCE = new StringSerializer();
042
043    /**
044     * Create a new instance of StringSerializer
045     */
046    private StringSerializer()
047    {
048        super( StringComparator.INSTANCE );
049    }
050
051
052    /**
053     * Create a new instance of StringSerializer with custom comparator
054     */
055    public StringSerializer( Comparator<String> comparator )
056    {
057        super( comparator );
058    }
059
060
061    /**
062     * A static method used to deserialize a String from a byte array.
063     * @param in The byte array containing the String
064     * @return A String
065     */
066    public static String deserialize( byte[] in )
067    {
068        return deserialize( in, 0 );
069    }
070
071
072    /**
073     * A static method used to deserialize a String from a byte array.
074     * @param in The byte array containing the String
075     * @return A String
076     */
077    public static String deserialize( byte[] in, int start )
078    {
079        int length = IntSerializer.deserialize( in, start );
080
081        if ( length == 0xFFFFFFFF )
082        {
083            return null;
084        }
085
086        if ( in.length < length + 4 + start )
087        {
088            throw new SerializerCreationException( "Cannot extract a String from a buffer with not enough bytes" );
089        }
090
091        return Strings.utf8ToString( in, start + 4, length );
092    }
093
094
095    /**
096     * A method used to deserialize a String from a byte array.
097     * @param in The byte array containing the String
098     * @return A String
099     */
100    public String fromBytes( byte[] in )
101    {
102        return deserialize( in, 0 );
103    }
104
105
106    /**
107     * A method used to deserialize a String from a byte array.
108     * @param in The byte array containing the String
109     * @return A String
110     */
111    public String fromBytes( byte[] in, int start )
112    {
113        int length = IntSerializer.deserialize( in, start );
114
115        if ( length == 0xFFFFFFFF )
116        {
117            return null;
118        }
119
120        if ( in.length < length + start )
121        {
122            throw new SerializerCreationException( "Cannot extract a String from a buffer with not enough bytes" );
123        }
124
125        return Strings.utf8ToString( in, start + 4, length );
126    }
127
128
129    /**
130     * Serialize a String. We store the length on 4 bytes, then the String
131     *
132     * @param buffer the Buffer that will contain the serialized value
133     * @param start the position in the buffer we will store the serialized String
134     * @param value the value to serialize
135     * @return The byte[] containing the serialized String
136     */
137    public static byte[] serialize( byte[] buffer, int start, String element )
138    {
139        int len = -1;
140
141        if ( element != null )
142        {
143            len = element.length();
144        }
145
146        switch ( len )
147        {
148            case 0:
149                buffer[start] = 0x00;
150                buffer[start + 1] = 0x00;
151                buffer[start + 2] = 0x00;
152                buffer[start + 3] = 0x00;
153
154                break;
155
156            case -1:
157                buffer[start] = ( byte ) 0xFF;
158                buffer[start + 1] = ( byte ) 0xFF;
159                buffer[start + 2] = ( byte ) 0xFF;
160                buffer[start + 3] = ( byte ) 0xFF;
161
162                break;
163
164            default:
165                try
166                {
167                    byte[] strBytes = element.getBytes( "UTF-8" );
168
169                    buffer = new byte[strBytes.length + 4];
170
171                    System.arraycopy( strBytes, 0, buffer, 4, strBytes.length );
172
173                    buffer[start] = ( byte ) ( strBytes.length >>> 24 );
174                    buffer[start + 1] = ( byte ) ( strBytes.length >>> 16 );
175                    buffer[start + 2] = ( byte ) ( strBytes.length >>> 8 );
176                    buffer[start + 3] = ( byte ) ( strBytes.length );
177                }
178                catch ( UnsupportedEncodingException uee )
179                {
180                    // if this happens something is really strange
181                    throw new SerializerCreationException( uee );
182                }
183        }
184
185        return buffer;
186    }
187
188
189    /**
190     * {@inheritDoc}
191     */
192    public byte[] serialize( String element )
193    {
194        int len = -1;
195
196        if ( element != null )
197        {
198            len = element.length();
199        }
200
201        byte[] bytes = null;
202
203        switch ( len )
204        {
205            case 0:
206                bytes = new byte[4];
207
208                bytes[0] = 0x00;
209                bytes[1] = 0x00;
210                bytes[2] = 0x00;
211                bytes[3] = 0x00;
212
213                break;
214
215            case -1:
216                bytes = new byte[4];
217
218                bytes[0] = ( byte ) 0xFF;
219                bytes[1] = ( byte ) 0xFF;
220                bytes[2] = ( byte ) 0xFF;
221                bytes[3] = ( byte ) 0xFF;
222
223                break;
224
225            default:
226                char[] chars = element.toCharArray();
227                byte[] tmpBytes = new byte[chars.length * 2];
228
229                int pos = 0;
230                len = 0;
231
232                for ( char c : chars )
233                {
234                    if ( ( c & 0xFF80 ) == 0 )
235                    {
236                        tmpBytes[pos++] = ( byte ) c;
237                    }
238                    else if ( ( c & 0xF800 ) == 0 )
239                    {
240                        tmpBytes[pos++] = ( byte ) ( ( byte ) 0x00C0 | ( byte ) ( ( c & 0x07C0 ) >> 6 ) );
241                        tmpBytes[pos++] = ( byte ) ( ( byte ) 0x80 | ( byte ) ( c & 0x003F ) );
242                    }
243                    else
244                    {
245                        tmpBytes[pos++] = ( byte ) ( ( byte ) 0x80 | ( byte ) ( c & 0x001F ) );
246                        tmpBytes[pos++] = ( byte ) ( ( byte ) 0x80 | ( byte ) ( c & 0x07C0 ) );
247                        tmpBytes[pos++] = ( byte ) ( ( byte ) 0xE0 | ( byte ) ( c & 0x7800 ) );
248                    }
249                }
250
251                bytes = new byte[pos + 4];
252
253                bytes[0] = ( byte ) ( pos >>> 24 );
254                bytes[1] = ( byte ) ( pos >>> 16 );
255                bytes[2] = ( byte ) ( pos >>> 8 );
256                bytes[3] = ( byte ) ( pos );
257
258                System.arraycopy( tmpBytes, 0, bytes, 4, pos );
259        }
260
261        return bytes;
262    }
263
264
265    /**
266     * {@inheritDoc}
267     * @throws IOException
268     */
269    public String deserialize( BufferHandler bufferHandler ) throws IOException
270    {
271        byte[] in = bufferHandler.read( 4 );
272
273        int len = IntSerializer.deserialize( in );
274
275        switch ( len )
276        {
277            case 0:
278                return "";
279
280            case -1:
281                return null;
282
283            default:
284                in = bufferHandler.read( len );
285
286                return Strings.utf8ToString( in );
287        }
288    }
289
290
291    /**
292     * {@inheritDoc}
293     */
294    public String deserialize( ByteBuffer buffer ) throws IOException
295    {
296        int len = buffer.getInt();
297
298        switch ( len )
299        {
300            case 0:
301                return "";
302
303            case -1:
304                return null;
305
306            default:
307                byte[] bytes = new byte[len];
308
309                buffer.get( bytes );
310                char[] chars = new char[len];
311                int clen = 0;
312
313                for ( int i = 0; i < len; i++ )
314                {
315                    byte b = bytes[i];
316
317                    if ( b >= 0 )
318                    {
319                        chars[clen++] = ( char ) b;
320                    }
321                    else
322                    {
323                        if ( ( b & 0xE0 ) == 0 )
324                        {
325                            // 3 bytes long char
326                            i++;
327                            byte b2 = bytes[i];
328                            i++;
329                            byte b3 = bytes[i];
330                            chars[clen++] = ( char ) ( ( ( b & 0x000F ) << 12 ) | ( ( b2 & 0x003F ) << 6 ) | ( ( b3 & 0x003F ) ) );
331                        }
332                        else
333                        {
334                            // 2 bytes long char
335                            i++;
336                            byte b2 = bytes[i];
337                            chars[clen++] = ( char ) ( ( ( b & 0x001F ) << 6 ) | ( b2 & 0x003F ) );
338                        }
339                    }
340                }
341
342                return new String( chars, 0, clen );
343        }
344    }
345
346
347    /**
348     * {@inheritDoc}
349     */
350    @Override
351    public int compare( String type1, String type2 )
352    {
353        if ( type1 == type2 )
354        {
355            return 0;
356        }
357
358        if ( type1 == null )
359        {
360            if ( type2 == null )
361            {
362                return 0;
363            }
364            else
365            {
366                return -1;
367            }
368        }
369        else
370        {
371            if ( type2 == null )
372            {
373                return 1;
374            }
375            else
376            {
377                return type1.compareTo( type2 );
378            }
379        }
380    }
381}