All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nuiton.io.SortedProperties Maven / Gradle / Ivy

There is a newer version: 2.4.1
Show newest version
package org.nuiton.io;

/*
 * #%L
 * Helper Maven Plugin :: API
 * $Id: SortedProperties.java 876 2012-11-11 08:14:19Z tchemit $
 * $HeadURL: http://svn.nuiton.org/svn/maven-helper-plugin/tags/maven-helper-plugin-2.1/helper-maven-plugin-api/src/main/java/org/nuiton/io/SortedProperties.java $
 * %%
 * Copyright (C) 2009 - 2012 Codelutin, Tony Chemit
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public 
 * License along with this program.  If not, see
 * .
 * #L%
 */

import org.nuiton.plugin.PluginHelper;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.Vector;

/**
 * Extend {@link Properties} to allow alphabetical order. Encoding could also
 * be defined but you must use this class only with Java 1.6.
 *
 * @author ruchaud 
 * @author tchemit 
 */
public class SortedProperties extends Properties {

    private static final long serialVersionUID = -1147150444452577558L;

    public static final String ENCODING_DEFAULT = "8859_1";

    public static final String ENCODING_LATIN1 = "iso-8859-1";

    public static final String ENCODING_ASCII = "us-ascii";

    /** A table of hex digits in upper case */
    private static final char[] hexDigitUpper = {
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
    };

    /** A table of hex digits in lower case */
    private static final char[] hexDigitLower = {
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
    };

    /** l'encoding par defaut a utiliser pour lire et ecrire le properties. */
    protected String defaultEncoding;

    /** un drapeau pour savoir s'il faut enlever l'entete generere */
    protected boolean removeHeader;

    /**
     * A flag to write unicode using the a lower letter.
     * 

* Example : {@code \u00e9} instead of {@code \u00E9}. */ final protected boolean unicodeLower; final protected char[] hexDigit; public SortedProperties() { this(ENCODING_DEFAULT); } public SortedProperties(String defaultEncoding) { this(defaultEncoding, true, false); } public SortedProperties(String defaultEncoding, boolean removeHeader) { this(defaultEncoding, removeHeader, false); } public SortedProperties(String defaultEncoding, boolean removeHeader, boolean unicodeLower) { this.defaultEncoding = defaultEncoding; this.removeHeader = removeHeader; this.unicodeLower = unicodeLower; hexDigit = unicodeLower ? hexDigitLower : hexDigitUpper; } public SortedProperties(Properties defaults) { super(defaults); defaultEncoding = ENCODING_DEFAULT; unicodeLower = false; hexDigit = hexDigitUpper; } @Override public Enumeration keys() { List objects = Collections.list(super.keys()); Vector result; try { // Attention, si les clef ne sont pas des string, ca ne marchera pas List list = PluginHelper.toGenericList(objects, String.class); Collections.sort(list); result = new Vector(list); } catch (IllegalArgumentException e) { // keys are not string !!! // can not sort keys result = new Vector(objects); } return result.elements(); } /** * Load all properties from given {@code src} file using defined {@code defaultEncoding}. * * @param src source file * @return this instance * @throws IOException if any io pb * @see #load(File, String) */ public SortedProperties load(File src) throws IOException { return load(src, defaultEncoding); } /** * Load Properties from {@code src} file using given {@code defaultEncoding}. * If this {@code encoding} is different from default ones * {@link #ENCODING_DEFAULT}, {@link #ENCODING_LATIN1} * and {@link #ENCODING_ASCII}. A specific {@link Reader} will * be used to read the file. * * @param src File where Properties will be loaded * @param encoding Encoding to use * @throws IOException for any file errors * @since 1.3 * @return this instance */ public SortedProperties load(File src, String encoding) throws IOException { Reader reader = new InputStreamReader(new FileInputStream(src), encoding); try { load(reader); } finally { reader.close(); } return this; } /** * Save properties in given {@code dst} file using defined {@code defaultEncoding}. * * @param dst output file * @throws IOException if any io pb * @see #store(File, String) */ public void store(File dst) throws IOException { store(dst, defaultEncoding); } /** * Store Properties in {@code output} file using given {@code defaultEncoding}. * If this {@code encoding} is different from default ones * {@link #ENCODING_DEFAULT}, {@link #ENCODING_LATIN1} * and {@link #ENCODING_ASCII}, a specific {@link Writer} will * be used to save the file. Otherwise the default Properties behavior * will escape unicode chars with \uxxxx. * * @param dst File to save Properties * @param encoding Encoding to use * @throws IOException for any file errors * @since 1.3 */ public void store(File dst, String encoding) throws IOException { OutputStream stream = new FileOutputStream(dst); try { storeEncode(stream, encoding); } finally { stream.close(); } } /** * If encoding is not the default Properties one, we will use a writer * to use this custom encoding. *

* We must call the right method depends on encoding, for a custom one * the {@link #store(Writer, String)} method is used otherwise the * default encoding 8859_1 must be used as superclass define it in * {@link #store(OutputStream, String)}. *

* Encoding {@link #ENCODING_DEFAULT}, * {@link #ENCODING_LATIN1} and {@link #ENCODING_ASCII} * are considered as default Properties one, so the old method will be use * escaping unicode chars like \uxxxx. * * @param stream OutputStream to save in * @param encoding Encoding to use * @throws IOException For any file errors */ protected void storeEncode(OutputStream stream, String encoding) throws IOException { // Specific encoding different from default ones if (!ENCODING_LATIN1.equalsIgnoreCase(encoding) && !ENCODING_DEFAULT.equals(encoding) && !ENCODING_ASCII.equalsIgnoreCase(encoding)) { Writer writer = new OutputStreamWriter(stream, encoding); try { store(writer, null); } finally { writer.close(); } } else { store(stream, null); } } /** * Save properties in given {@code stream} file. {@code defaultEncoding} defined * will be ignored to store the properties file. The default encoding is the * basic Properties one (Latin1 ISO), unicode chars will be escaped * like basic {@link #store(OutputStream, String)} method. * * @param dst output file * @throws IOException if any io pb */ public void store(OutputStream dst) throws IOException { store(dst, null); } @Override public void store(OutputStream out, String comments) throws IOException { // Keep same implementation as super Properties class. Even if the // encoding is modified, the resulting file always be an ASCII one // because of escUnicode flag in store0 method. This method is overriden // because of custom store0 method that can removeHeader. BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(out, "8859_1")); try { store0(writer, comments, true); } finally { writer.close(); } } @Override public void store(Writer writer, String comments) throws IOException { // Keep same implementation as super Properties class. This method // is overriden because of custom store0 method that can removeHeader. BufferedWriter bufferedWriter; if (writer instanceof BufferedWriter) { bufferedWriter = (BufferedWriter) writer; } else { bufferedWriter = new BufferedWriter(writer); } try { store0(bufferedWriter, comments, false); } finally { writer.close(); } } protected void store0(BufferedWriter bw, String comments, boolean escUnicode) throws IOException { if (comments != null) { writeComments(bw, comments); } // Here is the modification wanted if (!removeHeader) { bw.write("#" + new Date().toString()); bw.newLine(); } synchronized (this) { for (Enumeration e = keys(); e.hasMoreElements();) { String key = (String) e.nextElement(); String val = (String) get(key); key = saveConvert(key, true, escUnicode); /* No need to escape embedded and trailing spaces for value, hence * pass false to flag. */ val = saveConvert(val, false, escUnicode); bw.write(key + "=" + val); bw.newLine(); } } bw.flush(); } // --- Copy implementations from Properties superClass, the store0 method // is overriden and all method calls are private, so we copy the code here. protected String saveConvert(String theString, boolean escapeSpace, boolean escapeUnicode) { int len = theString.length(); int bufLen = len * 2; if (bufLen < 0) { bufLen = Integer.MAX_VALUE; } StringBuffer outBuffer = new StringBuffer(bufLen); for (int x = 0; x < len; x++) { char aChar = theString.charAt(x); // Handle common case first, selecting largest block that // avoids the specials below if ((aChar > 61) && (aChar < 127)) { if (aChar == '\\') { outBuffer.append('\\'); outBuffer.append('\\'); continue; } outBuffer.append(aChar); continue; } switch (aChar) { case ' ': if (x == 0 || escapeSpace) outBuffer.append('\\'); outBuffer.append(' '); break; case '\t': outBuffer.append('\\'); outBuffer.append('t'); break; case '\n': outBuffer.append('\\'); outBuffer.append('n'); break; case '\r': outBuffer.append('\\'); outBuffer.append('r'); break; case '\f': outBuffer.append('\\'); outBuffer.append('f'); break; case '=': // Fall through case ':': // Fall through case '#': // Fall through case '!': outBuffer.append('\\'); outBuffer.append(aChar); break; default: if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode) { outBuffer.append('\\'); outBuffer.append('u'); outBuffer.append(toHex((aChar >> 12) & 0xF)); outBuffer.append(toHex((aChar >> 8) & 0xF)); outBuffer.append(toHex((aChar >> 4) & 0xF)); outBuffer.append(toHex(aChar & 0xF)); } else { outBuffer.append(aChar); } } } return outBuffer.toString(); } public void writeComments(BufferedWriter bw, String comments) throws IOException { bw.write("#"); int len = comments.length(); int current = 0; int last = 0; char[] uu = new char[6]; uu[0] = '\\'; uu[1] = 'u'; while (current < len) { char c = comments.charAt(current); if (c > '\u00ff' || c == '\n' || c == '\r') { if (last != current) bw.write(comments.substring(last, current)); if (c > '\u00ff') { uu[2] = toHex((c >> 12) & 0xf); uu[3] = toHex((c >> 8) & 0xf); uu[4] = toHex((c >> 4) & 0xf); uu[5] = toHex(c & 0xf); bw.write(new String(uu)); } else { bw.newLine(); if (c == '\r' && current != len - 1 && comments.charAt(current + 1) == '\n') { current++; } if (current == len - 1 || (comments.charAt(current + 1) != '#' && comments.charAt(current + 1) != '!')) bw.write("#"); } last = current + 1; } current++; } if (last != current) bw.write(comments.substring(last, current)); bw.newLine(); } /** * Convert a nibble to a hex character * * @param nibble the nibble to convert. */ protected char toHex(int nibble) { return hexDigit[(nibble & 0xF)]; } }