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;
017
018import org.ini4j.spi.IniFormatter;
019import org.ini4j.spi.IniHandler;
020import org.ini4j.spi.IniParser;
021import org.ini4j.spi.RegBuilder;
022
023import java.io.File;
024import java.io.FileNotFoundException;
025import java.io.FileOutputStream;
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.InputStreamReader;
029import java.io.InterruptedIOException;
030import java.io.OutputStream;
031import java.io.OutputStreamWriter;
032import java.io.Reader;
033import java.io.Writer;
034
035import java.net.URL;
036
037public class Reg extends BasicRegistry implements Registry, Persistable, Configurable
038{
039    private static final long serialVersionUID = -1485602876922985912L;
040    protected static final String DEFAULT_SUFFIX = ".reg";
041    protected static final String TMP_PREFIX = "reg-";
042    private static final int STDERR_BUFF_SIZE = 8192;
043    private static final String PROP_OS_NAME = "os.name";
044    private static final boolean WINDOWS = Config.getSystemProperty(PROP_OS_NAME, "Unknown").startsWith("Windows");
045    private static final char CR = '\r';
046    private static final char LF = '\n';
047    private Config _config;
048    private File _file;
049
050    public Reg()
051    {
052        Config cfg = Config.getGlobal().clone();
053
054        cfg.setEscape(false);
055        cfg.setGlobalSection(false);
056        cfg.setEmptyOption(true);
057        cfg.setMultiOption(true);
058        cfg.setStrictOperator(true);
059        cfg.setEmptySection(true);
060        cfg.setPathSeparator(KEY_SEPARATOR);
061        cfg.setFileEncoding(FILE_ENCODING);
062        cfg.setLineSeparator(LINE_SEPARATOR);
063        _config = cfg;
064    }
065
066    public Reg(String registryKey) throws IOException
067    {
068        this();
069        read(registryKey);
070    }
071
072    public Reg(File input) throws IOException, InvalidFileFormatException
073    {
074        this();
075        _file = input;
076        load();
077    }
078
079    public Reg(URL input) throws IOException, InvalidFileFormatException
080    {
081        this();
082        load(input);
083    }
084
085    public Reg(InputStream input) throws IOException, InvalidFileFormatException
086    {
087        this();
088        load(input);
089    }
090
091    public Reg(Reader input) throws IOException, InvalidFileFormatException
092    {
093        this();
094        load(input);
095    }
096
097    public static boolean isWindows()
098    {
099        return WINDOWS;
100    }
101
102    @Override public Config getConfig()
103    {
104        return _config;
105    }
106
107    public void setConfig(Config value)
108    {
109        _config = value;
110    }
111
112    @Override public File getFile()
113    {
114        return _file;
115    }
116
117    @Override public void setFile(File value)
118    {
119        _file = value;
120    }
121
122    @Override public void load() throws IOException, InvalidFileFormatException
123    {
124        if (_file == null)
125        {
126            throw new FileNotFoundException();
127        }
128
129        load(_file);
130    }
131
132    @Override public void load(InputStream input) throws IOException, InvalidFileFormatException
133    {
134        load(new InputStreamReader(input, getConfig().getFileEncoding()));
135    }
136
137    @Override public void load(URL input) throws IOException, InvalidFileFormatException
138    {
139        load(new InputStreamReader(input.openStream(), getConfig().getFileEncoding()));
140    }
141
142    @Override public void load(Reader input) throws IOException, InvalidFileFormatException
143    {
144        int newline = 2;
145        StringBuilder buff = new StringBuilder();
146
147        for (int c = input.read(); c != -1; c = input.read())
148        {
149            if (c == LF)
150            {
151                newline--;
152                if (newline == 0)
153                {
154                    break;
155                }
156            }
157            else if ((c != CR) && (newline != 1))
158            {
159                buff.append((char) c);
160            }
161        }
162
163        if (buff.length() == 0)
164        {
165            throw new InvalidFileFormatException("Missing version header");
166        }
167
168        if (!buff.toString().equals(getVersion()))
169        {
170            throw new InvalidFileFormatException("Unsupported version: " + buff.toString());
171        }
172
173        IniParser.newInstance(getConfig()).parse(input, newBuilder());
174    }
175
176    @Override public void load(File input) throws IOException, InvalidFileFormatException
177    {
178        load(input.toURI().toURL());
179    }
180
181    public void read(String registryKey) throws IOException
182    {
183        File tmp = createTempFile();
184
185        try
186        {
187            regExport(registryKey, tmp);
188            load(tmp);
189        }
190        finally
191        {
192            tmp.delete();
193        }
194    }
195
196    @Override public void store() throws IOException
197    {
198        if (_file == null)
199        {
200            throw new FileNotFoundException();
201        }
202
203        store(_file);
204    }
205
206    @Override public void store(OutputStream output) throws IOException
207    {
208        store(new OutputStreamWriter(output, getConfig().getFileEncoding()));
209    }
210
211    @Override public void store(Writer output) throws IOException
212    {
213        output.write(getVersion());
214        output.write(getConfig().getLineSeparator());
215        output.write(getConfig().getLineSeparator());
216        store(IniFormatter.newInstance(output, getConfig()));
217    }
218
219    @Override public void store(File output) throws IOException
220    {
221        OutputStream stream = new FileOutputStream(output);
222
223        store(stream);
224        stream.close();
225    }
226
227    public void write() throws IOException
228    {
229        File tmp = createTempFile();
230
231        try
232        {
233            store(tmp);
234            regImport(tmp);
235        }
236        finally
237        {
238            tmp.delete();
239        }
240    }
241
242    protected IniHandler newBuilder()
243    {
244        return RegBuilder.newInstance(this);
245    }
246
247    @Override boolean isTreeMode()
248    {
249        return getConfig().isTree();
250    }
251
252    @Override char getPathSeparator()
253    {
254        return getConfig().getPathSeparator();
255    }
256
257    @Override boolean isPropertyFirstUpper()
258    {
259        return getConfig().isPropertyFirstUpper();
260    }
261
262    void exec(String[] args) throws IOException
263    {
264        Process proc = Runtime.getRuntime().exec(args);
265
266        try
267        {
268            int status = proc.waitFor();
269
270            if (status != 0)
271            {
272                Reader in = new InputStreamReader(proc.getErrorStream());
273                char[] buff = new char[STDERR_BUFF_SIZE];
274                int n = in.read(buff);
275
276                in.close();
277                throw new IOException(new String(buff, 0, n).trim());
278            }
279        }
280        catch (InterruptedException x)
281        {
282            throw (IOException) (new InterruptedIOException().initCause(x));
283        }
284    }
285
286    private File createTempFile() throws IOException
287    {
288        File ret = File.createTempFile(TMP_PREFIX, DEFAULT_SUFFIX);
289
290        ret.deleteOnExit();
291
292        return ret;
293    }
294
295    private void regExport(String registryKey, File file) throws IOException
296    {
297        requireWindows();
298        exec(new String[] { "cmd", "/c", "reg", "export", registryKey, file.getAbsolutePath() });
299    }
300
301    private void regImport(File file) throws IOException
302    {
303        requireWindows();
304        exec(new String[] { "cmd", "/c", "reg", "import", file.getAbsolutePath() });
305    }
306
307    private void requireWindows()
308    {
309        if (!WINDOWS)
310        {
311            throw new UnsupportedOperationException("Unsupported operating system or runtime environment");
312        }
313    }
314}