/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.catalina.mapper;
/** * Mapper, which implements the servlet API mapping rules (which are derived from the HTTP rules). * * @author Remy Maucherat
*/ publicfinalclass Mapper {
/** * Mapping from Context object to Context version to support RequestDispatcher mappings.
*/ privatefinal Map<Context,ContextVersion> contextObjectToContextVersionMap = new ConcurrentHashMap<>();
// --------------------------------------------------------- Public Methods
/** * Add a new host to the mapper. * * @param name Virtual host name * @param aliases Alias names for the virtual host * @param host Host object
*/ publicsynchronizedvoid addHost(String name, String[] aliases, Host host) {
name = renameWildcardHost(name);
MappedHost[] newHosts = new MappedHost[hosts.length + 1];
MappedHost newHost = new MappedHost(name, host); if (insertMap(hosts, newHosts, newHost)) {
hosts = newHosts; if (newHost.name.equals(defaultHostName)) {
defaultHost = newHost;
} if (log.isDebugEnabled()) {
log.debug(sm.getString("mapper.addHost.success", name));
}
} else {
MappedHost duplicate = hosts[find(hosts, name)]; if (duplicate.object == host) { // The host is already registered in the mapper. // E.g. it might have been added by addContextVersion() if (log.isDebugEnabled()) {
log.debug(sm.getString("mapper.addHost.sameHost", name));
}
newHost = duplicate;
} else {
log.error(sm.getString("mapper.duplicateHost", name, duplicate.getRealHostName())); // Do not add aliases, as removeHost(hostName) won't be able to // remove them return;
}
}
List<MappedHost> newAliases = new ArrayList<>(aliases.length); for (String alias : aliases) {
alias = renameWildcardHost(alias);
MappedHost newAlias = new MappedHost(alias, newHost); if (addHostAliasImpl(newAlias)) {
newAliases.add(newAlias);
}
}
newHost.addAliases(newAliases);
}
/** * Remove a host from the mapper. * * @param name Virtual host name
*/ publicsynchronizedvoid removeHost(String name) {
name = renameWildcardHost(name); // Find and remove the old host
MappedHost host = exactFind(hosts, name); if (host == null || host.isAlias()) { return;
}
MappedHost[] newHosts = hosts.clone(); // Remove real host and all its aliases int j = 0; for (int i = 0; i < newHosts.length; i++) { if (newHosts[i].getRealHost() != host) {
newHosts[j++] = newHosts[i];
}
}
hosts = Arrays.copyOf(newHosts, j);
}
/** * Add an alias to an existing host. * * @param name The name of the host * @param alias The alias to add
*/ publicsynchronizedvoid addHostAlias(String name, String alias) {
MappedHost realHost = exactFind(hosts, name); if (realHost == null) { // Should not be adding an alias for a host that doesn't exist but // just in case... return;
}
alias = renameWildcardHost(alias);
MappedHost newAlias = new MappedHost(alias, realHost); if (addHostAliasImpl(newAlias)) {
realHost.addAlias(newAlias);
}
}
privatesynchronizedboolean addHostAliasImpl(MappedHost newAlias) {
MappedHost[] newHosts = new MappedHost[hosts.length + 1]; if (insertMap(hosts, newHosts, newAlias)) {
hosts = newHosts; if (newAlias.name.equals(defaultHostName)) {
defaultHost = newAlias;
} if (log.isDebugEnabled()) {
log.debug(sm.getString("mapper.addHostAlias.success", newAlias.name, newAlias.getRealHostName()));
} returntrue;
} else {
MappedHost duplicate = hosts[find(hosts, newAlias.name)]; if (duplicate.getRealHost() == newAlias.getRealHost()) { // A duplicate Alias for the same Host. // A harmless redundancy. E.g. // <Host name="localhost"><Alias>localhost</Alias></Host> if (log.isDebugEnabled()) {
log.debug(sm.getString("mapper.addHostAlias.sameHost", newAlias.name, newAlias.getRealHostName()));
} returnfalse;
}
log.error(sm.getString("mapper.duplicateHostAlias", newAlias.name, newAlias.getRealHostName(),
duplicate.getRealHostName())); returnfalse;
}
}
/** * Remove a host alias * * @param alias The alias to remove
*/ publicsynchronizedvoid removeHostAlias(String alias) {
alias = renameWildcardHost(alias); // Find and remove the alias
MappedHost hostMapping = exactFind(hosts, alias); if (hostMapping == null || !hostMapping.isAlias()) { return;
}
MappedHost[] newHosts = new MappedHost[hosts.length - 1]; if (removeMap(hosts, newHosts, alias)) {
hosts = newHosts;
hostMapping.getRealHost().removeAlias(hostMapping);
}
}
/** * Replace {@link MappedHost#contextList} field in <code>realHost</code> and all its aliases with a new value.
*/ privatevoid updateContextList(MappedHost realHost, ContextList newContextList) {
realHost.contextList = newContextList; for (MappedHost alias : realHost.getAliases()) {
alias.contextList = newContextList;
}
}
/** * Add a new Context to an existing Host. * * @param hostName Virtual host name this context belongs to * @param host Host object * @param path Context path * @param version Context version * @param context Context object * @param welcomeResources Welcome files defined for this context * @param resources Static resources of the context * @param wrappers Information on wrapper mappings
*/ publicvoid addContextVersion(String hostName, Host host, String path, String version, Context context,
String[] welcomeResources, WebResourceRoot resources, Collection<WrapperMappingInfo> wrappers) {
hostName = renameWildcardHost(hostName);
MappedHost mappedHost = exactFind(hosts, hostName); if (mappedHost == null) {
addHost(hostName, new String[0], host);
mappedHost = exactFind(hosts, hostName); if (mappedHost == null) {
log.error(sm.getString("mapper.addContext.noHost", hostName)); return;
}
} if (mappedHost.isAlias()) {
log.error(sm.getString("mapper.addContext.hostIsAlias", hostName)); return;
} int slashCount = slashCount(path); synchronized (mappedHost) {
ContextVersion newContextVersion = new ContextVersion(version, path, slashCount, context, resources, welcomeResources); if (wrappers != null) {
addWrappers(newContextVersion, wrappers);
}
ContextList contextList = mappedHost.contextList;
MappedContext mappedContext = exactFind(contextList.contexts, path); if (mappedContext == null) {
mappedContext = new MappedContext(path, newContextVersion);
ContextList newContextList = contextList.addContext(mappedContext, slashCount); if (newContextList != null) {
updateContextList(mappedHost, newContextList);
contextObjectToContextVersionMap.put(context, newContextVersion);
}
} else {
ContextVersion[] contextVersions = mappedContext.versions;
ContextVersion[] newContextVersions = new ContextVersion[contextVersions.length + 1]; if (insertMap(contextVersions, newContextVersions, newContextVersion)) {
mappedContext.versions = newContextVersions;
contextObjectToContextVersionMap.put(context, newContextVersion);
} else { // Re-registration after Context.reload() // Replace ContextVersion with the new one int pos = find(contextVersions, version); if (pos >= 0 && contextVersions[pos].name.equals(version)) {
contextVersions[pos] = newContextVersion;
contextObjectToContextVersionMap.put(context, newContextVersion);
}
}
}
}
}
/** * Remove a context from an existing host. * * @param ctxt The actual context * @param hostName Virtual host name this context belongs to * @param path Context path * @param version Context version
*/ publicvoid removeContextVersion(Context ctxt, String hostName, String path, String version) {
ContextVersion[] contextVersions = context.versions;
ContextVersion[] newContextVersions = new ContextVersion[contextVersions.length - 1]; if (removeMap(contextVersions, newContextVersions, version)) { if (newContextVersions.length == 0) { // Remove the context
ContextList newContextList = contextList.removeContext(path); if (newContextList != null) {
updateContextList(host, newContextList);
}
} else {
context.versions = newContextVersions;
}
}
}
}
/** * Mark a context as being reloaded. Reversion of this state is performed by calling * <code>addContextVersion(...)</code> when context starts up. * * @param ctxt The actual context * @param hostName Virtual host name this context belongs to * @param contextPath Context path * @param version Context version
*/ publicvoid pauseContextVersion(Context ctxt, String hostName, String contextPath, String version) {
hostName = renameWildcardHost(hostName);
ContextVersion contextVersion = findContextVersion(hostName, contextPath, version, true); if (contextVersion == null || !ctxt.equals(contextVersion.object)) { return;
}
contextVersion.markPaused();
}
/** * Adds wrappers to the given context. * * @param contextVersion The context to which to add the wrappers * @param wrappers Information on wrapper mappings
*/ privatevoid addWrappers(ContextVersion contextVersion, Collection<WrapperMappingInfo> wrappers) { for (WrapperMappingInfo wrapper : wrappers) {
addWrapper(contextVersion, wrapper.getMapping(), wrapper.getWrapper(), wrapper.isJspWildCard(),
wrapper.isResourceOnly());
}
}
/** * Adds a wrapper to the given context. * * @param context The context to which to add the wrapper * @param path Wrapper mapping * @param wrapper The Wrapper object * @param jspWildCard true if the wrapper corresponds to the JspServlet and the mapping path contains a wildcard; * false otherwise * @param resourceOnly true if this wrapper always expects a physical resource to be present (such as a JSP)
*/ protectedvoid addWrapper(ContextVersion context, String path, Wrapper wrapper, boolean jspWildCard, boolean resourceOnly) {
synchronized (context) { if (path.endsWith("/*")) { // Wildcard wrapper
String name = path.substring(0, path.length() - 2);
MappedWrapper newWrapper = new MappedWrapper(name, wrapper, jspWildCard, resourceOnly);
MappedWrapper[] oldWrappers = context.wildcardWrappers;
MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1]; if (insertMap(oldWrappers, newWrappers, newWrapper)) {
context.wildcardWrappers = newWrappers; int slashCount = slashCount(newWrapper.name); if (slashCount > context.nesting) {
context.nesting = slashCount;
}
}
} elseif (path.startsWith("*.")) { // Extension wrapper
String name = path.substring(2);
MappedWrapper newWrapper = new MappedWrapper(name, wrapper, jspWildCard, resourceOnly);
MappedWrapper[] oldWrappers = context.extensionWrappers;
MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1]; if (insertMap(oldWrappers, newWrappers, newWrapper)) {
context.extensionWrappers = newWrappers;
}
} elseif (path.equals("/")) { // Default wrapper
MappedWrapper newWrapper = new MappedWrapper("", wrapper, jspWildCard, resourceOnly);
context.defaultWrapper = newWrapper;
} else { // Exact wrapper final String name; if (path.length() == 0) { // Special case for the Context Root mapping which is // treated as an exact match
name = "/";
} else {
name = path;
}
MappedWrapper newWrapper = new MappedWrapper(name, wrapper, jspWildCard, resourceOnly);
MappedWrapper[] oldWrappers = context.exactWrappers;
MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1]; if (insertMap(oldWrappers, newWrappers, newWrapper)) {
context.exactWrappers = newWrappers;
}
}
}
}
/** * Remove a wrapper from an existing context. * * @param hostName Virtual host name this wrapper belongs to * @param contextPath Context path this wrapper belongs to * @param version Context version this wrapper belongs to * @param path Wrapper mapping
*/ publicvoid removeWrapper(String hostName, String contextPath, String version, String path) {
hostName = renameWildcardHost(hostName);
ContextVersion contextVersion = findContextVersion(hostName, contextPath, version, true); if (contextVersion == null || contextVersion.isPaused()) { return;
}
removeWrapper(contextVersion, path);
}
if (log.isDebugEnabled()) {
log.debug(sm.getString("mapper.removeWrapper", context.name, path));
}
synchronized (context) { if (path.endsWith("/*")) { // Wildcard wrapper
String name = path.substring(0, path.length() - 2);
MappedWrapper[] oldWrappers = context.wildcardWrappers; if (oldWrappers.length == 0) { return;
}
MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length - 1]; if (removeMap(oldWrappers, newWrappers, name)) { // Recalculate nesting
context.nesting = 0; for (MappedWrapper newWrapper : newWrappers) { int slashCount = slashCount(newWrapper.name); if (slashCount > context.nesting) {
context.nesting = slashCount;
}
}
context.wildcardWrappers = newWrappers;
}
} elseif (path.startsWith("*.")) { // Extension wrapper
String name = path.substring(2);
MappedWrapper[] oldWrappers = context.extensionWrappers; if (oldWrappers.length == 0) { return;
}
MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length - 1]; if (removeMap(oldWrappers, newWrappers, name)) {
context.extensionWrappers = newWrappers;
}
} elseif (path.equals("/")) { // Default wrapper
context.defaultWrapper = null;
} else { // Exact wrapper
String name; if (path.length() == 0) { // Special case for the Context Root mapping which is // treated as an exact match
name = "/";
} else {
name = path;
}
MappedWrapper[] oldWrappers = context.exactWrappers; if (oldWrappers.length == 0) { return;
}
MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length - 1]; if (removeMap(oldWrappers, newWrappers, name)) {
context.exactWrappers = newWrappers;
}
}
}
}
/** * Add a welcome file to the given context. * * @param hostName The host where the given context can be found * @param contextPath The path of the given context * @param version The version of the given context * @param welcomeFile The welcome file to add
*/ publicvoid addWelcomeFile(String hostName, String contextPath, String version, String welcomeFile) {
hostName = renameWildcardHost(hostName);
ContextVersion contextVersion = findContextVersion(hostName, contextPath, version, false); if (contextVersion == null) { return;
} int len = contextVersion.welcomeResources.length + 1;
String[] newWelcomeResources = new String[len];
System.arraycopy(contextVersion.welcomeResources, 0, newWelcomeResources, 0, len - 1);
newWelcomeResources[len - 1] = welcomeFile;
contextVersion.welcomeResources = newWelcomeResources;
}
/** * Remove a welcome file from the given context. * * @param hostName The host where the given context can be found * @param contextPath The path of the given context * @param version The version of the given context * @param welcomeFile The welcome file to remove
*/ publicvoid removeWelcomeFile(String hostName, String contextPath, String version, String welcomeFile) {
hostName = renameWildcardHost(hostName);
ContextVersion contextVersion = findContextVersion(hostName, contextPath, version, false); if (contextVersion == null || contextVersion.isPaused()) { return;
} int match = -1; for (int i = 0; i < contextVersion.welcomeResources.length; i++) { if (welcomeFile.equals(contextVersion.welcomeResources[i])) {
match = i; break;
}
} if (match > -1) { int len = contextVersion.welcomeResources.length - 1;
String[] newWelcomeResources = new String[len];
System.arraycopy(contextVersion.welcomeResources, 0, newWelcomeResources, 0, match); if (match < len) {
System.arraycopy(contextVersion.welcomeResources, match + 1, newWelcomeResources, match, len - match);
}
contextVersion.welcomeResources = newWelcomeResources;
}
}
/** * Clear the welcome files for the given context. * * @param hostName The host where the context to be cleared can be found * @param contextPath The path of the context to be cleared * @param version The version of the context to be cleared
*/ publicvoid clearWelcomeFiles(String hostName, String contextPath, String version) {
hostName = renameWildcardHost(hostName);
ContextVersion contextVersion = findContextVersion(hostName, contextPath, version, false); if (contextVersion == null) { return;
}
contextVersion.welcomeResources = new String[0];
}
/** * Map the specified host name and URI, mutating the given mapping data. * * @param host Virtual host name * @param uri URI * @param version The version, if any, included in the request to be mapped * @param mappingData This structure will contain the result of the mapping operation * * @throws IOException if the buffers are too small to hold the results of the mapping.
*/ publicvoid map(MessageBytes host, MessageBytes uri, String version, MappingData mappingData) throws IOException {
/** * Map the specified URI relative to the context, mutating the given mapping data. * * @param context The actual context * @param uri URI * @param mappingData This structure will contain the result of the mapping operation * * @throws IOException if the buffers are too small to hold the results of the mapping.
*/ publicvoid map(Context context, MessageBytes uri, MappingData mappingData) throws IOException {
/** * Map the specified URI. * * @throws IOException If an error occurs while manipulating the URI during the mapping
*/ privatevoid internalMap(CharChunk host, CharChunk uri, String version, MappingData mappingData) throws IOException {
if (mappingData.host != null) { // The legacy code (dating down at least to Tomcat 4.1) just // skipped all mapping work in this case. That behaviour has a risk // of returning an inconsistent result. // I do not see a valid use case for it. thrownew AssertionError();
}
// Virtual host mapping
MappedHost[] hosts = this.hosts;
MappedHost mappedHost = exactFindIgnoreCase(hosts, host); if (mappedHost == null) { // Note: Internally, the Mapper does not use the leading * on a // wildcard host. This is to allow this shortcut. int firstDot = host.indexOf('.'); if (firstDot > -1) { int offset = host.getOffset(); try {
host.setOffset(firstDot + offset);
mappedHost = exactFindIgnoreCase(hosts, host);
} finally { // Make absolutely sure this gets reset
host.setOffset(offset);
}
} if (mappedHost == null) {
mappedHost = defaultHost; if (mappedHost == null) { return;
}
}
}
mappingData.host = mappedHost.object;
if (uri.isNull()) { // Can't map context or wrapper without a uri return;
}
int lastSlash = -1; int uriEnd = uri.getEnd(); int length = -1; boolean found = false;
MappedContext context = null; while (pos >= 0) {
context = contexts[pos]; if (uri.startsWith(context.name)) {
length = context.name.length(); if (uri.getLength() == length) {
found = true; break;
} elseif (uri.startsWithIgnoreCase("/", length)) {
found = true; break;
}
} if (lastSlash == -1) {
lastSlash = nthSlash(uri, contextList.nesting + 1);
} else {
lastSlash = lastSlash(uri);
}
uri.setEnd(lastSlash);
pos = find(contexts, uri);
}
uri.setEnd(uriEnd);
if (!found) { if (contexts[0].name.equals("")) {
context = contexts[0];
} else {
context = null;
}
} if (context == null) { return;
}
ContextVersion contextVersion = null;
ContextVersion[] contextVersions = context.versions; finalint versionCount = contextVersions.length; if (versionCount > 1) {
Context[] contextObjects = new Context[contextVersions.length]; for (int i = 0; i < contextObjects.length; i++) {
contextObjects[i] = contextVersions[i].object;
}
mappingData.contexts = contextObjects; if (version != null) {
contextVersion = exactFind(contextVersions, version);
}
} if (contextVersion == null) { // Return the latest version // The versions array is known to contain at least one element
contextVersion = contextVersions[versionCount - 1];
}
mappingData.context = contextVersion.object;
mappingData.contextSlashCount = contextVersion.slashCount;
// Wrapper mapping if (!contextVersion.isPaused()) {
internalMapWrapper(contextVersion, uri, mappingData);
}
}
/** * Wrapper mapping. * * @throws IOException if the buffers are too small to hold the results of the mapping.
*/ privatevoid internalMapWrapper(ContextVersion contextVersion, CharChunk path, MappingData mappingData) throws IOException {
int pathOffset = path.getOffset(); int pathEnd = path.getEnd(); boolean noServletPath = false;
int length = contextVersion.path.length(); if (length == (pathEnd - pathOffset)) {
noServletPath = true;
} int servletPath = pathOffset + length;
path.setOffset(servletPath);
// Rule 2 -- Prefix Match boolean checkJspWelcomeFiles = false;
MappedWrapper[] wildcardWrappers = contextVersion.wildcardWrappers; if (mappingData.wrapper == null) {
internalMapWildcardWrapper(wildcardWrappers, contextVersion.nesting, path, mappingData); if (mappingData.wrapper != null && mappingData.jspWildCard) { char[] buf = path.getBuffer(); if (buf[pathEnd - 1] == '/') { /* * Path ending in '/' was mapped to JSP servlet based on wildcard match (e.g., as specified in * url-pattern of a jsp-property-group. Force the context's welcome files, which are interpreted as * JSP files (since they match the url-pattern), to be considered. See Bugzilla 27664.
*/
mappingData.wrapper = null;
checkJspWelcomeFiles = true;
} else { // See Bugzilla 27704
mappingData.wrapperPath.setChars(buf, path.getStart(), path.getLength());
mappingData.pathInfo.recycle();
}
}
}
if (mappingData.wrapper == null && noServletPath &&
contextVersion.object.getMapperContextRootRedirectEnabled()) { // The path is empty, redirect to "/"
path.append('/');
pathEnd = path.getEnd();
mappingData.redirectPath.setChars(path.getBuffer(), pathOffset, pathEnd - pathOffset);
path.setEnd(pathEnd - 1); return;
}
/* * welcome file processing - take 2 Now that we have looked for welcome files with a physical backing, now look * for an extension mapping listed but may not have a physical backing to it. This is for the case of index.jsf, * index.do, etc. A watered down version of rule 4
*/ if (mappingData.wrapper == null) { boolean checkWelcomeFiles = checkJspWelcomeFiles; if (!checkWelcomeFiles) { char[] buf = path.getBuffer();
checkWelcomeFiles = (buf[pathEnd - 1] == '/');
} if (checkWelcomeFiles) { for (int i = 0; (i < contextVersion.welcomeResources.length) && (mappingData.wrapper == null); i++) {
path.setOffset(pathOffset);
path.setEnd(pathEnd);
path.append(contextVersion.welcomeResources[i], 0, contextVersion.welcomeResources[i].length());
path.setOffset(servletPath);
internalMapExtensionWrapper(extensionWrappers, path, mappingData, false);
}
int lastSlash = -1; int length = -1; int pos = find(wrappers, path); if (pos != -1) { boolean found = false; while (pos >= 0) { if (path.startsWith(wrappers[pos].name)) {
length = wrappers[pos].name.length(); if (path.getLength() == length) {
found = true; break;
} elseif (path.startsWithIgnoreCase("/", length)) {
found = true; break;
}
} if (lastSlash == -1) {
lastSlash = nthSlash(path, nesting + 1);
} else {
lastSlash = lastSlash(path);
}
path.setEnd(lastSlash);
pos = find(wrappers, path);
}
path.setEnd(pathEnd); if (found) {
mappingData.wrapperPath.setString(wrappers[pos].name); if (path.getLength() > length) {
mappingData.pathInfo.setChars(path.getBuffer(), path.getOffset() + length,
path.getLength() - length);
}
mappingData.requestPath.setChars(path.getBuffer(), path.getOffset(), path.getLength());
mappingData.wrapper = wrappers[pos].object;
mappingData.jspWildCard = wrappers[pos].jspWildCard;
mappingData.matchType = MappingMatch.PATH;
}
}
}
/** * Extension mappings. * * @param wrappers Set of wrappers to check for matches * @param path Path to map * @param mappingData Mapping data for result * @param resourceExpected Is this mapping expecting to find a resource
*/ privatevoid internalMapExtensionWrapper(MappedWrapper[] wrappers, CharChunk path, MappingData mappingData, boolean resourceExpected) { char[] buf = path.getBuffer(); int pathEnd = path.getEnd(); int servletPath = path.getOffset(); int slash = -1; for (int i = pathEnd - 1; i >= servletPath; i--) { if (buf[i] == '/') {
slash = i; break;
}
} if (slash >= 0) { int period = -1; for (int i = pathEnd - 1; i > slash; i--) { if (buf[i] == '.') {
period = i; break;
}
} if (period >= 0) {
path.setOffset(period + 1);
path.setEnd(pathEnd);
MappedWrapper wrapper = exactFind(wrappers, path); if (wrapper != null && (resourceExpected || !wrapper.resourceOnly)) {
mappingData.wrapperPath.setChars(buf, servletPath, pathEnd - servletPath);
mappingData.requestPath.setChars(buf, servletPath, pathEnd - servletPath);
mappingData.wrapper = wrapper.object;
mappingData.matchType = MappingMatch.EXTENSION;
}
path.setOffset(servletPath);
path.setEnd(pathEnd);
}
}
}
/** * Find a map element given its name in a sorted array of map elements. This will return the index for the closest * inferior or equal item in the given array.
*/ privatestatic <T> int find(MapElement<T>[] map, CharChunk name) { return find(map, name, name.getStart(), name.getEnd());
}
/** * Find a map element given its name in a sorted array of map elements. This will return the index for the closest * inferior or equal item in the given array.
*/ privatestatic <T> int find(MapElement<T>[] map, CharChunk name, int start, int end) {
int a = 0; int b = map.length - 1;
// Special cases: -1 and 0 if (b == -1) { return -1;
}
if (compare(name, start, end, map[0].name) < 0) { return -1;
} if (b == 0) { return 0;
}
int i = 0; while (true) {
i = (b + a) >>> 1; int result = compare(name, start, end, map[i].name); if (result == 1) {
a = i;
} elseif (result == 0) { return i;
} else {
b = i;
} if ((b - a) == 1) { int result2 = compare(name, start, end, map[b].name); if (result2 < 0) { return a;
} else { return b;
}
}
}
}
/** * Find a map element given its name in a sorted array of map elements. This will return the index for the closest * inferior or equal item in the given array.
*/ privatestatic <T> int findIgnoreCase(MapElement<T>[] map, CharChunk name) { return findIgnoreCase(map, name, name.getStart(), name.getEnd());
}
/** * Find a map element given its name in a sorted array of map elements. This will return the index for the closest * inferior or equal item in the given array.
*/ privatestatic <T> int findIgnoreCase(MapElement<T>[] map, CharChunk name, int start, int end) {
int a = 0; int b = map.length - 1;
// Special cases: -1 and 0 if (b == -1) { return -1;
} if (compareIgnoreCase(name, start, end, map[0].name) < 0) { return -1;
} if (b == 0) { return 0;
}
int i = 0; while (true) {
i = (b + a) >>> 1; int result = compareIgnoreCase(name, start, end, map[i].name); if (result == 1) {
a = i;
} elseif (result == 0) { return i;
} else {
b = i;
} if ((b - a) == 1) { int result2 = compareIgnoreCase(name, start, end, map[b].name); if (result2 < 0) { return a;
} else { return b;
}
}
}
}
/** * Find a map element given its name in a sorted array of map elements. This will return the index for the closest * inferior or equal item in the given array. * * @see #exactFind(MapElement[], String)
*/ privatestatic <T> int find(MapElement<T>[] map, String name) {
int a = 0; int b = map.length - 1;
// Special cases: -1 and 0 if (b == -1) { return -1;
}
if (name.compareTo(map[0].name) < 0) { return -1;
} if (b == 0) { return 0;
}
int i = 0; while (true) {
i = (b + a) >>> 1; int result = name.compareTo(map[i].name); if (result > 0) {
a = i;
} elseif (result == 0) { return i;
} else {
b = i;
} if ((b - a) == 1) { int result2 = name.compareTo(map[b].name); if (result2 < 0) { return a;
} else { return b;
}
}
}
}
/** * Find a map element given its name in a sorted array of map elements. This will return the element that you were * searching for. Otherwise it will return <code>null</code>. * * @see #find(MapElement[], String)
*/ privatestatic <T, E extends MapElement<T>> E exactFind(E[] map, String name) { int pos = find(map, name); if (pos >= 0) {
E result = map[pos]; if (name.equals(result.name)) { return result;
}
} returnnull;
}
/** * Find a map element given its name in a sorted array of map elements. This will return the element that you were * searching for. Otherwise it will return <code>null</code>.
*/ privatestatic <T, E extends MapElement<T>> E exactFind(E[] map, CharChunk name) { int pos = find(map, name); if (pos >= 0) {
E result = map[pos]; if (name.equals(result.name)) { return result;
}
} returnnull;
}
/** * Find a map element given its name in a sorted array of map elements. This will return the element that you were * searching for. Otherwise it will return <code>null</code>. * * @see #findIgnoreCase(MapElement[], CharChunk)
*/ privatestatic <T, E extends MapElement<T>> E exactFindIgnoreCase(E[] map, CharChunk name) { int pos = findIgnoreCase(map, name); if (pos >= 0) {
E result = map[pos]; if (name.equalsIgnoreCase(result.name)) { return result;
}
} returnnull;
}
/** * Compare given char chunk with String. Return -1, 0 or +1 if inferior, equal, or superior to the String.
*/ privatestaticint compare(CharChunk name, int start, int end, String compareTo) { int result = 0; char[] c = name.getBuffer(); int compareLen = compareTo.length(); int len = compareLen; if ((end - start) < len) {
len = end - start;
} for (int i = 0; (i < len) && (result == 0); i++) { char nameChar = c[i + start]; char compareToChar = compareTo.charAt(i); if (nameChar > compareToChar) {
result = 1;
} elseif (nameChar < compareToChar) {
result = -1;
}
} if (result == 0) { if (compareLen > (end - start)) {
result = -1;
} elseif (compareLen < (end - start)) {
result = 1;
}
} return result;
}
/** * Compare given char chunk with String ignoring case. Return -1, 0 or +1 if inferior, equal, or superior to the * String.
*/ privatestaticint compareIgnoreCase(CharChunk name, int start, int end, String compareTo) { int result = 0; char[] c = name.getBuffer(); int compareLen = compareTo.length(); int len = compareLen; if ((end - start) < len) {
len = end - start;
} for (int i = 0; (i < len) && (result == 0); i++) { int nameLower = Ascii.toLower(c[i + start]); int compareLower = Ascii.toLower(compareTo.charAt(i)); if (nameLower > compareLower) {
result = 1;
} elseif (nameLower < compareLower) {
result = -1;
}
} if (result == 0) { if (compareLen > (end - start)) {
result = -1;
} elseif (compareLen < (end - start)) {
result = 1;
}
} return result;
}
/** * Find the position of the last slash in the given char chunk.
*/ privatestaticint lastSlash(CharChunk name) { char[] c = name.getBuffer(); int end = name.getEnd(); int start = name.getStart(); int pos = end;
while (pos > start) { if (c[--pos] == '/') { break;
}
}
return pos;
}
/** * Find the position of the nth slash, in the given char chunk.
*/ privatestaticint nthSlash(CharChunk name, int n) { char[] c = name.getBuffer(); int end = name.getEnd(); int start = name.getStart(); int pos = start; int count = 0;
while (pos < end) { if ((c[pos++] == '/') && ((++count) == n)) {
pos--; break;
}
}
return pos;
}
/** * Return the slash count in a given string.
*/ privatestaticint slashCount(String name) { int pos = -1; int count = 0; while ((pos = name.indexOf('/', pos + 1)) != -1) {
count++;
} return count;
}
/** * Insert into the right place in a sorted MapElement array, and prevent duplicates.
*/ privatestatic <T> boolean insertMap(MapElement<T>[] oldMap, MapElement<T>[] newMap, MapElement<T> newElement) { int pos = find(oldMap, newElement.name); if ((pos != -1) && (newElement.name.equals(oldMap[pos].name))) { returnfalse;
}
System.arraycopy(oldMap, 0, newMap, 0, pos + 1);
newMap[pos + 1] = newElement;
System.arraycopy(oldMap, pos + 1, newMap, pos + 2, oldMap.length - pos - 1); returntrue;
}
/** * Insert into the right place in a sorted MapElement array.
*/ privatestatic <T> boolean removeMap(MapElement<T>[] oldMap, MapElement<T>[] newMap, String name) { int pos = find(oldMap, name); if ((pos != -1) && (name.equals(oldMap[pos].name))) {
System.arraycopy(oldMap, 0, newMap, 0, pos);
System.arraycopy(oldMap, pos + 1, newMap, pos, oldMap.length - pos - 1); returntrue;
} returnfalse;
}
/* * To simplify the mapping process, wild card hosts take the form ".apache.org" rather than "*.apache.org" * internally. However, for ease of use the external form remains "*.apache.org". Any host name passed into this * class needs to be passed through this method to rename and wild card host names from the external to internal * form.
*/ privatestatic String renameWildcardHost(String hostName) { if (hostName != null && hostName.startsWith("*.")) { return hostName.substring(1);
} else { return hostName;
}
}
// ------------------------------------------------- MapElement Inner Class
protectedabstractstaticclass MapElement<T> {
publicfinal String name; publicfinal T object;
public MapElement(String name, T object) { this.name = name; this.object = object;
}
}
// ------------------------------------------------------- Host Inner Class
/** * Link to the "real" MappedHost, shared by all aliases.
*/ privatefinal MappedHost realHost;
/** * Links to all registered aliases, for easy enumeration. This field is available only in the "real" MappedHost. * In an alias this field is <code>null</code>.
*/ privatefinal List<MappedHost> aliases;
/** * Constructor used for the primary Host * * @param name The name of the virtual host * @param host The host
*/ public MappedHost(String name, Host host) { super(name, host);
realHost = this;
contextList = new ContextList();
aliases = new CopyOnWriteArrayList<>();
}
/** * Constructor used for an Alias * * @param alias The alias of the virtual host * @param realHost The host the alias points to
*/ public MappedHost(String alias, MappedHost realHost) { super(alias, realHost.object); this.realHost = realHost; this.contextList = realHost.contextList; this.aliases = null;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.