com.okta.idx.sdk.api.config.DefaultPropertiesParser Maven / Gradle / Ivy
/*
* Copyright 2014 Stormpath, Inc.
* Modifications Copyright 2018 Okta, Inc.
*
* 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 com.okta.idx.sdk.api.config;
import com.okta.commons.lang.Assert;
import com.okta.commons.lang.Strings;
import com.okta.idx.sdk.api.io.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Scanner;
public class DefaultPropertiesParser implements PropertiesParser {
private static final Logger log = LoggerFactory.getLogger(DefaultPropertiesParser.class);
private static final String COMMENT_POUND = "#";
private static final String COMMENT_SEMICOLON = ";";
private static final char ESCAPE_TOKEN = '\\';
@Override
public Map parse(String source) {
Scanner scanner = new Scanner(source);
return parse(scanner);
}
@Override
public Map parse(Resource resource) throws IOException {
InputStream is = resource.getInputStream();
Scanner scanner = new Scanner(is, StandardCharsets.UTF_8.name());
return parse(scanner);
}
/**
* Loads the .properties-formatted text backed by the given Scanner. This implementation will close the
* scanner after it has finished loading.
*
* @param scanner the {@code Scanner} from which to read the .properties-formatted text
*/
public Map parse(Scanner scanner) {
Assert.notNull(scanner, "Scanner argument cannot be null.");
Map props = new LinkedHashMap<>();
StringBuilder lineBuffer = new StringBuilder();
while (scanner.hasNextLine()) {
String rawLine = scanner.nextLine();
String line = Strings.clean(rawLine);
if (line == null || line.startsWith(COMMENT_POUND) || line.startsWith(COMMENT_SEMICOLON)) {
//skip empty lines and comments:
continue;
}
if (isContinued(line)) {
//strip off the last continuation backslash:
line = line.substring(0, line.length() - 1);
lineBuffer.append(line);
continue;
} else {
lineBuffer.append(line);
}
line = lineBuffer.toString();
lineBuffer = new StringBuilder();
String[] kvPair = splitKeyValue(line);
props.put(kvPair[0], kvPair[1]);
}
return props;
}
protected static boolean isContinued(String line) {
if (!Strings.hasText(line)) {
return false;
}
int length = line.length();
//find the number of backslashes at the end of the line. If an even number, the
//backslashes are considered escaped. If an odd number, the line is considered continued on the next line
int backslashCount = 0;
for (int i = length - 1; i > 0; i--) {
if (line.charAt(i) == ESCAPE_TOKEN) {
backslashCount++;
} else {
break;
}
}
return backslashCount % 2 != 0;
}
private static boolean isKeyValueSeparatorChar(char c) {
return Character.isWhitespace(c) || c == ':' || c == '=';
}
private static boolean isCharEscaped(CharSequence s, int index) {
return index > 0 && s.charAt(index - 1) == ESCAPE_TOKEN;
}
//Protected to access in a test case - NOT considered part of the public API
private static String[] splitKeyValue(String keyValueLine) {
String line = Strings.clean(keyValueLine);
if (line == null) {
return new String[0];
}
StringBuilder keyBuffer = new StringBuilder();
StringBuilder valueBuffer = new StringBuilder();
boolean buildingKey = true; //we'll build the value next:
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
if (buildingKey) {
if (isKeyValueSeparatorChar(c) && !isCharEscaped(line, i)) {
buildingKey = false;//now start building the value
} else {
keyBuffer.append(c);
}
} else {
//swallow the separator chars before we start building the value
if (!(valueBuffer.length() == 0 && isKeyValueSeparatorChar(c) && !isCharEscaped(line, i))) {
valueBuffer.append(c);
}
}
}
String key = Strings.clean(keyBuffer.toString());
String value = Strings.clean(valueBuffer.toString());
if (key == null) {
String msg = "Line argument must contain a key. None was found.";
throw new IllegalArgumentException(msg);
}
log.trace("Discovered key/value pair: {} = {}", key, value);
return new String[]{key, value};
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy