| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.badlogic.gdx.utils; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.Reader; |
| import java.io.Writer; |
| import java.util.Date; |
| |
| import com.badlogic.gdx.utils.ObjectMap.Entry; |
| |
| /** {@code PropertiesUtils} is a helper class that allows you to load and store key/value pairs of an |
| * {@code ObjectMap<String,String>} with the same line-oriented syntax supported by {@code java.util.Properties}. */ |
| public final class PropertiesUtils { |
| |
| private static final int NONE = 0, SLASH = 1, UNICODE = 2, CONTINUE = 3, KEY_DONE = 4, IGNORE = 5; |
| |
| private static final String LINE_SEPARATOR = "\n"; |
| |
| private PropertiesUtils () { |
| } |
| |
| /** Adds to the specified {@code ObjectMap} the key/value pairs loaded from the {@code Reader} in a simple line-oriented format |
| * compatible with <code>java.util.Properties</code>. |
| * <p> |
| * The input stream remains open after this method returns. |
| * |
| * @param properties the map to be filled. |
| * @param reader the input character stream reader. |
| * @throws IOException if an error occurred when reading from the input stream. |
| * @throws IllegalArgumentException if a malformed Unicode escape appears in the input. */ |
| @SuppressWarnings("deprecation") |
| public static void load (ObjectMap<String, String> properties, Reader reader) throws IOException { |
| if (properties == null) throw new NullPointerException("ObjectMap cannot be null"); |
| if (reader == null) throw new NullPointerException("Reader cannot be null"); |
| int mode = NONE, unicode = 0, count = 0; |
| char nextChar, buf[] = new char[40]; |
| int offset = 0, keyLength = -1, intVal; |
| boolean firstChar = true; |
| |
| BufferedReader br = new BufferedReader(reader); |
| |
| while (true) { |
| intVal = br.read(); |
| if (intVal == -1) { |
| break; |
| } |
| nextChar = (char)intVal; |
| |
| if (offset == buf.length) { |
| char[] newBuf = new char[buf.length * 2]; |
| System.arraycopy(buf, 0, newBuf, 0, offset); |
| buf = newBuf; |
| } |
| if (mode == UNICODE) { |
| int digit = Character.digit(nextChar, 16); |
| if (digit >= 0) { |
| unicode = (unicode << 4) + digit; |
| if (++count < 4) { |
| continue; |
| } |
| } else if (count <= 4) { |
| throw new IllegalArgumentException("Invalid Unicode sequence: illegal character"); |
| } |
| mode = NONE; |
| buf[offset++] = (char)unicode; |
| if (nextChar != '\n') { |
| continue; |
| } |
| } |
| if (mode == SLASH) { |
| mode = NONE; |
| switch (nextChar) { |
| case '\r': |
| mode = CONTINUE; // Look for a following \n |
| continue; |
| case '\n': |
| mode = IGNORE; // Ignore whitespace on the next line |
| continue; |
| case 'b': |
| nextChar = '\b'; |
| break; |
| case 'f': |
| nextChar = '\f'; |
| break; |
| case 'n': |
| nextChar = '\n'; |
| break; |
| case 'r': |
| nextChar = '\r'; |
| break; |
| case 't': |
| nextChar = '\t'; |
| break; |
| case 'u': |
| mode = UNICODE; |
| unicode = count = 0; |
| continue; |
| } |
| } else { |
| switch (nextChar) { |
| case '#': |
| case '!': |
| if (firstChar) { |
| while (true) { |
| intVal = br.read(); |
| if (intVal == -1) { |
| break; |
| } |
| nextChar = (char)intVal; |
| if (nextChar == '\r' || nextChar == '\n') { |
| break; |
| } |
| } |
| continue; |
| } |
| break; |
| case '\n': |
| if (mode == CONTINUE) { // Part of a \r\n sequence |
| mode = IGNORE; // Ignore whitespace on the next line |
| continue; |
| } |
| // fall into the next case |
| case '\r': |
| mode = NONE; |
| firstChar = true; |
| if (offset > 0 || (offset == 0 && keyLength == 0)) { |
| if (keyLength == -1) { |
| keyLength = offset; |
| } |
| String temp = new String(buf, 0, offset); |
| properties.put(temp.substring(0, keyLength), temp.substring(keyLength)); |
| } |
| keyLength = -1; |
| offset = 0; |
| continue; |
| case '\\': |
| if (mode == KEY_DONE) { |
| keyLength = offset; |
| } |
| mode = SLASH; |
| continue; |
| case ':': |
| case '=': |
| if (keyLength == -1) { // if parsing the key |
| mode = NONE; |
| keyLength = offset; |
| continue; |
| } |
| break; |
| } |
| // if (Character.isWhitespace(nextChar)) { <-- not supported by GWT; replaced with isSpace. |
| if (Character.isSpace(nextChar)) { |
| if (mode == CONTINUE) { |
| mode = IGNORE; |
| } |
| // if key length == 0 or value length == 0 |
| if (offset == 0 || offset == keyLength || mode == IGNORE) { |
| continue; |
| } |
| if (keyLength == -1) { // if parsing the key |
| mode = KEY_DONE; |
| continue; |
| } |
| } |
| if (mode == IGNORE || mode == CONTINUE) { |
| mode = NONE; |
| } |
| } |
| firstChar = false; |
| if (mode == KEY_DONE) { |
| keyLength = offset; |
| mode = NONE; |
| } |
| buf[offset++] = nextChar; |
| } |
| if (mode == UNICODE && count <= 4) { |
| throw new IllegalArgumentException("Invalid Unicode sequence: expected format \\uxxxx"); |
| } |
| if (keyLength == -1 && offset > 0) { |
| keyLength = offset; |
| } |
| if (keyLength >= 0) { |
| String temp = new String(buf, 0, offset); |
| String key = temp.substring(0, keyLength); |
| String value = temp.substring(keyLength); |
| if (mode == SLASH) { |
| value += "\u0000"; |
| } |
| properties.put(key, value); |
| } |
| } |
| |
| /** Writes the key/value pairs of the specified <code>ObjectMap</code> to the output character stream in a simple line-oriented |
| * format compatible with <code>java.util.Properties</code>. |
| * <p> |
| * Every entry in the <code>ObjectMap</code> is written out, one per line. For each entry the key string is written, then an |
| * ASCII <code>=</code>, then the associated element string. For the key, all space characters are written with a preceding |
| * <code>\</code> character. For the element, leading space characters, but not embedded or trailing space characters, are |
| * written with a preceding <code>\</code> character. The key and element characters <code>#</code>, <code>!</code>, |
| * <code>=</code>, and <code>:</code> are written with a preceding backslash to ensure that they are properly loaded. |
| * <p> |
| * After the entries have been written, the output stream is flushed. The output stream remains open after this method returns. |
| * |
| * @param properties the {@code ObjectMap}. |
| * @param writer an output character stream writer. |
| * @param comment an optional comment to be written, or null. |
| * @exception IOException if writing this property list to the specified output stream throws an <tt>IOException</tt>. |
| * @exception NullPointerException if <code>writer</code> is null. */ |
| public static void store (ObjectMap<String, String> properties, Writer writer, String comment) throws IOException { |
| storeImpl(properties, writer, comment, false); |
| } |
| |
| private static void storeImpl (ObjectMap<String, String> properties, Writer writer, String comment, boolean escapeUnicode) |
| throws IOException { |
| if (comment != null) { |
| writeComment(writer, comment); |
| } |
| writer.write("#"); |
| writer.write(new Date().toString()); |
| writer.write(LINE_SEPARATOR); |
| |
| StringBuilder sb = new StringBuilder(200); |
| for (Entry<String, String> entry : properties.entries()) { |
| dumpString(sb, entry.key, true, escapeUnicode); |
| sb.append('='); |
| dumpString(sb, entry.value, false, escapeUnicode); |
| writer.write(LINE_SEPARATOR); |
| writer.write(sb.toString()); |
| sb.setLength(0); |
| } |
| writer.flush(); |
| } |
| |
| private static void dumpString (StringBuilder outBuffer, String string, boolean escapeSpace, boolean escapeUnicode) { |
| int len = string.length(); |
| for (int i = 0; i < len; i++) { |
| char ch = string.charAt(i); |
| // Handle common case first |
| if ((ch > 61) && (ch < 127)) { |
| outBuffer.append(ch == '\\' ? "\\\\" : ch); |
| continue; |
| } |
| switch (ch) { |
| case ' ': |
| if (i == 0 || escapeSpace) outBuffer.append("\\ "); |
| break; |
| case '\n': |
| outBuffer.append("\\n"); |
| break; |
| case '\r': |
| outBuffer.append("\\r"); |
| break; |
| case '\t': |
| outBuffer.append("\\t"); |
| break; |
| case '\f': |
| outBuffer.append("\\f"); |
| break; |
| case '=': // Fall through |
| case ':': // Fall through |
| case '#': // Fall through |
| case '!': |
| outBuffer.append('\\').append(ch); |
| break; |
| default: |
| if (((ch < 0x0020) || (ch > 0x007e)) & escapeUnicode) { |
| String hex = Integer.toHexString(ch); |
| outBuffer.append("\\u"); |
| for (int j = 0; j < 4 - hex.length(); j++) { |
| outBuffer.append('0'); |
| } |
| outBuffer.append(hex); |
| } else { |
| outBuffer.append(ch); |
| } |
| break; |
| } |
| } |
| } |
| |
| private static void writeComment (Writer writer, String comment) throws IOException { |
| writer.write("#"); |
| int len = comment.length(); |
| int curIndex = 0; |
| int lastIndex = 0; |
| while (curIndex < len) { |
| char c = comment.charAt(curIndex); |
| if (c > '\u00ff' || c == '\n' || c == '\r') { |
| if (lastIndex != curIndex) writer.write(comment.substring(lastIndex, curIndex)); |
| if (c > '\u00ff') { |
| String hex = Integer.toHexString(c); |
| writer.write("\\u"); |
| for (int j = 0; j < 4 - hex.length(); j++) { |
| writer.write('0'); |
| } |
| writer.write(hex); |
| } else { |
| writer.write(LINE_SEPARATOR); |
| if (c == '\r' && curIndex != len - 1 && comment.charAt(curIndex + 1) == '\n') { |
| curIndex++; |
| } |
| if (curIndex == len - 1 || (comment.charAt(curIndex + 1) != '#' && comment.charAt(curIndex + 1) != '!')) |
| writer.write("#"); |
| } |
| lastIndex = curIndex + 1; |
| } |
| curIndex++; |
| } |
| if (lastIndex != curIndex) writer.write(comment.substring(lastIndex, curIndex)); |
| writer.write(LINE_SEPARATOR); |
| } |
| } |