001/* ========================================================================
002 * JCommon : a free general purpose class library for the Java(tm) platform
003 * ========================================================================
004 *
005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006 * 
007 * Project Info:  http://www.jfree.org/jcommon/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it 
010 * under the terms of the GNU Lesser General Public License as published by 
011 * the Free Software Foundation; either version 2.1 of the License, or 
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but 
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
022 * USA.  
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
025 * in the United States and other countries.]
026 * 
027 * ------------------------------
028 * SortedConfigurationWriter.java
029 * ------------------------------
030 * (C)opyright 2003, 2004, by Thomas Morgner and Contributors.
031 *
032 * Original Author:  Thomas Morgner;
033 * Contributor(s):   -;
034 *
035 * $Id: SortedConfigurationWriter.java,v 1.4 2005/11/03 09:55:27 mungady Exp $
036 *
037 * Changes
038 * -------
039 *
040 */
041
042package org.jfree.util;
043
044import java.io.BufferedOutputStream;
045import java.io.File;
046import java.io.FileOutputStream;
047import java.io.IOException;
048import java.io.OutputStream;
049import java.io.OutputStreamWriter;
050import java.io.Writer;
051import java.util.ArrayList;
052import java.util.Collections;
053import java.util.Iterator;
054
055/**
056 * Writes a <code>Configuration</code> instance into a property file, where
057 * the keys are sorted by their name. Writing sorted keys make it easier for
058 * users to find and change properties in the file.
059 *
060 * @author Thomas Morgner
061 */
062public class SortedConfigurationWriter {
063    /**
064     * A constant defining that text should be escaped in a way
065     * which is suitable for property keys.
066     */
067    private static final int ESCAPE_KEY = 0;
068    /**
069     * A constant defining that text should be escaped in a way
070     * which is suitable for property values.
071     */
072    private static final int ESCAPE_VALUE = 1;
073    /**
074     * A constant defining that text should be escaped in a way
075     * which is suitable for property comments.
076     */
077    private static final int ESCAPE_COMMENT = 2;
078
079    /** The system-dependent End-Of-Line separator. */
080    private static final String END_OF_LINE = StringUtils.getLineSeparator();
081
082    /**
083     * The default constructor, does nothing.
084     */
085    public SortedConfigurationWriter() {
086    }
087
088    /**
089     * Returns a description for the given key. This implementation returns
090     * null to indicate that no description should be written. Subclasses can
091     * overwrite this method to provide comments for every key. These descriptions
092     * will be included as inline comments.
093     *
094     * @param key the key for which a description should be printed.
095     * @return the description or null if no description should be printed.
096     */
097    protected String getDescription(final String key) {
098        return null;
099    }
100
101    /**
102     * Saves the given configuration into a file specified by the given
103     * filename.
104     *
105     * @param filename the filename
106     * @param config the configuration
107     * @throws IOException if an IOError occurs.
108     */
109    public void save(final String filename, final Configuration config)
110        throws IOException {
111        save(new File(filename), config);
112    }
113
114    /**
115     * Saves the given configuration into a file specified by the given
116     * file object.
117     *
118     * @param file the target file
119     * @param config the configuration
120     * @throws IOException if an IOError occurs.
121     */
122    public void save(final File file, final Configuration config)
123        throws IOException {
124        final BufferedOutputStream out =
125            new BufferedOutputStream(new FileOutputStream(file));
126        save(out, config);
127        out.close();
128    }
129
130
131    /**
132     * Writes the configuration into the given output stream.
133     *
134     * @param outStream the target output stream
135     * @param config the configuration
136     * @throws IOException if writing fails.
137     */
138    public void save(final OutputStream outStream, final Configuration config)
139        throws IOException {
140        final ArrayList names = new ArrayList();
141
142        // clear all previously set configuration settings ...
143        final Iterator defaults = config.findPropertyKeys("");
144        while (defaults.hasNext()) {
145            final String key = (String) defaults.next();
146            names.add(key);
147        }
148
149        Collections.sort(names);
150
151        final OutputStreamWriter out =
152            new OutputStreamWriter(outStream, "iso-8859-1");
153
154        for (int i = 0; i < names.size(); i++) {
155            final String key = (String) names.get(i);
156            final String value = config.getConfigProperty(key);
157
158            final String description = getDescription(key);
159            if (description != null) {
160                writeDescription(description, out);
161            }
162            saveConvert(key, ESCAPE_KEY, out);
163            out.write("=");
164            saveConvert(value, ESCAPE_VALUE, out);
165            out.write(END_OF_LINE);
166        }
167        out.flush();
168
169    }
170
171    /**
172     * Writes a descriptive comment into the given print writer.
173     *
174     * @param text   the text to be written. If it contains more than
175     *               one line, every line will be prepended by the comment character.
176     * @param writer the writer that should receive the content.
177     * @throws IOException if writing fails
178     */
179    private void writeDescription(final String text, final Writer writer)
180        throws IOException {
181        // check if empty content ... this case is easy ...
182        if (text.length() == 0) {
183            return;
184        }
185
186        writer.write("# ");
187        writer.write(END_OF_LINE);
188        final LineBreakIterator iterator = new LineBreakIterator(text);
189        while (iterator.hasNext()) {
190            writer.write("# ");
191            saveConvert((String) iterator.next(), ESCAPE_COMMENT, writer);
192            writer.write(END_OF_LINE);
193        }
194    }
195
196    /**
197     * Performs the necessary conversion of an java string into a property
198     * escaped string.
199     *
200     * @param text       the text to be escaped
201     * @param escapeMode the mode that should be applied.
202     * @param writer     the writer that should receive the content.
203     * @throws IOException if writing fails
204     */
205    private void saveConvert(final String text, final int escapeMode,
206                             final Writer writer)
207        throws IOException {
208        final char[] string = text.toCharArray();
209
210        for (int x = 0; x < string.length; x++) {
211            final char aChar = string[x];
212            switch (aChar) {
213                case ' ':
214                    {
215                        if ((escapeMode != ESCAPE_COMMENT) 
216                                && (x == 0 || escapeMode == ESCAPE_KEY)) {
217                            writer.write('\\');
218                        }
219                        writer.write(' ');
220                        break;
221                    }
222                case '\\':
223                    {
224                        writer.write('\\');
225                        writer.write('\\');
226                        break;
227                    }
228                case '\t':
229                    {
230                        if (escapeMode == ESCAPE_COMMENT) {
231                            writer.write(aChar);
232                        }
233                        else {
234                            writer.write('\\');
235                            writer.write('t');
236                        }
237                        break;
238                    }
239                case '\n':
240                    {
241                        writer.write('\\');
242                        writer.write('n');
243                        break;
244                    }
245                case '\r':
246                    {
247                        writer.write('\\');
248                        writer.write('r');
249                        break;
250                    }
251                case '\f':
252                    {
253                        if (escapeMode == ESCAPE_COMMENT) {
254                            writer.write(aChar);
255                        }
256                        else {
257                            writer.write('\\');
258                            writer.write('f');
259                        }
260                        break;
261                    }
262                case '#':
263                case '"':
264                case '!':
265                case '=':
266                case ':':
267                    {
268                        if (escapeMode == ESCAPE_COMMENT) {
269                            writer.write(aChar);
270                        }
271                        else {
272                            writer.write('\\');
273                            writer.write(aChar);
274                        }
275                        break;
276                    }
277                default:
278                    if ((aChar < 0x0020) || (aChar > 0x007e)) {
279                        writer.write('\\');
280                        writer.write('u');
281                        writer.write(HEX_CHARS[(aChar >> 12) & 0xF]);
282                        writer.write(HEX_CHARS[(aChar >> 8) & 0xF]);
283                        writer.write(HEX_CHARS[(aChar >> 4) & 0xF]);
284                        writer.write(HEX_CHARS[aChar & 0xF]);
285                    }
286                    else {
287                        writer.write(aChar);
288                    }
289            }
290        }
291    }
292
293    /** A lookup-table. */
294    private static final char[] HEX_CHARS =
295        {'0', '1', '2', '3', '4', '5', '6', '7',
296         '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
297}