All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.logmanager.PropertyValues Maven / Gradle / Ivy

/*
 * Copyright 2018 Red Hat, 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 org.jboss.logmanager;

import java.util.Collections;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * A utility for converting objects into strings and strings into objects for storage in logging configurations.
 *
 * @author James R. Perkins
 */
@SuppressWarnings("WeakerAccess")
public class PropertyValues {

    private static final int KEY = 0;
    private static final int VALUE = 1;

    /**
     * Parses a string of key/value pairs into a map.
     * 

* The key/value pairs are separated by a comma ({@code ,}). The key and value are separated by an equals * ({@code =}). *

*

* If a key contains a {@code \} or an {@code =} it must be escaped by a preceding {@code \}. Example: {@code * key\==value,\\key=value}. *

*

* If a value contains a {@code \} or a {@code ,} it must be escaped by a preceding {@code \}. Example: {@code * key=part1\,part2,key2=value\\other}. *

* *

* If the value for a key is empty there is no trailing {@code =} after a key the will be {@code null}. *

* * @param s the string to parse * * @return a map of the key value pairs or an empty map if the string is {@code null} or empty */ public static Map stringToMap(final String s) { if (s == null || s.isEmpty()) return Collections.emptyMap(); final Map map = new LinkedHashMap<>(); final StringBuilder key = new StringBuilder(); final StringBuilder value = new StringBuilder(); final char[] chars = s.toCharArray(); int state = 0; for (int i = 0; i < chars.length; i++) { final char c = chars[i]; switch (state) { case KEY: { switch (c) { case '\\': { // Handle escapes if (chars.length > ++i) { final char next = chars[i]; if (next == '=' || next == '\\') { key.append(next); continue; } } throw new IllegalStateException("Escape character found at invalid position " + i + ". Only characters '=' and '\\' need to be escaped for a key."); } case '=': { state = VALUE; continue; } default: { key.append(c); continue; } } } case VALUE: { switch (c) { case '\\': { // Handle escapes if (chars.length > ++i) { final char next = chars[i]; if (next == ',' || next == '\\') { value.append(next); continue; } } throw new IllegalStateException("Escape character found at invalid position " + i + ". Only characters ',' and '\\' need to be escaped for a value."); } case ',': { // Only add if the key isn't empty if (key.length() > 0) { // Add the entry if (value.length() == 0) { map.put(key.toString(), null); } else { map.put(key.toString(), value.toString()); } // Clear the key key.setLength(0); } // Clear the value value.setLength(0); state = KEY; continue; } default: { value.append(c); continue; } } } default: // not reachable throw new IllegalStateException(); } } // Add the last entry if (key.length() > 0) { // Add the entry if (value.length() == 0) { map.put(key.toString(), null); } else { map.put(key.toString(), value.toString()); } } return Collections.unmodifiableMap(map); } /** * Parses a string of key/value pairs into an {@linkplain EnumMap enum map}. *

* The key/value pairs are separated by a comma ({@code ,}). The key and value are separated by an equals * ({@code =}). The key must be a valid {@linkplain Enum#valueOf(Class, String) enum value}. For convenience the * case of each character will be converted to uppercase and any dashes ({@code -}) will be converted to * underscores ({@code _}). *

*

* If a value contains a {@code \} or a {@code ,} it must be escaped by a preceding {@code \}. Example: {@code * key=part1\,part2,key2=value\\other}. *

* *

* If the value for a key is empty there is no trailing {@code =} after a key the value will be {@code null}. *

* * @param enumType the enum type * @param s the string to parse * * @return a map of the key value pairs or an empty map if the string is {@code null} or empty */ public static > EnumMap stringToEnumMap(final Class enumType, final String s) { return stringToEnumMap(enumType, s, true); } /** * Parses a string of key/value pairs into an {@linkplain EnumMap enum map}. *

* The key/value pairs are separated by a comma ({@code ,}). The key and value are separated by an equals * ({@code =}). The key must be a valid {@linkplain Enum#valueOf(Class, String) enum value}. For convenience any * dashes ({@code -}) will be converted to underscores ({@code _}). If {@code convertKeyCase} is set to * {@code true} the case will also be converted to uppercase for each key character. *

*

* If a value contains a {@code \} or a {@code ,} it must be escaped by a preceding {@code \}. Example: {@code * key=part1\,part2,key2=value\\other}. *

* *

* If the value for a key is empty there is no trailing {@code =} after a key the value will be {@code null}. *

* * @param enumType the enum type * @param s the string to parse * @param convertKeyCase {@code true} if the each character from the key should be converted to uppercase, * otherwise {@code false} to keep the case as is * * @return a map of the key value pairs or an empty map if the string is {@code null} or empty */ @SuppressWarnings("SameParameterValue") public static > EnumMap stringToEnumMap(final Class enumType, final String s, final boolean convertKeyCase) { final EnumMap result = new EnumMap<>(enumType); if (s == null || s.isEmpty()) return result; final StringBuilder key = new StringBuilder(); final StringBuilder value = new StringBuilder(); final char[] chars = s.toCharArray(); int state = 0; for (int i = 0; i < chars.length; i++) { final char c = chars[i]; switch (state) { case KEY: { switch (c) { case '=': { state = VALUE; continue; } case '-': { key.append('_'); continue; } default: { if (convertKeyCase) { key.append(Character.toUpperCase(c)); } else { key.append(c); } continue; } } } case VALUE: { switch (c) { case '\\': { // Handle escapes if (chars.length > ++i) { final char next = chars[i]; if (next == ',' || next == '\\') { value.append(next); continue; } } throw new IllegalStateException("Escape character found at invalid position " + i + ". Only characters ',' and '\\' need to be escaped for a value."); } case ',': { // Only add if the key isn't empty if (key.length() > 0) { // Add the value if (value.length() == 0) { result.put(E.valueOf(enumType, key.toString()), null); } else { result.put(E.valueOf(enumType, key.toString()), value.toString()); } // Clear the key key.setLength(0); } // Clear the value value.setLength(0); state = KEY; continue; } default: { value.append(c); continue; } } } default: // not reachable throw new IllegalStateException(); } } // Add the last entry if (key.length() > 0) { // Add the value if (value.length() == 0) { result.put(E.valueOf(enumType, key.toString()), null); } else { result.put(E.valueOf(enumType, key.toString()), value.toString()); } } return result; } /** * Converts a map into a string that can be parsed by {@link #stringToMap(String)}. Note that if this is an * {@link EnumMap} the {@link #mapToString(EnumMap)} will be used and the key will be the * {@linkplain Enum#name() enum name}. * * @param map the map to convert to a string * @param the type of the key * * @return a string value for that map that can be used for configuration properties * * @see #escapeKey(StringBuilder, String) * @see #escapeValue(StringBuilder, String) */ @SuppressWarnings("unchecked") public static String mapToString(final Map map) { if (map == null || map.isEmpty()) { return null; } if (map instanceof EnumMap) { return mapToString((EnumMap) map); } final StringBuilder sb = new StringBuilder(map.size() * 32); final Iterator> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { final Map.Entry entry = iterator.next(); escapeKey(sb, String.valueOf(entry.getKey())); sb.append('='); escapeValue(sb, entry.getValue()); if (iterator.hasNext()) { sb.append(','); } } return sb.toString(); } /** * Converts a map into a string that can be parsed by {@link #stringToMap(String)}. The kwy will be the * {@linkplain Enum#name() enum name}. * * @param map the map to convert to a string * @param the type of the key * * @return a string value for that map that can be used for configuration properties * * @see #escapeKey(StringBuilder, String) * @see #escapeValue(StringBuilder, String) */ public static > String mapToString(final EnumMap map) { if (map == null || map.isEmpty()) { return null; } final StringBuilder sb = new StringBuilder(map.size() * 32); final Iterator> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { final Map.Entry entry = iterator.next(); sb.append(entry.getKey().name()); sb.append('='); escapeValue(sb, entry.getValue()); if (iterator.hasNext()) { sb.append(','); } } return sb.toString(); } /** * Escapes a maps key value for serialization to a string. If the key contains a {@code \} or an {@code =} it will * be escaped by a preceding {@code \}. Example: {@code key\=} or {@code \\key}. * * @param sb the string builder to append the escaped key to * @param key the key */ public static void escapeKey(final StringBuilder sb, final String key) { final char[] chars = key.toCharArray(); for (int i = 0; i < chars.length; i++) { final char c = chars[i]; // Ensure that \ and = are escaped if (c == '\\') { final int n = i + 1; if (n >= chars.length) { sb.append('\\').append('\\'); } else { final char next = chars[n]; if (next == '\\' || next == '=') { // Nothing to do, already properly escaped sb.append(c); sb.append(next); i = n; } else { // Now we need to escape the \ sb.append('\\').append('\\'); } } } else if (c == '=') { sb.append('\\').append(c); } else { sb.append(c); } } } /** * Escapes a maps value for serialization to a string. If a value contains a {@code \} or a {@code ,} it will be * escaped by a preceding {@code \}. Example: {@code part1\,part2} or {@code value\\other}. * * @param sb the string builder to append the escaped value to * @param value the value */ public static void escapeValue(final StringBuilder sb, final String value) { if (value != null) { final char[] chars = value.toCharArray(); for (int i = 0; i < chars.length; i++) { final char c = chars[i]; // Ensure that \ and , are escaped if (c == '\\') { final int n = i + 1; if (n >= chars.length) { sb.append('\\').append('\\'); } else { final char next = chars[n]; if (next == '\\' || next == ',') { // Nothing to do, already properly escaped sb.append(c); sb.append(next); i = n; } else { // Now we need to escape the \ sb.append('\\').append('\\'); } } } else if (c == ',') { sb.append('\\').append(c); } else { sb.append(c); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy