/** * Copyright (C) 2016 Red Hat, Inc. and/or its affiliates. * * Licensed 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 org.jboss.errai.common.client.util; import java.util.HashMap; import java.util.Map; /** * A utility for parsing .properties files in GWT code. * * @author Max Barkley <mbarkley@redhat.com> */ public class Properties { /** * @param data * The contents of a .properties file. * @return A map of key-value pairs parsed from the .properties file contents. */ public static Map<String, String> load(final String data) { final ParseState parseState = new ParseState(data); while (parseState.next()); return parseState.properties; } private static class ParseState { private static enum State { LINE_START { @Override ParseState accept(final char input, final ParseState state) { if (input == '!' || input == '#') { state.state = COMMENT; state.index += 1; } else { state.state = PRE_KEY_WHITESPACE; } return state; } }, COMMENT { @Override ParseState accept(final char input, final ParseState state) { if (input == '\n') { state.state = LINE_START; } state.index += 1; return state; } }, PRE_KEY_WHITESPACE { @Override ParseState accept(final char input, final ParseState state) { if (Character.isWhitespace(input)) { state.index += 1; } else { state.state = KEY; } return state; } }, KEY { @Override ParseState accept(final char input, final ParseState state) { if (Character.isWhitespace(input)) { state.state = POST_KEY_WHITESPACE; } else if (isSeparator(input)) { state.state = PRE_VALUE_WHITESPACE; state.index += 1; } else { state.key.append(input); state.index += 1; } return state; } }, POST_KEY_WHITESPACE { @Override ParseState accept(final char input, final ParseState state) { if (Character.isWhitespace(input)) { state.index += 1; } else if (isSeparator(input)) { state.index += 1; state.state = PRE_VALUE_WHITESPACE; } else { state.state = VALUE_UNESCAPED; } return state; } }, PRE_VALUE_WHITESPACE { @Override ParseState accept(final char input, final ParseState state) { if (Character.isWhitespace(input)) { state.index += 1; } else { state.state = VALUE_UNESCAPED; } return state; } }, VALUE_UNESCAPED { @Override ParseState accept(final char input, final ParseState state) { if (input == '\\') { state.index += 1; state.state = VALUE_ESCAPED; } else if (input == '\n' || input == '\r') { state.state = LINE_ENDING; } else { state.index += 1; state.value.append(input); } return state; } }, VALUE_ESCAPED { @Override ParseState accept(final char input, final ParseState state) { if (Character.isWhitespace(input)) { state.state = VALUE_IGNORED; } else { state.index += 1; state.state = VALUE_UNESCAPED; switch (input) { case 'r': state.value.append('\r'); break; case 't': state.value.append('\t'); break; case 'n': state.value.append('\n'); break; case '=': state.value.append('='); break; case ':': state.value.append(':'); break; case '\\': state.value.append('\\'); break; case 'u': final String hexDigits = state.data.substring(state.index, state.index+4); state.index += 4; final int escapedChar = Integer.parseInt(hexDigits, 16); state.value.append((char) escapedChar); break; default: state.state = VALUE_IGNORED; return state; } } return state; } }, VALUE_IGNORED { @Override ParseState accept(final char input, final ParseState state) { if (input == '\n') { state.index += 1; state.state = PRE_VALUE_WHITESPACE; } else { state.index += 1; } return state; } }, LINE_END { @Override ParseState accept(final char input, final ParseState state) { state.state = LINE_START; state.index += 1; return state; } }, LINE_ENDING { @Override ParseState accept(final char input, final ParseState state) { state.state = LINE_END; if (input == '\r') { if (state.data.charAt(state.index+1) == '\n') { state.index += 1; } } return state; } }; abstract ParseState accept(char input, ParseState state); static boolean isSeparator(final char input) { return input == '=' || input == ':'; } } final StringBuilder key = new StringBuilder(), value = new StringBuilder(); State state = State.LINE_START; final String data; int index = 0; final Map<String, String> properties = new HashMap<>(); ParseState(final String date) { this.data = date; } boolean next() { if (index > data.length()) { return false; } if (State.LINE_END.equals(state) || index == data.length()) { if (key.length() > 0 || value.length() > 0) { properties.put(key.toString(), value.toString()); key.delete(0, key.length()); value.delete(0, value.length()); } if (index == data.length()) { index += 1; return false; } } final char c = data.charAt(index); state.accept(c, this); return true; } } private Properties() {} }