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 */
016package org.ini4j.spi;
017
018import org.ini4j.Registry;
019
020import org.ini4j.Registry.Type;
021
022import java.io.UnsupportedEncodingException;
023
024import java.nio.charset.Charset;
025
026import java.util.Arrays;
027
028public 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}