001/**
002 *   GRANITE DATA SERVICES
003 *   Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004 *
005 *   This file is part of the Granite Data Services Platform.
006 *
007 *   Granite Data Services is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *
012 *   Granite Data Services is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015 *   General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public
018 *   License along with this library; if not, write to the Free Software
019 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020 *   USA, or see <http://www.gnu.org/licenses/>.
021 */
022package org.granite.messaging.jmf.codec.std.impl;
023
024import java.io.IOException;
025import java.io.OutputStream;
026
027import org.granite.messaging.jmf.DumpContext;
028import org.granite.messaging.jmf.InputContext;
029import org.granite.messaging.jmf.OutputContext;
030import org.granite.messaging.jmf.codec.std.StringCodec;
031import org.granite.messaging.jmf.codec.std.impl.util.IntegerUtil;
032
033/**
034 * @author Franck WOLFF
035 */
036public class StringCodecImpl extends AbstractStandardCodec<String> implements StringCodec {
037        
038        protected static final int INDEX_OR_LENGTH_BYTE_COUNT_OFFSET = 4;
039
040        protected static final int UUID_FLAG = 0x40;
041        protected static final int UUID_LOWERCASE_FLAG = 0x00;
042        protected static final int UUID_UPPERCASE_FLAG = 0x10;
043        
044        protected static final char[] LOWER_HEX = "0123456789abcdef".toCharArray();
045        protected static final char[] UPPER_HEX = "0123456789ABCDEF".toCharArray();
046        
047        protected static final int[] HEX_INDICES = new int[256];
048        static {
049                for (int i = 0; i < HEX_INDICES.length; i++) {
050                        if (i >= '0' && i <= '9')
051                                HEX_INDICES[i] = i - '0';
052                        else if (i >= 'a' && i <= 'f')
053                                HEX_INDICES[i] = i - 'a' + 10;
054                        else if (i >= 'A' && i <= 'F')
055                                HEX_INDICES[i] = i - 'A' + 10;
056                        else
057                                HEX_INDICES[i] = -1;
058                }
059        }
060        
061        public int getObjectType() {
062                return JMF_STRING;
063        }
064
065        public Class<?> getObjectClass() {
066                return String.class;
067        }
068
069        public void encode(OutputContext ctx, String v) throws IOException {
070                final OutputStream os = ctx.getOutputStream();
071                
072                if (v.length() == 0) {
073                        os.write(JMF_STRING);
074                        os.write(0x00);
075                        return;
076                }
077                
078                int indexOfStoredString = ctx.indexOfString(v);
079                if (indexOfStoredString >= 0) {
080                        int count = IntegerUtil.significantIntegerBytesCount0(indexOfStoredString);
081                        os.write(0x80 | (count << INDEX_OR_LENGTH_BYTE_COUNT_OFFSET) | JMF_STRING);
082                        IntegerUtil.encodeInteger(ctx, indexOfStoredString, count);
083                }
084                else {
085                        ctx.addToStrings(v);
086                        
087                        int uuidCaseFlag = isUUID(v);
088                        if (uuidCaseFlag != -1)
089                                encodeUUID(ctx, v, uuidCaseFlag);
090                        else {
091//                              char[] cs = v.toCharArray();
092//                              byte[] bytes = new byte[cs.length * 3];
093//                              
094//                              int length = 0;
095//                              for (char c : cs) {
096//                                      if (c <= 0x7F)
097//                                              bytes[length++] = (byte)c;
098//                                      else if (c <= 0x7FF) {
099//                                              bytes[length++] = (byte)(0xC0 | ((c >> 6) & 0x1F));
100//                                              bytes[length++] = (byte)(0x80 | ((c >> 0) & 0x3F));
101//                                      }
102//                                      else {
103//                                              bytes[length++] = (byte)(0xE0 | ((c >> 12) & 0x0F));
104//                                              bytes[length++] = (byte)(0x80 | ((c >> 6) & 0x3F));
105//                                              bytes[length++] = (byte)(0xE0 | ((c >> 12) & 0x0F));
106//                                      }
107//                              }
108                                
109                                byte[] bytes = v.getBytes(UTF8);
110                                int length = bytes.length;
111                                int count = IntegerUtil.significantIntegerBytesCount0(length);
112                                
113                                os.write((count << INDEX_OR_LENGTH_BYTE_COUNT_OFFSET) | JMF_STRING);
114                                IntegerUtil.encodeInteger(ctx, length, count);
115                                os.write(bytes, 0, length);
116                        }
117                }
118        }
119        
120        public String decode(InputContext ctx, int parameterizedJmfType) throws IOException {
121                if ((parameterizedJmfType & 0x80) != 0) {
122                        int index = IntegerUtil.decodeInteger(ctx, (parameterizedJmfType >>> INDEX_OR_LENGTH_BYTE_COUNT_OFFSET) & 0x03);
123                        return ctx.getString(index);
124                }
125                
126                if ((parameterizedJmfType & UUID_FLAG) != 0) {
127                        String uid = decodeUUID(ctx, parameterizedJmfType);
128                        ctx.addToStrings(uid);
129                        return uid;
130                }
131                
132                int length = IntegerUtil.decodeInteger(ctx, (parameterizedJmfType >>> INDEX_OR_LENGTH_BYTE_COUNT_OFFSET) & 0x03);          
133                if (length == 0)
134                        return "";
135                
136                byte[] bytes = new byte[length];
137                ctx.safeReadFully(bytes);
138                String s = new String(bytes, UTF8);
139                ctx.addToStrings(s);
140                
141                return s;
142        }
143        
144        public void dump(DumpContext ctx, int parameterizedJmfType) throws IOException {
145                ctx.indentPrintLn(String.class.getName() + ": \"" + escape(decode(ctx, parameterizedJmfType)) + "\"");
146        }
147        
148        protected int isUUID(String v) {
149                if (v.length() != 36 || v.charAt(8) != '-')
150                        return -1;
151                
152                int flag = -1;
153                
154                for (int i = 0; i < 36; i++) {
155                        char c = v.charAt(i);
156                        
157                        switch (i) {
158                        case 8: case 13: case 18: case 23:
159                                if (c != '-')
160                                        return -1;
161                                break;
162                        default:
163                                if (!(c >= '0' && c <= '9')) {
164                                        if (c >= 'a' && c <= 'f') {
165                                                if (flag == -1)
166                                                        flag = UUID_LOWERCASE_FLAG;
167                                                else if (flag != UUID_LOWERCASE_FLAG)
168                                                        return -1;
169                                        }
170                                        else if (c >= 'A' && c <= 'F') {
171                                                if (flag == -1)
172                                                        flag = UUID_UPPERCASE_FLAG;
173                                                else if (flag != UUID_UPPERCASE_FLAG)
174                                                        return -1;
175                                        }
176                                        else
177                                                return -1;
178                                }
179                                break;
180                        }
181                }
182                
183                // No letters...
184                if (flag == -1)
185                        flag = UUID_LOWERCASE_FLAG;
186                
187                return flag;
188        }
189        
190        protected void encodeUUID(OutputContext ctx, String v, int caseFlag) throws IOException {
191                final OutputStream os = ctx.getOutputStream();
192                
193                os.write(caseFlag | UUID_FLAG | JMF_STRING);
194                
195                byte[] bytes = new byte[16];
196
197                int i = 0, j = 0;
198                while (i < 36) {
199                        char c1 = v.charAt(i++);
200                        if (c1 == '-')
201                                c1 = v.charAt(i++);
202                        char c2 = v.charAt(i++);
203                        
204                        bytes[j++] = (byte)(HEX_INDICES[c1] << 4 | HEX_INDICES[c2]);
205                }
206
207                os.write(bytes);
208        }
209        
210        protected String decodeUUID(InputContext ctx, int parameterizedJmfType) throws IOException {
211                final char[] hex = (parameterizedJmfType & UUID_UPPERCASE_FLAG) != 0 ? UPPER_HEX : LOWER_HEX;
212                
213                byte[] bytes = new byte[16];
214                ctx.safeReadFully(bytes);
215                
216                char[] chars = new char[36];
217                int i = 0;
218                for (byte b : bytes) {
219                        if (i == 8 || i == 13 || i == 18 || i == 23)
220                                chars[i++] = '-';
221                        chars[i++] = hex[(b & 0xF0) >>> 4];
222                        chars[i++] = hex[b & 0x0F];
223                }
224                
225                return new String(chars);
226        }
227}