diff --git a/datastructures-interpolation/LICENSE.txt b/datastructures-interpolation/LICENSE.txt new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/datastructures-interpolation/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. \ No newline at end of file diff --git a/datastructures-interpolation/NOTICE.txt b/datastructures-interpolation/NOTICE.txt new file mode 100644 index 0000000..b4cbfa0 --- /dev/null +++ b/datastructures-interpolation/NOTICE.txt @@ -0,0 +1,9 @@ +This work is taken from + +https://github.com/lantunes/interpolatd + +as of Sep 29, 2023 + +by Luis Antunes + +Apache License 2.0 diff --git a/datastructures-interpolation/src/main/java/module-info.java b/datastructures-interpolation/src/main/java/module-info.java new file mode 100644 index 0000000..0145e61 --- /dev/null +++ b/datastructures-interpolation/src/main/java/module-info.java @@ -0,0 +1,3 @@ +module org.xbib.datastructures.interpolation { + exports org.xbib.datastructures.interpolation; +} diff --git a/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/EnclosureClosingHandler.java b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/EnclosureClosingHandler.java new file mode 100644 index 0000000..fdb9948 --- /dev/null +++ b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/EnclosureClosingHandler.java @@ -0,0 +1,4 @@ +package org.xbib.datastructures.interpolation; + +public interface EnclosureClosingHandler extends SubstitutionHandler { +} diff --git a/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/EnclosureClosingHandlerImpl.java b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/EnclosureClosingHandlerImpl.java new file mode 100644 index 0000000..a9ebbee --- /dev/null +++ b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/EnclosureClosingHandlerImpl.java @@ -0,0 +1,33 @@ +package org.xbib.datastructures.interpolation; + +import java.util.regex.Pattern; + +public class EnclosureClosingHandlerImpl extends SubstitutionHandlerImpl + implements EnclosureClosingHandler { + + private final String opening; + private final String closing; + + private final Pattern pattern; + + public EnclosureClosingHandlerImpl(String opening, String closing, String characterClass) { + this.opening = opening; + this.closing = closing; + String quotedOpening = Pattern.quote(opening); + String quotedClosing = Pattern.quote(closing); + if (characterClass == null) { + characterClass = "[^" + quotedOpening + quotedClosing + "\\s]+"; + } + this.pattern = Pattern.compile("(" + quotedOpening + characterClass + quotedClosing + ")"); + } + + @Override + protected Pattern getPattern() { + return pattern; + } + + @Override + protected String getCaptured(String found) { + return found.substring(opening.length(), found.length() - closing.length()); + } +} diff --git a/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/EnclosureOpeningHandler.java b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/EnclosureOpeningHandler.java new file mode 100644 index 0000000..a7e31cf --- /dev/null +++ b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/EnclosureOpeningHandler.java @@ -0,0 +1,6 @@ +package org.xbib.datastructures.interpolation; + +public interface EnclosureOpeningHandler { + + EnclosureClosingHandler and(String closing); +} diff --git a/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/EnclosureOpeningHandlerImpl.java b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/EnclosureOpeningHandlerImpl.java new file mode 100644 index 0000000..60a229f --- /dev/null +++ b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/EnclosureOpeningHandlerImpl.java @@ -0,0 +1,27 @@ +package org.xbib.datastructures.interpolation; + +public class EnclosureOpeningHandlerImpl implements EnclosureOpeningHandler { + + private final String opening; + + private final String characterClass; + + private EnclosureClosingHandlerImpl closingHandler; + + public EnclosureOpeningHandlerImpl(String opening, String characterClass) { + this.opening = opening; + this.characterClass = characterClass; + } + + @Override + public EnclosureClosingHandler and(String closing) { + EnclosureClosingHandlerImpl closingHandler = + new EnclosureClosingHandlerImpl(opening, closing, characterClass); + this.closingHandler = closingHandler; + return closingHandler; + } + + public EnclosureClosingHandlerImpl getEnclosureClosingHandler() { + return closingHandler; + } +} diff --git a/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/EscapeHandler.java b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/EscapeHandler.java new file mode 100644 index 0000000..1dc4f3f --- /dev/null +++ b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/EscapeHandler.java @@ -0,0 +1,27 @@ +package org.xbib.datastructures.interpolation; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class EscapeHandler implements Interpolating { + + private final String escape; + private final Pattern pattern; + + public EscapeHandler(String escape) { + this.escape = escape; + this.pattern = Pattern.compile("(" + Pattern.quote(escape) + ")"); + } + + @Override + public List interpolate(String toInterpolate, T arg) { + List substitutions = new ArrayList(); + Matcher m = pattern.matcher(toInterpolate); + while (m.find()) { + substitutions.add(new Substitution(escape, "", m.start(), m.end(), true)); + } + return substitutions; + } +} diff --git a/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/Interpolating.java b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/Interpolating.java new file mode 100644 index 0000000..f0918b2 --- /dev/null +++ b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/Interpolating.java @@ -0,0 +1,8 @@ +package org.xbib.datastructures.interpolation; + +import java.util.List; + +public interface Interpolating { + + List interpolate(String toInterpolate, T arg); +} diff --git a/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/InterpolationHandler.java b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/InterpolationHandler.java new file mode 100644 index 0000000..9186805 --- /dev/null +++ b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/InterpolationHandler.java @@ -0,0 +1,8 @@ +package org.xbib.datastructures.interpolation; + +public interface InterpolationHandler { + + PrefixHandler prefixedBy(String prefix); + + EnclosureOpeningHandler enclosedBy(String opening); +} diff --git a/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/InterpolationHandlerImpl.java b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/InterpolationHandlerImpl.java new file mode 100644 index 0000000..87ed36b --- /dev/null +++ b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/InterpolationHandlerImpl.java @@ -0,0 +1,49 @@ +package org.xbib.datastructures.interpolation; + +import java.util.ArrayList; +import java.util.List; + +public class InterpolationHandlerImpl implements InterpolationHandler, Interpolating { + + private final String characterClass; + + private PrefixHandlerImpl prefixHandler; + + private EnclosureOpeningHandlerImpl enclosureOpeningHandler; + + public InterpolationHandlerImpl() { + this(null); + } + + public InterpolationHandlerImpl(String characterClass) { + this.characterClass = characterClass; + } + + @Override + public PrefixHandler prefixedBy(String prefix) { + PrefixHandlerImpl prefixHandler = new PrefixHandlerImpl(prefix, characterClass); + this.prefixHandler = prefixHandler; + return prefixHandler; + } + + @Override + public EnclosureOpeningHandler enclosedBy(String opening) { + EnclosureOpeningHandlerImpl enclosureOpeningHandler = + new EnclosureOpeningHandlerImpl(opening, characterClass); + this.enclosureOpeningHandler = enclosureOpeningHandler; + return enclosureOpeningHandler; + } + + @Override + public List interpolate(String toInterpolate, T arg) { + List substitutions = new ArrayList(); + if (prefixHandler != null) { + substitutions.addAll(prefixHandler.interpolate(toInterpolate, arg)); + + } else if (enclosureOpeningHandler != null) { + substitutions.addAll(enclosureOpeningHandler.getEnclosureClosingHandler() + .interpolate(toInterpolate, arg)); + } + return substitutions; + } +} diff --git a/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/Interpolator.java b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/Interpolator.java new file mode 100644 index 0000000..0d7fc58 --- /dev/null +++ b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/Interpolator.java @@ -0,0 +1,89 @@ +package org.xbib.datastructures.interpolation; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Interpolator { + + private final List> interpolating = new ArrayList<>(); + + public Interpolator() { + } + + public InterpolationHandler when() { + InterpolationHandlerImpl handler = new InterpolationHandlerImpl(); + interpolating.add(handler); + return handler; + } + + public InterpolationHandler when(String characterClass) { + InterpolationHandlerImpl handler = new InterpolationHandlerImpl(characterClass); + interpolating.add(handler); + return handler; + } + + public void escapeWith(String escape) { + interpolating.add(new EscapeHandler(escape)); + } + + public String interpolate(String toInterpolate, T arg) { + List substitutions = new ArrayList(); + for (Interpolating handler : interpolating) { + substitutions.addAll(handler.interpolate(toInterpolate, arg)); + } + Collections.sort(substitutions); + StringBuilder sb = new StringBuilder(toInterpolate); + int diff = 0; + int lastEnd = 0; + Substitution lastEscape = null; + for (int i = 0; i < substitutions.size(); i++) { + Substitution sub = substitutions.get(i); + if (sub.start() < lastEnd) { + continue; + } + if (sub.isEscape()) { + if (lastEscape != null && sub.isAfter(lastEscape)) { + continue; + } + if (isActualEscape(sub, substitutions, i)) { + lastEscape = sub; + } else { + continue; + } + } else if (lastEscape != null && sub.isAfter(lastEscape)) { + lastEnd = sub.end(); + continue; + } + if (sub.value() == null) { + continue; + } + sb.replace(sub.start() - diff, sub.end() - diff, sub.value()); + diff += sub.found().length() - sub.value().length(); + lastEnd = sub.end(); + } + return sb.toString(); + } + + private boolean isActualEscape(Substitution esc, List substitutions, int index) { + if (!hasNext(substitutions, index)) { + return false; + } + Substitution nextSub = getNext(substitutions, index); + if (!nextSub.isAfter(esc)) { + return false; + } + if (!nextSub.isEscape()) { + return true; + } + return isActualEscape(nextSub, substitutions, ++index); + } + + private boolean hasNext(List substitutions, int currentIndex) { + return (currentIndex + 1) < substitutions.size(); + } + + private Substitution getNext(List substitutions, int currentIndex) { + return substitutions.get(currentIndex + 1); + } +} diff --git a/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/PrefixHandler.java b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/PrefixHandler.java new file mode 100644 index 0000000..6f9c219 --- /dev/null +++ b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/PrefixHandler.java @@ -0,0 +1,4 @@ +package org.xbib.datastructures.interpolation; + +public interface PrefixHandler extends SubstitutionHandler { +} diff --git a/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/PrefixHandlerImpl.java b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/PrefixHandlerImpl.java new file mode 100644 index 0000000..e396668 --- /dev/null +++ b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/PrefixHandlerImpl.java @@ -0,0 +1,29 @@ +package org.xbib.datastructures.interpolation; + +import java.util.regex.Pattern; + +public class PrefixHandlerImpl extends SubstitutionHandlerImpl implements PrefixHandler { + + private final Pattern pattern; + + private final String prefix; + + public PrefixHandlerImpl(String prefix, String characterClass) { + this.prefix = prefix; + String quotedPrefix = Pattern.quote(prefix); + if (characterClass == null) { + characterClass = "[^" + quotedPrefix + "\\s]+"; + } + this.pattern = Pattern.compile("(" + quotedPrefix + characterClass + ")"); + } + + @Override + protected Pattern getPattern() { + return pattern; + } + + @Override + protected String getCaptured(String found) { + return found.substring(prefix.length()); + } +} diff --git a/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/Substitution.java b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/Substitution.java new file mode 100644 index 0000000..6cc7b09 --- /dev/null +++ b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/Substitution.java @@ -0,0 +1,51 @@ +package org.xbib.datastructures.interpolation; + +public class Substitution implements Comparable { + + private final String found; + private final String value; + private final int start; + private final int end; + private final boolean escape; + + public Substitution(String found, String value, int start, int end) { + this(found, value, start, end, false); + } + + public Substitution(String found, String value, int start, int end, boolean escape) { + this.found = found; + this.value = value; + this.start = start; + this.end = end; + this.escape = escape; + } + + public String found() { + return found; + } + + public String value() { + return value; + } + + public int start() { + return start; + } + + public int end() { + return end; + } + + public boolean isEscape() { + return escape; + } + + public boolean isAfter(Substitution that) { + return this.start() == that.end(); + } + + @Override + public int compareTo(Substitution that) { + return Integer.compare(this.start, that.start); + } +} diff --git a/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/SubstitutionHandler.java b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/SubstitutionHandler.java new file mode 100644 index 0000000..60624ec --- /dev/null +++ b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/SubstitutionHandler.java @@ -0,0 +1,6 @@ +package org.xbib.datastructures.interpolation; + +public interface SubstitutionHandler { + + void handleWith(Substitutor substitutor); +} diff --git a/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/SubstitutionHandlerImpl.java b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/SubstitutionHandlerImpl.java new file mode 100644 index 0000000..b807af6 --- /dev/null +++ b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/SubstitutionHandlerImpl.java @@ -0,0 +1,37 @@ +package org.xbib.datastructures.interpolation; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class SubstitutionHandlerImpl implements SubstitutionHandler, Interpolating { + + protected Substitutor substitutor; + + public SubstitutionHandlerImpl() { + } + + public void handleWith(Substitutor substitutor) { + this.substitutor = substitutor; + } + + protected abstract Pattern getPattern(); + + protected abstract String getCaptured(String found); + + @Override + public List interpolate(String toInterpolate, T arg) { + List substitutions = new ArrayList(); + if (substitutor != null) { + Matcher m = getPattern().matcher(toInterpolate); + while (m.find()) { + String found = m.group(1); + String captured = getCaptured(found); + String substitution = substitutor.substitute(captured, arg); + substitutions.add(new Substitution(found, substitution, m.start(), m.end())); + } + } + return substitutions; + } +} diff --git a/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/Substitutor.java b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/Substitutor.java new file mode 100644 index 0000000..1566405 --- /dev/null +++ b/datastructures-interpolation/src/main/java/org/xbib/datastructures/interpolation/Substitutor.java @@ -0,0 +1,6 @@ +package org.xbib.datastructures.interpolation; + +public interface Substitutor { + + String substitute(String captured, T arg); +} diff --git a/datastructures-interpolation/src/test/java/org/xbib/datastructures/interpolation/test/TestInterpolator.java b/datastructures-interpolation/src/test/java/org/xbib/datastructures/interpolation/test/TestInterpolator.java new file mode 100644 index 0000000..d1912c1 --- /dev/null +++ b/datastructures-interpolation/src/test/java/org/xbib/datastructures/interpolation/test/TestInterpolator.java @@ -0,0 +1,796 @@ +package org.xbib.datastructures.interpolation.test; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.xbib.datastructures.interpolation.Interpolator; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestInterpolator { + + private Interpolator interpolator; + + private ValueMap map; + + @BeforeEach + public void beforeEachTest() { + interpolator = new Interpolator<>(); + interpolator.when("[a-zA-Z0-9_]+").prefixedBy(":") + .handleWith((captured, map) -> map.getForPrefixed(captured)); + interpolator.when("[0-9]+").enclosedBy("*[").and("]") + .handleWith((captured, map) -> map.getForPrefixedBracketEnclosed(captured)); + interpolator.when().enclosedBy("{").and("}") + .handleWith((captured, map) -> map.getForBraceEnclosed(captured)); + interpolator.when().enclosedBy("[").and("]") + .handleWith((captured, map) -> map.getForBracketEnclosed(captured)); + interpolator.escapeWith("^"); + map = new ValueMap(); + } + + @Test + public void testStringReturnedUnmodifiedWhenNoSubstitutionsArePresent() { + map.put("foo", new Value().forPrefixed("bar")); + assertEquals("Hello World!", + interpolator.interpolate("Hello World!", map)); + } + + @Test + public void testSinglePrefixedSubstitued() { + map.put("name", new Value().forPrefixed("Tim")); + assertEquals("Hello Tim", + interpolator.interpolate("Hello :name", map)); + } + + @Test + public void testSinglePrefixedSubstituedInPresenceOfOtherStandalonePrefixes() { + map.put("name", new Value().forPrefixed("Tim")); + assertEquals("Hello : Tim :", + interpolator.interpolate("Hello : :name :", map)); + } + + @Test + public void testSinglePrefixedDoublyPrefixedIsSubstitued() { + map.put("name", new Value().forPrefixed("Tim")); + assertEquals("Hello :Tim", + interpolator.interpolate("Hello ::name", map)); + } + + @Test + public void testSinglePrefixedEnclosedByPrefixesIsSubstitued() { + map.put("name", new Value().forPrefixed("Tim")); + assertEquals("Hello :Tim:", + interpolator.interpolate("Hello ::name:", map)); + } + + @Test + public void testSinglePrefixedSubstituedInPresenceOfOtherPrefixedTerms() { + map.put("name", new Value().forPrefixed("Tim")); + assertEquals("Hello :someTerm Tim", + interpolator.interpolate("Hello :someTerm :name", map)); + } + + @Test + public void testSinglePrefixedSubstituedWithPrefixContainingValue() { + map.put("name", new Value().forPrefixed(":Tim")); + assertEquals("Hello :Tim", + interpolator.interpolate("Hello :name", map)); + } + + @Test + public void testSimilarPrefixedSubstitued() { + map.put("name", new Value().forPrefixed("Tim")); + map.put("name1", new Value().forPrefixed("John")); + assertEquals("Hello Tim John", + interpolator.interpolate("Hello :name :name1", map)); + } + + @Test + public void testSimilarPrefixedNotSubstitued() { + map.put("name", new Value().forPrefixed("Tim")); + assertEquals("Hello :name1", + interpolator.interpolate("Hello :name1", map)); + } + + @Test + public void testPrefixedSubstitutedWithNameOfAnotherPrefix() { + map.put("name", new Value().forPrefixed(":id")); + map.put("id", new Value().forPrefixed("1")); + assertEquals("Hello :id 1", + interpolator.interpolate("Hello :name :id", map)); + } + + @Test + public void testMultiplePrefixedSubstitued() { + map.put("firstName", new Value().forPrefixed("John")); + map.put("lastName", new Value().forPrefixed("Doe")); + assertEquals("Hello John Doe", + interpolator.interpolate("Hello :firstName :lastName", map)); + } + + @Test + public void testMultiplePrefixedSubstituedInPresenceOfOtherPrefixed() { + map.put("firstName", new Value().forPrefixed("John")); + map.put("lastName", new Value().forPrefixed("Doe")); + assertEquals("Hello : John : Doe :", + interpolator.interpolate("Hello : :firstName : :lastName :", map)); + } + + @Test + public void testMultiplePrefixedSubstituedWithPrefixContainingValues() { + map.put("firstName", new Value().forPrefixed(":John")); + map.put("lastName", new Value().forPrefixed(":Doe")); + assertEquals("Hello :John :Doe", + interpolator.interpolate("Hello :firstName :lastName", map)); + } + + @Test + public void testSingleBraceEnclosedSubstitued() { + map.put("name", new Value().forBraceEnclosed("Tim")); + assertEquals("Hello Tim", + interpolator.interpolate("Hello {name}", map)); + } + + @Test + public void testBraceEnclosedWithPrefixedNameNotSubstituted() { + map.put("name", new Value().forPrefixed("John") + .forBraceEnclosed(":name")); + assertEquals("Hello :name", + interpolator.interpolate("Hello {name}", map)); + } + + @Test + public void testPrefixedWithBraceEnclosedNameNotSubstituted() { + map.put("name", new Value().forPrefixed("{name}") + .forBraceEnclosed("Tim")); + assertEquals("Hello {name}", + interpolator.interpolate("Hello :name", map)); + } + + @Test + public void testSingleBraceEnclosedSubstituedInPresenceOfOtherBraces() { + map.put("name", new Value().forBraceEnclosed("Tim")); + assertEquals("Hello { Tim } {", + interpolator.interpolate("Hello { {name} } {", map)); + } + + @Test + public void testSingleBraceEnclosedSubstituedInPresenceOfOtherBraces_NoSpaces() { + map.put("name", new Value().forBraceEnclosed("Tim")); + assertEquals("Hello {Tim} {", + interpolator.interpolate("Hello {{name}} {", map)); + } + + @Test + public void testSingleBraceEnclosedSubstituedInPresenceOfOtherBraceEnclosedTerms() { + map.put("name", new Value().forBraceEnclosed("Tim")); + assertEquals("Hello {there} Tim", + interpolator.interpolate("Hello {there} {name}", map)); + } + + @Test + public void testSingleBraceEnclosedSubstituedWithBraceEnclosedValue() { + map.put("name", new Value().forBraceEnclosed("{Tim}")); + assertEquals("Hello {Tim}", + interpolator.interpolate("Hello {name}", map)); + } + + @Test + public void testMultipleBraceEnclosedSubstitued() { + map.put("firstName", new Value().forBraceEnclosed("John")); + map.put("lastName", new Value().forBraceEnclosed("Doe")); + assertEquals("Hello John Doe", + interpolator.interpolate("Hello {firstName} {lastName}", map)); + } + + @Test + public void testMultipleBraceEnclosedSubstituedInPresenceOfOtherBraces() { + map.put("firstName", new Value().forBraceEnclosed("John")); + map.put("lastName", new Value().forBraceEnclosed("Doe")); + assertEquals("Hello { John } { Doe } {", + interpolator.interpolate("Hello { {firstName} } { {lastName} } {", map)); + } + + @Test + public void testMultipleBraceEnclosedSubstituedInPresenceOfOtherBraces_NoSpaces() { + map.put("firstName", new Value().forBraceEnclosed("John")); + map.put("lastName", new Value().forBraceEnclosed("Doe")); + assertEquals("Hello {John} {Doe} {", + interpolator.interpolate("Hello {{firstName}} {{lastName}} {", map)); + } + + @Test + public void testMultipleBraceEnclosedSubstituedInPresenceOfOtherBraceEnclosedTerms() { + map.put("firstName", new Value().forBraceEnclosed("John")); + map.put("lastName", new Value().forBraceEnclosed("Doe")); + assertEquals("Hello {there} John Doe", + interpolator.interpolate("Hello {there} {firstName} {lastName}", map)); + } + + @Test + public void testMultipleBraceEnclosedSubstituedWithBraceEnclosedValue() { + map.put("firstName", new Value().forBraceEnclosed("{John}")); + map.put("lastName", new Value().forBraceEnclosed("{Doe}")); + assertEquals("Hello {John} {Doe}", + interpolator.interpolate("Hello {firstName} {lastName}", map)); + } + + @Test + public void testPrefixedAndBraceEnclosed_PrefixedValuesComeFirst() { + map.put("salutation", new Value().forPrefixed("Mr.")); + map.put("firstName", new Value().forBraceEnclosed("John")); + map.put("lastName", new Value().forBraceEnclosed("Doe")); + assertEquals("Hello Mr. John Doe", + interpolator.interpolate("Hello :salutation {firstName} {lastName}", map)); + } + + @Test + public void testPrefixedAndBraceEnclosed_BraceEnclosedComeFirst() { + map.put("salutation", new Value().forBraceEnclosed("Mr.")); + map.put("firstName", new Value().forPrefixed("John")); + map.put("lastName", new Value().forPrefixed("Doe")); + assertEquals("Hello Mr. John Doe", + interpolator.interpolate("Hello {salutation} :firstName :lastName", map)); + } + + @Test + public void testColonPrefixedBraceEnclosed() { + map.put("{name}", new Value().forPrefixed("John")); + map.put("name", new Value().forBraceEnclosed("Tim")); + assertEquals("Hello :Tim", + interpolator.interpolate("Hello :{name}", map)); + } + + @Test + public void testBraceEnclosedPrefixedValueSubstitutesBraceEnclosed_OneOccurrence() { + map.put("name", new Value().forPrefixed("John")); + map.put(":name", new Value().forBraceEnclosed("Tim")); + assertEquals("Hello Tim", + interpolator.interpolate("Hello {:name}", map)); + } + + @Test + public void testBraceEnclosedPrefixedValueSubstitutesBraceEnclosed_TwoOccurrences() { + map.put("first", new Value().forPrefixed("John")); + map.put("last", new Value().forPrefixed("Doe")); + map.put(":first", new Value().forBraceEnclosed("Tom")); + map.put(":last", new Value().forBraceEnclosed("Jerry")); + assertEquals("Hello Tom Jerry", + interpolator.interpolate("Hello {:first} {:last}", map)); + } + + @Test + public void testSquareBracketUnknownValuesIgnored() throws Exception { + map.put("request.body", new Value().forBracketEnclosed("Hello World!")); + assertEquals("Message: [some.value]", + interpolator.interpolate("Message: [some.value]", map)); + } + + @Test + public void testSquareBracketSubstituted() throws Exception { + map.put("request.body", new Value().forBracketEnclosed("Hello World!")); + assertEquals("Message: Hello World!", + interpolator.interpolate("Message: [request.body]", map)); + } + + @Test + public void testSquareBracketSubstituedInPresenceOfOtherSquareBrackets() throws Exception { + map.put("request.body", new Value().forBracketEnclosed("Hello World!")); + assertEquals("Message: [ Hello World! ] [", + interpolator.interpolate("Message: [ [request.body] ] [", map)); + } + + @Test + public void testSquareBracketSubstituedInPresenceOfOtherSquareBrackets_NoSpaces() throws Exception { + map.put("request.body", new Value().forBracketEnclosed("Hello World!")); + assertEquals("Message: [Hello World!] [", + interpolator.interpolate("Message: [[request.body]] [", map)); + } + + @Test + public void testSquareBracketSubstituedInPresenceOfOtherSquareBracketEnclosedTerms() throws Exception { + map.put("request.body", new Value().forBracketEnclosed("Tim")); + assertEquals("Hello [there] Tim", + interpolator.interpolate("Hello [there] [request.body]", map)); + } + + @Test + public void testSquareBracketSubstituedWithSquareBracketEnclosedValue() throws Exception { + map.put("request.body", new Value().forBracketEnclosed("[Tim]")); + assertEquals("Hello [Tim]", + interpolator.interpolate("Hello [request.body]", map)); + } + + @Test + public void testPrefixedAndBraceEnclosedAndSquareBracketUsedTogether() throws Exception { + map.put("salutation", new Value().forBraceEnclosed("Mr.")); + map.put("name", new Value().forPrefixed("John")); + map.put("request.body", new Value().forBracketEnclosed("Doe")); + assertEquals("Hello Mr. John Doe", + interpolator.interpolate("Hello {salutation} :name [request.body]", map)); + } + + @Test + public void testSinglePrefixedSquareBracketSubstitued() { + map.put("0", new Value().forPrefixedBracketEnclosed("Tim")); + assertEquals("Hello Tim", + interpolator.interpolate("Hello *[0]", map)); + } + + @Test + public void testSinglePrefixedSquareBracketSubstituedMultipleTimes() { + map.put("0", new Value().forPrefixedBracketEnclosed("Tim")); + assertEquals("Hello Tim Tim", + interpolator.interpolate("Hello *[0] *[0]", map)); + } + + @Test + public void testSinglePrefixedSquareBracketNotSubstituedWithIncorrectIndex() { + map.put("0", new Value().forPrefixedBracketEnclosed("Tim")); + assertEquals("Hello *[1]", + interpolator.interpolate("Hello *[1]", map)); + } + + @Test + public void testSinglePrefixedSquareBracketNotSubstituedIfFormattedIncorrectly() { + map.put("0", new Value().forPrefixedBracketEnclosed("Tim")); + assertEquals("Hello *[ 0]", + interpolator.interpolate("Hello *[ 0]", map)); + } + + @Test + public void testSinglePrefixedSquareBracketSubstituedWithSplatContainingValue() { + map.put("0", new Value().forPrefixedBracketEnclosed("*[0]")); + assertEquals("Hello *[0]", + interpolator.interpolate("Hello *[0]", map)); + } + + @Test + public void testSinglePrefixedSquareBracketSubstituedWithEmptyValue() { + map.put("0", new Value().forPrefixedBracketEnclosed("")); + assertEquals("Hello ", + interpolator.interpolate("Hello *[0]", map)); + } + + @Test + public void testPrefixedSquareBracketSubstituedWithDoubleDigitIndex() { + map.put("10", new Value().forPrefixedBracketEnclosed("k")); + assertEquals("Hello k", + interpolator.interpolate("Hello *[10]", map)); + } + + @Test + public void testTwoPrefixedSquareBracketsSubstitued() { + map.put("0", new Value().forPrefixedBracketEnclosed("John")); + map.put("1", new Value().forPrefixedBracketEnclosed("Doe")); + assertEquals("Hello John Doe", + interpolator.interpolate("Hello *[0] *[1]", map)); + } + + @Test + public void testOnlySecondOfTwoPrefixedSquareBracketsSubstitued() { + map.put("0", new Value().forPrefixedBracketEnclosed("John")); + map.put("1", new Value().forPrefixedBracketEnclosed("Doe")); + assertEquals("Hello Doe", + interpolator.interpolate("Hello *[1]", map)); + } + + @Test + public void testSubstitutionIncreasesOverallLengthOfString() { + map.put("0", new Value().forPrefixedBracketEnclosed("hello")); + map.put("1", new Value().forPrefixedBracketEnclosed("there")); + assertEquals("hello there", + interpolator.interpolate("*[0] *[1]", map)); + } + + @Test + public void testSubstitutionDecreasesOverallLengthOfString() { + map.put("0", new Value().forPrefixedBracketEnclosed("a")); + map.put("1", new Value().forPrefixedBracketEnclosed("b")); + assertEquals("a b", + interpolator.interpolate("*[0] *[1]", map)); + } + + @Test + public void testEscapeWithSinglePrefixedWhichExists() { + map.put("name", new Value().forPrefixed("John")); + assertEquals("Hello :name", + interpolator.interpolate("Hello ^:name", map)); + } + + @Test + public void testEscapeWithSinglePrefixedWhichDoesNotExist() { + assertEquals("Hello :name", + interpolator.interpolate("Hello ^:name", map)); + } + + @Test + public void testEscapeWithMultiplePrefixed_OneEscaped() { + map.put("name1", new Value().forPrefixed("John")); + map.put("name2", new Value().forPrefixed("Doe")); + assertEquals("Hello :name1 Doe", + interpolator.interpolate("Hello ^:name1 :name2", map)); + } + + @Test + public void testEscapeWithMultiplePrefixed_MultipleEscaped() { + map.put("name1", new Value().forPrefixed("John")); + map.put("name2", new Value().forPrefixed("Doe")); + assertEquals("Hello :name1 :name2", + interpolator.interpolate("Hello ^:name1 ^:name2", map)); + } + + @Test + public void testEscapeWithMultiplePrefixed_MultipleEscapedConcatenated() { + map.put("name1", new Value().forPrefixed("John")); + map.put("name2", new Value().forPrefixed("Doe")); + assertEquals("Hello :name1:name2", + interpolator.interpolate("Hello ^:name1^:name2", map)); + } + + @Test + public void testDoubleEscapeWithPrefixed() { + map.put("name", new Value().forPrefixed("John")); + assertEquals("Hello ^John", + interpolator.interpolate("Hello ^^:name", map)); + } + + @Test + public void testTripleEscapeWithPrefixed() { + map.put("name", new Value().forPrefixed("John")); + assertEquals("Hello ^:name", + interpolator.interpolate("Hello ^^^:name", map)); + } + + @Test + public void testQuadrupleEscapeWithPrefixed() { + map.put("name", new Value().forPrefixed("John")); + assertEquals("Hello ^^John", + interpolator.interpolate("Hello ^^^^:name", map)); + } + + @Test + public void testEscapeOnItsOwn() { + assertEquals("Hello ^ there", + interpolator.interpolate("Hello ^ there", map)); + } + + @Test + public void testDoubleEscapeOnItsOwn() { + assertEquals("Hello ^^ there", + interpolator.interpolate("Hello ^^ there", map)); + } + + @Test + public void testTripleEscapeOnItsOwn() { + assertEquals("Hello ^^^ there", + interpolator.interpolate("Hello ^^^ there", map)); + } + + @Test + public void testQuadrupleEscapeOnItsOwn() { + assertEquals("Hello ^^^^ there", + interpolator.interpolate("Hello ^^^^ there", map)); + } + + @Test + public void testEscapeOnItsOwnWithPrefixed() { + map.put("name", new Value().forPrefixed("John")); + assertEquals("Hello ^ John", + interpolator.interpolate("Hello ^ :name", map)); + } + + @Test + public void testDoubleEscapeOnItsOwnWithPrefixed() { + map.put("name", new Value().forPrefixed("John")); + assertEquals("Hello ^^ John", + interpolator.interpolate("Hello ^^ :name", map)); + } + + @Test + public void testTripleEscapeOnItsOwnWithPrefixed() { + map.put("name", new Value().forPrefixed("John")); + assertEquals("Hello ^^^ John", + interpolator.interpolate("Hello ^^^ :name", map)); + } + + @Test + public void testQuadrupleEscapeOnItsOwnWithPrefixed() { + map.put("name", new Value().forPrefixed("John")); + assertEquals("Hello ^^^^ John", + interpolator.interpolate("Hello ^^^^ :name", map)); + } + + @Test + public void testEscapeNextToNonInterpolatedValue() { + assertEquals("Hello ^there", + interpolator.interpolate("Hello ^there", map)); + } + + @Test + public void testDoubleEscapeNextToNonInterpolatedValue() { + assertEquals("Hello ^^there", + interpolator.interpolate("Hello ^^there", map)); + } + + @Test + public void testTripleEscapeNextToNonInterpolatedValue() { + assertEquals("Hello ^^^there", + interpolator.interpolate("Hello ^^^there", map)); + } + + @Test + public void testQuadrupleEscapeNextToNonInterpolatedValue() { + assertEquals("Hello ^^^^there", + interpolator.interpolate("Hello ^^^^there", map)); + } + + @Test + public void testEscapeAfterPrefixed() { + map.put("name", new Value().forPrefixed("John")); + assertEquals("Hello John^", + interpolator.interpolate("Hello :name^", map)); + } + + @Test + public void testDoubleEscapeAfterPrefixed() { + map.put("name", new Value().forPrefixed("John")); + assertEquals("Hello John^^", + interpolator.interpolate("Hello :name^^", map)); + } + + @Test + public void testTripleEscapeAfterPrefixed() { + map.put("name", new Value().forPrefixed("John")); + assertEquals("Hello John^^^", + interpolator.interpolate("Hello :name^^^", map)); + } + + @Test + public void testQuadrupleEscapeAfterPrefixed() { + map.put("name", new Value().forPrefixed("John")); + assertEquals("Hello John^^^^", + interpolator.interpolate("Hello :name^^^^", map)); + } + + @Test + public void testEscapeAfterNonInterpolatedValue() { + assertEquals("Hello there^", + interpolator.interpolate("Hello there^", map)); + } + + @Test + public void testDoubleEscapeAfterNonInterpolatedValue() { + assertEquals("Hello there^^", + interpolator.interpolate("Hello there^^", map)); + } + + @Test + public void testTripleEscapeAfterNonInterpolatedValue() { + assertEquals("Hello there^^^", + interpolator.interpolate("Hello there^^^", map)); + } + + @Test + public void testQuadrupleEscapeAfterNonInterpolatedValue() { + assertEquals("Hello there^^^^", + interpolator.interpolate("Hello there^^^^", map)); + } + + @Test + public void testEscapeIgnoredInsideBraceEnclosed() { + map.put("^name", new Value().forBraceEnclosed("Tim")); + assertEquals("Hello Tim", + interpolator.interpolate("Hello {^name}", map)); + } + + @Test + public void testEscapeWithSingleBraceEnclosedWhichExists() { + map.put("name", new Value().forBraceEnclosed("Tim")); + assertEquals("Hello {name}", + interpolator.interpolate("Hello ^{name}", map)); + } + + @Test + public void testEscapeWithSingleBraceEnclosedWhichDoesNotExist() { + assertEquals("Hello {name}", + interpolator.interpolate("Hello ^{name}", map)); + } + + @Test + public void testEscapeWithMultipleBraceEnclosed_OneEscaped() { + map.put("firstName", new Value().forBraceEnclosed("John")); + map.put("lastName", new Value().forBraceEnclosed("Doe")); + assertEquals("Hello {firstName} Doe", + interpolator.interpolate("Hello ^{firstName} {lastName}", map)); + } + + @Test + public void testEscapeWithMultipleBraceEnclosed_MultipleEscaped() { + map.put("firstName", new Value().forBraceEnclosed("John")); + map.put("lastName", new Value().forBraceEnclosed("Doe")); + assertEquals("Hello {firstName} {lastName}", + interpolator.interpolate("Hello ^{firstName} ^{lastName}", map)); + } + + @Test + public void testEscapeWithSingleSquareBracketWhichExists() throws Exception { + map.put("request?name", new Value().forBracketEnclosed("Tim")); + assertEquals("Hello [request?name]", + interpolator.interpolate("Hello ^[request?name]", map)); + } + + @Test + public void testEscapeWithSingleSquareBracketWhichDoesNotExist() throws Exception { + assertEquals("Hello [request?name]", + interpolator.interpolate("Hello ^[request?name]", map)); + } + + @Test + public void testEscapeWithMultipleSquareBrackets_OneEscaped() throws Exception { + map.put("request?name1", new Value().forBracketEnclosed("John")); + map.put("request?name2", new Value().forBracketEnclosed("Doe")); + assertEquals("Hello [request?name1] Doe", + interpolator.interpolate("Hello ^[request?name1] [request?name2]", map)); + } + + @Test + public void testEscapeWithMultipleSquareBrackets_MultipleEscaped() throws Exception { + map.put("request?name1", new Value().forBracketEnclosed("John")); + map.put("request?name2", new Value().forBracketEnclosed("Doe")); + assertEquals("Hello [request?name1] [request?name2]", + interpolator.interpolate("Hello ^[request?name1] ^[request?name2]", map)); + } + + @Test + public void testEscapeWithSinglePrefixedSquareBracketWhichExists() { + map.put("0", new Value().forPrefixedBracketEnclosed("Tim")); + assertEquals("Hello *[0]", + interpolator.interpolate("Hello ^*[0]", map)); + } + + @Test + public void testEscapeWithSinglePrefixedSquareBracketWhichDoesNotExist() { + assertEquals("Hello *[0]", + interpolator.interpolate("Hello ^*[0]", map)); + } + + @Test + public void testEscapeWithMultiplePrefixedSquareBrackets_OneEscaped() { + map.put("0", new Value().forPrefixedBracketEnclosed("John")); + map.put("1", new Value().forPrefixedBracketEnclosed("Doe")); + assertEquals("Hello *[0] Doe", + interpolator.interpolate("Hello ^*[0] *[1]", map)); + } + + @Test + public void testEscapeWithMultiplePrefixedSquareBrackets_MultipleEscaped() { + map.put("0", new Value().forPrefixedBracketEnclosed("John")); + map.put("1", new Value().forPrefixedBracketEnclosed("Doe")); + assertEquals("Hello *[0] *[1]", + interpolator.interpolate("Hello ^*[0] ^*[1]", map)); + } + + @Test + public void testEscapeBraceEnclosedWithPrefixedName() { + map.put("name", new Value().forPrefixed("John") + .forBraceEnclosed(":name")); + assertEquals("Hello {name}", + interpolator.interpolate("Hello ^{name}", map)); + } + + @Test + public void testEscapePrefixedWithBraceEnclosedName() { + map.put("name", new Value().forPrefixed("{name}") + .forBraceEnclosed("Tim")); + assertEquals("Hello :name", + interpolator.interpolate("Hello ^:name", map)); + } + + @Test + public void testPrefixedBracketEnclosedSubstitutedForAny() { + map.put("0", new Value().forAny("John")); + assertEquals("Hello John", + interpolator.interpolate("Hello *[0]", map)); + } + + @Test + public void testEscapeWithSinglePrefixedSquareBracketWhichExistsForAny() { + map.put("0", new Value().forAny("Tim")); + assertEquals("Hello *[0]", + interpolator.interpolate("Hello ^*[0]", map)); + } + + private static class ValueMap { + + private final Map map = new HashMap<>(); + + public void put(String key, Value value) { + map.put(key, value); + } + + public String getForPrefixed(String captured) { + Value val = map.get(captured); + if (hasForAny(val)) return val.getForAny(); + return val != null ? val.getForPrefixed() : null; + } + + public String getForBraceEnclosed(String captured) { + Value val = map.get(captured); + if (hasForAny(val)) return val.getForAny(); + return val != null ? val.getForBraceEnclosed() : null; + } + + public String getForBracketEnclosed(String captured) { + Value val = map.get(captured); + if (hasForAny(val)) return val.getForAny(); + return val != null ? val.getForBracketEnclosed() : null; + } + + public String getForPrefixedBracketEnclosed(String captured) { + Value val = map.get(captured); + if (hasForAny(val)) return val.getForAny(); + return val != null ? val.getForPrefixedBracketEnclosed() : null; + } + + private boolean hasForAny(Value val) { + return val != null && val.getForAny() != null; + } + } + + private static class Value { + + private String forAny; + private String forPrefixed; + private String forBraceEnclosed; + private String forBracketEnclosed; + private String forPrefixedBracketEnclosed; + + public Value forAny(String val) { + forAny = val; + return this; + } + + public Value forPrefixed(String val) { + forPrefixed = val; + return this; + } + + public Value forBraceEnclosed(String val) { + forBraceEnclosed = val; + return this; + } + + public Value forBracketEnclosed(String val) { + forBracketEnclosed = val; + return this; + } + + public Value forPrefixedBracketEnclosed(String val) { + forPrefixedBracketEnclosed = val; + return this; + } + + public String getForAny() { + return forAny; + } + + public String getForPrefixed() { + return forPrefixed; + } + + public String getForBraceEnclosed() { + return forBraceEnclosed; + } + + public String getForBracketEnclosed() { + return forBracketEnclosed; + } + + public String getForPrefixedBracketEnclosed() { + return forPrefixedBracketEnclosed; + } + } +} diff --git a/gradle.properties b/gradle.properties index ff9a6fb..dca8530 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group = org.xbib name = datastructures -version = 2.3.1 +version = 2.4.0 org.gradle.warning.mode = all diff --git a/settings.gradle b/settings.gradle index 09b5bbb..f2dbc34 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,8 +16,8 @@ dependencyResolutionManagement { versionCatalogs { libs { version('gradle', '8.1.1') - version('junit', '5.9.3') - version('jackson', '2.14.3') + version('junit', '5.10.0') + version('jackson', '2.15.2') library('junit-jupiter-api', 'org.junit.jupiter', 'junit-jupiter-api').versionRef('junit') library('junit-jupiter-params', 'org.junit.jupiter', 'junit-jupiter-params').versionRef('junit') library('junit-jupiter-engine', 'org.junit.jupiter', 'junit-jupiter-engine').versionRef('junit') @@ -43,6 +43,7 @@ dependencyResolutionManagement { } include 'datastructures-api' +include 'datastructures-interpolation' include 'datastructures-io' include 'datastructures-charset' include 'datastructures-common'