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

net.officefloor.web.route.ParameterWebRouteNode Maven / Gradle / Ivy

There is a newer version: 3.40.0
Show newest version
/*
 * OfficeFloor - http://www.officefloor.net
 * Copyright (C) 2005-2018 Daniel Sagenschneider
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 */
package net.officefloor.web.route;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.officefloor.frame.api.build.Indexed;
import net.officefloor.frame.api.function.ManagedFunctionContext;
import net.officefloor.server.http.HttpMethod;
import net.officefloor.server.http.ServerHttpConnection;
import net.officefloor.web.build.HttpValueLocation;
import net.officefloor.web.state.HttpArgument;

/**
 * {@link WebRouteNode} for a path parameter.
 * 
 * @author Daniel Sagenschneider
 */
public class ParameterWebRouteNode implements WebRouteNode {

	/**
	 * Terminating characters for the parameter.
	 */
	private final char[] terminatingCharacters;

	/**
	 * {@link Map} of terminating {@link Character} to
	 * {@link StaticWebRouteNode} instances.
	 */
	private final Map characterToChildren;

	/**
	 * {@link LeafWebRouteNode}.
	 */
	private LeafWebRouteNode leafNode;

	/**
	 * Instantiate.
	 * 
	 * @param nodes
	 *            Further {@link StaticWebRouteNode} instances.
	 * @param leafNode
	 *            {@link LeafWebRouteNode} should the parameter finish the path.
	 *            May be null if parameter is always embedded in
	 *            middle of the path.
	 */
	public ParameterWebRouteNode(StaticWebRouteNode[] nodes, LeafWebRouteNode leafNode) {
		this.leafNode = leafNode;

		// Create the terminating characters and map to children
		Set initialCharacters = new HashSet<>();
		Map> characterNodesMap = new HashMap<>();
		for (StaticWebRouteNode node : nodes) {
			char initialCharacter = node.getInitialCharacter();

			// Add mapping of child
			List characterNodes = characterNodesMap.get(initialCharacter);
			if (characterNodes == null) {
				characterNodes = new LinkedList<>();
				characterNodesMap.put(initialCharacter, characterNodes);
			}
			characterNodes.add(node);

			// Keep track of all initial characters
			initialCharacters.add(initialCharacter);
		}

		// Determine if able to complete path with parameter
		if (this.leafNode != null) {
			for (char pathEndCharacter : new char[] { '?', '#' }) {

				// Add mapping for path completion
				characterNodesMap.put(pathEndCharacter, Arrays.asList(this.leafNode));

				// Include the path end character
				initialCharacters.add(pathEndCharacter);
			}
		}

		// Create the array of terminating characters (faster matching)
		this.terminatingCharacters = new char[initialCharacters.size()];
		int index = 0;
		for (Character character : initialCharacters) {
			this.terminatingCharacters[index++] = character;
		}

		// Create the map of children
		// (size for ASCII characters to index avoiding collisions)
		this.characterToChildren = new HashMap<>(128);
		for (Character initialCharacter : characterNodesMap.keySet()) {
			List children = characterNodesMap.get(initialCharacter);

			// Load the initial character choice
			WebRouteNode[] childrenArray = children.toArray(new WebRouteNode[children.size()]);
			this.characterToChildren.put(initialCharacter, childrenArray);
		}
	}

	/**
	 * Includes the {@link HttpArgument}.
	 * 
	 * @param headPathArgument
	 *            Head {@link HttpArgument}.of arguments.
	 * @param argumentValue
	 *            {@link HttpArgument} value.
	 * @return Head {@link HttpArgument} of arguments including the argument.
	 */
	private HttpArgument includePathArgument(HttpArgument headPathArgument, String argumentValue) {
		HttpArgument argumentIncluded = new HttpArgument(null, argumentValue, HttpValueLocation.PATH);
		argumentIncluded.next = headPathArgument;
		return argumentIncluded;
	}

	/*
	 * ======================== WebRouteNode ==================
	 */

	@Override
	public boolean handle(HttpMethod method, String path, int index, HttpArgument headPathArgument,
			ServerHttpConnection connection, ManagedFunctionContext context) {

		// Capture starting position of parameter value
		final int parameterStart = index;

		// Loop until match, or end of path
		while (index < path.length()) {

			// Obtain the current character
			char character = path.charAt(index);

			// Determine if terminating character
			for (int c = 0; c < this.terminatingCharacters.length; c++) {
				char terminatingCharacter = this.terminatingCharacters[c];
				if (character == terminatingCharacter) {

					// Obtain parameter value
					String parameterValue = path.substring(parameterStart, index);

					// Attempt to terminate parameter
					WebRouteNode[] nodes = this.characterToChildren.get(character);
					for (int n = 0; n < nodes.length; n++) {
						WebRouteNode node = nodes[n];

						// Determine if handle route
						if (node.handle(method, path, index, this.includePathArgument(headPathArgument, parameterValue),
								connection, context)) {
							return true; // parameter terminated (route handled)
						}
					}
				}
			}

			// Increment for next character
			index++;
		}

		// As here, reached end of path
		if (this.leafNode != null) {

			// Ignore trailing '/' characters
			int parameterEnd = index - 1; // last index
			while (path.charAt(parameterEnd) == '/') {
				parameterEnd--;
			}

			// Obtain the parameter value (+1 as exclusive)
			String parameterValue = path.substring(parameterStart, parameterEnd + 1);

			// Handle by leaf
			return this.leafNode.handle(method, path, index, this.includePathArgument(headPathArgument, parameterValue),
					connection, context);
		}

		// As here, no match
		return false;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy