Content of file ResourcePath.java.html

<?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 :: Components</a> &gt; <a href="../index.html" class="el_bundle">it-tidalwave-northernwind-core</a> &gt; <a href="index.source.html" class="el_package">it.tidalwave.northernwind.core.model</a> &gt; <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 &quot;License&quot;); 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 &quot;AS IS&quot; 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 &quot;/&quot;, 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&lt;String&gt; 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&lt;String&gt; segments)
      {
<span class="fc" id="L83">        return new ResourcePath(segments);</span>
      }

    /*******************************************************************************************************************
     *
     * Creates an empty path, that is &quot;/&quot;.
     *
     ******************************************************************************************************************/
    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((&quot;/&quot;.equals(path) || &quot;&quot;.equals(path)) ? emptyList() : List.of(validated(path).split(&quot;/&quot;)));</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&lt;String&gt; 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 &quot;/foo/bar/baz&quot; and path is
     * &quot;/foo/bar&quot;, the returned clone represents &quot;/baz&quot;.
     *
     * @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(&quot;The path &quot; + path.asString() + &quot; doesn't start with &quot; + 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 &quot;/foo/bar/baz&quot;, &quot;foo&quot; 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 &quot;/foo/bar/baz&quot;,
     * &quot;baz&quot; 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 &quot;/foo/bar/baz&quot;,
     * {@code getSegment(1)} returns &quot;baz&quot;..
     *
     * @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 &quot;/foo/bar/baz.jpg&quot;, &quot;jpg&quot; 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(&quot;.&quot;) ? &quot;&quot; : trailing.replaceAll(&quot;^.*\\.&quot;, &quot;&quot;);</span>
      }

    /*******************************************************************************************************************
     *
     * Returns a clone without the leading segment. For instance, if the current object represents &quot;/foo/bar/baz&quot;,
     * the returned clone represents &quot;/bar/baz&quot;.
     *
     * @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 &quot;/foo/bar/baz&quot;,
     * the returned clone represents &quot;/foo/bar&quot;.
     *
     * @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() &amp;&amp; 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 &quot;/foo/bar/&quot;, and
     * &quot;baz&quot;, &quot;bax&quot; are given as argument, the returned clone represents &quot;/baz/bax/foo/bar&quot;.
     *
     * @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 &quot;/foo/bar/&quot;, and
     * &quot;baz&quot;, &quot;bax&quot; are given as argument, the returned clone represents &quot;/baz/bax/foo/bar&quot;.
     *
     * @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 &quot;/foo/bar/&quot;, and
     * &quot;/baz/bax&quot; is given as argument, the returned clone represents &quot;/foo/bar/baz/bax&quot;.
     *
     * @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 &quot;/foo/bar/&quot;, and
     * &quot;baz&quot;, &quot;bax&quot; are given as argument, the returned clone represents &quot;/foo/bar/baz/bax&quot;.
     *
     * @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 &quot;/&quot; and has no
     * trailing &quot;/&quot;. For empty paths &quot;/&quot; is returned.
     *
     * @return  the string representation
     *
     ******************************************************************************************************************/
    @Nonnull
    public String asString()
      {
<span class="fc" id="L350">        final var string = segments.stream().collect(joining(&quot;/&quot;, &quot;/&quot;, &quot;&quot;));</span>

        // FIXME: this check is probably redundant now that there are safety tests
this check is probably redundant now that there are safety tests
<span class="pc bpc" id="L353" title="1 of 2 branches missed."> if (string.contains(&quot;//&quot;))</span> { <span class="nc" id="L355"> throw new RuntimeException(&quot;Error in stringification: &quot; + string + &quot; - &quot; + this);</span> } <span class="fc" id="L358"> return string;</span> } /******************************************************************************************************************* * * {@inheritDoc} * ******************************************************************************************************************/ @Override @Nonnull public String toString() { try { <span class="fc" id="L371"> return asString();</span> } <span class="nc" id="L373"> catch (RuntimeException e)</span> { <span class="nc" id="L375"> return segments.toString();</span> } } /******************************************************************************************************************* * * * ******************************************************************************************************************/ @Nonnull private static String validated (@Nonnull final String path) { <span class="fc bfc" id="L387" title="All 4 branches covered."> if (path.startsWith(&quot;http:&quot;) || path.startsWith(&quot;https:&quot;))</span> { <span class="fc" id="L389"> throw new IllegalArgumentException(&quot;ResourcePath can't hold a URL&quot;);</span> } <span class="fc bfc" id="L392" title="All 2 branches covered."> final var start = path.startsWith(&quot;/&quot;) ? 1 : 0;</span> <span class="fc" id="L393"> return path.substring(start);</span> } /******************************************************************************************************************* * * * ******************************************************************************************************************/ @Nonnull private static List&lt;String&gt; validated (@Nonnull final List&lt;String&gt; segments) { <span class="fc bfc" id="L404" title="All 2 branches covered."> for (final var segment : segments)</span> { <span class="fc bfc" id="L406" title="All 2 branches covered."> if (&quot;&quot;.equals(segment))</span> { <span class="fc" id="L408"> throw new IllegalArgumentException(&quot;Empty segment in &quot; + segments);</span> } <span class="fc bfc" id="L411" title="All 2 branches covered."> if (segment.contains(&quot;/&quot;))</span> { <span class="fc" id="L413"> throw new IllegalArgumentException(&quot;Segments cannot contain a slash: &quot; + segments);</span> } <span class="fc" id="L415"> }</span> <span class="fc" id="L417"> return segments;</span> } } </pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.9.202303310957</span></div></body></html>