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

org.dbflute.helper.dfmap.DfMapStyle Maven / Gradle / Ivy

There is a newer version: 1.2.8
Show newest version
/*
 * Copyright 2014-2021 the original author or authors.
 *
 * 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.dbflute.helper.dfmap;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.dbflute.helper.dfmap.exception.DfMapDuplicateEntryException;
import org.dbflute.helper.dfmap.exception.DfMapParseFailureException;
import org.dbflute.helper.message.ExceptionMessageBuilder;

/**
 * The parser for DBFlute map style.
 * 
 * e.g. map-string as one liner
 *   map:{key1=value1;key2=list:{value21;value22;value23},key3=map:{key31=value31}}
 * 
 * e.g. map-string as formatted
 *   map:{
 *       ; key1 = value1
 *       ; key2 = list:{ value21 ; value22 ; value23 }
 *       ; key3 = map:{ key31 ; value31 }
 *   }
 * 
 * e.g. list-string
 *   list:{key1=value1,key2=list:{value21,value22,value23},key3=map:{key31=value31}}
 * 
* *

Detail Specification:

*
 * o all elements are trimmed e.g. map:{ sea   = land    } to {sea=land}
 * o empty or null value is treated as null e.g. map:{ sea = ; land = null } to both null
 * o empty elements are ignored e.g. list:{ ; sea ; ; land } to [sea, land]
 * o can escape control marks e.g. '{', ';' by escape character '\'
 * o but cannot escape escape character itself
 * o basically cannot change control mark, but you can override...
 * o can check duplicate entry of map key by option checkDuplicateEntry()
 * o can print as one liner by option printOneLiner()
 * o non-thread-safe, uses temporary variables
 * o but can recycle in same thread 
 * 
* @author jflute * @since 1.1.8 (2018/05/05 Saturday) */ public class DfMapStyle { // migrated MapListString, basically keeping compatible // this original code (MapListString) was written when jflute was very young // and migrated to this class with small refactoring for performance // all lines are very memorable... // =================================================================================== // Definition // ========== /** The object prefix of map. */ public static final String MAP_PREFIX = "map:"; /** The object prefix of list. */ public static final String LIST_PREFIX = "list:"; /** The control mark of begin-brace. */ public static final String BEGIN_BRACE = "{"; /** The control mark of end-brace. */ public static final String END_BRACE = "}"; /** The control mark of element-delimiter. */ public static final String ELEMENT_DELIMITER = ";"; /** The control mark of value-equal. */ public static final String VALUE_EQUAL = "="; /** The escape character for control marks. */ protected static final String ESCAPE_CHAR = "\\"; /** The temporary mark of escaped escape character. */ protected static final String ESCAPED_ESCAPE_MARK = "$$df:escapedEscape$$"; // =================================================================================== // Attribute // ========= // ----------------------------------------------------- // Object Prefix // ------------- /** The object prefix of map. e.g. "map:" (NotNull) */ protected final String _mapPrefix; /** The object prefix of list. e.g. "list:" (NotNull) */ protected final String _listPrefix; // ----------------------------------------------------- // Control Mark // ------------ /** The control mark of begin-brace. e.g. "{" (NotNull) */ protected final String _beginBrace; /** The control mark of end-brace. e.g. "}" (NotNull) */ protected final String _endBrace; /** The control mark of element-delimiter. e.g. ";" (NotNull) */ protected final String _elementDelimiter; /** The control mark of value-equal. e.g. "=" (NotNull) */ protected final String _valueEqual; /** The escape character for control marks. "\\" (NotNull) */ protected final String _escapeChar; // ----------------------------------------------------- // Cached Expression // ----------------- /** The cached expression of begin-brace for map. e.g. "map:{" */ protected final String _mappingBeginBrace; /** The cached expression of begin-brace for list. e.g. "list:{" */ protected final String _listingBeginBrace; // ----------------------------------------------------- // Temporary // --------- /** The whole map-string of logical remainder as temporary variable for parsing process. (NullAllowed: when no process) */ protected String _remainderString; /** The current index of remainder string for parsing process. */ protected int _currentRemainderIndex; // ----------------------------------------------------- // Option // ------ /** Does it check duplicate entry of map for generation? */ protected boolean _checkDuplicateEntry; /** Does it print as one-liner for building? */ protected boolean _printOneLiner; /** Is it without display side spaces? */ protected boolean _withoutDisplaySideSpace; // =================================================================================== // Constructor // =========== /** * Constructor for settings as default. */ public DfMapStyle() { this(MAP_PREFIX, LIST_PREFIX, BEGIN_BRACE, END_BRACE, ELEMENT_DELIMITER, VALUE_EQUAL, ESCAPE_CHAR); } // you can use as public in your constructor of sub-class protected DfMapStyle(String mapPrefix, String listPrefix, String beginBrace, String endBrace, String elementDelimiter, String valueEqual, String escapeChar) { assertStringNotNullAndNotTrimmedEmpty("mapPrefix", mapPrefix); assertStringNotNullAndNotTrimmedEmpty("listPrefix", listPrefix); assertStringNotNullAndNotTrimmedEmpty("beginBrace", beginBrace); assertStringNotNullAndNotTrimmedEmpty("endBrace", endBrace); assertStringNotNullAndNotTrimmedEmpty("delimiter", elementDelimiter); assertStringNotNullAndNotTrimmedEmpty("valueEqual", valueEqual); assertStringNotNullAndNotTrimmedEmpty("escapeChar", escapeChar); _mapPrefix = mapPrefix; _listPrefix = listPrefix; _beginBrace = beginBrace; _endBrace = endBrace; _elementDelimiter = elementDelimiter; _valueEqual = valueEqual; _escapeChar = escapeChar; _mappingBeginBrace = _mapPrefix + _beginBrace; _listingBeginBrace = _listPrefix + _beginBrace; } // ----------------------------------------------------- // Option // ------ /** * Check duplicate entry of map for generation. (throws exception if found) * @return this. (NotNull) */ public DfMapStyle checkDuplicateEntry() { _checkDuplicateEntry = true; return this; } /** * Print as one liner for building. * @return this. (NotNull) */ public DfMapStyle printOneLiner() { _printOneLiner = true; return this; } /** * Print without display side spaces.
* e.g. map:{ sea = mystic ; land = oneman } to map:{sea=mystic;land=oneman}. * @return this. (NotNull) */ public DfMapStyle withoutDisplaySideSpace() { _withoutDisplaySideSpace = true; return this; } // =================================================================================== // Object to String // ================ // ----------------------------------------------------- // to MapString // ------------ /** * Convert to map-string from the map object. * @param map The map object that has string keys. (NotNull, EmptyAllowed) * @return The string as map expression. (NotNull) */ public String toMapString(Map map) { assertObjectNotNull("map", map); final StringBuilder sb = new StringBuilder(); @SuppressWarnings("unchecked") final Map casted = (Map) map; final boolean printOneLiner = isPrintOneLiner(); final boolean withoutDisplaySideSpace = isWithoutDisplaySideSpace(); buildMapString(sb, casted, printOneLiner, "", printOneLiner ? "" : " ", withoutDisplaySideSpace); return sb.toString(); } protected void buildMapString(StringBuilder sb, Map map, boolean printOneLiner, String previousIndent, String currentIndent, boolean withoutDisplaySideSpace) { doBuildMapStringBegin(sb, map, printOneLiner, previousIndent, currentIndent); if (!map.isEmpty()) { int index = 0; for (Entry entry : map.entrySet()) { doBuildMapStringCurrentEntry(sb, printOneLiner, previousIndent, currentIndent, withoutDisplaySideSpace, index, entry.getKey(), entry.getValue()); ++index; } if (printOneLiner) { appendDisplaySideSpace(sb, withoutDisplaySideSpace); } else { sb.append(ln()).append(previousIndent); } } doBuildMapStringEnd(sb, map, printOneLiner, previousIndent, currentIndent); } protected void doBuildMapStringBegin(StringBuilder sb, Map map, boolean printOneLiner, String previousIndent, String currentIndent) { sb.append(_mapPrefix).append(_beginBrace); } protected void doBuildMapStringCurrentEntry(StringBuilder sb, boolean printOneLiner, String previousIndent, String currentIndent, boolean withoutDisplaySideSpace, int index, String key, Object value) { if (printOneLiner) { if (index > 0) { if (!isWithoutDisplaySideSpace()) { sb.append(" "); } sb.append(_elementDelimiter); } } else { sb.append(ln()).append(currentIndent).append(_elementDelimiter); } appendDisplaySideSpace(sb, withoutDisplaySideSpace); sb.append(escapeControlMarkAsMap(key)); appendDisplaySideSpace(sb, withoutDisplaySideSpace); sb.append(_valueEqual); if (!isWithoutDisplaySideSpace()) { sb.append(" "); } if (value instanceof Map) { @SuppressWarnings("unchecked") final Map valueMap = (Map) value; final String nextIndent = deriveNextIndentOfBuildingMapString(printOneLiner, previousIndent, currentIndent); buildMapString(sb, valueMap, printOneLiner, currentIndent, nextIndent, withoutDisplaySideSpace); } else if (value instanceof List) { @SuppressWarnings("unchecked") final List valueList = (List) value; final String nextIndent = deriveNextIndentOfBuildingMapString(printOneLiner, previousIndent, currentIndent); buildListString(sb, valueList, printOneLiner, currentIndent, nextIndent, withoutDisplaySideSpace); } else { final Map resolvedMap = resolvePotentialMapOfBuildingMapString(value); if (resolvedMap != null) { final String nextIndent = deriveNextIndentOfBuildingMapString(printOneLiner, previousIndent, currentIndent); buildMapString(sb, resolvedMap, printOneLiner, currentIndent, nextIndent, withoutDisplaySideSpace); } else { sb.append(escapeControlMarkAsMap(value)); } } } protected void doBuildMapStringEnd(StringBuilder sb, Map map, boolean printOneLiner, String previousIndent, String currentIndent) { sb.append(_endBrace); } // ----------------------------------------------------- // to ListString // ------------- /** * Convert to list-string from the list object. * @param list The list object that has object elements. (NotNull, EmptyAllowed) * @return The string as list expression. (NotNull) */ public String toListString(List list) { assertObjectNotNull("list", list); final StringBuilder sb = new StringBuilder(); @SuppressWarnings("unchecked") final List casted = (List) list; final boolean printOneLiner = isPrintOneLiner(); final boolean withoutDisplaySideSpace = isWithoutDisplaySideSpace(); buildListString(sb, casted, printOneLiner, "", printOneLiner ? "" : " ", withoutDisplaySideSpace); return sb.toString(); } protected void buildListString(StringBuilder sb, List list, boolean printOneLiner, String previousIndent, String currentIndent, boolean withoutDisplaySideSpace) { doBuildListStringBegin(sb, list, printOneLiner, previousIndent, currentIndent); if (!list.isEmpty()) { int index = 0; for (Object value : list) { doBuildListStringCurrentElement(sb, printOneLiner, previousIndent, currentIndent, withoutDisplaySideSpace, index, value); ++index; } if (printOneLiner) { appendDisplaySideSpace(sb, withoutDisplaySideSpace); } else { sb.append(ln()).append(previousIndent); } } doBuildListStringEnd(sb, list, printOneLiner, previousIndent, currentIndent); } protected void doBuildListStringBegin(StringBuilder sb, List list, boolean printOneLiner, String previousIndent, String currentIndent) { sb.append(_listPrefix).append(_beginBrace); } protected void doBuildListStringCurrentElement(StringBuilder sb, boolean printOneLiner, String previousIndent, String currentIndent, boolean withoutDisplaySideSpace, int index, Object value) { if (printOneLiner) { if (index > 0) { appendDisplaySideSpace(sb, withoutDisplaySideSpace); sb.append(_elementDelimiter); } } else { sb.append(ln()).append(currentIndent).append(_elementDelimiter); } appendDisplaySideSpace(sb, withoutDisplaySideSpace); if (value instanceof Map) { @SuppressWarnings("unchecked") final Map valueMap = (Map) value; final String nextIndent = deriveNextIndentOfBuildingMapString(printOneLiner, previousIndent, currentIndent); buildMapString(sb, valueMap, printOneLiner, currentIndent, nextIndent, withoutDisplaySideSpace); } else if (value instanceof List) { @SuppressWarnings("unchecked") final List valueList = (List) value; final String nextIndent = deriveNextIndentOfBuildingMapString(printOneLiner, previousIndent, currentIndent); buildListString(sb, valueList, printOneLiner, currentIndent, nextIndent, withoutDisplaySideSpace); } else { final Map resolvedMap = resolvePotentialMapOfBuildingMapString(value); if (resolvedMap != null) { final String nextIndent = deriveNextIndentOfBuildingMapString(printOneLiner, previousIndent, currentIndent); buildMapString(sb, resolvedMap, printOneLiner, currentIndent, nextIndent, withoutDisplaySideSpace); } else { sb.append(escapeControlMarkAsList(value)); } } } protected void doBuildListStringEnd(StringBuilder sb, List list, boolean printOneLiner, String previousIndent, String currentIndent) { sb.append(_endBrace); } // ----------------------------------------------------- // Assist Logic // ------------ protected String deriveNextIndentOfBuildingMapString(boolean printOneLiner, String previousIndent, String currentIndent) { return printOneLiner ? "" : generally_calculateNextIndent(previousIndent, currentIndent); } protected Map resolvePotentialMapOfBuildingMapString(Object value) { // value may be null // you can override for your bean that can be map like this: // e.g. // if (value instanceof Something) { // return ((Something) value).toMap(); // } else { // return super.resolvePotentialMapOfBuildingMapString(value); // } return null; // returning null means non-map value } protected void appendDisplaySideSpace(StringBuilder sb, boolean withoutDisplaySideSpace) { if (!withoutDisplaySideSpace) { sb.append(" "); } } // =================================================================================== // Object from String // ================== // ----------------------------------------------------- // from MapString // -------------- /** * Convert to map object from the map-string. * @param mapString The string as map expression. (NotNull, NotEmpty) * @return The map for the map-string, mutable for compatible. (NotNull, EmptyAllowed) */ public Map fromMapString(String mapString) { assertStringNotNullAndNotTrimmedEmpty("mapString", mapString); beginRemainder(mapString); try { assertFirstMapString(); ltrimRemainder(); removePrefixMapingBeginBrace(); final Map map = newStringObjectMap(); parseMapString(map); assertLastRemainderAsMap(map); return map; } finally { endRemainder(); } } // ----------------------------------------------------- // to MapString // ------------ /** * Convert to map object from list-string. * @param listString The string as list expression. (NotNull, NotEmpty) * @return The list for the list-string, mutable for compatible. (NotNull, EmptyAllowed) */ public List fromListString(String listString) { assertStringNotNullAndNotTrimmedEmpty("listString", listString); beginRemainder(listString); try { assertFirstListString(); ltrimRemainder(); removePrefixListingBeginBrace(); final List list = newObjectList(); parseListString(list); assertLastRemainderAsList(list); return list; } finally { endRemainder(); } } // =================================================================================== // Parse // ===== /** * Parse the current remainder string as map. * @param currentMap The current map made by parse process. (NotNull) */ protected void parseMapString(Map currentMap) { while (true) { if (!prepareLoopBeginning()) { return; } // _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ // here, remainder string should starts with the key of the map // _/_/_/_/_/_/_/_/_/_/ final int equalIndex = indexOfEqual(); assertMapStringEqualIndex(equalIndex, currentMap); final String mapKey = substringRemainderFront(equalIndex); removePrefixTargetIndexPlus(equalIndex, _valueEqual.length()); ltrimRemainder(); // _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ // here, remainder string should starts with the value of the map // _/_/_/_/_/_/_/_/_/_/ if (startsWithMapBeginBrace()) { removePrefixMapingBeginBrace(); parseMapString(setupNestMap(currentMap, mapKey)); if (closeAfterParseNestMapList()) { return; } continue; } if (startsWithListBeginBrace()) { removePrefixListingBeginBrace(); parseListString(setupNestList(currentMap, mapKey)); if (closeAfterParseNestMapList()) { return; } continue; } final int delimiterIndex = indexOfDelimiter(); final int endBraceIndex = indexOfEndBrace(); assertMapStringEndBraceIndex(endBraceIndex, currentMap); if (delimiterIndex >= 0 && delimiterIndex < endBraceIndex) { // delimiter exists // e.g. value1 ; key2=value2} final String mapValue = substringRemainderFront(delimiterIndex); registerToMap(currentMap, filterParsedMapKey(mapKey), filterParsedMapValue(mapValue)); // because the map element continues since the delimiter, // skip the delimiter and continue the loop removePrefixTargetIndexPlus(delimiterIndex, _elementDelimiter.length()); continue; } // e.g. value1} ; key2=value2} final String mapValue = substringRemainderScope(_currentRemainderIndex, endBraceIndex); registerToMap(currentMap, filterParsedMapKey(mapKey), filterParsedMapValue(mapValue)); // analyzing map is over, so close and return. closeByEndBraceIndex(endBraceIndex); return; } } /** * Parse the current remainder string as list. * @param currentList The current list made by parse process. (NotNull) */ protected void parseListString(List currentList) { while (true) { if (!prepareLoopBeginning()) { return; } // _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ // here, remainder string should starts with the value of the list // _/_/_/_/_/_/_/_/_/_/ if (startsWithMapBeginBrace()) { removePrefixMapingBeginBrace(); parseMapString(setupNestMap(currentList)); if (closeAfterParseNestMapList()) { return; } continue; } if (startsWithListBeginBrace()) { removePrefixListingBeginBrace(); parseListString(setupNestList(currentList)); if (closeAfterParseNestMapList()) { return; } continue; } final int delimiterIndex = indexOfDelimiter(); final int endBraceIndex = indexOfEndBrace(); assertListStringEndBraceIndex(endBraceIndex, currentList); if (delimiterIndex >= 0 && delimiterIndex < endBraceIndex) { // delimiter exists // e.g. value1 ; value2 ; value3} final String listValue = substringRemainderFront(delimiterIndex); currentList.add(filterParsedListValue(listValue)); // because the list element continues since the delimiter, // skip the delimiter and continue the loop. removePrefixTargetIndexPlus(delimiterIndex, _elementDelimiter.length()); continue; } // e.g. value1}, value2, } final String listValue = substringRemainderFront(endBraceIndex); currentList.add(filterParsedListValue(listValue)); // analyzing list is over, so close and return closeByEndBraceIndex(endBraceIndex); return; } } /** * prepare loop beginning. * @return Does it loop next? */ protected boolean prepareLoopBeginning() { removePrefixAllDelimiter(); if (isRemainderOver()) { // analyzing is over return false; } if (startsWithEndBrace()) { // analyzing current map is over removePrefixEndBrace(); return false; } return true; } /** * Close after parse nest map list. * @return Is is closed? */ protected boolean closeAfterParseNestMapList() { if (startsWithEndBrace()) { removePrefixEndBrace(); return true; } return false; } /** * Close by end-brace index. * @param endBraceIndex The index of end-brace. (NotMinus) */ protected void closeByEndBraceIndex(int endBraceIndex) { _currentRemainderIndex = endBraceIndex; removePrefixEndBrace(); } // =================================================================================== // Index of Control Mark // ===================== protected int indexOfStartBrace() { return findIndexOfControlMark(_beginBrace); } protected int indexOfEndBrace() { return findIndexOfControlMark(_endBrace); } protected int indexOfDelimiter() { return findIndexOfControlMark(_elementDelimiter); } protected int indexOfEqual() { return findIndexOfControlMark(_valueEqual); } protected int findIndexOfControlMark(String controlMark) { String current = _remainderString; if (isEscapeCharEscape()) { // performance headache but basically it does not come here // because of unsupported escaping escape-char final String escapedEscapeChar = convertToEscapedMark(_escapeChar); current = generally_replace(current, escapedEscapeChar, convertToLengthSpace(escapedEscapeChar)); } int currentBeginIndex = _currentRemainderIndex; while (true) { final int index = current.indexOf(controlMark, currentBeginIndex); if (index < 0) { // not found return index; } if (index > 0) { final String lastChar = current.substring(index - 1, index); if (_escapeChar.equals(lastChar)) { // escaped final int nextIndex = index + _escapeChar.length(); currentBeginIndex = nextIndex; continue; } } return index; // found } } protected String convertToLengthSpace(String value) { final StringBuilder sb = new StringBuilder(); for (int i = 0; i < value.length(); i++) { sb.append(" "); } return sb.toString(); } // =================================================================================== // Remove Prefix of Control Mark // ============================= /** * Remove prefix and all delimiters. */ protected void removePrefixAllDelimiter() { ltrimRemainder(); while (true) { if (!startsWithDelimiter()) { break; } if (startsWithDelimiter()) { removePrefixDelimiter(); ltrimRemainder(); } } } /** * Remove prefix map-prefix and begin-brace. */ protected void removePrefixMapingBeginBrace() { doRemovePrefix(_mappingBeginBrace); } /** * Remove prefix list-prefix and begin-brace. */ protected void removePrefixListingBeginBrace() { doRemovePrefix(_listingBeginBrace); } /** * Remove prefix end-brace. */ protected void removePrefixEndBrace() { doRemovePrefix(_endBrace); } /** * Remove prefix delimiter. */ protected void removePrefixDelimiter() { doRemovePrefix(_elementDelimiter); } /** * Remove prefix as mark. * @param prefixString The string for prefix. (NotNull) */ protected void doRemovePrefix(String prefixString) { assertRemovingPrefix(prefixString); final int nonTrimmedIndex = indexOfRemainderNonTrimmedChar(); _currentRemainderIndex = nonTrimmedIndex + prefixString.length(); } /** * Remove prefix by the index and plus count. * @param index The base index. (NotMinus) * @param plusCount The plus count for index. (NotMinus) */ protected void removePrefixTargetIndexPlus(int index, int plusCount) { _currentRemainderIndex = index + plusCount; } // =================================================================================== // Starts/Ends with Control Mark // ============================= /** * Does it start with the map-prefix? * @return The determination, true or false. */ protected boolean startsWithMapBeginBrace() { final int index = indexOfRemainderNonTrimmedChar(); return index >= 0 && _remainderString.startsWith(_mappingBeginBrace, index); } /** * Does it start with the list-prefix? * @return The determination, true or false. */ protected boolean startsWithListBeginBrace() { final int index = indexOfRemainderNonTrimmedChar(); return index >= 0 && _remainderString.startsWith(_listingBeginBrace, index); } /** * Does it start with the delimiter? * @return The determination, true or false. */ protected boolean startsWithDelimiter() { final int index = indexOfRemainderNonTrimmedChar(); return index >= 0 && _remainderString.startsWith(_elementDelimiter, index); } /** * Does it start with end-brace? * @return The determination, true or false. */ protected boolean startsWithEndBrace() { final int index = indexOfRemainderNonTrimmedChar(); return index >= 0 && _remainderString.startsWith(_endBrace, index); } protected boolean startsWithPrefix(String prefix) { final int index = indexOfRemainderNonTrimmedChar(); return index >= 0 && _remainderString.startsWith(prefix, index); } /** * Does it end with end-brace? * @return The determination, true or false. */ protected boolean endsWithEndBrace() { return generally_endsWith(_remainderString, _endBrace); } // =================================================================================== // Map Object // ========== // ----------------------------------------------------- // Register // -------- protected void registerToMap(Map currentMap, String key, Object element) { if (isCheckDuplicateEntry()) { assertDuplicateEntryInMap(currentMap, key, element); } currentMap.put(key, element); } protected void assertDuplicateEntryInMap(Map currentMap, String key, Object element) { if (currentMap.containsKey(key)) { throwDfMapDuplicateEntryException(currentMap, key, element); } } protected void throwDfMapDuplicateEntryException(Map currentMap, String key, Object element) { final ExceptionMessageBuilder br = new ExceptionMessageBuilder(); br.addNotice("Duplicate entry in the map string."); br.addItem("Already Registered Entry"); br.addElement(key); br.addElement(currentMap.get(key)); br.addItem("New Duplicate Entry"); br.addElement(key); if (element instanceof Map) { // might be lazy registered so might be empty (then not show) final Map map = (Map) element; if (!map.isEmpty()) { br.addElement(map); } } else { br.addElement(element); } setupMessageRemainderString(br); final String msg = br.buildExceptionMessage(); throw new DfMapDuplicateEntryException(msg); } // ----------------------------------------------------- // Set up // ------ /** * Set up new-created nest map as element of the current map. * @param currentMap the current map to set up. (NotNull) * @param mapKey The key of nest map. (NotNull) * @return The new-created nest map. (NotNull) */ protected Map setupNestMap(Map currentMap, String mapKey) { final Map nestMap = newStringObjectMap(); registerToMap(currentMap, filterParsedMapKey(mapKey), nestMap); return nestMap; } /** * Set up new-created nest map as element of the current list. * @param currentList the current list to set up. (NotNull) * @return The new-created nest map. (NotNull) */ protected Map setupNestMap(List currentList) { final Map nestMap = newStringObjectMap(); currentList.add(nestMap); return nestMap; } /** * Set up new-created nest list as element of the current map. * @param currentMap the current map to set up. (NotNull) * @param mapKey The key of nest map. (NotNull) * @return The new-created nest list. (NotNull) */ protected List setupNestList(Map currentMap, String mapKey) { final List nestList = newObjectList(); registerToMap(currentMap, filterParsedMapKey(mapKey), nestList); return nestList; } /** * Set up new-created nest list as element of the current list. * @param currentList the current map to set up. (NotNull) * @return The new-created nest list. (NotNull) */ protected List setupNestList(List currentList) { final List nestList = newObjectList(); currentList.add(nestList); return nestList; } // ----------------------------------------------------- // Filter // ------ protected String filterParsedMapKey(String value) { if (value == null) { return null; } final String unescaped = unescapeControlMarkAsMap(value.trim()); return doFilterParsedMapListNullExpression(unescaped); } protected String filterParsedMapValue(String value) { if (value == null) { return null; } final String unescaped = unescapeControlMarkAsMap(value.trim()); return doFilterParsedMapListNullExpression(unescaped); } protected String filterParsedListValue(String value) { if (value == null) { return null; } final String unescaped = unescapeControlMarkAsList(value.trim()); return doFilterParsedMapListNullExpression(unescaped); } protected String doFilterParsedMapListNullExpression(String filtered) { return ("".equals(filtered) || "null".equals(filtered)) ? null : filtered; } // ----------------------------------------------------- // Map/List Instance // ----------------- /** * New string-object map. * @return The new-created map. (NotNull) */ public Map newStringObjectMap() { // public for client return new LinkedHashMap(); } /** * New object-type list. * @return The new-created list. (NotNull) */ public List newObjectList() { // public for client return new ArrayList(); } // =================================================================================== // Escape Mark // =========== // ----------------------------------------------------- // Escape // ------ // escape is for e.g. toMapString(), toListString() /** * Escape control marks as plain string in the map key and value. * @param value The value, might contain control marks. (NullAllowed: if null, return null) * @return The escaped string of the value. (NullAllowed: when the value is null) */ public String escapeControlMarkAsMap(Object value) { // public for tools return doEscapeControlMark(value, /*ignoreEqual*/false); } /** * Escape control marks as plain string in the list value. * @param value The value, might contain control marks. (NullAllowed: if null, return null) * @return The escaped string of the value. (NullAllowed: when the value is null) */ public String escapeControlMarkAsList(Object value) { // public for tools return doEscapeControlMark(value, isIgnoreEqualAsEscapeControlMarkInList()); } protected boolean isIgnoreEqualAsEscapeControlMarkInList() { // for e.g. toMapString(), toListString() // now the equal "=" in list value, e.g. list:{ sea => land }, is escaped in spite of unnecessary // I want to fix it but keep compatible to avoid rare case trouble so no change with extension point // (and also other abstractions exist in other case...) // // so all control marks are escaped in both map and list (is specification of MapStyle) // and small abstractions (receiving scope is wide) are allowed like this: // o list:{ sea => land }, list:{ sea \=> land } are parsed as list:{ sea => land } // o map:{key1=value1=value2} are parsed as map:{ key1 = value1=value2 } return false; } protected String doEscapeControlMark(Object value, boolean ignoreEqual) { if (value == null) { return null; } String filtered = value.toString(); if (isEscapeCharEscape()) { filtered = generally_replace(filtered, _escapeChar, convertToEscapedMark(_escapeChar)); } filtered = generally_replace(filtered, _beginBrace, convertToEscapedMark(_beginBrace)); filtered = generally_replace(filtered, _endBrace, convertToEscapedMark(_endBrace)); filtered = generally_replace(filtered, _elementDelimiter, convertToEscapedMark(_elementDelimiter)); if (!ignoreEqual) { filtered = generally_replace(filtered, _valueEqual, convertToEscapedMark(_valueEqual)); } return filtered; } // ----------------------------------------------------- // Unescape // -------- // unescape is for e.g. fromMapString(), fromListString() /** * Unescape control marks as plain string in the map key and value. * @param value The value, might contain escaped control marks. (NullAllowed: if null, return null) * @return The unescaped string of the value. (NullAllowed: when the value is null) */ public String unescapeControlMarkAsMap(String value) { // public for tools return doUnescapeControlMark(value, /*ignoreEqual*/false); } /** * Unescape control marks as plain string in the list value. * @param value The value, might contain escaped control marks. (NullAllowed: if null, return null) * @return The unescaped string of the value. (NullAllowed: when the value is null) */ public String unescapeControlMarkAsList(String value) { // public for tools return doUnescapeControlMark(value, isIgnoreEqualAsUnescapeControlMarkInList()); } protected boolean isIgnoreEqualAsUnescapeControlMarkInList() { // for e.g. fromMapString(), fromListString() return false; // same reason as isIgnoreEqualAsEscapeControlMarkInList() } protected String doUnescapeControlMark(String value, boolean ignoreEqual) { if (value == null) { return null; } String filtered = value; final String escapedEscapeMark = ESCAPED_ESCAPE_MARK; if (isEscapeCharEscape()) { filtered = generally_replace(filtered, convertToEscapedMark(_escapeChar), escapedEscapeMark); } filtered = generally_replace(filtered, convertToEscapedMark(_beginBrace), _beginBrace); filtered = generally_replace(filtered, convertToEscapedMark(_endBrace), _endBrace); filtered = generally_replace(filtered, convertToEscapedMark(_elementDelimiter), _elementDelimiter); if (!ignoreEqual) { filtered = generally_replace(filtered, convertToEscapedMark(_valueEqual), _valueEqual); } if (isEscapeCharEscape()) { filtered = generally_replace(filtered, escapedEscapeMark, _escapeChar); } return filtered; } // ----------------------------------------------------- // Assist Logic // ------------ protected String convertToEscapedMark(String mark) { return _escapeChar + mark; } protected boolean isEscapeCharEscape() { // escape for escape char is unsupported (unneeded) // so fixedly returns false // // compatibility is treated as important here // o "\\n = \n" in convertValueMap.dataprop can directly work // o plain "\" is can directly work // // [specification] // escape char without control mark is treated as plain value // e.g. "a\b\c" // // previous escape char of control mark is always treated as escape char // e.g. "\;" // // if any spaces between the escape char and the control mark exist, // the escape char is plain value, e.g. "\ ;" // return false; } // =================================================================================== // Parsing Assert // ============== // ----------------------------------------------------- // First MapString // --------------- protected void assertFirstMapString() { if (_remainderString == null) { throwDfMapStringBasicFailureException("The map-string should not be null."); } if (!startsWithMapBeginBrace()) { throwDfMapStringBasicFailureException("The map-string should start with '" + _mappingBeginBrace + "'."); } if (!endsWithEndBrace()) { throwDfMapStringBasicFailureException("The map-string should end with '" + _endBrace + "'."); } // #for_now difficult to count non-escaped marks without performance cost (also thinking "map:" in value) by jflute (2018/05/05) //final int beginBraceCount = countControlMarkInRemainder(_beginBrace); //final int endBraceCount = countControlMarkInRemainder(_endBrace); //if (beginBraceCount != endBraceCount) { // throwDfMapStringDifferentCountBracesException(beginBraceCount, endBraceCount); //} } protected void throwDfMapStringBasicFailureException(String notice) { final ExceptionMessageBuilder br = new ExceptionMessageBuilder(); br.addNotice(notice); setupMessageRemainderString(br); final String msg = br.buildExceptionMessage(); throw new DfMapParseFailureException(msg); } protected void throwDfMapStringDifferentCountBracesException(int beginCount, int endCount) { final ExceptionMessageBuilder br = new ExceptionMessageBuilder(); br.addNotice("Different count between begin braces and end braces."); br.addItem("Advice"); br.addElement("Make sure braces on your map-string."); br.addElement("For example:"); br.addElement(" (x): map:{ sea = "); br.addElement(" (x): map:{ sea = map:{ }"); br.addElement(" (o): map:{ sea = land }"); br.addElement(" (o): map:{ sea = map:{ land = piari } }"); br.addElement(""); br.addElement("While, map-string can escape control marks"); br.addElement("so pay attention to last char of value like this:"); br.addElement(" (x): map:{ sea = C:\\land\\piari\\} // last '}' escaped by escape char"); br.addElement(" (o): map:{ sea = C:\\land\\piari\\ } // space helps you at the case"); br.addItem("Brace Count"); br.addElement("begin: " + beginCount); br.addElement("end: " + endCount); setupMessageRemainderString(br); final String msg = br.buildExceptionMessage(); throw new DfMapParseFailureException(msg); } // ----------------------------------------------------- // First ListString // ---------------- protected void assertFirstListString() { if (_remainderString == null) { throwListStringBasicFailureException("The list string should not be null."); } if (!startsWithListBeginBrace()) { throwListStringBasicFailureException("The list string should start with '" + _listingBeginBrace + "'."); } if (!endsWithEndBrace()) { throwListStringBasicFailureException("The list string should end with '" + _endBrace + "'."); } // #for_now difficult to count non-escaped marks without performance cost (also thinking "map:" in value) by jflute (2018/05/05) //final int startBraceCount = countControlMarkInRemainder(_beginBrace); //final int endBraceCount = countControlMarkInRemainder(_endBrace); //if (startBraceCount != endBraceCount) { // throwDfListStringDifferentCountBracesException(startBraceCount, endBraceCount); //} } protected void throwListStringBasicFailureException(String notice) { final ExceptionMessageBuilder br = new ExceptionMessageBuilder(); br.addNotice(notice); setupMessageRemainderString(br); final String msg = br.buildExceptionMessage(); throw new DfMapParseFailureException(msg); } protected void throwDfListStringDifferentCountBracesException(int beginCount, int endCount) { final ExceptionMessageBuilder br = new ExceptionMessageBuilder(); br.addNotice("Different count between begin braces and end braces."); br.addItem("Advice"); br.addElement("Make sure braces on your list-string."); br.addElement("For example:"); br.addElement(" (x): list:{ sea"); br.addElement(" (x): list:{ sea ; map:{ }"); br.addElement(" (o): list:{ sea ; land }"); br.addElement(" (o): list:{ sea ; map:{ land = piari } }"); br.addElement(""); br.addElement("While, list-string can escape control marks"); br.addElement("so pay attention to last char of value like this:"); br.addElement(" (x): list:{ sea = C:\\land\\piari\\} // last '}' escaped by escape char"); br.addElement(" (o): list:{ sea = C:\\land\\piari\\ } // space helps you at the case"); br.addItem("Brace Count"); br.addElement("begin: " + beginCount); br.addElement("end: " + endCount); setupMessageRemainderString(br); final String msg = br.buildExceptionMessage(); throw new DfMapParseFailureException(msg); } // #hope count non-escaped mark here without performance cost by jflute (2018/05/05) //protected int countControlMarkInRemainder(String controlMark) { // only used in exception so don't mind performance // final String remainder = dangrous_substringRemainderString(_currentRemainderIndex); // return Srl.count(remainder, controlMark); // expects tuning of Srl in the future //} // ----------------------------------------------------- // ValueEqual Index // ---------------- protected void assertMapStringEqualIndex(int valueEqualIndex, Map currentMap) { if (_remainderString == null) { // basically no way here, just in case final String notice = "The remainderString should not be null:"; throwDfMapValueEqualFailureException(notice, valueEqualIndex, currentMap); } if (valueEqualIndex < 0) { final String notice = "Not found the value-equal mark in the map-string."; throwDfMapValueEqualFailureException(notice, valueEqualIndex, currentMap); } // following checks are for internal mistake final int logicalLength = lengthRemainder(); if (logicalLength < (_remainderString.length() - valueEqualIndex)) { // deep (or unneeded?) check (written by younger jflute) final String notice = "The remainderString length should be greater than equalIndex:"; throwDfMapValueEqualFailureException(notice, valueEqualIndex, currentMap); } final String extractedMark = substringRemainderScope(valueEqualIndex, valueEqualIndex + _valueEqual.length()); if (!extractedMark.equals(_valueEqual)) { // different value-equal mark final String notice = "The remainderString should have value-equal mark at value-equal index:"; throwDfMapValueEqualFailureException(notice, valueEqualIndex, currentMap); } } protected void throwDfMapValueEqualFailureException(String notice, int valueEqualIndex, Map currentMap) { final ExceptionMessageBuilder br = new ExceptionMessageBuilder(); br.addNotice(notice); br.addItem("ValueEqual Index"); br.addElement(valueEqualIndex); br.addItem("Making Map"); br.addElement(currentMap); setupMessageRemainderString(br); final String msg = br.buildExceptionMessage(); throw new DfMapParseFailureException(msg); } // ----------------------------------------------------- // EndBrace Index // -------------- protected void assertMapStringEndBraceIndex(int endBraceIndex, Map makingMap) { if (endBraceIndex < 0) { final String notice = "Not found the end-brace in the map-string."; throwDfMapStringEndBraceFailureException(notice, endBraceIndex, makingMap); } // following checks are for internal mistake if (_remainderString == null) { final String notice = "The remainderString should not be null:"; throwDfMapStringEndBraceFailureException(notice, endBraceIndex, makingMap); } final int logicalLength = lengthRemainder(); if (logicalLength < (_remainderString.length() - endBraceIndex)) { final String notice = "The remainderString length should be greater than end-brace index:"; throwDfMapStringEndBraceFailureException(notice, endBraceIndex, makingMap); } substringRemainderScope(endBraceIndex, endBraceIndex + _endBrace.length()); final String extractedMark = substringRemainderScope(endBraceIndex, endBraceIndex + _endBrace.length()); if (!extractedMark.equals(_endBrace)) { final String notice = "The remainderString should have end-brace at the end-brace index:"; throwDfMapStringEndBraceFailureException(notice, endBraceIndex, makingMap); } } protected void throwDfMapStringEndBraceFailureException(String notice, int endBraceIndex, Map currentMap) { final ExceptionMessageBuilder br = new ExceptionMessageBuilder(); br.addNotice(notice); br.addItem("EndBrace Index"); br.addElement(endBraceIndex); br.addItem("Making Map"); br.addElement(currentMap); setupMessageRemainderString(br); final String msg = br.buildExceptionMessage(); throw new DfMapParseFailureException(msg); } protected void assertListStringEndBraceIndex(int endBraceIndex, List makingList) { if (endBraceIndex < 0) { final String notice = "Not found the end brace."; throwDfListStringEndBraceFailureException(notice, endBraceIndex, makingList); } // following checks are for internal mistake if (_remainderString == null) { final String notice = "The remainderString should not be null:"; throwDfListStringEndBraceFailureException(notice, endBraceIndex, makingList); } final int logicalLength = lengthRemainder(); if (logicalLength < (_remainderString.length() - endBraceIndex)) { final String notice = "The remainderString length should be greater than end-brace index:"; throwDfListStringEndBraceFailureException(notice, endBraceIndex, makingList); } final String extractedMark = substringRemainderScope(endBraceIndex, endBraceIndex + _endBrace.length()); if (!extractedMark.equals(_endBrace)) { final String notice = "The remainderString should have end-brace at the end-brace index:"; throwDfListStringEndBraceFailureException(notice, endBraceIndex, makingList); } } protected void throwDfListStringEndBraceFailureException(String notice, int endBraceIndex, List makingList) { final ExceptionMessageBuilder br = new ExceptionMessageBuilder(); br.addNotice(notice); br.addItem("EndBrace Index"); br.addElement(endBraceIndex); br.addItem("Making List"); br.addElement(makingList); setupMessageRemainderString(br); final String msg = br.buildExceptionMessage(); throw new DfMapParseFailureException(msg); } // ----------------------------------------------------- // Removing Prefix // --------------- protected void assertRemovingPrefix(String prefixString) { if (_remainderString == null) { final String notice = "The remainderString should not be null."; throwDfMapRemovingPrefixFailureException(notice, prefixString); } if (prefixString == null) { final String notice = "The prefixString should not be null."; throwDfMapRemovingPrefixFailureException(notice, prefixString); } if (!startsWithPrefix(prefixString)) { final String notice = "The remainderString shuold start with the prefixString."; throwDfMapRemovingPrefixFailureException(notice, prefixString); } } protected void throwDfMapRemovingPrefixFailureException(String notice, String prefixString) { final ExceptionMessageBuilder br = new ExceptionMessageBuilder(); br.addNotice(notice); br.addItem("Prefix String"); br.addElement(prefixString); setupMessageRemainderString(br); final String msg = br.buildExceptionMessage(); throw new DfMapParseFailureException(msg); } // ----------------------------------------------------- //  Last Assertion // -------------- protected void assertLastRemainderAsMap(Map generatedMap) { if (indexOfRemainderNonTrimmedChar() >= 0) { throwDfMapStringUnneededStringFoundException(generatedMap); } } protected void throwDfMapStringUnneededStringFoundException(Map generatedMap) { final ExceptionMessageBuilder br = new ExceptionMessageBuilder(); br.addNotice("The last remainderString as map should be empty string but ..."); br.addItem("Generated Map"); br.addElement(generatedMap); setupMessageRemainderString(br); final String msg = br.buildExceptionMessage(); throw new DfMapParseFailureException(msg); } protected void assertLastRemainderAsList(List generatedList) { if (indexOfRemainderNonTrimmedChar() >= 0) { throwDfListStringUnneededStringFoundException(generatedList); } } protected void throwDfListStringUnneededStringFoundException(List generatedList) { final ExceptionMessageBuilder br = new ExceptionMessageBuilder(); br.addNotice("The last remainderString as list should be empty string but ..."); br.addItem("Generated List"); br.addElement(generatedList); setupMessageRemainderString(br); final String msg = br.buildExceptionMessage(); throw new DfMapParseFailureException(msg); } // ----------------------------------------------------- // Message Pattern // --------------- protected void setupMessageRemainderString(ExceptionMessageBuilder br) { br.addItem("Remainder String"); br.addElement(_remainderString); br.addItem("Current Remainder Index"); if (_remainderString != null) { // checking just in case br.addElement(_currentRemainderIndex + " (max:" + _remainderString.length() + ")"); if (_currentRemainderIndex >= 0 && _remainderString.length() >= _currentRemainderIndex) { // more check br.addElement(cut(_remainderString.substring(_currentRemainderIndex), 30, "...")); } } else { br.addElement(_currentRemainderIndex); } br.addItem("Defined Control Mark"); br.addElement(_mapPrefix + " " + _listPrefix + " " + _beginBrace + " " + _endBrace + " " + _elementDelimiter + " " + _valueEqual); } protected String cut(String str, int length, String suffix) { assertObjectNotNull("str", str); return str.length() > length ? (str.substring(0, length) + (suffix != null ? suffix : "")) : str; } // =================================================================================== // Remainder String // ================ protected void beginRemainder(String mapString) { _remainderString = mapString; _currentRemainderIndex = 0; } protected void endRemainder() { _remainderString = null; _currentRemainderIndex = 0; } protected int indexOfRemainderNonTrimmedChar() { return generally_indexOfNonTrimmedChar(_remainderString, _currentRemainderIndex); } protected boolean isRemainderOver() { if (_remainderString.length() == _currentRemainderIndex) { // may be almost here return true; } if (indexOfRemainderNonTrimmedChar() < 0) { // with loop so also check just-over before this return true; } return false; } protected int lengthRemainder() { return _remainderString.length() - _currentRemainderIndex; } protected void ltrimRemainder() { final int index = indexOfRemainderNonTrimmedChar(); if (index > 0) { _currentRemainderIndex = index; } } protected String substringRemainderFront(int endIndex) { return _remainderString.substring(_currentRemainderIndex, endIndex); } protected String substringRemainderScope(int beginIndex, int endIndex) { return _remainderString.substring(beginIndex, endIndex); } // =================================================================================== // General Helper // ============== // no use of instance variable (can be static, but non-static for overriding) protected String generally_replace(String str, String fromStr, String toStr) { StringBuilder sb = null; // lazy load int pos = 0; int pos2 = 0; do { pos = str.indexOf(fromStr, pos2); if (pos2 == 0 && pos < 0) { // first loop and not found return str; // without creating StringBuilder } if (sb == null) { sb = new StringBuilder(); } if (pos == 0) { sb.append(toStr); pos2 = fromStr.length(); } else if (pos > 0) { sb.append(str.substring(pos2, pos)); sb.append(toStr); pos2 = pos + fromStr.length(); } else { // (pos < 0) second or after loop only sb.append(str.substring(pos2)); return sb.toString(); } } while (true); } protected boolean generally_endsWith(String str, String suffix) { int remainderIndex = generally_lastIndexOfNonTrimmedChar(str); for (int i = suffix.length() - 1; i >= 0; i--, remainderIndex--) { final char branceCh = suffix.charAt(i); final char remainderCh = str.charAt(remainderIndex); if (branceCh != remainderCh) { return false; } } return true; } protected int generally_indexOfNonTrimmedChar(String str, int offsetIndex) { for (int index = offsetIndex; index < str.length(); index++) { final char ch = str.charAt(index); if (ch > ' ') { return index; } } return -1; } protected int generally_lastIndexOfNonTrimmedChar(String str) { for (int index = str.length() - 1; index >= 0; index--) { final char ch = str.charAt(index); if (ch > ' ') { return index; } } return -1; } protected String generally_calculateNextIndent(String previousIndent, String currentIndent) { final StringBuilder sb = new StringBuilder(); sb.append(currentIndent); final int indentLength = currentIndent.length() - previousIndent.length(); for (int i = 0; i < indentLength; i++) { sb.append(" "); } return sb.toString(); } protected String ln() { return "\n"; } protected void assertStringNotNullAndNotTrimmedEmpty(String variableName, String value) { assertObjectNotNull("variableName", variableName); assertObjectNotNull(variableName, value); if (value.trim().length() == 0) { String msg = "The value should not be empty: variableName=" + variableName + " value=" + value; throw new IllegalArgumentException(msg); } } protected void assertObjectNotNull(String variableName, Object value) { if (variableName == null) { throw new IllegalArgumentException("The variableName should not be null."); } if (value == null) { throw new IllegalArgumentException("The argument '" + variableName + "' should not be null."); } } // =================================================================================== // Accessor // ======== public boolean isCheckDuplicateEntry() { return _checkDuplicateEntry; } public boolean isPrintOneLiner() { return _printOneLiner; } public boolean isWithoutDisplaySideSpace() { return _withoutDisplaySideSpace; } }