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

io.ultreia.java4all.i18n.spi.I18nProperties Maven / Gradle / Ivy

There is a newer version: 4.0-beta-27
Show newest version
package io.ultreia.java4all.i18n.spi;

/*-
 * #%L
 * I18n :: Spi
 * %%
 * Copyright (C) 2018 Code Lutin, Ultreia.io
 * %%
 * 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 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.
 * 

* Created by tchemit on 06/11/2018. * * @author Tony Chemit - [email protected] */ @SuppressWarnings("WeakerAccess") public class I18nProperties extends Properties { private static final String ENCODING_DEFAULT = "8859_1"; private static final String ENCODING_LATIN1 = "iso-8859-1"; private static final String ENCODING_ASCII = "us-ascii"; private static final long serialVersionUID = -1147150444452577558L; /** 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' }; /** * A flag to write unicode using the a lower letter. *

* Example : {@code \u00e9} instead of {@code \u00E9}. */ private final boolean unicodeLower; private final char[] hexDigit; /** l'encoding par defaut a utiliser pour lire et ecrire le properties. */ private String defaultEncoding; /** un drapeau pour savoir s'il faut enlever l'entete generere */ private boolean removeHeader; public I18nProperties() { this(ENCODING_DEFAULT); } public I18nProperties(String defaultEncoding) { this(defaultEncoding, true, false); } public I18nProperties(String defaultEncoding, boolean removeHeader) { this(defaultEncoding, removeHeader, false); } public I18nProperties(String defaultEncoding, boolean removeHeader, boolean unicodeLower) { this.defaultEncoding = defaultEncoding; this.removeHeader = removeHeader; this.unicodeLower = unicodeLower; hexDigit = unicodeLower ? hexDigitLower : hexDigitUpper; } public I18nProperties(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 @SuppressWarnings("unchecked") List list = (List) objects; 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 I18nProperties 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 * @return this instance * @throws IOException for any file errors * @since 1.3 */ public I18nProperties load(File src, String encoding) throws IOException { try (Reader reader = new InputStreamReader(new FileInputStream(src), encoding)) { load(reader); } 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 { try (OutputStream stream = new FileOutputStream(dst)) { storeEncode(stream, encoding); } } /** * 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)) { try (Writer writer = new OutputStreamWriter(stream, encoding)) { store(writer, null); } } 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. try (BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(out, "8859_1"))) { store0(writer, comments, true); } } @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; } StringBuilder outBuffer = new StringBuilder(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. * @return the converted nibble */ private char toHex(int nibble) { return hexDigit[(nibble & 0xF)]; } }