    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}