001    package org.bukkit.configuration.file;
002    
003    import com.google.common.base.Charsets;
004    import com.google.common.io.Files;
005    
006    import org.apache.commons.lang.Validate;
007    import org.bukkit.configuration.InvalidConfigurationException;
008    
009    import java.io.BufferedReader;
010    import java.io.File;
011    import java.io.FileInputStream;
012    import java.io.FileNotFoundException;
013    import java.io.FileOutputStream;
014    import java.io.IOException;
015    import java.io.InputStream;
016    import java.io.InputStreamReader;
017    import java.io.OutputStreamWriter;
018    import java.io.Reader;
019    import java.io.Writer;
020    import java.nio.charset.Charset;
021    
022    import org.bukkit.configuration.Configuration;
023    import org.bukkit.configuration.MemoryConfiguration;
024    import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
025    
026    /**
027     * This is a base class for all File based implementations of {@link
028     * Configuration}
029     */
030    public abstract class FileConfiguration extends MemoryConfiguration {
031        /**
032         * This value specified that the system default encoding should be
033         * completely ignored, as it cannot handle the ASCII character set, or it
034         * is a strict-subset of UTF8 already (plain ASCII).
035         *
036         * @deprecated temporary compatibility measure
037         */
038        @Deprecated
039        public static final boolean UTF8_OVERRIDE;
040        /**
041         * This value specifies if the system default encoding is unicode, but
042         * cannot parse standard ASCII.
043         *
044         * @deprecated temporary compatibility measure
045         */
046        @Deprecated
047        public static final boolean UTF_BIG;
048        /**
049         * This value specifies if the system supports unicode.
050         *
051         * @deprecated temporary compatibility measure
052         */
053        @Deprecated
054        public static final boolean SYSTEM_UTF;
055        static {
056            final byte[] testBytes = Base64Coder.decode("ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ent8fX4NCg==");
057            final String testString = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\r\n";
058            final Charset defaultCharset = Charset.defaultCharset();
059            final String resultString = new String(testBytes, defaultCharset);
060            final boolean trueUTF = defaultCharset.name().contains("UTF");
061            UTF8_OVERRIDE = !testString.equals(resultString) || defaultCharset.equals(Charset.forName("US-ASCII"));
062            SYSTEM_UTF = trueUTF || UTF8_OVERRIDE;
063            UTF_BIG = trueUTF && UTF8_OVERRIDE;
064        }
065    
066        /**
067         * Creates an empty {@link FileConfiguration} with no default values.
068         */
069        public FileConfiguration() {
070            super();
071        }
072    
073        /**
074         * Creates an empty {@link FileConfiguration} using the specified {@link
075         * Configuration} as a source for all default values.
076         *
077         * @param defaults Default value provider
078         */
079        public FileConfiguration(Configuration defaults) {
080            super(defaults);
081        }
082    
083        /**
084         * Saves this {@link FileConfiguration} to the specified location.
085         * <p>
086         * If the file does not exist, it will be created. If already exists, it
087         * will be overwritten. If it cannot be overwritten or created, an
088         * exception will be thrown.
089         * <p>
090         * This method will save using the system default encoding, or possibly
091         * using UTF8.
092         *
093         * @param file File to save to.
094         * @throws IOException Thrown when the given file cannot be written to for
095         *     any reason.
096         * @throws IllegalArgumentException Thrown when file is null.
097         */
098        public void save(File file) throws IOException {
099            Validate.notNull(file, "File cannot be null");
100    
101            Files.createParentDirs(file);
102    
103            String data = saveToString();
104    
105            Writer writer = new OutputStreamWriter(new FileOutputStream(file), UTF8_OVERRIDE && !UTF_BIG ? Charsets.UTF_8 : Charset.defaultCharset());
106    
107            try {
108                writer.write(data);
109            } finally {
110                writer.close();
111            }
112        }
113    
114        /**
115         * Saves this {@link FileConfiguration} to the specified location.
116         * <p>
117         * If the file does not exist, it will be created. If already exists, it
118         * will be overwritten. If it cannot be overwritten or created, an
119         * exception will be thrown.
120         * <p>
121         * This method will save using the system default encoding, or possibly
122         * using UTF8.
123         *
124         * @param file File to save to.
125         * @throws IOException Thrown when the given file cannot be written to for
126         *     any reason.
127         * @throws IllegalArgumentException Thrown when file is null.
128         */
129        public void save(String file) throws IOException {
130            Validate.notNull(file, "File cannot be null");
131    
132            save(new File(file));
133        }
134    
135        /**
136         * Saves this {@link FileConfiguration} to a string, and returns it.
137         *
138         * @return String containing this configuration.
139         */
140        public abstract String saveToString();
141    
142        /**
143         * Loads this {@link FileConfiguration} from the specified location.
144         * <p>
145         * All the values contained within this configuration will be removed,
146         * leaving only settings and defaults, and the new values will be loaded
147         * from the given file.
148         * <p>
149         * If the file cannot be loaded for any reason, an exception will be
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.
155         *
156         * @param file File to load from.
157         * @throws FileNotFoundException Thrown when the given file cannot be
158         *     opened.
159         * @throws IOException Thrown when the given file cannot be read.
160         * @throws InvalidConfigurationException Thrown when the given file is not
161         *     a valid Configuration.
162         * @throws IllegalArgumentException Thrown when file is null.
163         */
164        public void load(File file) throws FileNotFoundException, IOException, InvalidConfigurationException {
165            Validate.notNull(file, "File cannot be null");
166    
167            final FileInputStream stream = new FileInputStream(file);
168    
169            load(new InputStreamReader(stream, UTF8_OVERRIDE && !UTF_BIG ? Charsets.UTF_8 : Charset.defaultCharset()));
170        }
171    
172        /**
173         * Loads this {@link FileConfiguration} from the specified stream.
174         * <p>
175         * All the values contained within this configuration will be removed,
176         * leaving only settings and defaults, and the new values will be loaded
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.
181         *
182         * @param stream Stream to load from
183         * @throws IOException Thrown when the given file cannot be read.
184         * @throws InvalidConfigurationException Thrown when the given file is not
185         *     a valid Configuration.
186         * @throws IllegalArgumentException Thrown when stream is null.
187         * @deprecated This does not consider encoding
188         * @see #load(Reader)
189         */
190        @Deprecated
191        public void load(InputStream stream) throws IOException, InvalidConfigurationException {
192            Validate.notNull(stream, "Stream cannot be null");
193    
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);
212    
213            StringBuilder builder = new StringBuilder();
214    
215            try {
216                String line;
217    
218                while ((line = input.readLine()) != null) {
219                    builder.append(line);
220                    builder.append('\n');
221                }
222            } finally {
223                input.close();
224            }
225    
226            loadFromString(builder.toString());
227        }
228    
229        /**
230         * Loads this {@link FileConfiguration} from the specified location.
231         * <p>
232         * All the values contained within this configuration will be removed,
233         * leaving only settings and defaults, and the new values will be loaded
234         * from the given file.
235         * <p>
236         * If the file cannot be loaded for any reason, an exception will be
237         * thrown.
238         *
239         * @param file File to load from.
240         * @throws FileNotFoundException Thrown when the given file cannot be
241         *     opened.
242         * @throws IOException Thrown when the given file cannot be read.
243         * @throws InvalidConfigurationException Thrown when the given file is not
244         *     a valid Configuration.
245         * @throws IllegalArgumentException Thrown when file is null.
246         */
247        public void load(String file) throws FileNotFoundException, IOException, InvalidConfigurationException {
248            Validate.notNull(file, "File cannot be null");
249    
250            load(new File(file));
251        }
252    
253        /**
254         * Loads this {@link FileConfiguration} from the specified string, as
255         * opposed to from file.
256         * <p>
257         * All the values contained within this configuration will be removed,
258         * leaving only settings and defaults, and the new values will be loaded
259         * from the given string.
260         * <p>
261         * If the string is invalid in any way, an exception will be thrown.
262         *
263         * @param contents Contents of a Configuration to load.
264         * @throws InvalidConfigurationException Thrown if the specified string is
265         *     invalid.
266         * @throws IllegalArgumentException Thrown if contents is null.
267         */
268        public abstract void loadFromString(String contents) throws InvalidConfigurationException;
269    
270        /**
271         * Compiles the header for this {@link FileConfiguration} and returns the
272         * result.
273         * <p>
274         * This will use the header from {@link #options()} -> {@link
275         * FileConfigurationOptions#header()}, respecting the rules of {@link
276         * FileConfigurationOptions#copyHeader()} if set.
277         *
278         * @return Compiled header
279         */
280        protected abstract String buildHeader();
281    
282        @Override
283        public FileConfigurationOptions options() {
284            if (options == null) {
285                options = new FileConfigurationOptions(this);
286            }
287    
288            return (FileConfigurationOptions) options;
289        }
290    }