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    
042    package org.jfree.util;
043    
044    import java.io.BufferedOutputStream;
045    import java.io.File;
046    import java.io.FileOutputStream;
047    import java.io.IOException;
048    import java.io.OutputStream;
049    import java.io.OutputStreamWriter;
050    import java.io.Writer;
051    import java.util.ArrayList;
052    import java.util.Collections;
053    import 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     */
062    public 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    }