public class EmbeddedTypeInfo {

    private final String rel;

    private final Class<? extends HalRepresentation> type;

    private final List<EmbeddedTypeInfo> nestedTypeInfo;

    /** Creates a new EmbeddedTypeInfo by link-relation type and Java class of embedded items. */
    private EmbeddedTypeInfo(final String rel,
                             final Class<? extends HalRepresentation> type,
                             final List<EmbeddedTypeInfo> nestedTypeInfo) {
        this.rel = rel;
        this.type = type;
        this.nestedTypeInfo = nestedTypeInfo;
    }

    public static EmbeddedTypeInfo withEmbedded(final String rel,
                                                final Class<? extends HalRepresentation> embeddedType,
                                                final EmbeddedTypeInfo... nestedTypeInfo) {
        if (nestedTypeInfo == null || nestedTypeInfo.length == 0) {
            return new EmbeddedTypeInfo(rel, embeddedType, emptyList());
        } else {
            return new EmbeddedTypeInfo(rel, embeddedType, asList(nestedTypeInfo));
        }
    }

    public static EmbeddedTypeInfo withEmbedded(final String rel,
                                                final Class<? extends HalRepresentation> embeddedType,
                                                final List<EmbeddedTypeInfo> nestedTypeInfo) {
        return new EmbeddedTypeInfo(rel, embeddedType, nestedTypeInfo);
    }

    /**
     * @return The link-relation type used to identify items of the embedded type.
     */
    public String getRel() {
        return rel;
    }

    /**
     * @return The Java class used to deserialize the embedded items for the link-relation type
     */
    public Class<? extends HalRepresentation> getType() {
        return type;
    }

    public List<EmbeddedTypeInfo> getNestedTypeInfo() {
        return nestedTypeInfo != null ? nestedTypeInfo : emptyList();
    }

    @Override
    public String toString() {
        return "EmbeddedTypeInfo{" +
                "rel='" + rel + '\'' +
                ", type=" + type.getSimpleName() +
                ", nestedTypeInfo=" + nestedTypeInfo +
                '}';
    }
} @JsonIgnoreProperties(ignoreUnknown = true)
@JsonPropertyOrder(alphabetic = false)
public class HalRepresentation {

    @JsonProperty(value = "_links")
    @JsonInclude(NON_NULL)
    private volatile Links links;
    @JsonProperty(value = "_embedded")
    @JsonInclude(NON_NULL)
    private volatile Embedded embedded;
    @JsonAnySetter
    private Map<String,JsonNode> attributes = new LinkedHashMap<>();
    @JsonIgnore
    private volatile Curies curies;

    /**
     *
     * @since 0.1.0
     */
    public HalRepresentation() {
        this(null, null, emptyCuries());
    }

    /**
     * Creates a HalRepresentation having {@link Links}
     *
     * @param links the Links of the HalRepresentation
     * @since 0.1.0
     */
    public HalRepresentation(final Links links) {
        this(links, null, emptyCuries());
    }

    /**
     * Creates a HalRepresentation having {@link Links} and a Curies that can be used
     * to configure the link-relation types that should always be rendered as an array of links.
     *
     * @param links the Links of the HalRepresentation
     * @param curies the Curies used to resolve curies
     * @since 1.0.0
     * @deprecated This method will most likely not be required by any users of edison-hal. Please contact me,
     * if you need this, otherwise the constructor will be removed in 3.0.0
     */
    @Deprecated
    public HalRepresentation(final Links links, final Curies curies) {
        this(links, null, curies);
    }

    /**
     * <p>
     *     Creates a HalRepresentation with {@link Links} and {@link Embedded} objects.
     * </p>
     * <p>
     *     If the Links do contain CURIs, the link-relation types of the embedded objects are shortened.
     * </p>
     *
     * @param links the Links of the HalRepresentation
     * @param embedded the Embedded items of the HalRepresentation
     * @since 0.1.0
     */
    @JsonCreator
    public HalRepresentation(final @JsonProperty("_links") Links links,
                             final @JsonProperty("_embedded") Embedded embedded) {
        this.curies = emptyCuries();
        this.links = links == null || links.isEmpty()
                ? null
                : links.using(this.curies);
        this.embedded = embedded == null || embedded.isEmpty()
                ? null
                : embedded.using(curies);
    }

    /**
     * <p>
     *     Creates a HalRepresentation with {@link Links}, {@link Embedded} objects and a Curies used to
     *     resolve curies from parent representations.
     * </p>
     * <p>
     *     If the Links do contain CURIs, the matching link-relation types of links and embedded objects are shortened.
     * </p>
     *
     * @param links the Links of the HalRepresentation
     * @param embedded the Embedded items of the HalRepresentation
     * @param curies the Curies used to resolve curies
     * @since 1.0.0
     * @deprecated This method will most likely not be required by any users of edison-hal. Please contact me,
     * if you need this, otherwise the constructor will be removed in 3.0.0
     */
    @Deprecated
    public HalRepresentation(final Links links,
                             final Embedded embedded,
                             final Curies curies) {
        this.curies = curies;
        this.links = links == null || links.isEmpty()
                ? null
                : links.using(this.curies);
        this.embedded = embedded == null || embedded.isEmpty()
                ? null
                : embedded.using(this.curies);
    }

    /**
     *
     * @return the Curies used by this HalRepresentation.
     */
    Curies getCuries() {
        return curies;
    }

    /**
     * Returns the Links of the HalRepresentation.
     *
     * @return Links
     * @since 0.1.0
     */
    @JsonIgnore
    public Links getLinks() {
        return links != null ? links : emptyLinks();
    }

    /**
     * Add links to the HalRepresentation.
     * <p>
     *     Links are only added if they are not {@link Link#isEquivalentTo(Link) equivalent}
     *     to already existing links.
     * </p>
     * @param links links that are added to this HalRepresentation
     * @return this
     */
    protected HalRepresentation add(final Links links) {
        this.links = this.links != null
                ? copyOf(this.links).with(links).build()
                : links.using(this.curies);
        if (embedded != null) {
            embedded = embedded.using(this.curies);
        }
        return this;
    }

    /**
     * Returns the Embedded objects of the HalRepresentation.
     *
     * @return Embedded, possibly beeing {@link Embedded#isEmpty() empty}
     */
    @JsonIgnore
    public Embedded getEmbedded() {
        return embedded != null ? embedded : emptyEmbedded();
    }

    /**
     * Returns extra attributes that were not mapped to properties of the HalRepresentation.
     *
     * @return map containing unmapped attributes
     */
    @JsonAnyGetter
    public Map<String, JsonNode> getAttributes() {
        if (!attributes.isEmpty()) {
            // For some reason, Jackson is reversing the order of extra attributes.
            // In order to avoid confusion, the attribute ordering is reversed here:
            Map<String, JsonNode> orderedMap = new LinkedHashMap<>();
            List<String> keys = new ArrayList<>(attributes.keySet());
            reverse(keys);
            keys.forEach(k -> orderedMap.put(k, attributes.get(k)));
            return orderedMap;
        } else {
            return attributes;
        }
    }

    /**
     * Returns the value of an extra attribute as a JsonNode, or null if no such attribute is present.
     *
     * @param name the name of the attribute
     * @return JsonNode or null
     */
    @JsonIgnore
    public JsonNode getAttribute(final String name) {
        return attributes.get(name);
    }

    /**
     * Adds embedded items for a link-relation type to the HalRepresentation.
     * <p>
     *     If {@code rel} is already present, it is replaced by the new embedded items.
     * </p>
     *
     * @param rel the link-relation type of the embedded items that are added or replaced
     * @param embeddedItems the new values for the specified link-relation type
     * @return this
     * @since 0.5.0
     */
    protected HalRepresentation withEmbedded(final String rel, final List<? extends HalRepresentation> embeddedItems) {
        embedded = copyOf(embedded).with(rel, embeddedItems).using(curies).build();
        return this;
    }

    /**
     * Adds an embedded item for a link-relation type to the HalRepresentation.
     * <p>
     *     The embedded item will be rendered as a single resource object.
     * </p>
     * <p>
     *     If {@code rel} is already present, it is replaced by the new embedded items.
     * </p>
     *
     * @param rel the link-relation type of the embedded item that is added or replaced
     * @param embeddedItem the new value for the specified link-relation type
     * @return this
     *
     * @since 2.0.0
     */
    protected HalRepresentation withEmbedded(final String rel, final HalRepresentation embeddedItem) {
        embedded = copyOf(embedded).with(rel, embeddedItem).using(curies).build();
        return this;
    }

    /**
     * Merges the Curies of an embedded resource with the Curies of this resource and updates
     * link-relation types in _links and _embedded items.
     *
     * @param curies the Curies of the embedding resource
     * @return this
     */
    HalRepresentation mergeWithEmbedding(final Curies curies) {
        this.curies = this.curies.mergeWith(curies);
        if (this.links != null) {

            removeDuplicateCuriesFromEmbedding(curies);

            this.links = this.links.using(this.curies);
            if (embedded != null) {
                embedded = embedded.using(this.curies);
            }
        } else {
            if (embedded != null) {
                embedded = embedded.using(curies);
            }
        }
        return this;
    }

    private void removeDuplicateCuriesFromEmbedding(final Curies curies) {
        if (this.links.hasLink("curies")) {
            final List<Link> curiLinks = new ArrayList<>(this.links.getLinksBy("curies"));
            curies.getCuries().forEach(curi -> {
                curiLinks.removeIf((link -> link.isEquivalentTo(curi)));
            });
            this.links = copyOf(this.links).replace("curies", curiLinks).build();
        }
    }

    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        HalRepresentation that = (HalRepresentation) o;

        if (links != null ? !links.equals(that.links) : that.links != null) return false;
        return embedded != null ? embedded.equals(that.embedded) : that.embedded == null;

    }

    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    @Override
    public int hashCode() {
        int result = links != null ? links.hashCode() : 0;
        result = 31 * result + (embedded != null ? embedded.hashCode() : 0);
        return result;
    }

    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    @Override
    public String toString() {
        return "HalRepresentation{" +
                "links=" + links +
                ", embedded=" + embedded +
                '}';
    }
} @JsonInclude(NON_ABSENT)
public class Link {

    @JsonIgnore
    private String rel;
    @JsonProperty
    private String href;
    @JsonProperty
    private Boolean templated;
    @JsonProperty
    private String type;
    @JsonProperty
    private String hreflang;
    @JsonProperty
    private String title;
    @JsonProperty
    private String name;
    @JsonProperty
    private String deprecation;
    @JsonProperty
    private String profile;

    /**
     * Create a link having only rel and href.
     * <p>
     *     If href is an URI template, the created link {@link #isTemplated() is templated} and {@link #templated}
     *     will be true.
     * </p>
     *
     * @since 0.1.0
     */
    private Link(final String rel, final String href) {
        this(rel, href, null, null, null, null, null, null);
    }

    /**
     * Create a link with all attributes.
     * <p>
     *     If href is an URI template, the created link {@link #isTemplated() is templated} and {@link #templated}
     *     will be true.
     * </p>
     *
     * @param rel mandatory link-relation type
     * @param href mandatory href or URI template
     * @param type optional media type
     * @param hrefLang optional href language
     * @param title optional human-readable title
     * @param name optional name
     * @param profile profile of the linked resource
     * @param deprecation information about whether or not the link is deprecated
     *
     * @since 0.1.0
     */
    private Link(final String rel,
                 final String href,
                 final String type,
                 final String hrefLang,
                 final String title,
                 final String name,
                 final String profile,
                 final String deprecation) {
        this.rel = rel;
        this.href = href;
        this.type = type;
        this.hreflang = hrefLang;
        this.title = title;
        this.name = name;
        this.profile = profile;
        this.deprecation = deprecation;
        if (fromTemplate(href).getVariables().length > 0) {
            this.templated = TRUE;
        }
    }

    /**
     * Create a 'self' link from a href.
     *
     * @param href href of the linked resource
     * @return Link
     *
     * @see <a href="http://www.iana.org/assignments/link-relations/link-relations.xhtml">IANA link-relations</a>
     * @since 0.1.0
     */
    public static Link self(final String href) {
        return new Link("self", href);
    }

    /**
     * <p>
     *     Create a 'curies' link (compact URI) with name and a URI template for the link-relation type.
     * </p>
     * <p>
     *     Curies may be used for brevity for custom link-relation type URIs. Curiess are established within a HAL document
     *     via a set of Link Objects with the relation type "curies" on the root Resource Object.
     *     These links contain a URI template with the token 'rel', and are named via the "name" property.
     * </p>
     * <pre><code>
     *     {
     *       "_links": {
     *         "self": { "href": "/orders" },
     *         "curies": [{
     *           "name": "acme",
     *           "href": "http://docs.acme.com/relations/{rel}",
     *           "templated": true
     *         }],
     *         "acme:widgets": { "href": "/widgets" }
     *       }
     *     }
     * </code></pre>
     *
     * @param name the short name of the CURI
     * @param relTemplate the template used to build link-relation types. Must contain a {rel} placeholder
     * @return Link
     *
     * @see <a href="http://www.iana.org/assignments/link-relations/link-relations.xhtml">IANA link-relations</a>
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-8.2">draft-kelly-json-hal-08#section-8.2</a>
     * @since 0.3.0
     */
    public static Link curi(final String name, final String relTemplate) {
        if (!relTemplate.contains("{rel}")) {
            throw new IllegalArgumentException("Not a CURI template. Template is required to contain a {rel} placeholder");
        }
        return new Link("curies", relTemplate, null, null, null, name, null, null);
    }

    /**
     * Create a 'profile' link from a href
     *
     * @param href the linked profile
     * @return Link
     *
     * @see <a href="http://www.iana.org/assignments/link-relations/link-relations.xhtml">IANA link-relations</a>
     * @since 0.1.0
     */
    public static Link profile(final String href) {
        return new Link("profile", href);
    }

    /**
     * Create a 'item' link from a href.
     * <p>
     *     If href is an URI template, the created link {@link #isTemplated() is templated} and {@link #templated}
     *     will be true.
     * </p>
     *
     * @param href the linked item
     * @return Link
     *
     * @see <a href="http://www.iana.org/assignments/link-relations/link-relations.xhtml">IANA link-relations</a>
     * @since 0.1.0
     */
    public static Link item(final String href) {
        return new Link("item", href);
    }

    /**
     * Create a 'collection' link from a href
     * <p>
     *     If href is an URI template, the created link {@link #isTemplated() is templated} and {@link #templated}
     *     will be true.
     * </p>
     *
     * @param href the linked collection
     * @return Link
     *
     * @see <a href="http://www.iana.org/assignments/link-relations/link-relations.xhtml">IANA link-relations</a>
     * @since 0.1.0
     */
    public static Link collection(final String href) {
        return new Link("collection", href);
    }

    /**
     * Create a link from a link-relation type and href.
     * <p>
     *     If href is an URI template, the created link {@link #isTemplated() is templated} and {@link #templated}
     *     will be true.
     * </p>
     *
     * @param rel registered link-relation type, or URI identifying a custom link-relation type.
     * @param href href of the linked resource
     * @return Link
     *
     * @see <a href="http://www.iana.org/assignments/link-relations/link-relations.xhtml">IANA link-relations</a>
     * @since 0.1.0
     */
    public static Link link(final String rel, final String href) {
        return new Link(rel, href);
    }

    /**
     * Create a Builder instance with mandatory link-relation type and href
     * <p>
     *     If href is an URI template, the link created by the Builder will be {@link #isTemplated() templated}.
     * </p>
     *
     * @param rel  the link-relation type of the link
     * @param href the href of the linked resource
     * @return a Builder for a Link.
     *
     * @since 0.1.0
     */
    public static Builder linkBuilder(final String rel, final String href) {
        return new Builder(rel, href);
    }

    /**
     * Create a Builder instance and initialize it from a prototype Link.
     *
     * @param prototype the prototype link
     * @return a Builder for a Link.
     *
     * @since 0.1.0
     */
    public static Builder copyOf(final Link prototype) {
        return new Builder(prototype.rel, prototype.href)
                .withType(prototype.type)
                .withProfile(prototype.profile)
                .withTitle(prototype.title)
                .withName(prototype.name)
                .withDeprecation(prototype.deprecation)
                .withHrefLang(prototype.hreflang);
    }

    /**
     * Returns the link-relation type of the link.
     *
     * @return link-relation type
     *
     * @since 0.2.0
     */
    @JsonIgnore
    public String getRel() {
        return rel;
    }

    /**
     * Returns the href of the link.
     * <p>
     * The "href" property is REQUIRED.
     * </p>
     * <p>
     * Its value is either a URI [RFC3986] or a URI Template [RFC6570]. Templates can
     * be expanded by creating an {@link com.damnhandy.uri.template.UriTemplate}.
     * </p>
     * <p>
     * If the value is a URI Template then the Link Object SHOULD have a
     * "templated" attribute whose value is true.
     * </p>
     *
     * @return href of the linked resource, or URI template, if the link is {@code templated}.
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.1">draft-kelly-json-hal-08#section-5.1</a>
     * @since 0.2.0
     */
    @JsonIgnore
    public String getHref() {
        return href;
    }

    /**
     * Returns the href of the link as a {@link UriTemplate} that can be expanded by providing the required template variables.
     *
     * @return uri template of the linked resource
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.1">draft-kelly-json-hal-08#section-5.1</a>
     * @since 0.5.1
     */
    @JsonIgnore
    public UriTemplate getHrefAsTemplate() {
        return UriTemplate.fromTemplate(href);
    }

    /**
     * Returns true, if the link is templated, false otherwise.
     * <p>
     * The "templated" property is OPTIONAL.
     * </p>
     * <p>
     * Its value is boolean and SHOULD be true when the Link Object's "href"
     * property is a URI Template.
     * </p>
     * <p>
     * Its value SHOULD be considered false if it is undefined or any other
     * value than true.
     * </p>
     *
     * @return boolean
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.2">draft-kelly-json-hal-08#section-5.2</a>
     * @since 0.2.0
     */
    @JsonIgnore
    public boolean isTemplated() {
        return templated != null ? templated : false;
    }

    /**
     * Returns the type of the link, or an empty String if no type is specified.
     * <p>
     * The "type" property is OPTIONAL.
     * </p>
     * <p>
     * Its value is a string used as a hint to indicate the media type
     * expected when dereferencing the target resource.
     * </p>
     *
     * @return type
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.3">draft-kelly-json-hal-08#section-5.3</a>
     * @since 0.2.0
     */
    @JsonIgnore
    public String getType() {
        return type != null ? type : "";
    }

    /**
     * Returns the hreflang of the link, or an empty String if no hreflang is specified.
     * <p>
     * The "hreflang" property is OPTIONAL.
     * </p>
     * <p>
     * Its value is a string and is intended for indicating the language of
     * the target resource (as defined by [RFC5988]).
     * </p>
     *
     * @return hreflang or empty string
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.8">draft-kelly-json-hal-08#section-5.8</a>
     * @since 0.2.0
     */
    @JsonIgnore
    public String getHreflang() {
        return hreflang != null ? hreflang : "";
    }

    /**
     * Returns the title of the link, or an empty String if no title is specified.
     * <p>
     * The "title" property is OPTIONAL.
     * </p>
     * <p>
     * Its value is a string and is intended for labelling the link with a
     * human-readable identifier (as defined by [RFC5988]).
     * </p>
     *
     * @return title or empty string
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.7">draft-kelly-json-hal-08#section-5.7</a>
     * @since 0.2.0
     */
    @JsonIgnore
    public String getTitle() {
        return title != null ? title : "";
    }

    /**
     * Returns the name of the link, or an empty String if no name is specified.
     * <p>
     * The "name" property is OPTIONAL.
     * </p>
     * <p>
     * Its value MAY be used as a secondary key for selecting Link Objects
     * which share the same relation type.
     * </p>
     *
     * @return name or empty string
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.5">draft-kelly-json-hal-08#section-5.5</a>
     * @since 0.2.0
     */
    @JsonIgnore
    public String getName() {
        return name != null ? name : "";
    }

    /**
     * Returns the profile of the link, or an empty String if no profile is specified.
     * <p>
     * The "profile" property is OPTIONAL.
     * </p>
     * <p>
     * Its value is a string which is a URI that hints about the profile of the target resource.
     * </p>
     *
     * @return profile or empty string
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.6">draft-kelly-json-hal-08#section-5.6</a>
     * @since 0.2.0
     */
    @JsonIgnore
    public String getProfile() {
        return profile != null ? profile : "";
    }

    /**
     * Returns the deprecation information, or an empty string, if the link is not deprecated.
     * <p>
     * The "deprecation" property is OPTIONAL.
     * </p>
     * <p>
     * Its presence indicates that the link is to be deprecated (i.e.
     * removed) at a future date.  Its value is a URL that SHOULD provide
     * further information about the deprecation.
     * </p>
     * <p>
     * A client SHOULD provide some notification (for example, by logging a
     * warning message) whenever it traverses over a link that has this
     * property.  The notification SHOULD include the deprecation property's
     * value so that a client manitainer can easily find information about
     * the deprecation.
     * </p>
     * @return URL
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.4">draft-kelly-json-hal-08#section-5.4</a>
     * @since 0.2.0
     */
    @JsonIgnore
    public String getDeprecation() {
        return deprecation != null ? deprecation : "";
    }

    /**
     * Two links are considerered equivalent, if they have the same {@link #getRel() link-relation type} and are
     * pointing to the {@link #getHref() same resource} in the same {@link #getType() representation} and
     * {@link #getProfile() profile}.
     *
     * @param other other link
     * @return true if the links are equivalent, false otherwise.
     */
    public boolean isEquivalentTo(final Link other) {
        return
                getRel().equals(other.getRel()) &&
                        getHref().equals(other.getHref()) &&
                        getType().equals(other.getType()) &&
                        getProfile().equals(other.getProfile());
    }

    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Link link = (Link) o;

        if (rel != null ? !rel.equals(link.rel) : link.rel != null) return false;
        if (href != null ? !href.equals(link.href) : link.href != null) return false;
        if (templated != null ? !templated.equals(link.templated) : link.templated != null) return false;
        if (type != null ? !type.equals(link.type) : link.type != null) return false;
        if (hreflang != null ? !hreflang.equals(link.hreflang) : link.hreflang != null) return false;
        if (title != null ? !title.equals(link.title) : link.title != null) return false;
        if (name != null ? !name.equals(link.name) : link.name != null) return false;
        if (profile != null ? !profile.equals(link.profile) : link.profile != null) return false;
        return deprecation != null ? deprecation.equals(link.deprecation) : link.deprecation == null;

    }

    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    @Override
    public int hashCode() {
        int result = rel != null ? rel.hashCode() : 0;
        result = 31 * result + (href != null ? href.hashCode() : 0);
        result = 31 * result + (templated != null ? templated.hashCode() : 0);
        result = 31 * result + (type != null ? type.hashCode() : 0);
        result = 31 * result + (hreflang != null ? hreflang.hashCode() : 0);
        result = 31 * result + (title != null ? title.hashCode() : 0);
        result = 31 * result + (name != null ? name.hashCode() : 0);
        result = 31 * result + (profile != null ? profile.hashCode() : 0);
        result = 31 * result + (deprecation != null ? deprecation.hashCode() : 0);
        return result;
    }

    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    @Override
    public String toString() {
        return "Link{" +
                "rel='" + getRel() + '\'' +
                ", href='" + getHref() + '\'' +
                ", templated=" + isTemplated() +
                ", type='" + getType() + '\'' +
                ", hreflang='" + getHreflang() + '\'' +
                ", title='" + getTitle() + '\'' +
                ", name='" + getName() + '\'' +
                ", profile='" + getProfile() + '\'' +
                ", deprecation=" + getDeprecation() +
                '}';
    }

    /**
     * A Builder used to build complex links.
     *
     * @since 0.1.0
     */
    public static class Builder {
        private String rel;
        private String href;
        private String type;
        private String hrefLang;
        private String title;
        private String name;
        private String profile;
        private String deprecation;

        /**
         * Create a Builder instance with mandatory link-relation type and href
         * <p>
         *     If href is an URI template, the created link {@link #isTemplated() is templated} and {@link #templated}
         *     will be true.
         * </p>
         *
         * @param rel  the link-relation type of the link
         * @param href the href of the linked resource
         *
         * @since 0.1.0
         */
        private Builder(final String rel, final String href) {
            this.rel = rel;
            this.href = href;
        }

        /**
         * Set the rel of the linked resource
         *
         * @param rel link-relation type
         * @return this
         *
         * @since 0.4.0
         */
        public Builder withRel(final String rel) {
            if (rel == null || rel.isEmpty()) {
                throw new IllegalArgumentException("The link-relation type is mandatory");
            }
            this.rel = rel;
            return this;
        }

        /**
         * Set the href of the linked resource
         * <p>
         *     If href is an URI template, the created link {@link #isTemplated() is templated} and {@link #templated}
         *     will be true.
         * </p>
         *
         * @param href href
         * @return this
         *
         * @since 0.4.0
         */
        public Builder withHref(final String href) {
            if (rel == null || rel.isEmpty()) {
                throw new IllegalArgumentException("The href parameter is mandatory");
            }
            this.href = href;
            return this;
        }

        /**
         * Set the media type of the linked resource
         *
         * @param type media type
         * @return this
         *
         * @since 0.1.0
         */
        public Builder withType(final String type) {
            this.type = type;
            return this;
        }

        /**
         * Set the language of the linked resource
         *
         * @param hrefLang the hreflang of the Link
         * @return this
         * @since 0.1.0
         */
        public Builder withHrefLang(final String hrefLang) {
            this.hrefLang = hrefLang;
            return this;
        }

        /**
         * Set the title attribute
         *
         * @param title the title of the linked resource.
         * @return this
         *
         * @since 0.1.0
         */
        public Builder withTitle(final String title) {
            this.title = title;
            return this;
        }

        /**
         * Set the name attribute.
         *
         * @param name the name of the linked resource.
         * @return this
         *
         * @since 0.1.0
         */
        public Builder withName(final String name) {
            this.name = name;
            return this;
        }

        /**
         * Set the profile attribute
         *
         * @param profile the profile of the representation
         * @return this
         *
         * @since 0.1.0
         */
        public Builder withProfile(final String profile) {
            this.profile = profile;
            return this;
        }

        /**
         * <p>
         *     Set deprecation attribute.
         * </p>
         * <p>
         *     Its presence indicates that the link is to be deprecated (i.e.
         *     removed) at a future date.  Its value is a URL that SHOULD provide
         *     further information about the deprecation.
         * </p>
         *
         * @param deprecation URL pointing to further information
         * @return this
         * @since 0.1.0
         */
        public Builder withDeprecation(final String deprecation) {
            this.deprecation = deprecation;
            return this;
        }

        /**
         * Builds the Link instance.
         *
         * @return Link
         *
         * @since 0.1.0
         */
        public Link build() {
            return new Link(rel, href, type, hrefLang, title, name, profile, deprecation);
        }
    }
} public static Embedded emptyEmbedded() {
    return new Embedded(null);
} public static EmbeddedTypeInfo withEmbedded(final String rel,
                                            final Class<? extends HalRepresentation> embeddedType,
                                            final EmbeddedTypeInfo... nestedTypeInfo) {
    if (nestedTypeInfo == null || nestedTypeInfo.length == 0) {
        return new EmbeddedTypeInfo(rel, embeddedType, emptyList());
    } else {
        return new EmbeddedTypeInfo(rel, embeddedType, asList(nestedTypeInfo));
    }
} @JsonInclude(NON_ABSENT)
public class Link {

    @JsonIgnore
    private String rel;
    @JsonProperty
    private String href;
    @JsonProperty
    private Boolean templated;
    @JsonProperty
    private String type;
    @JsonProperty
    private String hreflang;
    @JsonProperty
    private String title;
    @JsonProperty
    private String name;
    @JsonProperty
    private String deprecation;
    @JsonProperty
    private String profile;

    /**
     * Create a link having only rel and href.
     * <p>
     *     If href is an URI template, the created link {@link #isTemplated() is templated} and {@link #templated}
     *     will be true.
     * </p>
     *
     * @since 0.1.0
     */
    private Link(final String rel, final String href) {
        this(rel, href, null, null, null, null, null, null);
    }

    /**
     * Create a link with all attributes.
     * <p>
     *     If href is an URI template, the created link {@link #isTemplated() is templated} and {@link #templated}
     *     will be true.
     * </p>
     *
     * @param rel mandatory link-relation type
     * @param href mandatory href or URI template
     * @param type optional media type
     * @param hrefLang optional href language
     * @param title optional human-readable title
     * @param name optional name
     * @param profile profile of the linked resource
     * @param deprecation information about whether or not the link is deprecated
     *
     * @since 0.1.0
     */
    private Link(final String rel,
                 final String href,
                 final String type,
                 final String hrefLang,
                 final String title,
                 final String name,
                 final String profile,
                 final String deprecation) {
        this.rel = rel;
        this.href = href;
        this.type = type;
        this.hreflang = hrefLang;
        this.title = title;
        this.name = name;
        this.profile = profile;
        this.deprecation = deprecation;
        if (fromTemplate(href).getVariables().length > 0) {
            this.templated = TRUE;
        }
    }

    /**
     * Create a 'self' link from a href.
     *
     * @param href href of the linked resource
     * @return Link
     *
     * @see <a href="http://www.iana.org/assignments/link-relations/link-relations.xhtml">IANA link-relations</a>
     * @since 0.1.0
     */
    public static Link self(final String href) {
        return new Link("self", href);
    }

    /**
     * <p>
     *     Create a 'curies' link (compact URI) with name and a URI template for the link-relation type.
     * </p>
     * <p>
     *     Curies may be used for brevity for custom link-relation type URIs. Curiess are established within a HAL document
     *     via a set of Link Objects with the relation type "curies" on the root Resource Object.
     *     These links contain a URI template with the token 'rel', and are named via the "name" property.
     * </p>
     * <pre><code>
     *     {
     *       "_links": {
     *         "self": { "href": "/orders" },
     *         "curies": [{
     *           "name": "acme",
     *           "href": "http://docs.acme.com/relations/{rel}",
     *           "templated": true
     *         }],
     *         "acme:widgets": { "href": "/widgets" }
     *       }
     *     }
     * </code></pre>
     *
     * @param name the short name of the CURI
     * @param relTemplate the template used to build link-relation types. Must contain a {rel} placeholder
     * @return Link
     *
     * @see <a href="http://www.iana.org/assignments/link-relations/link-relations.xhtml">IANA link-relations</a>
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-8.2">draft-kelly-json-hal-08#section-8.2</a>
     * @since 0.3.0
     */
    public static Link curi(final String name, final String relTemplate) {
        if (!relTemplate.contains("{rel}")) {
            throw new IllegalArgumentException("Not a CURI template. Template is required to contain a {rel} placeholder");
        }
        return new Link("curies", relTemplate, null, null, null, name, null, null);
    }

    /**
     * Create a 'profile' link from a href
     *
     * @param href the linked profile
     * @return Link
     *
     * @see <a href="http://www.iana.org/assignments/link-relations/link-relations.xhtml">IANA link-relations</a>
     * @since 0.1.0
     */
    public static Link profile(final String href) {
        return new Link("profile", href);
    }

    /**
     * Create a 'item' link from a href.
     * <p>
     *     If href is an URI template, the created link {@link #isTemplated() is templated} and {@link #templated}
     *     will be true.
     * </p>
     *
     * @param href the linked item
     * @return Link
     *
     * @see <a href="http://www.iana.org/assignments/link-relations/link-relations.xhtml">IANA link-relations</a>
     * @since 0.1.0
     */
    public static Link item(final String href) {
        return new Link("item", href);
    }

    /**
     * Create a 'collection' link from a href
     * <p>
     *     If href is an URI template, the created link {@link #isTemplated() is templated} and {@link #templated}
     *     will be true.
     * </p>
     *
     * @param href the linked collection
     * @return Link
     *
     * @see <a href="http://www.iana.org/assignments/link-relations/link-relations.xhtml">IANA link-relations</a>
     * @since 0.1.0
     */
    public static Link collection(final String href) {
        return new Link("collection", href);
    }

    /**
     * Create a link from a link-relation type and href.
     * <p>
     *     If href is an URI template, the created link {@link #isTemplated() is templated} and {@link #templated}
     *     will be true.
     * </p>
     *
     * @param rel registered link-relation type, or URI identifying a custom link-relation type.
     * @param href href of the linked resource
     * @return Link
     *
     * @see <a href="http://www.iana.org/assignments/link-relations/link-relations.xhtml">IANA link-relations</a>
     * @since 0.1.0
     */
    public static Link link(final String rel, final String href) {
        return new Link(rel, href);
    }

    /**
     * Create a Builder instance with mandatory link-relation type and href
     * <p>
     *     If href is an URI template, the link created by the Builder will be {@link #isTemplated() templated}.
     * </p>
     *
     * @param rel  the link-relation type of the link
     * @param href the href of the linked resource
     * @return a Builder for a Link.
     *
     * @since 0.1.0
     */
    public static Builder linkBuilder(final String rel, final String href) {
        return new Builder(rel, href);
    }

    /**
     * Create a Builder instance and initialize it from a prototype Link.
     *
     * @param prototype the prototype link
     * @return a Builder for a Link.
     *
     * @since 0.1.0
     */
    public static Builder copyOf(final Link prototype) {
        return new Builder(prototype.rel, prototype.href)
                .withType(prototype.type)
                .withProfile(prototype.profile)
                .withTitle(prototype.title)
                .withName(prototype.name)
                .withDeprecation(prototype.deprecation)
                .withHrefLang(prototype.hreflang);
    }

    /**
     * Returns the link-relation type of the link.
     *
     * @return link-relation type
     *
     * @since 0.2.0
     */
    @JsonIgnore
    public String getRel() {
        return rel;
    }

    /**
     * Returns the href of the link.
     * <p>
     * The "href" property is REQUIRED.
     * </p>
     * <p>
     * Its value is either a URI [RFC3986] or a URI Template [RFC6570]. Templates can
     * be expanded by creating an {@link com.damnhandy.uri.template.UriTemplate}.
     * </p>
     * <p>
     * If the value is a URI Template then the Link Object SHOULD have a
     * "templated" attribute whose value is true.
     * </p>
     *
     * @return href of the linked resource, or URI template, if the link is {@code templated}.
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.1">draft-kelly-json-hal-08#section-5.1</a>
     * @since 0.2.0
     */
    @JsonIgnore
    public String getHref() {
        return href;
    }

    /**
     * Returns the href of the link as a {@link UriTemplate} that can be expanded by providing the required template variables.
     *
     * @return uri template of the linked resource
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.1">draft-kelly-json-hal-08#section-5.1</a>
     * @since 0.5.1
     */
    @JsonIgnore
    public UriTemplate getHrefAsTemplate() {
        return UriTemplate.fromTemplate(href);
    }

    /**
     * Returns true, if the link is templated, false otherwise.
     * <p>
     * The "templated" property is OPTIONAL.
     * </p>
     * <p>
     * Its value is boolean and SHOULD be true when the Link Object's "href"
     * property is a URI Template.
     * </p>
     * <p>
     * Its value SHOULD be considered false if it is undefined or any other
     * value than true.
     * </p>
     *
     * @return boolean
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.2">draft-kelly-json-hal-08#section-5.2</a>
     * @since 0.2.0
     */
    @JsonIgnore
    public boolean isTemplated() {
        return templated != null ? templated : false;
    }

    /**
     * Returns the type of the link, or an empty String if no type is specified.
     * <p>
     * The "type" property is OPTIONAL.
     * </p>
     * <p>
     * Its value is a string used as a hint to indicate the media type
     * expected when dereferencing the target resource.
     * </p>
     *
     * @return type
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.3">draft-kelly-json-hal-08#section-5.3</a>
     * @since 0.2.0
     */
    @JsonIgnore
    public String getType() {
        return type != null ? type : "";
    }

    /**
     * Returns the hreflang of the link, or an empty String if no hreflang is specified.
     * <p>
     * The "hreflang" property is OPTIONAL.
     * </p>
     * <p>
     * Its value is a string and is intended for indicating the language of
     * the target resource (as defined by [RFC5988]).
     * </p>
     *
     * @return hreflang or empty string
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.8">draft-kelly-json-hal-08#section-5.8</a>
     * @since 0.2.0
     */
    @JsonIgnore
    public String getHreflang() {
        return hreflang != null ? hreflang : "";
    }

    /**
     * Returns the title of the link, or an empty String if no title is specified.
     * <p>
     * The "title" property is OPTIONAL.
     * </p>
     * <p>
     * Its value is a string and is intended for labelling the link with a
     * human-readable identifier (as defined by [RFC5988]).
     * </p>
     *
     * @return title or empty string
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.7">draft-kelly-json-hal-08#section-5.7</a>
     * @since 0.2.0
     */
    @JsonIgnore
    public String getTitle() {
        return title != null ? title : "";
    }

    /**
     * Returns the name of the link, or an empty String if no name is specified.
     * <p>
     * The "name" property is OPTIONAL.
     * </p>
     * <p>
     * Its value MAY be used as a secondary key for selecting Link Objects
     * which share the same relation type.
     * </p>
     *
     * @return name or empty string
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.5">draft-kelly-json-hal-08#section-5.5</a>
     * @since 0.2.0
     */
    @JsonIgnore
    public String getName() {
        return name != null ? name : "";
    }

    /**
     * Returns the profile of the link, or an empty String if no profile is specified.
     * <p>
     * The "profile" property is OPTIONAL.
     * </p>
     * <p>
     * Its value is a string which is a URI that hints about the profile of the target resource.
     * </p>
     *
     * @return profile or empty string
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.6">draft-kelly-json-hal-08#section-5.6</a>
     * @since 0.2.0
     */
    @JsonIgnore
    public String getProfile() {
        return profile != null ? profile : "";
    }

    /**
     * Returns the deprecation information, or an empty string, if the link is not deprecated.
     * <p>
     * The "deprecation" property is OPTIONAL.
     * </p>
     * <p>
     * Its presence indicates that the link is to be deprecated (i.e.
     * removed) at a future date.  Its value is a URL that SHOULD provide
     * further information about the deprecation.
     * </p>
     * <p>
     * A client SHOULD provide some notification (for example, by logging a
     * warning message) whenever it traverses over a link that has this
     * property.  The notification SHOULD include the deprecation property's
     * value so that a client manitainer can easily find information about
     * the deprecation.
     * </p>
     * @return URL
     *
     * @see <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.4">draft-kelly-json-hal-08#section-5.4</a>
     * @since 0.2.0
     */
    @JsonIgnore
    public String getDeprecation() {
        return deprecation != null ? deprecation : "";
    }

    /**
     * Two links are considerered equivalent, if they have the same {@link #getRel() link-relation type} and are
     * pointing to the {@link #getHref() same resource} in the same {@link #getType() representation} and
     * {@link #getProfile() profile}.
     *
     * @param other other link
     * @return true if the links are equivalent, false otherwise.
     */
    public boolean isEquivalentTo(final Link other) {
        return
                getRel().equals(other.getRel()) &&
                        getHref().equals(other.getHref()) &&
                        getType().equals(other.getType()) &&
                        getProfile().equals(other.getProfile());
    }

    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Link link = (Link) o;

        if (rel != null ? !rel.equals(link.rel) : link.rel != null) return false;
        if (href != null ? !href.equals(link.href) : link.href != null) return false;
        if (templated != null ? !templated.equals(link.templated) : link.templated != null) return false;
        if (type != null ? !type.equals(link.type) : link.type != null) return false;
        if (hreflang != null ? !hreflang.equals(link.hreflang) : link.hreflang != null) return false;
        if (title != null ? !title.equals(link.title) : link.title != null) return false;
        if (name != null ? !name.equals(link.name) : link.name != null) return false;
        if (profile != null ? !profile.equals(link.profile) : link.profile != null) return false;
        return deprecation != null ? deprecation.equals(link.deprecation) : link.deprecation == null;

    }

    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    @Override
    public int hashCode() {
        int result = rel != null ? rel.hashCode() : 0;
        result = 31 * result + (href != null ? href.hashCode() : 0);
        result = 31 * result + (templated != null ? templated.hashCode() : 0);
        result = 31 * result + (type != null ? type.hashCode() : 0);
        result = 31 * result + (hreflang != null ? hreflang.hashCode() : 0);
        result = 31 * result + (title != null ? title.hashCode() : 0);
        result = 31 * result + (name != null ? name.hashCode() : 0);
        result = 31 * result + (profile != null ? profile.hashCode() : 0);
        result = 31 * result + (deprecation != null ? deprecation.hashCode() : 0);
        return result;
    }

    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    @Override
    public String toString() {
        return "Link{" +
                "rel='" + getRel() + '\'' +
                ", href='" + getHref() + '\'' +
                ", templated=" + isTemplated() +
                ", type='" + getType() + '\'' +
                ", hreflang='" + getHreflang() + '\'' +
                ", title='" + getTitle() + '\'' +
                ", name='" + getName() + '\'' +
                ", profile='" + getProfile() + '\'' +
                ", deprecation=" + getDeprecation() +
                '}';
    }

    /**
     * A Builder used to build complex links.
     *
     * @since 0.1.0
     */
    public static class Builder {
        private String rel;
        private String href;
        private String type;
        private String hrefLang;
        private String title;
        private String name;
        private String profile;
        private String deprecation;

        /**
         * Create a Builder instance with mandatory link-relation type and href
         * <p>
         *     If href is an URI template, the created link {@link #isTemplated() is templated} and {@link #templated}
         *     will be true.
         * </p>
         *
         * @param rel  the link-relation type of the link
         * @param href the href of the linked resource
         *
         * @since 0.1.0
         */
        private Builder(final String rel, final String href) {
            this.rel = rel;
            this.href = href;
        }

        /**
         * Set the rel of the linked resource
         *
         * @param rel link-relation type
         * @return this
         *
         * @since 0.4.0
         */
        public Builder withRel(final String rel) {
            if (rel == null || rel.isEmpty()) {
                throw new IllegalArgumentException("The link-relation type is mandatory");
            }
            this.rel = rel;
            return this;
        }

        /**
         * Set the href of the linked resource
         * <p>
         *     If href is an URI template, the created link {@link #isTemplated() is templated} and {@link #templated}
         *     will be true.
         * </p>
         *
         * @param href href
         * @return this
         *
         * @since 0.4.0
         */
        public Builder withHref(final String href) {
            if (rel == null || rel.isEmpty()) {
                throw new IllegalArgumentException("The href parameter is mandatory");
            }
            this.href = href;
            return this;
        }

        /**
         * Set the media type of the linked resource
         *
         * @param type media type
         * @return this
         *
         * @since 0.1.0
         */
        public Builder withType(final String type) {
            this.type = type;
            return this;
        }

        /**
         * Set the language of the linked resource
         *
         * @param hrefLang the hreflang of the Link
         * @return this
         * @since 0.1.0
         */
        public Builder withHrefLang(final String hrefLang) {
            this.hrefLang = hrefLang;
            return this;
        }

        /**
         * Set the title attribute
         *
         * @param title the title of the linked resource.
         * @return this
         *
         * @since 0.1.0
         */
        public Builder withTitle(final String title) {
            this.title = title;
            return this;
        }

        /**
         * Set the name attribute.
         *
         * @param name the name of the linked resource.
         * @return this
         *
         * @since 0.1.0
         */
        public Builder withName(final String name) {
            this.name = name;
            return this;
        }

        /**
         * Set the profile attribute
         *
         * @param profile the profile of the representation
         * @return this
         *
         * @since 0.1.0
         */
        public Builder withProfile(final String profile) {
            this.profile = profile;
            return this;
        }

        /**
         * <p>
         *     Set deprecation attribute.
         * </p>
         * <p>
         *     Its presence indicates that the link is to be deprecated (i.e.
         *     removed) at a future date.  Its value is a URL that SHOULD provide
         *     further information about the deprecation.
         * </p>
         *
         * @param deprecation URL pointing to further information
         * @return this
         * @since 0.1.0
         */
        public Builder withDeprecation(final String deprecation) {
            this.deprecation = deprecation;
            return this;
        }

        /**
         * Builds the Link instance.
         *
         * @return Link
         *
         * @since 0.1.0
         */
        public Link build() {
            return new Link(rel, href, type, hrefLang, title, name, profile, deprecation);
        }
    }
} public final class LinkPredicates {

    private LinkPredicates() {}

    /**
     * Returns a Predicate that is matching every link.
     *
     * @return Predicate used to select links
     */
    public static Predicate<Link> always() {
        return link -> true;
    }

    /**
     * Returns a Predicate that is matching links having the specified type {@link Link#getType() type}
     *
     * @param type the expected media type of the link
     * @return Predicate used to select links
     */
    public static Predicate<Link> havingType(final String type) {
        return link -> type.equals(link.getType());
    }

    /**
     * Returns a Predicate that is matching links having the specified type {@link Link#getType() type}, or no type
     * at all.
     *
     * @param type the expected media type of the link
     * @return Predicate used to select links
     */
    public static Predicate<Link> optionallyHavingType(final String type) {
        return havingType(type).or(havingType(""));
    }

    /**
     * Returns a Predicate that is matching links having the specified profile {@link Link#getProfile() profile}
     *
     * @param profile the expected profile of the link
     * @return Predicate used to select links
     */
    public static Predicate<Link> havingProfile(final String profile) {
        return link -> profile.equals(link.getProfile());
    }

    /**
     * Returns a Predicate that is matching links having the specified profile {@link Link#getProfile() profile}, or
     * no profile at all.
     *
     * @param profile the expected profile of the link
     * @return Predicate used to select links
     */
    public static Predicate<Link> optionallyHavingProfile(final String profile) {
        return havingProfile(profile).or(havingProfile(""));
    }

    /**
     * Returns a Predicate that is matching links having the specified name {@link Link#getName() name}
     *
     * @param name the expected name of the link
     * @return Predicate used to select links
     */
    public static Predicate<Link> havingName(final String name) {
        return link -> name.equals(link.getName());
    }

    /**
     * Returns a Predicate that is matching links having the specified name {@link Link#getName() name}, or
     * no name at all.
     *
     * @param name the expected name of the link
     * @return Predicate used to select links
     */
    public static Predicate<Link> optionallyHavingName(final String name) {
        return havingName(name).or(havingName(""));
    }
} public static Links emptyLinks() {
    return new Links();
} public static Builder linkingTo() {
    return new Builder();
} public class Traverson {

    static class Hop {
        private static final JsonPointer EMPTY = JsonPointer.compile("");

        /** Link-relation type of the hop. */
        final String rel;
        final Predicate<Link> predicate;
        /** URI-template variables used when following the hop. */
        final Map<String,Object> vars;
        /** Ignore possibly embedded items. */
        final boolean ignoreEmbedded;

        Hop(final String rel,
            final Predicate<Link> predicate,
            final Map<String, Object> vars,
            final boolean ignoreEmbedded) {
            this.rel = rel;
            this.predicate = predicate;
            this.vars = vars;
            this.ignoreEmbedded = ignoreEmbedded;
        }
    }

    static class Hops {
        private static final JsonPointer CURRENT = JsonPointer.compile("");
        private final List<Hop> hops = new ArrayList<>();

        Hops(final Hop... hops) {
            if (hops.length > 0) {
                this.hops.addAll(asList(hops));
            }
        }

        public static Hops of() {
            return new Hops();
        }

        public static Hops of(final Hop... hops) {
            return new Hops(hops);
        }

        void add(final Hop hop) {
            if (hops.size() > 0) {
                hops.add(hop);
            } else {
                hops.add(hop);
            }
        }

        public Hop get(int index) {
            return hops.get(index);
        }

        public boolean isEmpty() {
            return hops.isEmpty();
        }

        public int size() {
            return hops.size();
        }

        public Hop pop() {
            return hops.remove(0);
        }

    }

    private static final Logger LOG = getLogger(Traverson.class);

    public static final ObjectMapper DEFAULT_JSON_MAPPER = new ObjectMapper();
    static {
        DEFAULT_JSON_MAPPER.configure(ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
        DEFAULT_JSON_MAPPER.findAndRegisterModules();
    }

    private final ObjectMapper objectMapper;
    private final LinkResolver linkResolver;

    private URL startWith;
    private URL contextUrl;
    private List<? extends HalRepresentation> lastResult;
    private Hops hops = Hops.of();

    private Traverson(final LinkResolver linkResolver, final ObjectMapper objectMapper) {
        this.linkResolver = linkResolver;
        this.objectMapper = objectMapper;
    }

    /**
     * <p>
     *     Create a Traverson that is using the given {@link Function} to resolve a Link and return a HAL+JSON document.
     * </p>
     * <p>
     *     The function will be called by the Traverson, whenever a Link must be followed. The Traverson will
     *     take care of URI templates (templated links), so the implementation of the function can rely on the
     *     Link parameter to be not {@link Link#isTemplated() templated}.
     * </p>
     * <p>
     *     Typical implementations of the Function will rely on some HTTP client. Especially in this case,
     *     the function should take care of the link's {@link Link#getType() type} and {@link Link#getProfile()},
     *     so the proper HTTP Accept header is used.
     * </p>
     * @param linkResolver A function that gets a Link of a resource and returns a HAL+JSON document.
     * @return Traverson
     */
    public static Traverson traverson(final LinkResolver linkResolver) {
        return new Traverson(linkResolver, DEFAULT_JSON_MAPPER);
    }

    /**
     * <p>
     *     Create a Traverson that is using the given {@link Function} to resolve a Link and return a HAL+JSON document.
     * </p>
     * <p>
     *     The function will be called by the Traverson, whenever a Link must be followed. The Traverson will
     *     take care of URI templates (templated links), so the implementation of the function can rely on the
     *     Link parameter to be not {@link Link#isTemplated() templated}.
     * </p>
     * <p>
     *     Typical implementations of the Function will rely on some HTTP client. Especially in this case,
     *     the function should take care of the link's {@link Link#getType() type} and {@link Link#getProfile()},
     *     so the proper HTTP Accept header is used.
     * </p>
     *
     * @param linkResolver A function that gets a Link of a resource and returns a HAL+JSON document.
     * @param objectMapper The ObjectMapper instance used to parse documents.
     * @return Traverson
     *
     * @since 2.0.0
     */
    public static Traverson traverson(final LinkResolver linkResolver,
                                      final ObjectMapper objectMapper) {
        if (objectMapper.isEnabled(ACCEPT_SINGLE_VALUE_AS_ARRAY)) {
            return new Traverson(linkResolver, objectMapper);
        } else {
            return new Traverson(linkResolver, objectMapper
                    .copy()
                    .configure(ACCEPT_SINGLE_VALUE_AS_ARRAY, true));
        }
    }

    /**
     * Creates a Map containing key-value pairs used as parameters for UriTemplate variables.
     * <p>
     *     Basically, this method is only adding some semantic sugar, so you can write code like this:
     * </p>
     * <pre><code>
     *     traverson(this::httpGet)
     *             .startWith("http://example.org")
     *             .follow("foo", withVars("param1", "value1", "param2", "value2"))
     *             .getResource();
     * </code></pre>
     *
     * @param key the first key
     * @param value the value associated with the first key
     * @param more Optionally more key-value pairs. The number of parameters is must even, keys must be Strings.
     * @return Map
     */
    public static Map<String, Object> withVars(final String key, final Object value, final Object... more) {
        if (more != null && more.length % 2 != 0) {
            throw new IllegalArgumentException("The number of template variables (key-value pairs) must be even");
        }
        final Map<String, Object> map = new HashMap<>();
        map.put(key, value);
        if (more != null) {
            for (int i=0; i<more.length; i+=2) {
                map.put(more[i].toString(), more[i+1]);
            }
        }
        return map;
    }

    /**
     * Creates a list of link-relation types used to {@link #follow(List) follow} multiple hops, optionally
     * using {@link #follow(List, Map) uri template variables}.
     * <p>
     *     This method is only adding some semantic sugar, so you can write code like this:
     * </p>
     * <pre><code>
     *     traverson(this::httpGet)
     *             .startWith("http://example.org")
     *             .follow(
     *                     <strong>hops("foo", "bar"),</strong>
     *                     withVars("param1", "value1", "param2", "value2"))
     *             .getResource();
     * </code></pre>
     *
     * @param rel link-relation type
     * @param moreRels optionally more link-relation types
     * @return list of link-relation types
     */
    public static List<String> hops(final String rel, final String... moreRels) {
        if (moreRels != null) {
            return new ArrayList<String>() {{
                add(requireNonNull(rel));
                addAll(asList(moreRels));
            }};
        } else {
            return singletonList(requireNonNull(rel));
        }
    }

    /**
     * Start traversal at the application/hal+json resource idenfied by {@code uri}.
     *
     * @param uriTemplate the {@code URI} of the initial HAL resource.
     * @param vars uri-template variables used to build links.
     * @return Traverson initialized with the {@link HalRepresentation} identified by {@code uri}.
     */
    public Traverson startWith(final String uriTemplate, final Map<String, Object> vars) {
        return startWith(fromTemplate(uriTemplate).expand(vars));
    }

    /**
     * Start traversal at the application/hal+json resource idenfied by {@code uri}.
     *
     * @param uri the {@code URI} of the initial HAL resource.
     * @return Traverson initialized with the {@link HalRepresentation} identified by {@code uri}.
     */
    public Traverson startWith(final String uri) {
        startWith = hrefToUrl(uri);
        contextUrl = startWith;
        lastResult = null;
        return this;
    }

    /**
     * Start traversal at the given HAL resource.
     *
     * <p>
     *     It is expected, that the HalRepresentation has an absolute 'self' link, or that all links to other
     *     resources are absolute links. If this is not assured, {@link #startWith(URL, HalRepresentation)} must
     *     be used, so relative links can be resolved.
     * </p>
     *
     * @param resource the initial HAL resource.
     * @return Traverson initialized with the specified {@link HalRepresentation}.
     */
    public Traverson startWith(final HalRepresentation resource) {
        this.startWith = null;
        this.lastResult = singletonList(requireNonNull(resource));
        Optional<Link> self = resource.getLinks().getLinkBy("self");
        if (self.isPresent()) {
                this.contextUrl = linkToUrl(self.get());
        } else {
            resource
                    .getLinks()
                    .stream()
                    .filter(link -> !link.getHref().matches("http.*//.*"))
                    .findAny()
                    .ifPresent(link -> {throw new IllegalArgumentException("Unable to construct Traverson from HalRepresentation w/o self link but containing relative links. Please try Traverson.startWith(URL, HalRepresentation)");});
        }
        return this;
    }

    /**
     * Start traversal at the given HAL resource, using the {@code contextUrl} to resolve relative links.
     *
     * <p>
     *     If the {@code resource} is obtained from another Traverson, {@link Traverson#getCurrentContextUrl()}
     *     can be called to get this URL.
     * </p>
     *
     * @param contextUrl URL of the Traverson's current context, used to resolve relative links
     * @param resource the initial HAL resource.
     * @return Traverson initialized with the specified {@link HalRepresentation} and {@code contextUrl}.
     * @since 1.0.0
     */
    public Traverson startWith(final URL contextUrl, final HalRepresentation resource) {
        this.startWith = null;
        this.contextUrl = requireNonNull(contextUrl);
        this.lastResult = singletonList(requireNonNull(resource));
        return this;
    }

    /**
     * Follow the first {@link Link} of the current resource, selected by its link-relation type.
     * <p>
     *     If the current node has {@link Embedded embedded} items with the specified {@code rel},
     *     these items are used instead of resolving the associated {@link Link}.
     * </p>
     * <p>
     *     Sometimes, only a subset of a linked resource is embedded into the resource. In this case,
     *     embedded items can be ignored by using {@link #followLink(String)} instead of this method.
     * </p>
     *
     * @param rel the link-relation type of the followed link
     * @return this
     */
    public Traverson follow(final String rel) {
        return follow(rel, always(), emptyMap());
    }

    /**
     * Follow the first {@link Link} of the current resource, selected by its link-relation type.
     * <p>
     *     Other than {@link #follow(String)}, this method will ignore possibly embedded resources
     *     with the same link-relation type. Only if the link is missing, the embedded resource is
     *     used.
     * </p>
     *
     * @param rel the link-relation type of the followed link
     * @return this
     * @since 2.0.0
     */
    public Traverson followLink(final String rel) {
        return followLink(rel, always(), emptyMap());
    }

    /**
     * Follow the first {@link Link} of the current resource that is matching the link-relation type and
     * the {@link LinkPredicates predicate}.
     * <p>
     *     If the current node has {@link Embedded embedded} items with the specified {@code rel},
     *     these items are used instead of resolving the associated {@link Link}.
     * </p>
     * <p>
     *     Sometimes, only a subset of a linked resource is embedded into the resource. In this case,
     *     embedded items can be ignored by using {@link #followLink(String,Predicate)} instead of this method.
     * </p>
     *
     * @param rel the link-relation type of the followed link
     * @param predicate the predicate used to select the link to follow
     * @return this
     * @since 1.0.0
     */
    public Traverson follow(final String rel,
                            final Predicate<Link> predicate) {
        return follow(rel, predicate, emptyMap());
    }

    /**
     * Follow the first {@link Link} of the current resource that is matching the link-relation type and
     * the {@link LinkPredicates predicate}.
     * <p>
     *     Other than {@link #follow(String, Predicate)}, this method will ignore possibly embedded resources
     *     with the same link-relation type. Only if the link is missing, the embedded resource is
     *     used.
     * </p>
     *
     * @param rel the link-relation type of the followed link
     * @param predicate the predicate used to select the link to follow
     * @return this
     * @since 2.0.0
     */
    public Traverson followLink(final String rel,
                                final Predicate<Link> predicate) {
        return followLink(rel, predicate, emptyMap());
    }

    /**
     * Follow multiple link-relation types, one by one.
     * <p>
     *     Embedded items are used instead of resolving links, if present in the returned HAL documents.
     * </p>
     *
     * @param rels the link-relation types of the followed links
     * @return this
     * @since 1.0.0
     */
    public Traverson follow(final List<String> rels) {
        return follow(rels, always(), emptyMap());
    }

    /**
     * Follow multiple link-relation types, one by one, and select the links using the specified
     * {@link LinkPredicates predicate}.
     * <p>
     *     Embedded items are used instead of resolving links, if present in the returned HAL documents.
     * </p>
     *
     * @param rels the link-relation types of the followed links
     * @param predicate the predicated used to select the link to follow
     * @return this
     * @since 1.0.0
     */
    public Traverson follow(final List<String> rels, 
                            final Predicate<Link> predicate) {
        return follow(rels, predicate, emptyMap());
    }

    /**
     * Follow multiple link-relation types, one by one.
     * <p>
     *     Templated links are expanded to URIs using the specified template variables.
     * </p>
     * <p>
     *     Embedded items are used instead of resolving links, if present in the returned HAL documents.
     * </p>
     *
     * @param rels the link-relation types of the followed links
     * @param vars uri-template variables used to build links.
     * @return this
     * @since 1.0.0
     */
    public Traverson follow(final List<String> rels, 
                            final Map<String, Object> vars) {
        for (String rel : rels) {
            follow(rel, always(), vars);
        }
        return this;
    }

    /**
     * Follow multiple link-relation types, one by one. The {@link LinkPredicates predicate} is used to select
     * the matching links, if there are more than one per link-relation type.
     * <p>
     *     Templated links are expanded to URIs using the specified template variables.
     * </p>
     * <p>
     *     Embedded items are used instead of resolving links, if present in the returned HAL documents.
     * </p>
     *
     * @param rels the link-relation types of the followed links
     * @param predicate the predicate used to select the link to follow
     * @param vars uri-template variables used to build links.
     * @return this
     * @since 1.0.0
     */
    public Traverson follow(final List<String> rels, 
                            final Predicate<Link> predicate, 
                            final Map<String, Object> vars) {
        for (String rel : rels) {
            follow(rel, predicate, vars);
        }
        return this;
    }

    /**
     * Follow the first {@link Link} of the current resource, selected by its link-relation type.
     * <p>
     *     Templated links are expanded to URIs using the specified template variables.
     * </p>
     * <p>
     *     If the current node has {@link Embedded embedded} items with the specified {@code rel},
     *     these items are used instead of resolving the associated {@link Link}.
     * </p>
     * <p>
     *     Sometimes, only a subset of a linked resource is embedded into the resource. In this case,
     *     embedded items can be ignored by using {@link #followLink(String,Map)} instead of this method.
     * </p>
     *
     * @param rel the link-relation type of the followed link
     * @param vars uri-template variables used to build links.
     * @return this
     * @since 1.0.0
     */
    public Traverson follow(final String rel, 
                            final Map<String, Object> vars) {
        return follow(rel, always(), vars);
    }

    /**
     * Follow the first {@link Link} of the current resource, selected by its link-relation type.
     * <p>
     *     Templated links are expanded to URIs using the specified template variables.
     * </p>
     * <p>
     *     Other than {@link #follow(String, Map)}, this method will ignore possibly embedded resources
     *     with the same link-relation type. Only if the link is missing, the embedded resource is
     *     used.
     * </p>
     *
     * @param rel the link-relation type of the followed link
     * @param vars uri-template variables used to build links.
     * @return this
     * @since 2.0.0
     */
    public Traverson followLink(final String rel, 
                                final Map<String, Object> vars) {
        return followLink(rel, always(), vars);
    }

    /**
     * Follow the first {@link Link} of the current resource, selected by its link-relation type. The
     * {@link LinkPredicates predicate} is used to select the matching link, if multiple links per link-relation type
     * are available.
     * <p>
     *     Templated links are expanded to URIs using the specified template variables.
     * </p>
     * <p>
     *     If the current node has {@link Embedded embedded} items with the specified {@code rel},
     *     these items are used instead of resolving the associated {@link Link}.
     * </p>
     *
     * @param rel the link-relation type of the followed link
     * @param predicate the predicate used to select the link to follow.
     * @param vars uri-template variables used to build links.
     * @return this
     * @since 1.0.0
     */
    public Traverson follow(final String rel, 
                            final Predicate<Link> predicate, 
                            final Map<String, Object> vars) {
        checkState();
        hops.add(new Hop(rel, predicate, vars, false));
        return this;
    }

    /**
     * Follow the first {@link Link} of the current resource, selected by its link-relation type. The
     * {@link LinkPredicates predicate} is used to select the matching link, if multiple links are available for the
     * specified link-relation type.
     * <p>
     *     Templated links are expanded to URIs using the specified template variables.
     * </p>
     * <p>
     *     Other than {@link #follow(String, Predicate, Map)}, this method will ignore possibly embedded resources
     *     with the same link-relation type. Only if the link is missing, the embedded resource is
     *     used.
     * </p>
     *
     * @param rel the link-relation type of the followed link
     * @param predicate the predicate used to select the link to follow.
     * @param vars uri-template variables used to build links.
     * @return this
     * @since 2.0.0
     */
    public Traverson followLink(final String rel, 
                                final Predicate<Link> predicate, 
                                final Map<String, Object> vars) {
        checkState();
        hops.add(new Hop(rel, predicate, vars, true));
        return this;
    }

    /**
     * Iterates over pages by following 'next' links. For every page, a {@code Traverson} is created and provided as a
     * parameter to the callback function.
     *
     * <pre>
     *                              next
     *     ... --&gt; HalRepresentation --&gt; HalRepresentation
     *                     |                     |
     *                     v N item              v N item
     *              HalRepresentation     HalRepresentation
     * </pre>
     *
     * <p>
     *     The {@code Traverson} is backed by a {@link HalRepresentation} with no type information. If pages may contain
     *     embedded items, and if a specific sub-type of HalRepresentation is required for items,
     *     {@link #paginateNext(EmbeddedTypeInfo,PageHandler)} or {@link #paginateNextAs(Class,EmbeddedTypeInfo,PageHandler)}
     *     should be used instead of this method.
     * </p>
     *
     * <p>
     *     Iteration stops if the callback returns {@code false}, or if the last page is processed.
     * </p>
     *
     * @param pageHandler the callback used to process pages of items.
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 1.0.0
     */
    public void paginateNext(final PageHandler pageHandler) throws IOException {
        paginateAs("next", HalRepresentation.class, null, pageHandler);
    }

    /**
     * Iterates over pages by following 'next' links. For every page, a {@code Traverson} is created and provided as a
     * parameter to the callback function.
     *
     * <pre>
     *                              next
     *     ... --&gt; HalRepresentation --&gt; HalRepresentation
     *                     |                     |
     *                     v N item              v N item
     *               &lt;embedded type&gt;      &lt;embedded type&gt;
     * </pre>
     *
     * <p>
     *     The {@code Traverson} is backed by a {@link HalRepresentation} with {@link EmbeddedTypeInfo}. This way it
     *     is possible to access items embedded into the page resources as specific subtypes of HalRepresentation.
     * </p>
     *
     * <p>
     *     Iteration stops if the callback returns {@code false}, or if the last page is processed.
     * </p>
     *
     * @param embeddedTypeInfo type information about possibly embedded items.
     * @param pageHandler the callback used to process pages of items.
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 1.0.0
     */
    public void paginateNext(final EmbeddedTypeInfo embeddedTypeInfo,
                             final PageHandler pageHandler) throws IOException {
        paginateAs("next", HalRepresentation.class, embeddedTypeInfo, pageHandler);
    }

    /**
     * Iterates over pages by following 'next' links. For every page, a {@code Traverson} is created and provided as a
     * parameter to the callback function.
     *
     * <pre>
     *                        next
     *     ... --&gt; &lt;page type&gt; --&gt; &lt;page type&gt;
     *                 |                 |
     *                 v N item          v N item
     *         HalRepresentation  HalRepresentation
     * </pre>
     *
     * <p>
     *     The {@code Traverson} is backed by a representation of {@code pageType}, but without
     *     {@link EmbeddedTypeInfo}. This way it
     *     is possible to access special attributes of the page by calling {@code pageTraverson.getResourceAs(pageType)}
     *     in the callback function. If the paged items need to be accessed as a subtype of HalRepresentation, you
     *     can call pageTraverson.follow(rel).streamAs(MyItemType.class) - but ONLY if the items are not embedded into
     *     the page.
     * </p>
     *
     * <p>
     *     For embedded items having a subtype of HalRepresentation, {@link #paginateNextAs(Class, EmbeddedTypeInfo, PageHandler)}
     *     must be used instead of this method, otherwise a {@code ClassCastException} will be thrown.
     * </p>
     *
     * <p>
     *     Iteration stops if the callback returns {@code false}, or if the last page is processed.
     * </p>
     *
     * @param pageType the subtype of HalRepresentation of the page resources
     * @param pageHandler callback function called for every page
     * @param <T> subtype of HalRepresentation
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 1.0.0
     */
    public <T extends HalRepresentation> void paginateNextAs(final Class<T> pageType,
                                                             final PageHandler pageHandler) throws IOException {
        paginateAs("next", pageType, null, pageHandler);
    }

    /**
     * Iterates over pages by following 'next' links. For every page, a {@code Traverson} is created and provided as a
     * parameter to the callback function.
     *
     * <pre>
     *                        next
     *     ... --&gt; &lt;page type&gt; --&gt; &lt;page type&gt;
     *                  |               |
     *                  v N item        v N item
     *           &lt;embedded type&gt;  &lt;embedded type&gt;
     * </pre>
     *
     * <p>
     *     The {@code Traverson} is backed by a representation of {@code pageType} with {@link EmbeddedTypeInfo}.
     *     This way it is possible to access items embedded into the page resources as specific subtypes of
     *     HalRepresentation.
     * </p>
     *
     * <p>
     *     Iteration stops if the callback returns {@code false}, or if the last page is processed.
     * </p>
     *
     *
     * @param pageType the subtype of HalRepresentation of the page resources
     * @param embeddedTypeInfo type information of the (possibly embedded) items of a page
     * @param pageHandler callback function called for every page
     * @param <T> subtype of HalRepresentation
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 1.0.0
     */
    public <T extends HalRepresentation> void paginateNextAs(final Class<T> pageType,
                                                             final EmbeddedTypeInfo embeddedTypeInfo,
                                                             final PageHandler pageHandler) throws IOException {
        paginateAs("next", pageType, embeddedTypeInfo, pageHandler);
    }

    /**
     * Iterates over pages by following 'prev' links. For every page, a {@code Traverson} is created and provided as a
     * parameter to the callback function.
     *
     * <pre>
     *                              prev
     *     ... --&gt; HalRepresentation --&gt; HalRepresentation
     *                     |                     |
     *                     v N item              v N item
     *              HalRepresentation     HalRepresentation
     * </pre>
     *
     * <p>
     *     The {@code Traverson} is backed by a {@link HalRepresentation} with no type information. If pages may contain
     *     embedded items, and if a specific sub-type of HalRepresentation is required for items,
     *     {@link #paginateNext(EmbeddedTypeInfo,PageHandler)} or {@link #paginateNextAs(Class,EmbeddedTypeInfo,PageHandler)}
     *     should be used instead of this method.
     * </p>
     *
     * <p>
     *     Iteration stops if the callback returns {@code false}, or if the last page is processed.
     * </p>
     *
     * @param pageHandler the callback used to process pages of items.
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 1.0.0
     */
    public void paginatePrev(final PageHandler pageHandler) throws IOException {
        paginateAs("prev", HalRepresentation.class, null, pageHandler);
    }

    /**
     * Iterates over pages by following 'prev' links. For every page, a {@code Traverson} is created and provided as a
     * parameter to the callback function.
     *
     * <pre>
     *                              prev
     *     ... --&gt; HalRepresentation --&gt; HalRepresentation
     *                     |                     |
     *                     v N item              v N item
     *               &lt;embedded type&gt;      &lt;embedded type&gt;
     * </pre>
     *
     * <p>
     *     The {@code Traverson} is backed by a {@link HalRepresentation} with {@link EmbeddedTypeInfo}. This way it
     *     is possible to access items embedded into the page resources as specific subtypes of HalRepresentation.
     * </p>
     *
     * <p>
     *     Iteration stops if the callback returns {@code false}, or if the last page is processed.
     * </p>
     *
     * @param embeddedTypeInfo type information of the (possibly embedded) items of a page
     * @param pageHandler callback function called for every page
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 1.0.0
     */
    public void paginatePrev(final EmbeddedTypeInfo embeddedTypeInfo,
                             final PageHandler pageHandler) throws IOException {
        paginateAs("prev", HalRepresentation.class, embeddedTypeInfo, pageHandler);
    }

    /**
     * Iterates over pages by following 'prev' links. For every page, a {@code Traverson} is created and provided as a
     * parameter to the callback function.
     *
     * <pre>
     *                         prev
     *     ... --&gt; &lt;page type&gt; --&gt; &lt;page type&gt;
     *                 |                 |
     *                 v N item          v N item
     *          HalRepresentation   HalRepresentation
     * </pre>
     *
     * <p>
     *     The {@code Traverson} is backed by a representation of {@code pageType}, but without
     *     {@link EmbeddedTypeInfo}. This way it is possible to access special attributes of the page by
     *     calling {@code pageTraverson.getResourceAs(pageType)} in the callback function. If the paged items need to
     *     be accessed as a subtype of HalRepresentation, you can call pageTraverson.follow(rel).streamAs(MyItemType.class)
     *     - but ONLY if the items are not embedded into the page.
     * </p>
     *
     * <p>
     *     For embedded items having a subtype of HalRepresentation, {@link #paginatePrevAs(Class, EmbeddedTypeInfo, PageHandler)}
     *     must be used instead of this method, otherwise a {@code ClassCastException} will be thrown.
     * </p>
     *
     * <p>
     *     Iteration stops if the callback returns {@code false}, or if the last page is processed.
     * </p>
     *
     * @param pageType the subtype of HalRepresentation of the page resources
     * @param pageHandler callback function called for every page
     * @param <T> subtype of HalRepresentation
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 1.0.0
     */
    public <T extends HalRepresentation> void paginatePrevAs(final Class<T> pageType,
                                                             final PageHandler pageHandler) throws IOException {
        paginateAs("prev", pageType, null, pageHandler);
    }

    /**
     * Iterates over pages by following 'prev' links. For every page, a {@code Traverson} is created and provided as a
     * parameter to the callback function.
     *
     * <pre>
     *                         prev
     *     ... --&gt; &lt;page type&gt; --&gt; &lt;page type&gt;
     *                  |               |
     *                  v N item        v N item
     *           &lt;embedded type&gt;  &lt;embedded type&gt;
     * </pre>
     *
     * <p>
     *     The {@code Traverson} is backed by a representation of {@code pageType} with {@link EmbeddedTypeInfo}.
     *     This way it is possible to access items embedded into the page resources as specific subtypes of
     *     HalRepresentation.
     * </p>
     *
     * <p>
     *     Iteration stops if the callback returns {@code false}, or if the last page is processed.
     * </p>
     *
     *
     * @param pageType the subtype of HalRepresentation of the page resources
     * @param embeddedTypeInfo type information of the (possibly embedded) items of a page
     * @param pageHandler callback function called for every page
     * @param <T> subtype of HalRepresentation
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 1.0.0
     */
    public <T extends HalRepresentation> void paginatePrevAs(final Class<T> pageType,
                                                             final EmbeddedTypeInfo embeddedTypeInfo,
                                                             final PageHandler pageHandler) throws IOException {
        paginateAs("prev", pageType, embeddedTypeInfo, pageHandler);
    }

    /**
     * Iterates over pages by following {code rel} links. For every page, a {@code Traverson} is created and provided as a
     * parameter to the {@link PageHandler} function.
     *
     * <pre>
     *                         &lt;rel&gt;
     *     ... --&gt; &lt;page type&gt; --&gt; &lt;page type&gt;
     *                  |                |
     *                  v N item         v N item
     *           &lt;embedded type&gt;   &lt;embedded type&gt;
     * </pre>
     *
     * <p>
     *     The {@code Traverson} is backed by a {@link HalRepresentation} with {@link EmbeddedTypeInfo}.
     *     This way it is possible to access items embedded into the page resources as specific subtypes of
     *     HalRepresentation.
     * </p>
     *
     * <p>
     *     Iteration stops if the callback returns {@code false}, or if the last page is processed.
     * </p>
     *
     * @param rel link-relation type of the links used to traverse pages
     * @param embeddedTypeInfo type information of the (possibly embedded) items of a page
     * @param pageHandler callback function called for every page
     * @param <T> subtype of HalRepresentation
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 2.0.0
     */
    public <T extends HalRepresentation> void paginate(final String rel,
                                                       final EmbeddedTypeInfo embeddedTypeInfo,
                                                       final PageHandler pageHandler) throws IOException {
        paginateAs(rel, HalRepresentation.class, embeddedTypeInfo, pageHandler);
    }

    /**
     * Iterates over pages by following {code rel} links. For every page, a {@code Traverson} is created and provided as a
     * parameter to the {@link PageHandler} function.
     *
     * <pre>
     *                         &lt;rel&gt;
     *     ... --&gt; &lt;page type&gt; --&gt; &lt;page type&gt;
     *                  |                |
     *                  v N item         v N item
     *           &lt;embedded type&gt;   &lt;embedded type&gt;
     * </pre>
     *
     * <p>
     *     The {@code Traverson} is backed by a representation of {@code pageType} with {@link EmbeddedTypeInfo}.
     *     This way it is possible to access items embedded into the page resources as specific subtypes of
     *     HalRepresentation.
     * </p>
     *
     * <p>
     *     Iteration stops if the callback returns {@code false}, or if the last page is processed.
     * </p>
     *
     * @param rel link-relation type of the links used to traverse pages
     * @param pageType the subtype of HalRepresentation of the page resources
     * @param embeddedTypeInfo type information of the (possibly embedded) items of a page
     * @param pageHandler callback function called for every page
     * @param <T> subtype of HalRepresentation
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 2.0.0
     */
    public <T extends HalRepresentation> void paginateAs(final String rel,
                                                         final Class<T> pageType,
                                                         final EmbeddedTypeInfo embeddedTypeInfo,
                                                         final PageHandler pageHandler) throws IOException {
        Optional<T> currentPage = getResourceAs(pageType, embeddedTypeInfo);
        while (currentPage.isPresent()
                && pageHandler.apply(traverson(linkResolver).startWith(contextUrl, currentPage.get()))
                && currentPage.get().getLinks().getRels().contains(rel)) {
            currentPage = follow(rel).getResourceAs(pageType, embeddedTypeInfo);
        }
    }

    /**
     * Follow the {@link Link}s of the current resource, selected by its link-relation type and returns a {@link Stream}
     * containing the returned {@link HalRepresentation HalRepresentations}.
     * <p>
     *     If the current node has {@link Embedded embedded} items with the specified {@code rel},
     *     these items are used instead of resolving the associated {@link Link}.
     * </p>
     * <p>
     *     Many times, you do not need the HalRepresentations, but subtypes of HalRepresentation,
     *     so you are able to access custom attributes of your resources. In this case, you need
     *     to use {@link #streamAs(Class)} instead of this method.
     * </p>
     *
     * @return this
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 1.0.0
     */
    public Stream<HalRepresentation> stream() throws IOException {
        return streamAs(HalRepresentation.class, null);
    }

    /**
     * Follow the {@link Link}s of the current resource, selected by its link-relation type and returns a {@link Stream}
     * containing the returned {@link HalRepresentation HalRepresentations}.
     * <p>
     *     Templated links are expanded to URIs using the specified template variables.
     * </p>
     * <p>
     *     If the current node has {@link Embedded embedded} items with the specified {@code rel},
     *     these items are used instead of resolving the associated {@link Link}.
     * </p>
     * @param type the specific type of the returned HalRepresentations
     * @param <T> type of the returned HalRepresentations
     * @return this
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 1.0.0
     */
    public <T extends HalRepresentation> Stream<T> streamAs(final Class<T> type) throws IOException {
        return streamAs(type, null);
    }

    /**
     * Follow the {@link Link}s of the current resource, selected by its link-relation type and returns a {@link Stream}
     * containing the returned {@link HalRepresentation HalRepresentations}.
     * <p>
     *     The EmbeddedTypeInfo is used to define the specific type of embedded items.
     * </p>
     * <p>
     *     Templated links are expanded to URIs using the specified template variables.
     * </p>
     * <p>
     *     If the current node has {@link Embedded embedded} items with the specified {@code rel},
     *     these items are used instead of resolving the associated {@link Link}.
     * </p>
     * @param type the specific type of the returned HalRepresentations
     * @param embeddedTypeInfo specification of the type of embedded items
     * @param moreEmbeddedTypeInfos more embedded type-infos
     * @param <T> type of the returned HalRepresentations
     * @return this
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 1.0.0
     */
    @SuppressWarnings("unchecked")
    public <T extends HalRepresentation> Stream<T> streamAs(final Class<T> type,
                                                            final EmbeddedTypeInfo embeddedTypeInfo,
                                                            final EmbeddedTypeInfo... moreEmbeddedTypeInfos) throws IOException {
        if (moreEmbeddedTypeInfos == null || moreEmbeddedTypeInfos.length == 0) {
            return streamAs(type, embeddedTypeInfo != null ? singletonList(embeddedTypeInfo) : emptyList());
        } else {
            final List<EmbeddedTypeInfo> typeInfos = new ArrayList<>();
            typeInfos.add(requireNonNull(embeddedTypeInfo));
            typeInfos.addAll(asList(moreEmbeddedTypeInfos));
            return streamAs(type, typeInfos);
        }
    }

    /**
     * Follow the {@link Link}s of the current resource, selected by its link-relation type and returns a {@link Stream}
     * containing the returned {@link HalRepresentation HalRepresentations}.
     * <p>
     *     The EmbeddedTypeInfo is used to define the specific type of embedded items.
     * </p>
     * <p>
     *     Templated links are expanded to URIs using the specified template variables.
     * </p>
     * <p>
     *     If the current node has {@link Embedded embedded} items with the specified {@code rel},
     *     these items are used instead of resolving the associated {@link Link}.
     * </p>
     * @param type the specific type of the returned HalRepresentations
     * @param embeddedTypeInfo specification of the type of embedded items
     * @param <T> type of the returned HalRepresentations
     * @return this
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 1.0.0
     */
    @SuppressWarnings("unchecked")
    public <T extends HalRepresentation> Stream<T> streamAs(final Class<T> type,
                                                            final List<EmbeddedTypeInfo> embeddedTypeInfo) throws IOException {
        checkState();
        try {
            if (startWith != null) {
                lastResult = traverseInitialResource(type, embeddedTypeInfo, true);
            } else if (!hops.isEmpty()) {
                lastResult = traverseHop(lastResult.get(0), type, embeddedTypeInfo, true);
            }
            return (Stream<T>) lastResult.stream();
        } catch (final Exception e) {
            LOG.error(e.getMessage(), e);
            throw e;
        }
    }

    /**
     * Return the selected resource as HalRepresentation.
     * <p>
     *     If there are multiple matching representations, the first node is returned.
     * </p>
     * <p>
     *     Many times, you do not need the HalRepresentation, but a subtype of HalRepresentation,
     *     so you are able to access custom attributes of your resource. In this case, you need
     *     to use {@link #getResourceAs(Class)} instead of this method.
     * </p>
     *
     * @return HalRepresentation
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 1.0.0
     */
    public Optional<HalRepresentation> getResource() throws IOException {
        return getResourceAs(HalRepresentation.class, null);
    }

    /**
     * Return the selected resource and return it in the specified type.
     * <p>
     *     If there are multiple matching representations, the first node is returned.
     * </p>
     * @param type the subtype of the HalRepresentation used to parse the resource.
     * @param <T> the subtype of HalRepresentation of the returned resource.
     * @return HalRepresentation
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 1.0.0
     */
    public <T extends HalRepresentation> Optional<T> getResourceAs(final Class<T> type) throws IOException {
        return getResourceAs(type, null);
    }

    /**
     * Return the selected resource and return it in the specified type.
     * <p>
     *     The EmbeddedTypeInfo is used to define the specific type of embedded items.
     * </p>
     * <p>
     *     If there are multiple matching representations, the first node is returned.
     * </p>
     * @param type the subtype of the HalRepresentation used to parse the resource.
     * @param embeddedTypeInfo specification of the type of embedded items
     * @param moreEmbeddedTypeInfos more type infors for embedded items
     * @param <T> the subtype of HalRepresentation of the returned resource.
     * @return HalRepresentation
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 1.0.0
     */
    public <T extends HalRepresentation> Optional<T> getResourceAs(final Class<T> type,
                                                                   final EmbeddedTypeInfo embeddedTypeInfo,
                                                                   final EmbeddedTypeInfo... moreEmbeddedTypeInfos) throws IOException {
        if (moreEmbeddedTypeInfos == null || moreEmbeddedTypeInfos.length == 0) {
            return getResourceAs(type, embeddedTypeInfo != null ? singletonList(embeddedTypeInfo) : emptyList());
        } else {
            final List<EmbeddedTypeInfo> typeInfos = new ArrayList<>();
            typeInfos.add(requireNonNull(embeddedTypeInfo));
            typeInfos.addAll(asList(moreEmbeddedTypeInfos));
            return getResourceAs(type, typeInfos);
        }
    }

    /**
     * Return the selected resource and return it in the specified type.
     * <p>
     *     The EmbeddedTypeInfo is used to define the specific type of embedded items.
     * </p>
     * <p>
     *     If there are multiple matching representations, the first node is returned.
     * </p>
     * @param type the subtype of the HalRepresentation used to parse the resource.
     * @param embeddedTypeInfos specification of the type of embedded items
     * @param <T> the subtype of HalRepresentation of the returned resource.
     * @return HalRepresentation
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     * @since 1.0.0
     */
    public <T extends HalRepresentation> Optional<T> getResourceAs(final Class<T> type, 
                                                                   final List<EmbeddedTypeInfo> embeddedTypeInfos) throws IOException {
        checkState();
        try {
            if (startWith != null) {
                lastResult = traverseInitialResource(type, embeddedTypeInfos, false);
            } else if (!hops.isEmpty()) {
                lastResult = traverseHop(lastResult.get(0), type, embeddedTypeInfos, false);
            }
            if (lastResult.size() > 0) {
                return Optional.of(type.cast(lastResult.get(0)));
            } else {
                return Optional.empty();
            }
        } catch (final Exception e) {
            LOG.error(e.getMessage());
            throw e;
        }
    }

    /**
     * Returns the current contextUrl of the Traverson.
     * <p>
     *     The contextUrl is used to resolve relative links / HREFs to other resources.
     * </p>
     *
     * @return URL of the Traverson's current context.
     * @since 1.0.0
     */
    public URL getCurrentContextUrl() {
        return contextUrl;
    }

    /**
     *
     * @param resultType the Class of the returned HalRepresentation.
     * @param embeddedTypeInfo type information about embedded items
     * @param retrieveAll false if only a single resource is returned, true otherwise.
     * @param <T> type parameter of the returned HalRepresentation
     * @return list of zero or more HalRepresentations.
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     */
    private <T extends HalRepresentation> List<T> traverseInitialResource(final Class<T> resultType,
                                                                          final List<EmbeddedTypeInfo> embeddedTypeInfo,
                                                                          final boolean retrieveAll) throws IOException {
        /*
        #hops = N; N > 0
        max nesting-level in embeddedTypeInfo = M; M >= 0
        */
        final Link initial = self(startWith.toString());
        LOG.trace("Starting with {}", startWith);
        this.startWith = null;
        if (hops.isEmpty()) {
            /*
            0. N=0, M=0:
            getResource(startwith, pageType)
            */
            return singletonList(
                    getResource(initial, resultType, embeddedTypeInfo)
            );
        } else {
            final HalRepresentation firstHop;
            // Follow startWith URL, but have a look at the next hop, so we can parse the resource
            // with respect to pageType and embeddedTypeInfo:
            if (hops.size() == 1) {
                final Hop hop = hops.get(0);
                if (embeddedTypeInfo == null || embeddedTypeInfo.isEmpty()) {
                    /*
                    1. N=1, M=0 (keine TypeInfos):
                    Die zurückgegebene Representation soll vom Typ resultType sein.

                    startWith könnte hop 0 embedden, oder es könnten zwei Resourcen angefragt werden.

                    a) getResource(startwith, HalRepresentation.class, embeddedTypeInfo(hop-0-rel, pageType))
                    b) getResource(current, pageType)
                    */
                    firstHop = getResource(initial, HalRepresentation.class, withEmbedded(hop.rel, resultType));
                } else {
                    /*
                    2. N=1, M>0 (mit TypeInfos)
                    Die zurückgegebene Representation soll vom Typ pageType sein und eingebettete Items gemäss der embeddedTypeInfo enthalten.

                    startWith könnte hop 0 embedden, oder es könnten zwei Resourcen angefragt werden.

                    a) getResource(startwith, HalRepresentation.class, embeddedTypeInfo(hop-0-rel, pageType, embeddedTypeInfo))
                    b) getResource(current, pageType, embeddedTypeInfo)
                    */
                    //firstHop = getResource(current, pageType, embeddedTypeInfo);
                    firstHop = getResource(initial, HalRepresentation.class, withEmbedded(hop.rel, resultType, embeddedTypeInfo));
                }
            } else {
                /*
                3. N>=2, M=0
                Die zurückgegebene Representation soll vom Typ pageType sein.

                startWith könnte hop 0 und 1 embedden, oder es könnten zwei Resourcen angefragt werden, von denen die zweite
                den hop 1 embedded, oder es könnten drei Resource angefragt werden.

                a) getResource(startWith, HalRepresentation.class, embeddedTypeInfo(hop-0-rel, HalRepresentation.class, hop-1-rel, pageType,)
                b) getResource(current, HalRepresentation.class, embeddedTypeInfo(hop-1-rel, pageType)
                c) getResource(current, pageType)
                */
                firstHop = getResource(initial, HalRepresentation.class, embeddedTypeInfoFor(hops, resultType, embeddedTypeInfo));
            }
            return traverseHop(firstHop, resultType, embeddedTypeInfo, retrieveAll);
        }
    }

    /**
     *
     * @param current HalRepresentation of the current hop
     * @param resultType the Class of the returned HalRepresentation.
     * @param embeddedTypeInfo type information about embedded items
     * @param retrieveAll false if only a single resource is returned, true otherwise.
     * @param <T> type parameter of the returned HalRepresentation
     * @return list of zero or more HalRepresentations.
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     */
    @SuppressWarnings("unchecked")
    private <T extends HalRepresentation> List<T> traverseHop(final HalRepresentation current,
                                                              final Class<T> resultType,
                                                              final List<EmbeddedTypeInfo> embeddedTypeInfo,
                                                              final boolean retrieveAll) throws IOException {

        final List<T> response;
        final Hop currentHop = hops.pop();
        LOG.trace("Following {}", currentHop.rel);
        final List<Link> links = current
                .getLinks()
                .getLinksBy(currentHop.rel, currentHop.predicate);
        if (!currentHop.ignoreEmbedded || links.isEmpty()) {
            // the next hop could possibly be already available as an embedded object:
            final List<? extends HalRepresentation> embeddedItems = hops.isEmpty()
                    ? current.getEmbedded().getItemsBy(currentHop.rel, resultType)
                    : current.getEmbedded().getItemsBy(currentHop.rel);

            if (!embeddedItems.isEmpty()) {
                LOG.trace("Returning {} embedded {}", embeddedItems.size(), currentHop.rel);
                return hops.isEmpty()
                        ? (List<T>) embeddedItems
                        : traverseHop(embeddedItems.get(0), resultType, embeddedTypeInfo, retrieveAll);
            }
        }
        if (!links.isEmpty()) {
            final Link expandedLink = resolve(this.contextUrl, expand(links.get(0), currentHop.vars));
            if (hops.isEmpty()) { // last hop
                if (retrieveAll) {
                    LOG.trace("Following {} {} links", links.size(), currentHop.rel);
                    final List<T> representations = new ArrayList<>();
                    for (final Link link : links) {
                        representations.add(getResource(resolve(this.contextUrl, expand(link, currentHop.vars)), resultType, embeddedTypeInfo));
                    }
                    response = representations;
                } else {
                    this.contextUrl = linkToUrl(expandedLink);
                    response = singletonList(getResource(expandedLink, resultType, embeddedTypeInfo));
                }
            } else {
                this.contextUrl = linkToUrl(expandedLink);
                final HalRepresentation resource = getResource(expandedLink, HalRepresentation.class, embeddedTypeInfoFor(hops, resultType, embeddedTypeInfo));
                response = traverseHop(resource, resultType, embeddedTypeInfo, retrieveAll);
            }
        } else {
            final String msg = format("Can not follow hop %s: no matching links found in resource %s", currentHop.rel, current);
            LOG.error(msg);
            response = emptyList();
        }
        return response;
    }

    private Link expand(final Link link, 
                        final Map<String,Object> vars) {
        if (link.isTemplated()) {
            final String href = fromTemplate(link.getHref()).expand(vars);
            return copyOf(link)
                    .withHref(href)
                    .withRel(link.getRel())
                    .build();
        } else {
            return link;
        }
    }

    /**
     * Retrieve the HAL resource identified by {@code uri} and return the representation as a HalRepresentation.
     *
     * @param link the Link of the resource to retrieve, or null, if the contextUrl should be resolved.
     * @throws IllegalArgumentException if resolving URLs is failing
     * @param resultType the Class of the returned HalRepresentation.
     * @param embeddedTypeInfo type information about embedded items
     * @param <T> type parameter of the returned HalRepresentation
     * @return list of zero or more HalRepresentations.
     */
    private <T extends HalRepresentation> T getResource(final Link link,
                                                        final Class<T> resultType,
                                                        final EmbeddedTypeInfo embeddedTypeInfo) throws IOException {
        return getResource(link, resultType, singletonList(embeddedTypeInfo));
    }

    /**
     * Retrieve the HAL resource identified by {@code uri} and return the representation as a HalRepresentation.
     *
     * @param link the Link of the resource to retrieve, or null, if the contextUrl should be resolved.
     * @param type the expected type of the returned resource
     * @param embeddedType type information to specify the type of embedded resources.
     * @param <T> the type of the returned HalRepresentation
     * @return HalRepresentation
     * @throws IllegalArgumentException if resolving URLs is failing
     * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
     * @throws JsonParseException if the json document can not be parsed by Jackson's ObjectMapper
     * @throws JsonMappingException if the input JSON structure can not be mapped to the specified HalRepresentation type
     */
    private <T extends HalRepresentation> T getResource(final Link link,
                                                        final Class<T> type,
                                                        final List<EmbeddedTypeInfo> embeddedType) throws IOException {
        LOG.trace("Fetching resource href={} rel={} as type={} with embeddedType={}", link.getHref(), link.getRel(), type.getSimpleName(), embeddedType);
        final String json;
        try {
            json = linkResolver.apply(link);
            LOG.trace("Got {}", json);
        } catch (final IOException | RuntimeException e) {
            LOG.error("Failed to fetch resource href={}: {}", link.getHref(), e.getMessage());
            throw e;
        }
        try {
            return embeddedType != null && !embeddedType.isEmpty()
                    ? parse(json, objectMapper).as(type, embeddedType)
                    : parse(json, objectMapper).as(type);
        } catch (final RuntimeException e) {
            LOG.error("Failed to parse resource href={} rel={} as type={} with embeddedType={}", link.getHref(), link.getRel(), type.getSimpleName(), embeddedType);
            throw e;
        }
    }

    /**
     * Resolved a link using the URL of the current resource and returns it as an absolute Link.
     *
     * @param contextUrl the URL of the current context
     * @param link optional link to follow
     * @return absolute Link
     * @throws IllegalArgumentException if resolving the link is failing
     */
    private static Link resolve(final URL contextUrl, 
                                final Link link) {
        if (link != null && link.isTemplated()) {
            final String msg = "Link must not be templated";
            LOG.error(msg);
            throw new IllegalStateException(msg);
        }
        if (link == null) {
            return self(contextUrl.toString());
        } else {
            return copyOf(link).withHref(resolveHref(contextUrl, link.getHref()).toString()).build();
        }
    }

    static EmbeddedTypeInfo embeddedTypeInfoFor(final Hops hops,
                                                final Class<? extends HalRepresentation> pageType,
                                                final List<EmbeddedTypeInfo> embeddedTypeInfo) {
        if (hops.isEmpty()) {
            throw new IllegalArgumentException("Hops must not be empty");
        }
        EmbeddedTypeInfo typeInfo = embeddedTypeInfo != null && !embeddedTypeInfo.isEmpty()
                ? withEmbedded(hops.get(hops.size()-1).rel, pageType, embeddedTypeInfo)
                : withEmbedded(hops.get(hops.size()-1).rel, pageType);
        for (int i = hops.size()-2; i >= 0; i--) {
            typeInfo = withEmbedded(hops.get(i).rel, HalRepresentation.class, typeInfo);
        }
        return typeInfo;
    }

    private static URL linkToUrl(final Link link) {
        if (link.isTemplated()) {
            final String msg = format("Unable to create URL from templated link %s", link);
            LOG.error(msg);
            throw new IllegalArgumentException(msg);
        }
        return hrefToUrl(link.getHref());
    }

    private static URL hrefToUrl(final String href) {
        try {
            return new URL(href);
        } catch (final MalformedURLException e) {
            final String msg = format("Unable to create URL from href '%s': %s", href, e.getMessage());
            LOG.error(msg, e);
            throw new IllegalArgumentException(msg, e);
        }
    }

    private static URL resolveHref(final URL contextUrl, final String href) {
        try {
            return contextUrl == null ? new URL(href) : new URL(contextUrl, href);
        } catch (final MalformedURLException e) {
            final String msg = format("Unable to resolve URL from contextUrl %s and href '%s': %s", contextUrl, href, e.getMessage());
            LOG.error(msg, e);
            throw new IllegalArgumentException(msg, e);
        }
    }

    /**
     * Checks the current state of the Traverson.
     *
     * @throws IllegalStateException if some error occured during traversion
     */
    private void checkState() {
        if (startWith == null && lastResult == null) {
            final String msg = "Please call startWith(uri) first.";
            LOG.error(msg);
            throw new IllegalStateException(msg);
        }
    }
}
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.otto.edison.hal.EmbeddedTypeInfo;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Link;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.slf4j.Logger;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static de.otto.edison.hal.Embedded.emptyEmbedded;
import static de.otto.edison.hal.EmbeddedTypeInfo.withEmbedded;
import static de.otto.edison.hal.Link.*;
import static de.otto.edison.hal.LinkPredicates.*;
import static de.otto.edison.hal.Links.emptyLinks;
import static de.otto.edison.hal.Links.linkingTo;
import static de.otto.edison.hal.traverson.Traverson.*;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.slf4j.LoggerFactory.getLogger;
package de.otto.edison.hal.traverson;




public class TraversonTest {

    private static final Logger LOG = getLogger(TraversonTest.class);

    @Rule
    public TestName testName = new TestName();

    @Before
    public void logTestName() throws Throwable {
        LOG.trace("============== {} ===================", testName.getMethodName());
    }

    ////////////////////////////////////
    // Basics:
    ////////////////////////////////////

    @Test(expected = IllegalArgumentException.class)
    public void shouldNotCreateParametersWithUnevenNumberOfArguments() {
        withVars("foo", "bar", "foobar");
    }

    @Test
    public void shouldCreateParameters() {
        assertThat(withVars("foo", "bar").containsKey("foo"), is(true));
    }

    @Test
    public void shouldCreateMoreParameters() {
        assertThat(withVars("foo", "bar", "oof", "rab").values(), contains("bar", "rab"));
    }

    @Test
    public void shouldCreateEvenMoreParameters() {
        assertThat(withVars("foo", "bar", "oof", "rab", "ofo", "abr").values(), contains("bar", "rab", "abr"));
    }

    @Test
    public void shouldGetEmptyHalRepresentation() throws IOException {
        final Traverson t = traverson(s->"{}").startWith("http://example.com/");
        assertThat(t.getResource().get().getLinks(), is(emptyLinks()));
        assertThat(t.getResource().get().getEmbedded(), is(emptyEmbedded()));