/*
* Copyright 2012 Jason Miller
*
* 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 jj.http.server.uri;
import io.netty.handler.codec.http.QueryStringDecoder;
import java.nio.file.Paths;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jj.util.StringUtils;
/**
* Simple immutable carrier of a match against a URI, potentially preceded by
* a hex SHA1 hash. determines if the URI is versioned - either a hash is
* the first path particle, or an explicit version is either on the filename
* or a path preceding it. Examples of URIs that are considered versioned are
* <ul>
* <li>/jquery-2.0.2.min.js</li>
* <li>/jquery-2.0.2.js</li>
* <li>/fancybox-2.1.5/fancybox.pack.js</li>
* <li>/fancybox/fancybox-2.1.5.pack.js</li>
* </ul>
*
* specifically, some series of numbers separated by dots, optionally followed by
* one of the words 'alpha', 'beta', or 'pre' optionally separated by a dot or a dash,
* optionally followed by one of the words 'pack' or 'min' separated by a dot or a
* dash.
* @author jason
*
*/
public class URIMatch {
private static final Pattern URI_PATTERN = Pattern.compile("^/(?:([\\da-f]{40})/)?(.*?)(?:\\.([^.]+))?$");
private static final Pattern VERSION_PATTERN = Pattern.compile("-\\d+(?:[.]\\d+)*(?:[.-]?(?:alpha|beta|pre))?(?:(?:[.-](?:min|pack))?$|/)");
/** the complete URI including any leading and trailing / */
public final String uri;
/** the resource SHA1 hash, if present */
public final String sha1;
/**
* the name portion of the uri. this is the portion after any
* sha1 hash, without leading or trailing slashes,
* and also not including any extensions
*/
public final String name;
/**
* the extension, as commonly understood - the portion following the last dot in the path,
* not including the dot or any trailing slash
*/
public final String extension;
/**
* the path from the public root, analogous to a resource name,
* although route matching rules may virtualize this.
* composed of name + '.' + extension.
* does not include leading or trailing slashes
*/
public final String path;
/**
* true if considered to be versioned as detailed in the class comment
*/
public final boolean versioned;
public URIMatch(final String uri) {
assert uri != null : "uri must not be null";
QueryStringDecoder qsd = new QueryStringDecoder(uri.startsWith("/") ? uri : "/" + uri);
this.uri = "/".equals(uri) ? uri : (Paths.get(qsd.path()).normalize().toString() + (qsd.path().endsWith("/") ? "/" : ""));
Matcher matcher = URI_PATTERN.matcher(this.uri);
String shaCandidate = null;
String nameCandidate = null;
String extensionCandidate = null;
if (matcher.matches()) {
shaCandidate = matcher.group(1);
nameCandidate = matcher.group(2);
if (nameCandidate != null && nameCandidate.endsWith("/")) {
nameCandidate = nameCandidate.substring(0, nameCandidate.length() - 1);
}
extensionCandidate = matcher.group(3);
if (extensionCandidate != null && extensionCandidate.endsWith("/")) {
extensionCandidate = extensionCandidate.substring(0, extensionCandidate.length() - 1);
}
}
sha1 = shaCandidate;
name = nameCandidate;
extension = extensionCandidate;
path = name == null ? null : name + (extensionCandidate == null ? "" : "." + extensionCandidate);
versioned = sha1 != null || (nameCandidate != null && VERSION_PATTERN.matcher(nameCandidate).find());
}
@Override
public boolean equals(Object other) {
return other != null && other instanceof URIMatch && StringUtils.equals(((URIMatch)other).uri, uri);
}
@Override
public String toString() {
return getClass().getSimpleName() + "{ " +
"uri: " + uri +
", sha1: " + sha1 +
", name: " + name +
", extension: " + extension +
", path: " + path +
", versioned: " + versioned + " }";
}
}