001    /*
002     * Copyright 2005,2009 Ivan SZKIBA
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.ini4j.spi;
017    
018    import org.ini4j.Registry;
019    
020    import org.ini4j.Registry.Type;
021    
022    import java.io.UnsupportedEncodingException;
023    
024    import java.nio.charset.Charset;
025    
026    import java.util.Arrays;
027    
028    public class RegEscapeTool extends EscapeTool
029    {
030        private static final RegEscapeTool INSTANCE = ServiceFinder.findService(RegEscapeTool.class);
031        private static final Charset HEX_CHARSET = Charset.forName("UTF-16LE");
032        private static final int LOWER_DIGIT = 0x0f;
033        private static final int UPPER_DIGIT = 0xf0;
034        private static final int DIGIT_SIZE = 4;
035    
036        public static final RegEscapeTool getInstance()
037        {
038            return INSTANCE;
039        }
040    
041        public TypeValuesPair decode(String raw)
042        {
043            Type type = type(raw);
044            String value = (type == Type.REG_SZ) ? unquote(raw) : raw.substring(type.toString().length() + 1);
045            String[] values;
046    
047            switch (type)
048            {
049    
050                case REG_EXPAND_SZ:
051                case REG_MULTI_SZ:
052                    value = bytes2string(binary(value));
053                    break;
054    
055                case REG_DWORD:
056                    value = String.valueOf(Long.parseLong(value, HEX_RADIX));
057                    break;
058    
059                case REG_SZ:
060                    break;
061    
062                default:
063                    break;
064            }
065    
066            if (type == Type.REG_MULTI_SZ)
067            {
068                values = splitMulti(value);
069            }
070            else
071            {
072                values = new String[] { value };
073            }
074    
075            return new TypeValuesPair(type, values);
076        }
077    
078        public String encode(TypeValuesPair data)
079        {
080            String ret = null;
081    
082            if (data.getType() == Type.REG_SZ)
083            {
084                ret = quote(data.getValues()[0]);
085            }
086            else if (data.getValues()[0] != null)
087            {
088                ret = encode(data.getType(), data.getValues());
089            }
090    
091            return ret;
092        }
093    
094        byte[] binary(String value)
095        {
096            byte[] bytes = new byte[value.length()];
097            int idx = 0;
098            int shift = DIGIT_SIZE;
099    
100            for (int i = 0; i < value.length(); i++)
101            {
102                char c = value.charAt(i);
103    
104                if (Character.isWhitespace(c))
105                {
106                    continue;
107                }
108    
109                if (c == ',')
110                {
111                    idx++;
112                    shift = DIGIT_SIZE;
113                }
114                else
115                {
116                    int digit = Character.digit(c, HEX_RADIX);
117    
118                    if (digit >= 0)
119                    {
120                        bytes[idx] |= digit << shift;
121                        shift = 0;
122                    }
123                }
124            }
125    
126            return Arrays.copyOfRange(bytes, 0, idx + 1);
127        }
128    
129        String encode(Type type, String[] values)
130        {
131            StringBuilder buff = new StringBuilder();
132    
133            buff.append(type.toString());
134            buff.append(Type.SEPARATOR_CHAR);
135            switch (type)
136            {
137    
138                case REG_EXPAND_SZ:
139                    buff.append(hexadecimal(values[0]));
140                    break;
141    
142                case REG_DWORD:
143                    buff.append(String.format("%08x", Long.parseLong(values[0])));
144                    break;
145    
146                case REG_MULTI_SZ:
147                    int n = values.length;
148    
149                    for (int i = 0; i < n; i++)
150                    {
151                        buff.append(hexadecimal(values[i]));
152                        buff.append(',');
153                    }
154    
155                    buff.append("00,00");
156                    break;
157    
158                default:
159                    buff.append(values[0]);
160                    break;
161            }
162    
163            return buff.toString();
164        }
165    
166        String hexadecimal(String value)
167        {
168            StringBuilder buff = new StringBuilder();
169    
170            if ((value != null) && (value.length() != 0))
171            {
172                byte[] bytes = string2bytes(value);
173    
174                for (int i = 0; i < bytes.length; i++)
175                {
176                    buff.append(Character.forDigit((bytes[i] & UPPER_DIGIT) >> DIGIT_SIZE, HEX_RADIX));
177                    buff.append(Character.forDigit(bytes[i] & LOWER_DIGIT, HEX_RADIX));
178                    buff.append(',');
179                }
180    
181                buff.append("00,00");
182            }
183    
184            return buff.toString();
185        }
186    
187        Registry.Type type(String raw)
188        {
189            Registry.Type type;
190    
191            if (raw.charAt(0) == DOUBLE_QUOTE)
192            {
193                type = Registry.Type.REG_SZ;
194            }
195            else
196            {
197                int idx = raw.indexOf(Registry.TYPE_SEPARATOR);
198    
199                type = (idx < 0) ? Registry.Type.REG_SZ : Registry.Type.fromString(raw.substring(0, idx));
200            }
201    
202            return type;
203        }
204    
205        // XXX Java 1.4 compatibility hack
206        private String bytes2string(byte[] bytes)
207        {
208            String str;
209    
210            try
211            {
212                str = new String(bytes, 0, bytes.length - 2, HEX_CHARSET);
213            }
214            catch (NoSuchMethodError x)
215            {
216                try
217                {
218                    str = new String(bytes, 0, bytes.length, HEX_CHARSET.name());
219                }
220                catch (UnsupportedEncodingException ex)
221                {
222                    throw new IllegalStateException(ex);
223                }
224            }
225    
226            return str;
227        }
228    
229        private String[] splitMulti(String value)
230        {
231            int len = value.length();
232            int start;
233            int end;
234            int n = 0;
235    
236            start = 0;
237            for (end = value.indexOf(0, start); end >= 0; end = value.indexOf(0, start))
238            {
239                n++;
240                start = end + 1;
241                if (start >= len)
242                {
243                    break;
244                }
245            }
246    
247            String[] values = new String[n];
248    
249            start = 0;
250            for (int i = 0; i < n; i++)
251            {
252                end = value.indexOf(0, start);
253                values[i] = value.substring(start, end);
254                start = end + 1;
255            }
256    
257            return values;
258        }
259    
260        // XXX Java 1.4 compatibility hack
261        private byte[] string2bytes(String value)
262        {
263            byte[] bytes;
264    
265            try
266            {
267                bytes = value.getBytes(HEX_CHARSET);
268            }
269            catch (NoSuchMethodError x)
270            {
271                try
272                {
273                    bytes = value.getBytes(HEX_CHARSET.name());
274                }
275                catch (UnsupportedEncodingException ex)
276                {
277                    throw new IllegalStateException(ex);
278                }
279            }
280    
281            return bytes;
282        }
283    }