Java tutorial
/* * Copyright 2015 LINE Corporation * * LINE Corporation 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 com.linecorp.armeria.server; import static com.google.common.base.MoreObjects.firstNonNull; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; final class GlobPathMapping extends AbstractPathMapping { static final String PREFIX = "glob:"; static final int PREFIX_LEN = PREFIX.length(); private static final String[] INT_TO_STRING; static { INT_TO_STRING = new String[64]; for (int i = 0; i < INT_TO_STRING.length; i++) { INT_TO_STRING[i] = String.valueOf(i); } } private static String int2str(int value) { if (value < INT_TO_STRING.length) { return INT_TO_STRING[value]; } else { return Integer.toString(value); } } private final String glob; private final Pattern pattern; private final int numParams; private final Set<String> paramNames; private final String loggerName; private final String strVal; GlobPathMapping(String glob) { final PatternAndParamCount patternAndParamCount = globToRegex(glob); this.glob = glob; pattern = patternAndParamCount.pattern; numParams = patternAndParamCount.numParams; final ImmutableSet.Builder<String> paramNames = ImmutableSet.builder(); for (int i = 0; i < numParams; i++) { paramNames.add(int2str(i)); } this.paramNames = paramNames.build(); loggerName = loggerName(glob); strVal = PREFIX + glob; } @Override protected PathMappingResult doApply(String path, @Nullable String query) { final Matcher m = pattern.matcher(path); if (!m.matches()) { return PathMappingResult.empty(); } if (numParams == 0) { return PathMappingResult.of(path, query); } final ImmutableMap.Builder<String, String> params = ImmutableMap.builder(); for (int i = 1; i <= numParams; i++) { final String value = firstNonNull(m.group(i), ""); params.put(int2str(i - 1), value); } return PathMappingResult.of(path, query, params.build()); } @Override public Set<String> paramNames() { return paramNames; } @Override public String loggerName() { return loggerName; } @Override public String metricName() { return glob; } @VisibleForTesting Pattern asRegex() { return pattern; } @Override public int hashCode() { return strVal.hashCode(); } @Override public boolean equals(Object obj) { return obj instanceof GlobPathMapping && (this == obj || glob.equals(((GlobPathMapping) obj).glob)); } @Override public String toString() { return strVal; } static PatternAndParamCount globToRegex(String glob) { boolean createGroup; int numGroups = 0; if (glob.charAt(0) != '/') { glob = "/**/" + glob; createGroup = false; // Do not capture the prefix a user did not specify. } else { createGroup = true; } final int pathPatternLen = glob.length(); final StringBuilder buf = new StringBuilder(pathPatternLen).append("^/"); int asterisks = 0; char beforeAsterisk = '/'; for (int i = 1; i < pathPatternLen; i++) { // Start from '1' to skip the first '/'. final char c = glob.charAt(i); if (c == '*') { asterisks++; if (asterisks > 2) { throw new IllegalArgumentException("contains a path pattern with invalid wildcard characters: " + glob + " (only * and ** are allowed)"); } continue; } switch (asterisks) { case 1: if (createGroup) { buf.append('('); numGroups++; } // Handle '/*/' specially. if (beforeAsterisk == '/' && c == '/') { buf.append("[^/]+"); } else { buf.append("[^/]*"); } if (createGroup) { buf.append(')'); } else { createGroup = true; } break; case 2: // Handle '/**/' specially. if (beforeAsterisk == '/' && c == '/') { buf.append("(?:"); if (createGroup) { buf.append('('); numGroups++; } buf.append(".+"); if (createGroup) { buf.append(')'); } else { createGroup = false; } buf.append("/)?"); asterisks = 0; continue; } else { if (createGroup) { buf.append('('); numGroups++; } buf.append(".*"); if (createGroup) { buf.append(')'); } else { createGroup = false; } break; } } asterisks = 0; beforeAsterisk = c; switch (c) { case '\\': case '.': case '^': case '$': case '?': case '+': case '{': case '}': case '[': case ']': case '(': case ')': case '|': buf.append('\\'); buf.append(c); break; default: buf.append(c); } } // Handle the case where the pattern ends with asterisk(s). switch (asterisks) { case 1: if (createGroup) { buf.append('('); numGroups++; } if (beforeAsterisk == '/') { // '/*<END>' buf.append("[^/]+"); } else { buf.append("[^/]*"); } if (createGroup) { buf.append(')'); } break; case 2: if (createGroup) { buf.append('('); numGroups++; } buf.append(".*"); if (createGroup) { buf.append(')'); } break; } return new PatternAndParamCount(Pattern.compile(buf.append('$').toString()), numGroups); } private static final class PatternAndParamCount { final Pattern pattern; final int numParams; PatternAndParamCount(Pattern pattern, int numParams) { this.pattern = pattern; this.numParams = numParams; } } }