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

org.shredzone.commons.view.manager.ViewPattern Maven / Gradle / Ivy

The newest version!
/*
 * Shredzone Commons
 *
 * Copyright (C) 2012 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Library 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 General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this program.  If not, see .
 */

package org.shredzone.commons.view.manager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;

import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.concurrent.Immutable;

import org.shredzone.commons.view.PathContext;
import org.shredzone.commons.view.Signature;
import org.shredzone.commons.view.annotation.View;
import org.shredzone.commons.view.util.PathUtils;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

/**
 * A view pattern is a URL pattern for a view. It is used to detect which view is used for
 * handling a HTTP request, and what parameters are used.
 * 

* {@link ViewPattern ViewPatterns} are immutable. * * @author Richard "Shred" Körber */ @ParametersAreNonnullByDefault @Immutable public class ViewPattern implements Comparable { private static final Pattern PATH_PART = Pattern.compile("\\$\\{([^\\}]+)\\}"); private final String pattern; private final ViewInvoker invoker; private final Signature signature; private final Pattern regEx; private final List expression; private final List parameter; private final int weight; private final String qualifier; /** * Instantiates a new view pattern. * * @param anno * {@link View} annotation * @param invoker * {@link ViewInvoker} for rendering this view */ public ViewPattern(View anno, ViewInvoker invoker) { this.invoker = invoker; this.pattern = anno.pattern(); if (anno.qualifier() != null && !anno.qualifier().isEmpty()) { this.qualifier = anno.qualifier(); } else { this.qualifier = null; } String[] sig = anno.signature(); if (sig != null && sig.length > 0) { this.signature = new Signature(sig); } else { this.signature = null; } List expList = new ArrayList<>(); List paramList = new ArrayList<>(); StringBuilder pb = new StringBuilder(); compilePattern(this.pattern, pb, expList, paramList); this.regEx = Pattern.compile(pb.toString()); this.expression = Collections.unmodifiableList(expList); this.parameter = Collections.unmodifiableList(paramList); this.weight = computeWeight(this.pattern); } /** * Gets the signature stored in this {@link ViewPattern}. * * @return {@link Signature} */ public Signature getSignature() { return signature; } /** * Gets the {@link ViewInvoker} to be used for rendering. * * @return {@link ViewInvoker} */ public ViewInvoker getInvoker() { return invoker; } /** * Gets the weight of this {@link ViewPattern}. If more than one {@link ViewPattern} * matches the requested URL, the one with the highest weight is taken. * * @return weight */ public int getWeight() { return weight; } /** * Gets the view's URL pattern. * * @return URL pattern */ public String getPattern() { return pattern; } /** * Gets a regular expression {@link Pattern} to match a URL against this * {@link ViewPattern}. This regular expression can be used to quickly find view * candidates for a request URL. * * @return regular expression {@link Pattern}. */ public @Nonnull Pattern getRegEx() { return regEx; } /** * Returns an {@link Expression} for each placeholder in the pattern. The expressions * are used for building an URL to this view. * * @return List of {@link Expression} */ public @Nonnull List getExpression() { return expression; } /** * Returns a list of parameter strings for each placeholder in the pattern. * * @return List of parameters */ public @Nonnull List getParameters() { return parameter; } /** * Returns the qualifier of this pattern. * * @return Qualifier of this pattern, or {@code null} for the default qualifier. */ public String getQualifier() { return qualifier; } /** * Matches the requested URL against this {@link ViewPattern}. * * @param path * the requested URL * @return {@code true} if this {@link ViewPattern} matches the given URL, and thus is * a candidate for rendering */ public boolean matches(String path) { return regEx.matcher(path).matches(); } /** * Resolves a requested URL path. For each placeholder in the view pattern, the * placeholder name and its value in the URL path is returned in a map. * * @param path * the requested URL to be resolved * @return Map containing the placeholder names and its values */ public Map resolve(String path) { Matcher m = regEx.matcher(path); if (!m.matches()) { return null; } if (m.groupCount() != parameter.size()) { throw new IllegalStateException("regex group count " + m.groupCount() + " does not match parameter count " + parameter.size()); } // TODO: only use decode when #encode() was used return IntStream.range(0, parameter.size()).collect( HashMap::new, (map, ix) -> map.put(parameter.get(ix), PathUtils.decode(m.group(ix + 1))), Map::putAll ); } /** * Evaluates the given {@link EvaluationContext} and builds an URL to the appropriate * view. * * @param context * {@link EvaluationContext} to be used * @param data * {@link PathContext} containing all data required for building the URL * @return URL that was built, or {@code null} if the {@link PathContext} did not * contain all necessary data for building the URL */ public String evaluate(EvaluationContext context, PathContext data) { StringBuilder sb = new StringBuilder(); for (Expression expr : expression) { String value = expr.getValue(context, data, String.class); if (value == null) { // A part resolved to null, so this ViewPattern is unable // to build a path from the given PathData. return null; } sb.append(value); } // Remove ugly double slashes int pos; while ((pos = sb.indexOf("//")) >= 0) { sb.deleteCharAt(pos); } return sb.toString(); } /** * Compiles a view pattern. Generates a parameter list, a list of expressions for * building URLs to this view, and a regular expression for matching URLs against this * view pattern. * * @param pstr * the view pattern * @param pattern * {@link StringBuilder} to assemble the regular expression in * @param expList * List of {@link Expression} to assemble expressions in * @param paramList * List to assemble parameters in */ private void compilePattern(String pstr, StringBuilder pattern, List expList, List paramList) { ExpressionParser parser = new SpelExpressionParser(); int previous = 0; Matcher m = PATH_PART.matcher(pstr); while (m.find()) { String fixedPart = pstr.substring(previous, m.start()); if (fixedPart.indexOf('\'') >= 0) { throw new IllegalArgumentException("path parameters must not contain \"'\""); } String expressionPart = m.group(1); pattern.append(Pattern.quote(fixedPart)); pattern.append("([^/]*)"); paramList.add(expressionPart); expList.add(parser.parseExpression('\'' + fixedPart + '\'')); expList.add(parser.parseExpression(expressionPart)); previous = m.end(); } String postPart = pstr.substring(previous); pattern.append(Pattern.quote(postPart)); expList.add(parser.parseExpression('\'' + postPart + '\'')); } /** * Computes the weight of the pattern. The weight is computed by a score where every * path delimiter '/' counts 10, constant character counts 5 and every path parameter * counts 1. * * @param pstr * view pattern to weight * @return weight of this pattern */ private int computeWeight(String pstr) { int count = 0; int pos = 0; while (pos < pstr.length()) { char ch = pstr.charAt(pos); if (ch == '/') { count += 10; } else if (ch == '$' && pos + 1 < pstr.length() && pstr.charAt(pos + 1) == '{') { int end = pstr.indexOf('}', pos); if (end >= 0) { pos = end; count += 1; } } else { count += 5; } pos++; } return count; } @Override public int compareTo(ViewPattern o) { return o.getWeight() - getWeight(); } @Override public boolean equals(Object obj) { if (obj == null || (!(obj instanceof ViewPattern))) { return false; } return compareTo((ViewPattern) obj) == 0; } @Override public int hashCode() { return Integer.hashCode(getWeight()); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy