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

dev.ikm.tinkar.ext.binding.interpolation.Interpolator Maven / Gradle / Ivy

There is a newer version: 1.78.0
Show newest version
/*
 * Copyright © 2015 Integrated Knowledge Management ([email protected])
 *
 * 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 dev.ikm.tinkar.ext.binding.interpolation;

import dev.ikm.tinkar.ext.binding.interpolation.interpolate.EmptyInterpolate;
import dev.ikm.tinkar.ext.binding.interpolation.interpolate.ListInterpolate;
import dev.ikm.tinkar.ext.binding.interpolation.interpolate.SingleInterpolate;
import dev.ikm.tinkar.ext.binding.interpolation.interpolate.StreamInterpolate;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

/**
 * Interpolator provides a general purpose interpolation engine that can handle Empty (no input), single input, List
 * input, and Stream input. The main process for interpolation is to read the provided template top to bottom. When a
 * key, emphasized by ${key}, in the template is identified (top to bottom), the associated Interpolation function will
 * be performed and the result will be written to the provided Consumer (via the run method). This is an attempt to reduce
 * overhead and or memory footprint when interpolating among lots of records.
 */
public class Interpolator {

	private final String template;
	private final List interpolations;

	private Interpolator(String template, List interpolations) {
		this.template = template;
		this.interpolations = interpolations;
	}

	/**
	 * Perform the interpolation on the provide String template. Within this run there is one exception that can be
	 * thrown and will be thrown when the template contains a key that has no correlating interpolation action.
	 * @param output - A functional way to enable the user to handle the outputs of the string
	 *                                interpolation
	 */
	public void run(Consumer output) {

		//Sort to identify order to fire interpolations based on passed in template
		Map sortedInterpolations = sortInterpolations();

		//Iterate through sorted interpolations to write non-interpolated strings out and perform interpolations
		AtomicInteger previousEnd = new AtomicInteger(0);
		sortedInterpolations.forEach((keyIndex, value) -> {
            int endIndex = keyIndex + value.getKey().length() - 1;

            if (previousEnd.get() == 0) {
				if (keyIndex != 0) {
					output.accept(template.substring(0, keyIndex));
				}
                //interpolate
                value.interpolate(output);
            } else {
                output.accept(template.substring(previousEnd.get() + 1, keyIndex));
                value.interpolate(output);
            }

            previousEnd.set(endIndex);
        });

		//Add remaining string from the template
		output.accept(template.substring(previousEnd.get() + 1));
	}

	/**
	 * In order to handle possibly large interpolation use cases, sorting the provided Interpolation objects into the
	 * order in which they are used (top to bottom) within the template, helps with performance.
	 * @return Sorted Map with Key being based on starting index of template for interpolation and the value being the
	 * interpolation object itself.
	 */
	private Map sortInterpolations() {
		Map interpolationLocations = new LinkedHashMap<>();
		interpolations.forEach(interpolation -> {
			interpolationLocations.put(template.indexOf(interpolation.getKey()), interpolation);
		});

		List> entries = new ArrayList<>(interpolationLocations.entrySet());
		entries.sort(Comparator.comparingInt(Map.Entry::getKey));

		Map sortedInterpolations = new LinkedHashMap<>();
		for (Map.Entry entry : entries) {
			sortedInterpolations.put(entry.getKey(), entry.getValue());
		}
		return sortedInterpolations;
	}

	/**
	 * Builder class that implements the builder pattern for configuring the overarching Interpolator class.
	 */
	public static class Builder {

		private final List interpolations;
		private final String template;

		public Builder(String template) {
			this.interpolations = new ArrayList<>();
			this.template = template;
		}

		public Builder empty(String key, EmptyInterpolate emptyInterpolate) {
			interpolations.add(new EmptyInterpolation(key, emptyInterpolate));
			return this;
		}

		public  Builder single(String key, T input, SingleInterpolate singleInterpolate) {
			interpolations.add(new SingleInterpolation<>(key, input, singleInterpolate));
			return this;
		}

		public  Builder list(String key, List input, ListInterpolate listInterpolate) {
			interpolations.add(new ListInterpolation<>(key, input, listInterpolate));
			return this;
		}

		public  Builder stream(String key, Stream input, StreamInterpolate streamInterpolate) {
			interpolations.add(new StreamInterpolation<>(key, input, streamInterpolate));
			return this;
		}

		public Interpolator build() {
			//Identify any missing Interpolations
			checkForMissingInterpolations();
			return new Interpolator(template, interpolations);
		}

		/**
		 * This method processed the interpolation template and compares all keys identified with the keys that are bound
		 * to a specific interpolation. If there ever is any keys found in the template that are orphaned from an
		 * interpolation the entire Interpolator will throw a RunTimeException
		 */
		private void checkForMissingInterpolations() {
			//Regex to find all ${} based keys within the template
			ArrayList foundKeys = new ArrayList<>();
			Matcher matcher = Pattern
					.compile("\\$\\{[^{|}]*\\}")
					.matcher(template);
			while (matcher.find()) {
				foundKeys.add(matcher.group());
			}

			//For each discovered key compare to the list of known keys associated with interpolations. If no matches are
			//found then throw Run Time Exception
			foundKeys.forEach(foundKey -> {
				long keyMatches = interpolations.stream()
						.map(Interpolation::getKey)
						.filter(foundKey::equals).count();

				if (keyMatches == 0) {
					throw new RuntimeException("Interpolation '" + foundKey + "' not found");
				}
			});
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy