package aQute.lib.utf8properties; import static java.nio.charset.StandardCharsets.ISO_8859_1; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.StringWriter; import java.io.Writer; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharsetDecoder; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Properties; 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. * <p> * 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<CharsetDecoder> decoders = Collections .unmodifiableList(Arrays.asList(UTF_8.newDecoder(), ISO_8859_1.newDecoder())); public UTF8Properties(Properties p) { super(p); } public UTF8Properties() {} public void load(InputStream in, File file, Reporter reporter) throws IOException { String source = decode(IO.read(in)); load(source, file, reporter); } public void load(String source, File file, Reporter reporter) throws IOException { PropertiesParser parser = new PropertiesParser(source, file == null ? null : file.getAbsolutePath(), reporter, this); parser.parse(); } public void load(File file, Reporter reporter) throws Exception { String source = decode(IO.read(file)); load(source, file, reporter); } @Override public void load(InputStream in) throws IOException { load(new NonClosingInputStream(in), null, null); } @Override public void load(Reader r) throws IOException { String source = IO.collect(new NonClosingReader(r)); load(source, null, null); } private String decode(byte[] buffer) throws IOException { ByteBuffer bb = ByteBuffer.wrap(buffer); CharBuffer cb = CharBuffer.allocate(buffer.length * 4); for (CharsetDecoder decoder : decoders) { 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 } @Override public void store(OutputStream out, String msg) throws IOException { StringWriter sw = new StringWriter(); super.store(sw, null); String[] lines = sw.toString().split("\n\r?"); for (String line : lines) { if (line.startsWith("#")) continue; out.write(line.getBytes(UTF_8)); out.write("\n".getBytes(UTF_8)); } } @Override public void store(Writer out, String msg) throws IOException { StringWriter sw = new StringWriter(); super.store(sw, null); String[] lines = sw.toString().split("\n\r?"); for (String line : lines) { if (line.startsWith("#")) continue; out.write(line); out.write("\n"); } } 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) { UTF8Properties result = new UTF8Properties(defaults); Pattern regex = Pattern.compile(pattern); for (Map.Entry<Object,Object> entry : entrySet()) { String key = (String) entry.getKey(); String value = (String) entry.getValue(); value = regex.matcher(value).replaceAll(replacement); result.put(key, value); } return result; } }