/* * Copyright 2013 Netflix, Inc. * * 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 feign.codec; import java.io.IOException; import java.io.Reader; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import static feign.Util.checkNotNull; import static java.lang.String.format; import static java.util.regex.Pattern.DOTALL; import static java.util.regex.Pattern.compile; /** * Static utility methods pertaining to {@code Decoder} instances.
*
*
* Pattern Decoders
*
* Pattern decoders typically require less initialization, dependencies, and * code than reflective decoders, but not can be awkward to those unfamiliar * with regex. Typical use of pattern decoders is to grab a single field from an * xml response, or parse a list of Strings. The pattern decoders here * facilitate these use cases. */ public class Decoders { /** * guava users will implement this with {@code ApplyFirstGroup}. * * @param intended result type */ public interface ApplyFirstGroup { /** * create a new instance from the non-null {@code firstGroup} specified. */ T apply(String firstGroup); } /** * shortcut for
new TransformFirstGroup(pattern, applyFirstGroup){}
when * {@code String} is the type you are decoding into.
*
* Ex. to pull the first interesting element from an xml response:
*

*

   * decodeFirstDirPoolID = firstGroup("<DirPoolID[ˆ>]*>([ˆ<]+)</DirPoolID>");
   * 
*/ public static Decoder.TextStream firstGroup(String pattern) { return new TransformFirstGroup(pattern, IDENTITY) { }; } /** * shortcut for
new TransformEachFirstGroup(pattern, applyFirstGroup){}
when * {@code List} is the type you are decoding into.
* Ex. to pull a list zones names, which are http paths starting with * {@code /Rest/Zone/}:
*

*

   * decodeListOfZonesNames = eachFirstGroup("/REST/Zone/([ˆ/]+)/");
   * 
*/ public static Decoder.TextStream> eachFirstGroup(String pattern) { return new TransformEachFirstGroup(pattern, IDENTITY) { }; } private static String toString(Reader reader) throws IOException { return TO_STRING.decode(reader, null).toString(); } private static final StringDecoder TO_STRING = new StringDecoder(); private static final ApplyFirstGroup IDENTITY = new ApplyFirstGroup() { @Override public String apply(String firstGroup) { return firstGroup; } }; /** * The first match group is applied to {@code applyGroups} and result * returned. If no matches are found, the response is null;
* Ex. to pull the first interesting element from an xml response:
*

*

   * decodeFirstDirPoolID = new TransformFirstGroup<Long>("<DirPoolID[ˆ>]*>([ˆ<]+)</DirPoolID>", ToLong.INSTANCE) {
   * };
   * 
*/ public static class TransformFirstGroup implements Decoder.TextStream { private final Pattern patternForMatcher; private final ApplyFirstGroup applyFirstGroup; /** * You must subclass this, in order to prevent type erasure on {@code T} * . In addition to making a concrete type, you can also use the * following form. *

*
*

*

     * new TransformFirstGroup<Foo>(pattern, applyFirstGroup) {
     * }; // note the curly braces ensures no type erasure!
     * 
*/ protected TransformFirstGroup(String pattern, ApplyFirstGroup applyFirstGroup) { this.patternForMatcher = compile(checkNotNull(pattern, "pattern"), DOTALL); this.applyFirstGroup = checkNotNull(applyFirstGroup, "applyFirstGroup"); } @Override public T decode(Reader reader, Type type) throws IOException { Matcher matcher = patternForMatcher.matcher(Decoders.toString(reader)); if (matcher.find()) { return applyFirstGroup.apply(matcher.group(1)); } return null; } @Override public String toString() { return format("decode groups from %s into %s", patternForMatcher, applyFirstGroup); } } /** * On the each find the first match group is applied to * {@code applyFirstGroup} and added to the list returned. If no matches are * found, the response is an empty list;
* Ex. to pull a list zones constructed from http paths starting with * {@code /Rest/Zone/}: *

*
*

*

   * decodeListOfZones = new TransformEachFirstGroup("/REST/Zone/([ˆ/]+)/", ToZone.INSTANCE) {
   * };
   * 
*/ public static class TransformEachFirstGroup implements Decoder.TextStream> { private final Pattern patternForMatcher; private final ApplyFirstGroup applyFirstGroup; /** * You must subclass this, in order to prevent type erasure on {@code T} * . In addition to making a concrete type, you can also use the * following form. *

*
*

*

     * new TransformEachFirstGroup<Foo>(pattern, applyFirstGroup) {
     * }; // note the curly braces ensures no type erasure!
     * 
*/ protected TransformEachFirstGroup(String pattern, ApplyFirstGroup applyFirstGroup) { this.patternForMatcher = compile(checkNotNull(pattern, "pattern"), DOTALL); this.applyFirstGroup = checkNotNull(applyFirstGroup, "applyFirstGroup"); } @Override public List decode(Reader reader, Type type) throws IOException { Matcher matcher = patternForMatcher.matcher(Decoders.toString(reader)); List result = new ArrayList(); while (matcher.find()) { result.add(applyFirstGroup.apply(matcher.group(1))); } return result; } @Override public String toString() { return format("decode %s into list elements, where each group(1) is transformed with %s", patternForMatcher, applyFirstGroup); } } }