Commits

Wesley Wolfe authored 8ac95b65e07
Change YamlConfiguration encoding styles.

On JVMs with UTF-8 default encoding, this commit has no change in behavior. On JVMs with ascii default encoding (like some minimal linux installa- tions), this commit now uses UTF-8 for YamlConfiguration operations. Because all ascii is valid UTF-8, there is no feature degradation or data loss during the transition. On JVMs with any non-unicode but ascii-compliant encoding, this commit now forces YamlConfiguration to escape special characters when writing to files, effectively rendering the encoding to be plain ascii. Any affected file will now be able to migrate to UTF-8 in the future without data-loss or explicit conversion. When reading files, YamlConfiguration will use the system default encoding to handle any incoming non-utf8 data, with the expectation that any newly written file is still compliant with the system's default encoding. On JVMs with any non-unicode, but ascii-incompliant encoding (this may be the case for some Eastern character sets on Windows systems), this change is breaking, but is justified in claim that these systems would otherwise be unable to read YamlConfiguration for implementation dependent settings or from plugins themselves. For these systems, all uses of the encoding will be forced to use UTF-8 in all cases, and is effectively treated as if it was configured to be UTF-8 by default. On JVMs with unicode encoding of UTF-16 or UTF-32, the ability to load any configurations from almost any source prior to this change would have been unfeasible, if not impossible. As of this change, however, these systems now behave as expected when writing or reading files. However, when reading from any plugin jar, UTF-8 will be used, matching a super-majority of plugin developer base and requirements for the plugin.yml. Plugin developers may now mark their plugin as UTF-8 compliant, as documented in the PluginDescriptionFile class. This change will cause the appropriate APIs in JavaPlugin to ignore any system default encoding, instead using a Reader with the UTF-8 encoding, effectively rendering the jar system independent. This does not affect the aformentioned JVM settings for reading and writing files. To coincide with these changes, YamlConfiguration methods that utilize a stream are now deprecated to encourage use of a more strict denotation. File methods carry system-specific behaviors to prevent unncessary data loss during the transitional phase, while Reader methods are now provided that have a very well-defined encoder behavior. For the transition from InputStream methods to Reader methods, an API has been added to JavaPlugin to provide a Reader that matches the previous behavior as well as compliance to the UTF-8 flag in the PluginDescriptionFile. Addresses BUKKIT-314, BUKKIT-1466, BUKKIT-3377
No tags

src/main/java/org/bukkit/configuration/file/FileConfiguration.java

Modified
1 1 package org.bukkit.configuration.file;
2 2
3 +import com.google.common.base.Charsets;
3 4 import com.google.common.io.Files;
4 5
5 6 import org.apache.commons.lang.Validate;
6 7 import org.bukkit.configuration.InvalidConfigurationException;
8 +
7 9 import java.io.BufferedReader;
8 10 import java.io.File;
9 11 import java.io.FileInputStream;
10 12 import java.io.FileNotFoundException;
11 -import java.io.FileWriter;
13 +import java.io.FileOutputStream;
12 14 import java.io.IOException;
13 15 import java.io.InputStream;
14 16 import java.io.InputStreamReader;
17 +import java.io.OutputStreamWriter;
18 +import java.io.Reader;
19 +import java.io.Writer;
20 +import java.nio.charset.Charset;
21 +
15 22 import org.bukkit.configuration.Configuration;
16 23 import org.bukkit.configuration.MemoryConfiguration;
24 +import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
17 25
18 26 /**
19 27 * This is a base class for all File based implementations of {@link
20 28 * Configuration}
21 29 */
22 30 public abstract class FileConfiguration extends MemoryConfiguration {
31 + /**
32 + * This value specified that the system default encoding should be
33 + * completely ignored, as it cannot handle the ASCII character set, or it
34 + * is a strict-subset of UTF8 already (plain ASCII).
35 + *
36 + * @deprecated temporary compatibility measure
37 + */
38 + @Deprecated
39 + public static final boolean UTF8_OVERRIDE;
40 + /**
41 + * This value specifies if the system default encoding is unicode, but
42 + * cannot parse standard ASCII.
43 + *
44 + * @deprecated temporary compatibility measure
45 + */
46 + @Deprecated
47 + public static final boolean UTF_BIG;
48 + /**
49 + * This value specifies if the system supports unicode.
50 + *
51 + * @deprecated temporary compatibility measure
52 + */
53 + @Deprecated
54 + public static final boolean SYSTEM_UTF;
55 + static {
56 + final byte[] testBytes = Base64Coder.decode("ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ent8fX4NCg==");
57 + final String testString = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\r\n";
58 + final Charset defaultCharset = Charset.defaultCharset();
59 + final String resultString = new String(testBytes, defaultCharset);
60 + final boolean trueUTF = defaultCharset.name().contains("UTF");
61 + UTF8_OVERRIDE = !testString.equals(resultString) || defaultCharset.equals(Charset.forName("US-ASCII"));
62 + SYSTEM_UTF = trueUTF || UTF8_OVERRIDE;
63 + UTF_BIG = trueUTF && UTF8_OVERRIDE;
64 + }
65 +
23 66 /**
24 67 * Creates an empty {@link FileConfiguration} with no default values.
25 68 */
26 69 public FileConfiguration() {
27 70 super();
28 71 }
29 72
30 73 /**
31 74 * Creates an empty {@link FileConfiguration} using the specified {@link
32 75 * Configuration} as a source for all default values.
36 79 public FileConfiguration(Configuration defaults) {
37 80 super(defaults);
38 81 }
39 82
40 83 /**
41 84 * Saves this {@link FileConfiguration} to the specified location.
42 85 * <p>
43 86 * If the file does not exist, it will be created. If already exists, it
44 87 * will be overwritten. If it cannot be overwritten or created, an
45 88 * exception will be thrown.
89 + * <p>
90 + * This method will save using the system default encoding, or possibly
91 + * using UTF8.
46 92 *
47 93 * @param file File to save to.
48 94 * @throws IOException Thrown when the given file cannot be written to for
49 95 * any reason.
50 96 * @throws IllegalArgumentException Thrown when file is null.
51 97 */
52 98 public void save(File file) throws IOException {
53 99 Validate.notNull(file, "File cannot be null");
54 100
55 101 Files.createParentDirs(file);
56 102
57 103 String data = saveToString();
58 104
59 - FileWriter writer = new FileWriter(file);
105 + Writer writer = new OutputStreamWriter(new FileOutputStream(file), UTF8_OVERRIDE && !UTF_BIG ? Charsets.UTF_8 : Charset.defaultCharset());
60 106
61 107 try {
62 108 writer.write(data);
63 109 } finally {
64 110 writer.close();
65 111 }
66 112 }
67 113
68 114 /**
69 115 * Saves this {@link FileConfiguration} to the specified location.
70 116 * <p>
71 117 * If the file does not exist, it will be created. If already exists, it
72 118 * will be overwritten. If it cannot be overwritten or created, an
73 119 * exception will be thrown.
120 + * <p>
121 + * This method will save using the system default encoding, or possibly
122 + * using UTF8.
74 123 *
75 124 * @param file File to save to.
76 125 * @throws IOException Thrown when the given file cannot be written to for
77 126 * any reason.
78 127 * @throws IllegalArgumentException Thrown when file is null.
79 128 */
80 129 public void save(String file) throws IOException {
81 130 Validate.notNull(file, "File cannot be null");
82 131
83 132 save(new File(file));
92 141
93 142 /**
94 143 * Loads this {@link FileConfiguration} from the specified location.
95 144 * <p>
96 145 * All the values contained within this configuration will be removed,
97 146 * leaving only settings and defaults, and the new values will be loaded
98 147 * from the given file.
99 148 * <p>
100 149 * If the file cannot be loaded for any reason, an exception will be
101 150 * thrown.
151 + * <p>
152 + * This will attempt to use the {@link Charset#defaultCharset()} for
153 + * files, unless {@link #UTF8_OVERRIDE} but not {@link #UTF_BIG} is
154 + * specified.
102 155 *
103 156 * @param file File to load from.
104 157 * @throws FileNotFoundException Thrown when the given file cannot be
105 158 * opened.
106 159 * @throws IOException Thrown when the given file cannot be read.
107 160 * @throws InvalidConfigurationException Thrown when the given file is not
108 161 * a valid Configuration.
109 162 * @throws IllegalArgumentException Thrown when file is null.
110 163 */
111 164 public void load(File file) throws FileNotFoundException, IOException, InvalidConfigurationException {
112 165 Validate.notNull(file, "File cannot be null");
113 166
114 - load(new FileInputStream(file));
167 + final FileInputStream stream = new FileInputStream(file);
168 +
169 + load(new InputStreamReader(stream, UTF8_OVERRIDE && !UTF_BIG ? Charsets.UTF_8 : Charset.defaultCharset()));
115 170 }
116 171
117 172 /**
118 173 * Loads this {@link FileConfiguration} from the specified stream.
119 174 * <p>
120 175 * All the values contained within this configuration will be removed,
121 176 * leaving only settings and defaults, and the new values will be loaded
122 177 * from the given stream.
178 + * <p>
179 + * This will attempt to use the {@link Charset#defaultCharset()}, unless
180 + * {@link #UTF8_OVERRIDE} or {@link #UTF_BIG} is specified.
123 181 *
124 182 * @param stream Stream to load from
125 183 * @throws IOException Thrown when the given file cannot be read.
126 184 * @throws InvalidConfigurationException Thrown when the given file is not
127 185 * a valid Configuration.
128 186 * @throws IllegalArgumentException Thrown when stream is null.
187 + * @deprecated This does not consider encoding
188 + * @see #load(Reader)
129 189 */
190 + @Deprecated
130 191 public void load(InputStream stream) throws IOException, InvalidConfigurationException {
131 192 Validate.notNull(stream, "Stream cannot be null");
132 193
133 - InputStreamReader reader = new InputStreamReader(stream);
134 - StringBuilder builder = new StringBuilder();
135 - BufferedReader input = new BufferedReader(reader);
194 + load(new InputStreamReader(stream, UTF8_OVERRIDE ? Charsets.UTF_8 : Charset.defaultCharset()));
195 + }
196 +
197 + /**
198 + * Loads this {@link FileConfiguration} from the specified reader.
199 + * <p>
200 + * All the values contained within this configuration will be removed,
201 + * leaving only settings and defaults, and the new values will be loaded
202 + * from the given stream.
203 + *
204 + * @param reader the reader to load from
205 + * @throws IOException thrown when underlying reader throws an IOException
206 + * @throws InvalidConfigurationException thrown when the reader does not
207 + * represent a valid Configuration
208 + * @throws IllegalArgumentException thrown when reader is null
209 + */
210 + public void load(Reader reader) throws IOException, InvalidConfigurationException {
211 + BufferedReader input = reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader);
136 212
213 + StringBuilder builder = new StringBuilder();
137 214
138 215 try {
139 216 String line;
140 217
141 218 while ((line = input.readLine()) != null) {
142 219 builder.append(line);
143 220 builder.append('\n');
144 221 }
145 222 } finally {
146 223 input.close();

Everything looks good. We'll let you know here if there's anything you should know about.

Add shortcut