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

com.github.ldeitos.validators.util.PathDecoder Maven / Gradle / Ivy

package com.github.ldeitos.validators.util;

import static java.lang.String.format;
import static org.apache.commons.lang3.StringUtils.split;
import static org.apache.commons.lang3.math.NumberUtils.toInt;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.github.ldeitos.exception.InvalidPathConfigurationException;

/**
 * Decoder to build a {@link Path} instance based in a string representation of
 * a path, like "address[home].country.name", for example.
 *
 * @author Leandro Deitos
 *
 * @since 0.9.0
 */
public class PathDecoder {
	private static final Pattern ITERABLE_PATH_PATTERN = Pattern.compile("^(\\w+)(\\[(.+)\\]|(\\((.+)\\)))");

	private static final Pattern INVALID_ITERABLE_PATH_PATTERN = Pattern.compile("\\w+\\[\\w+\\.\\w*\\]");

	private static final Pattern NUMERIC = Pattern.compile("\\d+");

	private static final Pattern INDEXED_ITERABLE = Pattern.compile("(\\(.+\\))");

	private static final int PATH_NAME = 1;

	private static final int ITERABLE_KEY = 3;

	private static final int ITERABLE_INDEX = 5;

	/**
	 * Decodes a string representation of path, like
	 * "address[home].country.name", to {@link Path} object.
	 *
	 * @param fullPath
	 *            String representation of a path.
	 * @return {@link Path} instance decoded from string representation of path.
	 *
	 * @since 0.9.0
	 */
	public static Path decodePath(String fullPath) {
		validateKeyFormat(fullPath);
		String[] paths = split(fullPath, ".");
		PathBuilder pathBuilder = new PathBuilder();

		for (String pathPart : paths) {
			pathBuilder.addPath(decodeSimplePath(pathPart));
		}

		return pathBuilder.getPath();
	}

	private static Path decodeSimplePath(String fullPath) {
		Path path = null;
		Matcher matcherIterable = ITERABLE_PATH_PATTERN.matcher(fullPath);

		if (matcherIterable.matches()) {
			String pathName = matcherIterable.group(PATH_NAME);
			Matcher matcherIndexed = INDEXED_ITERABLE.matcher(fullPath);

			if (matcherIndexed.find()) {
				String index = matcherIterable.group(ITERABLE_INDEX);
				validateIndexFormat(index);
				path = new Path(pathName, toInt(index));
			} else {
				String key = matcherIterable.group(ITERABLE_KEY);
				path = new Path(pathName, key);
			}
		} else {
			path = new Path(fullPath);
		}

		return path;
	}

	private static void validateIndexFormat(String iterable) {
		if (!NUMERIC.matcher(iterable).matches()) {
			InvalidPathConfigurationException.throwNew(format("[%s] - Invalid format to indexed "
				+ "iterable path, only numbers are allowed.", iterable));
		}
	}

	private static void validateKeyFormat(String fullPath) {
		Matcher matcher = INVALID_ITERABLE_PATH_PATTERN.matcher(fullPath);
		if (matcher.find()) {

			InvalidPathConfigurationException.throwNew(format("Invalid format to mapped iterable path: "
			    + "-> %s <- Maybe a \".\" used in key map value? (i.e: [not.use.dot])", fullPath));
		}
	}

	/**
	 * 

* Move iterable key or index to next path in path chain and identify the * path as "shifted". Necessary because in fluent API to build constraint * the key or index is applied in target leaf, not in original map or list. *

*

* If input {@link Path} is marked as "shifted", nothing is done. *

* * @param path * Root {@link Path} of path chain. */ static void shiftIterables(Path path) { if (path == null || path.isShifted()) { return; } Path nextPath = path.getNext(); shiftIterables(nextPath); if (path.isIterable() && nextPath != null) { nextPath.setKey(path.getKey()); nextPath.setIndex(path.getIndex()); path.setKey(null); path.setIndex(null); } path.setShifted(true); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy