Java tutorial
/** * Copyright (c) 2015 Mark S. Kolich * http://mark.koli.ch * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package com.kolich.curacao.mappers.request.matchers; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.kolich.curacao.CuracaoContext; import org.slf4j.Logger; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import static org.slf4j.LoggerFactory.getLogger; public final class DefaultCuracaoRegexPathMatcher implements CuracaoPathMatcher { private static final Logger logger__ = getLogger(DefaultCuracaoRegexPathMatcher.class); /** * A regex for finding/extracting named captured groups in * another regex. */ private static final Pattern NAMED_GROUPS_REGEX = Pattern.compile("\\(\\?<([a-zA-Z][a-zA-Z0-9]*)>"); /** * Acts as an internal cache that maps a routing key to a formal * pre-compiled {@link Pattern}. Routing keys are the String's used * inside of routing annotations. For example, the routing key associated * with <tt>@RequestMapping("foo/bar/")</tt> is "foo/bar/". * * A single instance of this cache is gracefully shared by all regex * based path matchers. */ private static final class PatternCache { // This makes use of the "Initialization-on-demand holder idiom" which is // discussed in detail here: // http://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom // As such, this is totally thread safe and performant. private static class LazyHolder { private static final PatternCache instance__ = new PatternCache(); } private static final PatternCache getInstance() { return LazyHolder.instance__; } private final Map<String, Pattern> cache_; private PatternCache() { cache_ = Maps.newLinkedHashMap(); } public final synchronized Pattern getPattern(final String key) { Pattern p = null; if ((p = cache_.get(key)) == null) { // No pattern has been compiled yet for the incoming key. // This may fail miserably if the regex attached to the // routing annotation is malformed, in which case, we will // bail here guaranteeing that this routing key will ~not~ // match the path we're tasked with checking. p = Pattern.compile(key); cache_.put(key, p); } return p; } } @Nullable @Override public Map<String, String> match(@Nonnull final CuracaoContext context, @Nonnull final String key, @Nonnull final String path) throws Exception { Map<String, String> result = null; try { // Load the pre-compiled pattern associated with the routing key. final Pattern p = PatternCache.getInstance().getPattern(key); // Build the matcher, and check if the regex matches the path. final Matcher m = p.matcher(path); if (m.matches()) { // required to prep matcher result = getNamedGroupsAndValues(p.toString(), m); } } catch (Exception e) { logger__.warn("Failed to match route using regex (key={}, path={})", key, path, e); } return result; // Immutable } @Nonnull private static final ImmutableMap<String, String> getNamedGroupsAndValues(final String regex, final Matcher m) { final ImmutableSet<String> groups = getNamedGroups(regex); // If the provided regex has no capture groups, there's no point in // actually trying to build a new map to hold the results if (groups.isEmpty()) { return ImmutableMap.of(); // Empty, immutable map } else { final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); // For each extract "capture group" in the regex... for (final String groupName : groups) { final String value; if ((value = m.group(groupName)) != null) { // Only non-null values are injected into the map. builder.put(groupName, value); } } return builder.build(); // Immutable } } /** * Given a regex as a String, returns an {@link ImmutableSet} representing * the named capture groups in the regex. For example, <tt>^(?<foo>\w+)</tt> * would return an {@link ImmutableSet} with a single entry "foo" * corresponding to the named capture group "foo". */ @Nonnull private static final ImmutableSet<String> getNamedGroups(final String regex) { final ImmutableSet.Builder<String> builder = ImmutableSet.builder(); final Matcher m = NAMED_GROUPS_REGEX.matcher(regex); while (m.find()) { builder.add(m.group(1)); } return builder.build(); // Immutable } }