<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../../jacoco-resources/report.gif" type="image/gif"/><title>ResourcePath.java</title><link rel="stylesheet" href="../../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../../index.html" class="el_report">NorthernWind :: Frontend :: Media</a> > <a href="../index.html" class="el_bundle">it-tidalwave-northernwind-core</a> > <a href="index.source.html" class="el_package">it.tidalwave.northernwind.core.model</a> > <span class="el_source">ResourcePath.java</span></div><h1>ResourcePath.java</h1><pre class="source lang-java linenums">/*
* #%L
* *********************************************************************************************************************
*
* NorthernWind - lightweight CMS
* http://northernwind.tidalwave.it - git clone https://bitbucket.org/tidalwave/northernwind-src.git
* %%
* Copyright (C) 2011 - 2023 Tidalwave s.a.s. (http://tidalwave.it)
* %%
* *********************************************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* *********************************************************************************************************************
*
*
* *********************************************************************************************************************
* #L%
*/
package it.tidalwave.northernwind.core.model;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
import java.util.List;
import java.io.Serializable;
import it.tidalwave.northernwind.util.UrlEncoding;
import lombok.EqualsAndHashCode;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.*;
import static it.tidalwave.util.CollectionUtils.concatAll;
/***********************************************************************************************************************
*
* This class encapsulate a path, that is a sequence of segments separated by a "/", and provides methods to manipulate
* it.
*
* @author Fabrizio Giudici
*
**********************************************************************************************************************/
<span class="pc bpc" id="L48" title="8 of 14 branches missed.">@Immutable @EqualsAndHashCode</span>
public class ResourcePath implements Serializable
{
private static final long serialVersionUID = 1L;
<span class="fc" id="L53"> public static final ResourcePath EMPTY = new ResourcePath();</span>
@Nonnull
/* package */ final List<String> segments;
/*******************************************************************************************************************
*
* Creates an instance out of a string.
*
* @param path the path as string
* @return the path
*
******************************************************************************************************************/
@Nonnull
public static ResourcePath of (@Nonnull final String path)
{
<span class="fc" id="L69"> return new ResourcePath(path);</span>
}
/*******************************************************************************************************************
*
* Creates an instance out of a list of segments.
*
* @param segments the path as a sequence of segments
* @return the path
*
******************************************************************************************************************/
@Nonnull
public static ResourcePath of (@Nonnull final List<String> segments)
{
<span class="fc" id="L83"> return new ResourcePath(segments);</span>
}
/*******************************************************************************************************************
*
* Creates an empty path, that is "/".
*
******************************************************************************************************************/
private ResourcePath()
{
<span class="fc" id="L93"> this(emptyList());</span>
<span class="fc" id="L94"> }</span>
/*******************************************************************************************************************
*
* Creates an instance out of a string.
*
* @param path the path
*
******************************************************************************************************************/
private ResourcePath (@Nonnull final String path)
{
<span class="fc bfc" id="L105" title="All 4 branches covered."> this(("/".equals(path) || "".equals(path)) ? emptyList() : List.of(validated(path).split("/")));</span>
<span class="fc" id="L106"> }</span>
/*******************************************************************************************************************
*
* Creates an instance out of a collection of segments.
*
* @param segments the segments
*
******************************************************************************************************************/
/* package */ ResourcePath (@Nonnull final List<String> segments)
<span class="fc" id="L116"> {</span>
<span class="fc" id="L117"> this.segments = validated(segments);</span>
<span class="fc" id="L118"> }</span>
/*******************************************************************************************************************
*
* Returns a clone path which is relative to the given path. For instance, if this is "/foo/bar/baz" and path is
* "/foo/bar", the returned clone represents "/baz".
*
* @param path the path to which we're computing the relative position
* @return the clone
* @throws IllegalArgumentException if path is not a prefix of this
*
******************************************************************************************************************/
@Nonnull
public ResourcePath relativeTo (@Nonnull final ResourcePath path)
{
<span class="fc bfc" id="L133" title="All 2 branches covered."> if (!segments.subList(0, path.segments.size()).equals(path.segments))</span>
{
<span class="fc" id="L135"> throw new IllegalArgumentException("The path " + path.asString() + " doesn't start with " + asString());</span>
}
<span class="fc" id="L138"> return ResourcePath.of(segments.subList(path.segments.size(), segments.size()));</span>
}
/*******************************************************************************************************************
*
* Returns the leading segment of this path. For instance, if the current object represents "/foo/bar/baz", "foo" is
* returned.
*
* @return the leading segment of this path
*
******************************************************************************************************************/
@Nonnull
public String getLeading()
{
<span class="fc" id="L152"> return segments.get(0);</span>
}
/*******************************************************************************************************************
*
* Returns the trailing segment of this path. For instance, if the current object represents "/foo/bar/baz",
* "baz" is returned.
*
* @return the trailing segment of this path
*
******************************************************************************************************************/
@Nonnull
public String getTrailing()
{
<span class="fc" id="L166"> return segments.get(segments.size() - 1);</span>
}
/*******************************************************************************************************************
*
* Returns a segment of this path. For instance, if the current object represents "/foo/bar/baz",
* {@code getSegment(1)} returns "baz"..
*
* @param index the index of the segment
* @return the segment
*
******************************************************************************************************************/
@Nonnull
public String getSegment (@Nonnegative final int index)
{
<span class="nc" id="L181"> return segments.get(index);</span>
}
/*******************************************************************************************************************
*
* Returns the file extension of this path. For instance, if this object represents "/foo/bar/baz.jpg", "jpg" is
* returned.
*
* @return the file extension of this path
*
******************************************************************************************************************/
@Nonnull
public String getExtension()
{
<span class="fc" id="L195"> final var trailing = getTrailing();</span>
<span class="fc bfc" id="L196" title="All 2 branches covered."> return !trailing.contains(".") ? "" : trailing.replaceAll("^.*\\.", "");</span>
}
/*******************************************************************************************************************
*
* Returns a clone without the leading segment. For instance, if the current object represents "/foo/bar/baz",
* the returned clone represents "/bar/baz".
*
* @return the clone
*
******************************************************************************************************************/
@Nonnull
public ResourcePath withoutLeading()
{
<span class="fc" id="L210"> return ResourcePath.of(segments.subList(1, segments.size()));</span>
}
/*******************************************************************************************************************
*
* Returns a clone without the trailing segment. For instance, if the current object represents "/foo/bar/baz",
* the returned clone represents "/foo/bar".
*
* @return the clone
*
******************************************************************************************************************/
@Nonnull
public ResourcePath withoutTrailing()
{
<span class="fc" id="L224"> return ResourcePath.of(segments.subList(0, segments.size() - 1));</span>
}
/*******************************************************************************************************************
*
* Returns {@code true} if the leading segment of this path is the given one.
*
* @param leadingSegment the expected leading segment
* @return {@code true} if this path starts with the given leading segment
*
******************************************************************************************************************/
public boolean startsWith (@Nonnull final String leadingSegment)
{
<span class="fc bfc" id="L237" title="All 4 branches covered."> return !segments.isEmpty() && getLeading().equals(leadingSegment);</span>
}
/*******************************************************************************************************************
*
* Returns the count of segments in this path.
*
* @return the count of segments
*
******************************************************************************************************************/
@Nonnegative
public int getSegmentCount()
{
<span class="fc" id="L250"> return segments.size();</span>
}
/*******************************************************************************************************************
*
* Returns {@code true} if this paths is empty.
*
* @return {@code true} if the path is empty
*
******************************************************************************************************************/
@Nonnegative
public boolean isEmpty()
{
<span class="fc" id="L263"> return segments.isEmpty();</span>
}
/*******************************************************************************************************************
*
* Returns a clone with the given prepended path. For instance, if this object represents "/foo/bar/", and
* "baz", "bax" are given as argument, the returned clone represents "/baz/bax/foo/bar".
*
* @param path the path to prepend
* @return the clone
*
******************************************************************************************************************/
@Nonnull
public ResourcePath prependedWith (@Nonnull final ResourcePath path)
{
<span class="fc" id="L278"> return ResourcePath.of(concatAll(path.segments, this.segments));</span>
}
/*******************************************************************************************************************
*
* Returns a clone with the given prepended path. For instance, if this object represents "/foo/bar/", and
* "baz", "bax" are given as argument, the returned clone represents "/baz/bax/foo/bar".
*
* @param path the path to prepend
* @return the clone
*
******************************************************************************************************************/
@Nonnull
public ResourcePath prependedWith (@Nonnull final String path)
{
<span class="fc" id="L293"> return prependedWith(ResourcePath.of(path));</span>
}
/*******************************************************************************************************************
*
* Returns a clone with the given appended path. For instance, if this object represents "/foo/bar/", and
* "/baz/bax" is given as argument, the returned clone represents "/foo/bar/baz/bax".
*
* @param path the path to prepend
* @return the clone
*
******************************************************************************************************************/
@Nonnull
public ResourcePath appendedWith (@Nonnull final ResourcePath path)
{
<span class="fc" id="L308"> return ResourcePath.of(concatAll(this.segments, path.segments));</span>
}
/*******************************************************************************************************************
*
* Returns a clone with the given appended path. For instance, if this object represents "/foo/bar/", and
* "baz", "bax" are given as argument, the returned clone represents "/foo/bar/baz/bax".
*
* @param path the path to prepend
* @return the clone
*
******************************************************************************************************************/
@Nonnull
public ResourcePath appendedWith (@Nonnull final String path)
{
<span class="fc" id="L323"> return appendedWith(ResourcePath.of(path));</span>
}
/*******************************************************************************************************************
*
* Returns a URL-decoded clone.
*
* @return the clone
*
******************************************************************************************************************/
@Nonnull
public ResourcePath urlDecoded()
{
<span class="fc" id="L336"> return ResourcePath.of(segments.stream().map(UrlEncoding::decodedUtf8).collect(toList()));</span>
}
/*******************************************************************************************************************
*
* Returns the string representation of this path. This representation always starts with a leading "/" and has no
* trailing "/". For empty paths "/" is returned.
*
* @return the string representation
*
******************************************************************************************************************/
@Nonnull
public String asString()
{
<span class="fc" id="L350"> final var string = segments.stream().collect(joining("/", "/", ""));</span>
// FIXME: this check is probably redundant now that there are safety tests
this check is probably redundant now that there are safety tests