aQute.lib.utf8properties.UTF8Properties Maven / Gradle / Ivy
package aQute.lib.utf8properties;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetDecoder;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import aQute.lib.io.IO;
import aQute.lib.io.NonClosingInputStream;
import aQute.lib.io.NonClosingReader;
import aQute.service.reporter.Reporter;
/**
* Properties were by default read as ISO-8859-1 characters. However, in the
* last 10 years most builds use UTF-8. Since this is in general a global
* setting, it is very awkward to use ISO-8859-1. In general, it is not often a
* problem since most of Java is written with the basic ASCII encoding. However,
* we want to do this right. So in bnd we generally use this UTF-8 Properties
* class. This class always writes UTF-8. However, it will try to read UTF-8
* first. If this fails, it will try ISO-8859-1, and the last attempt is the
* platform default.
*
* This class can (and probably should) be used anywhere a Properties class is
* used.
*/
public class UTF8Properties extends Properties {
private static final long serialVersionUID = 1L;
private static final List> decoders = Collections.unmodifiableList(
Arrays.asList(ThreadLocal.withInitial(UTF_8::newDecoder), ThreadLocal.withInitial(ISO_8859_1::newDecoder)));
public UTF8Properties(Properties p) {
super(p);
}
public UTF8Properties(File file, String[] syntaxHeaders) throws Exception {
this(file, null, syntaxHeaders);
}
public UTF8Properties(File file) throws Exception {
this(file, null, null);
}
public UTF8Properties(File file, Reporter reporter, String[] syntaxHeaders) throws Exception {
load(file, reporter, fromArray(syntaxHeaders));
}
public UTF8Properties(File file, Reporter reporter) throws Exception {
load(file, reporter, (Collection) null);
}
public UTF8Properties() {}
private static Collection fromArray(String[] array) {
return (array != null) ? Arrays.asList(array) : null;
}
public void load(InputStream in, File file, Reporter reporter, String[] syntaxHeaders) throws IOException {
load(in, file, reporter, fromArray(syntaxHeaders));
}
public void load(InputStream in, File file, Reporter reporter, Collection syntaxHeaders)
throws IOException {
String source = decode(IO.read(in));
load(source, file, reporter, syntaxHeaders);
}
public void load(InputStream in, File file, Reporter reporter) throws IOException {
load(in, file, reporter, (Collection) null);
}
public void load(String source, File file, Reporter reporter) throws IOException {
load(source, file, reporter, (Collection) null);
}
public void load(String source, File file, Reporter reporter, String[] syntaxHeaders) throws IOException {
load(source, file, reporter, fromArray(syntaxHeaders));
}
public void load(String source, File file, Reporter reporter, Collection syntaxHeaders) throws IOException {
PropertiesParser parser = new PropertiesParser(source, file == null ? null : file.getAbsolutePath(), reporter,
this, syntaxHeaders);
parser.parse();
}
public void load(File file, Reporter reporter, String[] syntaxHeaders) throws Exception {
load(file, reporter, fromArray(syntaxHeaders));
}
public void load(File file, Reporter reporter, Collection syntaxHeaders) throws Exception {
String source = decode(IO.read(file));
load(source, file, reporter, syntaxHeaders);
}
public void load(File file, Reporter reporter) throws Exception {
this.load(file, reporter, (Collection) null);
}
@Override
public synchronized void load(InputStream in) throws IOException {
load(new NonClosingInputStream(in), null, null, (Collection) null);
}
@Override
public synchronized void load(Reader r) throws IOException {
String source = IO.collect(new NonClosingReader(r));
load(source, null, null, (Collection) null);
}
private String decode(byte[] buffer) {
ByteBuffer bb = ByteBuffer.wrap(buffer);
CharBuffer cb = CharBuffer.allocate(buffer.length * 4);
for (ThreadLocal tl : decoders) {
CharsetDecoder decoder = tl.get();
boolean success = !decoder.decode(bb, cb, true)
.isError();
if (success) {
decoder.flush(cb);
}
decoder.reset();
if (success) {
return cb.flip()
.toString();
}
bb.rewind();
cb.clear();
}
return new String(buffer); // default decoding
}
private static final Pattern LINE_SPLITTER = Pattern.compile("\n\r?");
@Override
public void store(Writer writer, String msg) throws IOException {
CharArrayWriter sw = new CharArrayWriter();
super.store(sw, null);
String[] lines = LINE_SPLITTER.split(sw.toString());
for (String line : lines) {
if (line.startsWith("#"))
continue;
writer.write(line);
writer.write('\n');
}
}
@Override
public void store(OutputStream out, String msg) throws IOException {
Writer writer = new OutputStreamWriter(out, UTF_8);
try {
store(writer, msg);
} finally {
writer.flush();
}
}
public void store(File file) throws IOException {
try (OutputStream out = IO.outputStream(file)) {
store(out, null);
}
}
public void store(OutputStream out) throws IOException {
store(out, null);
}
/**
* Replace a string in all the values. This can be used to preassign
* variables that change. For example, the base directory ${.} for a loaded
* properties.
*
* @return A new UTF8Properties with the replacement.
*/
public UTF8Properties replaceAll(String pattern, String replacement) {
return replaceAll(Pattern.compile(pattern), replacement);
}
private UTF8Properties replaceAll(Pattern regex, String replacement) {
UTF8Properties result = new UTF8Properties(defaults);
for (Map.Entry