All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sourceforge.urin.Segment Maven / Gradle / Ivy

There is a newer version: 5.2
Show newest version
/*
 * Copyright 2024 Mark Slater
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

package net.sourceforge.urin;

import java.util.List;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static net.sourceforge.urin.CharacterSetMembershipFunction.P_CHAR;
import static net.sourceforge.urin.PercentEncodingPartial.PercentEncoding.percentEncodingString;
import static net.sourceforge.urin.PercentEncodingPartial.PercentEncoding.specifiedValueEncoding;

/**
 * A segment of a URI's path.
 * 

* Note that the special segments "{@code .}" and "{@code ..}" are obtained via the factory methods {@link #dot()} and {@link #dotDot()} * respectively. Passing "{@code .}" or "{@code ..}" as an argument to the factory method {@link #segment(String)} is not equivalent, * as the argument to this method is a literal string, i.e. subject to encoding where necessary. *

* Immutable and thread safe. * * @param The type of value represented by the segment - {@code String} in the general case. * @see RFC 3986 - Path */ public abstract class Segment { /** * The {@code MakingDecoder} used by standard segments. */ public static final MakingDecoder, String, String> STRING_SEGMENT_MAKING_DECODER = new MakingDecoder, String, String>(PercentEncodingPartial.noOp()) { @Override protected Segment makeOne(final String value) { return segment(value); } }; private static final PercentEncodingPartial.PercentEncoding PERCENT_ENCODING = specifiedValueEncoding(".", specifiedValueEncoding("..", percentEncodingString(new PercentEncoder(P_CHAR)))); private Segment() { } /** * The segment "{@code .}", referring to the current location in the path name hierarchy. * * @param The type of value represented by the segment - {@code String} in the general case. * @return The segment "{@code .}", referring to the current location in the path name hierarchy */ public static Segment dot() { return new Segment() { @Override public boolean hasValue() { return false; } @Override public ENCODES value() { throw new UnsupportedOperationException("Attempt to get value of . segment"); } @Override List> incorporate(final Segment next) { return singletonList(next); } @Override String asString() { return "."; } @Override boolean isEmpty() { return false; } @Override public boolean equals(final Object object) { return this == object || !(object == null || getClass() != object.getClass()); } @Override public int hashCode() { return 37; } @Override public String toString() { return "Segment{.}"; } }; } /** * The segment "{@code ..}", referring to the parent location in the path name hierarchy. * * @param The type of value represented by the segment - {@code String} in the general case. * @return The segment "{@code ..}", referring to the current location in the path name hierarchy */ public static Segment dotDot() { return new Segment() { @Override public boolean hasValue() { return false; } @Override public ENCODES value() { throw new UnsupportedOperationException("Attempt to get value of .. segment"); } @Override List> incorporate(final Segment next) { return asList(this, next); } @Override String asString() { return ".."; } @Override boolean isEmpty() { return false; } @Override public boolean equals(final Object object) { return this == object || !(object == null || getClass() != object.getClass()); } @Override public int hashCode() { return 17; } @Override public String toString() { return "Segment{..}"; } }; } /** * An empty segment - one that is encoded as "" in a URI. * * @param The type of value represented by the segment - {@code String} in the general case. * @return The empty segment - one that is encoded as "" in a URI */ public static Segment empty() { return new Segment() { @Override public boolean hasValue() { return false; } @Override public ENCODES value() { throw new UnsupportedOperationException("Attempt to get value of empty segment"); } @Override List> incorporate(final Segment next) { if (dotDot().equals(next)) { return singletonList(dot()); } else { return asList(this, next); } } @Override String asString() { return ""; } @Override boolean isEmpty() { return true; } @Override public boolean equals(final Object object) { return this == object || !(object == null || getClass() != object.getClass()); } @Override public int hashCode() { return 19; } @Override public String toString() { return "Segment{empty}"; } }; } /** * Factory method for creating {@code Segment}s. * * @param segment any {@code String} to represent as a {@code Segment}. * @return a {@code Segment} representing the given {@code String}. */ public static Segment segment(final String segment) { return segment(segment, PercentEncodingPartial.noOp()); } /** * Factory method for creating non-{@code String} {@code Segment}s. * * @param the type of {@code Object} the {@code Segment} encodes. * @param segment any {@code Object} to represent as a {@code Segment}. * @param percentEncodingPartial an encoding from {@code T} to {@code String}. * @return a {@code Segment} representing the given {@code Object}. */ public static Segment segment(final T segment, final PercentEncodingPartial percentEncodingPartial) { final ValueSegment result = new ValueSegment<>(segment, percentEncodingPartial); return result.isEmpty() ? empty() : result; // TODO I think this is unhelpful except for a trailing segment as e.g. .//b/c is hard to get values from } static Segment parse(final String encodedSegment, final MakingDecoder, ?, String> segmentMakingDecoder) throws ParseException { switch (encodedSegment) { case "": return empty(); case ".": return dot(); case "..": return dotDot(); default: return segmentMakingDecoder.toMaker(PERCENT_ENCODING).make(encodedSegment); } } final boolean containsColon() { return asString().indexOf(':') != -1; } abstract String asString(); abstract boolean isEmpty(); /** * Returns true if {@code value()} can be called on this {@code Segment}. This method * returns false for empty, {@code .} and {@code ..} segments. * * @return true if {@code value()} can be called on this {@code Segment}. */ public abstract boolean hasValue(); /** * Gets the (non-encoded) value of this segment, if it is a type that has a value, or throws {@code UnsupportedOperationException} otherwise. *

* Dot segments ({@code .} and {@code ..}) and the empty segment do not have values, and will throw {@code UnsupportedOperationException}. * This can be tested by equality with the objects returned by {@link #dot()}, {@link #dotDot()}, and {@link #empty()} methods, or by calling {@code hasValue()}. * * @return the (non-encoded) value of this segment. * @throws UnsupportedOperationException if this is a segment that does not represent a value. */ public abstract ENCODES value(); abstract List> incorporate(Segment next); private static final class ValueSegment extends Segment { // TODO could this extend empty? Or a common superclass? They share an implementation of incorporate private final PercentEncodingUnaryValue delegate; private ValueSegment(final ENCODES value, final PercentEncodingPartial percentEncodingPartial) { final PercentEncodingPartial.PercentEncoding apply = percentEncodingPartial.apply(PERCENT_ENCODING); this.delegate = new SegmentEncodingUnaryValue<>(value, apply); } @Override public boolean hasValue() { return true; } @Override public ENCODES value() { return delegate.value; } @Override List> incorporate(final Segment next) { if (dotDot().equals(next)) { return singletonList(dot()); } else { return asList(this, next); } } @Override String asString() { return delegate.asString(); } @Override boolean isEmpty() { return "".equals(asString()); } @Override public boolean equals(final Object object) { if (this == object) { return true; } else if (object == null || getClass() != object.getClass()) { return false; } final ValueSegment that = (ValueSegment) object; return delegate.equals(that.delegate); } @Override public int hashCode() { return delegate.hashCode(); } @Override public String toString() { return "Segment{value='" + delegate + "'}"; } } private static final class SegmentEncodingUnaryValue extends PercentEncodingUnaryValue { SegmentEncodingUnaryValue(final ENCODES value, final PercentEncodingPartial.PercentEncoding percentEncoding) { super(value, percentEncoding); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy