001/*-------------------------------------------------------------------------+ 002| | 003| Copyright (c) 2005-2018 The ConQAT Project | 004| | 005| Licensed under the Apache License, Version 2.0 (the "License"); | 006| you may not use this file except in compliance with the License. | 007| You may obtain a copy of the License at | 008| | 009| http://www.apache.org/licenses/LICENSE-2.0 | 010| | 011| Unless required by applicable law or agreed to in writing, software | 012| distributed under the License is distributed on an "AS IS" BASIS, | 013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 014| See the License for the specific language governing permissions and | 015| limitations under the License. | 016| | 017+-------------------------------------------------------------------------*/ 018package org.conqat.lib.commons.uniformpath; 019 020import java.util.Arrays; 021import java.util.Collections; 022import java.util.List; 023import java.util.ListIterator; 024 025import org.conqat.lib.commons.collections.CollectionUtils; 026import org.conqat.lib.commons.string.StringUtils; 027 028import com.google.common.base.Preconditions; 029import com.google.common.collect.Iterables; 030 031/** 032 * A relative uniform path that can be resolved against an absolute uniform 033 * path. In contrast to absolute uniform paths, relative paths are not typed. 034 */ 035public final class RelativeUniformPath { 036 037 /** 038 * The constituent segments of this uniform path, e.g. for the path 039 * {@code src/main} this would be the array {@code [src, main]}. Segments may 040 * consist of dots or be empty. 041 */ 042 private final String[] segments; 043 044 private RelativeUniformPath(String... pathSegments) { 045 this(Arrays.asList(pathSegments)); 046 } 047 048 private RelativeUniformPath(Iterable<String> pathSegments) { 049 this.segments = Iterables.toArray(pathSegments, String.class); 050 } 051 052 /** 053 * Creates a relative uniform path from the given segments. No resolution of 054 * relative segments (such as {@code ../src}) is attempted at this stage. 055 * 056 * @see #of(List) 057 */ 058 public static RelativeUniformPath of(String... segments) { 059 Preconditions.checkNotNull(segments, UniformPath.SEGMENTS_LIST_MAY_NOT_BE_NULL); 060 061 return of(Arrays.asList(segments)); 062 } 063 064 /** 065 * Creates a relative uniform path from the given segments. No resolution of 066 * relative segments (such as {@code ../src}) is attempted at this stage. 067 */ 068 public static RelativeUniformPath of(List<String> segments) { 069 Preconditions.checkNotNull(segments, UniformPath.SEGMENTS_LIST_MAY_NOT_BE_NULL); 070 071 for (String segment : segments) { 072 UniformPath.checkSegmentValidity(segment, segments, s -> s == null, "empty segment"); 073 UniformPath.checkSegmentValidity(segment, segments, 074 s -> StringUtils.splitWithEscapeCharacter(s, "/").size() > 1, "contains unescaped slash"); 075 } 076 if (segments.isEmpty()) { 077 return new RelativeUniformPath(new String[0]); 078 } 079 return new RelativeUniformPath(segments); 080 } 081 082 /** 083 * Resolves relative path segments such as (such as {@code src/main/../java}) to 084 * the absolute segments of the path (e.g. {@code [src, java]}). 085 */ 086 /* package */ static List<String> resolveRelativeSegments(List<String> pathSegments) { 087 List<String> canonicalSegments = CollectionUtils.filter(pathSegments, 088 segment -> !(StringUtils.isEmpty(segment) || segment.equals("."))); 089 ListIterator<String> canonicalSegmentsIterator = canonicalSegments.listIterator(); 090 while (canonicalSegmentsIterator.hasNext()) { 091 if (canonicalSegmentsIterator.next().equals("..")) { 092 // Remove both previous element and the ".." item 093 canonicalSegmentsIterator.previous(); 094 canonicalSegmentsIterator.remove(); 095 if (!canonicalSegmentsIterator.hasPrevious()) { 096 throw new IllegalArgumentException( 097 "Invalid path (refers outside the root folder): " + String.join("/", pathSegments)); 098 } 099 canonicalSegmentsIterator.previous(); 100 canonicalSegmentsIterator.remove(); 101 } 102 } 103 return canonicalSegments; 104 } 105 106 /** Returns a read-only view of the segments of this relative uniform path. */ 107 /* package */ List<String> getSegments() { 108 return Collections.unmodifiableList(Arrays.asList(segments)); 109 } 110 111 /** Returns the name of the last segment of this path. */ 112 public String getLastSegment() { 113 if (segments.length == 0) { 114 throw new IllegalArgumentException("Cannot get the last segment of the root path"); 115 } 116 return segments[segments.length - 1]; 117 } 118 119 /** 120 * Creates a new {@link RelativeUniformPath} by prepending the given suffixes to 121 * the relative uniform path. 122 */ 123 public RelativeUniformPath addSuffix(String... suffix) { 124 String[] newSegments = Arrays.copyOf(this.segments, this.segments.length + suffix.length); 125 System.arraycopy(suffix, 0, newSegments, this.segments.length, suffix.length); 126 return RelativeUniformPath.of(newSegments); 127 } 128 129 /** 130 * Resolves this relative path against the given absolute path, performing path 131 * canonicalization in the process (i.e. ".." will be resolved to parent 132 * segment). 133 */ 134 public UniformPath resolveAgainstAbsolutePath(UniformPath uniformPath) { 135 return uniformPath.resolve(this); 136 } 137 138 @Override 139 public String toString() { 140 return String.join("/", segments); 141 } 142 143 @Override 144 public int hashCode() { 145 return Arrays.hashCode(segments); 146 } 147 148 @Override 149 public boolean equals(Object obj) { 150 if (this == obj) { 151 return true; 152 } 153 if (obj == null) { 154 return false; 155 } 156 if (!(obj instanceof RelativeUniformPath)) { 157 return false; 158 } 159 RelativeUniformPath other = (RelativeUniformPath) obj; 160 return Arrays.equals(segments, other.segments); 161 } 162}