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

com.sigpwned.nio.spi.s3.lite.PosixLikePathRepresentation Maven / Gradle / Ivy

Go to download

A lightweight Java NIO.2 service provider for S3, allowing Java IO operations to be performed on S3 objects using the `s3` and `s3x` URI schemes.

The newest version!
/*-
 * =================================LICENSE_START==================================
 * AWS Java NIO SPI for S3 Lite
 * ====================================SECTION=====================================
 * Copyright (C) 2023 Andy Boothe
 * ====================================SECTION=====================================
 * 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.
 * ==================================LICENSE_END===================================
 */

/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier:
 * Apache-2.0
 */

package com.sigpwned.nio.spi.s3.lite;

import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * A class to hold a string representation of a Posix like path pointing to an S3 object or
 * "directory". Provide methods to obtain views of the representation according to Posix conventions
 * and the directories implicit in S3 paths. The most substantial difference with true Posix paths
 * is that in {@code java.nio.Path, /foo/baa/} will be represented as {@code /foo/baa} whereas in
 * this representation the trailing slash must be retained to infer that {@code baa} is a
 * "directory".
 *
 * Borrowed with love from awslabs/aws-java-nio-spi-for-s3. Acceptable per Apache License.
 */
class PosixLikePathRepresentation {
  private static final String PATH_SEPARATOR = S3FileSystemProvider.SEPARATOR;
  static final PosixLikePathRepresentation ROOT = new PosixLikePathRepresentation(PATH_SEPARATOR);
  static final PosixLikePathRepresentation EMPTY_PATH = new PosixLikePathRepresentation("");
  private static final char PATH_SEPARATOR_CHAR = PATH_SEPARATOR.charAt(0);

  private String path;

  PosixLikePathRepresentation(String path) {
    if (path == null) {
      throw new IllegalArgumentException("path may not be null");
    }
    this.path = path;
  }

  PosixLikePathRepresentation(char[] path) {
    new PosixLikePathRepresentation(new String(path));
  }

  /**
   * Construct a path representation from a series of elements. If {@code more} is {@code null} or
   * empty then {@code first} is assumed to contain the entire path. Elements will be concatenated
   * with the standard separator to form the final representation. Any redundant separators at the
   * beginning or end of the string will be removed, e.g. {@code "/", "/foo} will become
   * {@code "/foo"}. Directory aliases {@code ".", ".."} will be retained in the path.
   * 

* The current implementation is permissive and assumes most characters are legal in the path * however care should be taken when using characters which although legal in S3 can cause * encoding problems or may not be legal in Posix paths. *

* . *

* When {@code first} begins with "/" it is assumed to be absolute and the root if first is only * "/" and {@code more} is empty or null *

* . *

* When {@code first} is the only element and it ends with "/" then a directory is assumed. * Likewise if {@code more} is present and the last item of {@code more} ends with "/" then a * directory is assumed. *

* * @param first the first element of the path, or the whole path if {@code more} is not defined. * May not be null or empty unless {@code more} is also undefined. * @param more zero or more path elements, {@code EMPTY_PATH} if {@code first} is null or empty * @return the representation of the path constructed from the arguments with non-redundant * separators. */ static PosixLikePathRepresentation of(String first, String... more) { if ((first == null || first.trim().isEmpty()) && !(more == null || more.length == 0)) { throw new IllegalArgumentException( "The first element of the path may not be null or empty when more exists"); } if (first == null || first.trim().isEmpty()) { return EMPTY_PATH; } Deque allParts = new LinkedList(); allParts.add(first); allParts.addAll(collectMore(more)); if (allParts.peekLast() == null) { throw new RuntimeException( "the last element of the path representation is unexpectedly null"); } boolean endsWithSeparator = hasTrailingSeparatorString(allParts.peekLast()); boolean startsWithSeparator = isAbsoluteString(allParts.peekFirst()); String path = partsToPathString(allParts, endsWithSeparator, startsWithSeparator); return new PosixLikePathRepresentation(path); } private static String partsToPathString(Deque allParts, boolean endsWithSeparator, boolean startsWithSeparator) { String path = allParts.stream().flatMap(part -> Arrays.stream(part.split("/+"))) .filter(p -> !p.isEmpty()).collect(Collectors.joining(PATH_SEPARATOR)); if (endsWithSeparator && !hasTrailingSeparatorString(path)) { path = path + PATH_SEPARATOR; } if (startsWithSeparator && !isAbsoluteString(path)) { path = PATH_SEPARATOR + path; } return path; } private static List collectMore(String[] more) { if (more != null && more.length != 0) { return Arrays.stream(more).filter(Objects::nonNull).filter(p -> !p.isEmpty()) .collect(Collectors.toList()); } return Collections.emptyList(); } /** * Does this path represent the root of the bucket * * @return true if and only if this path has only length 1 and contains only "/" */ boolean isRoot() { return isRootString(path); } private static boolean isRootString(String path) { return path.equals(PATH_SEPARATOR); } /** * Is the path resolvable without any further information (e.g. not relative to some other * location). * * @return true if this path begins with '/' */ boolean isAbsolute() { return isAbsoluteString(path); } private static boolean isAbsoluteString(String path) { return !(path == null) && !path.isEmpty() && path.charAt(0) == PATH_SEPARATOR_CHAR; } /** * While S3 doesn't have directories, the following POSIX path representations would be considered * directories when S3 is used: *

* "/", "foo/", "./", "../", ".", "..", "", "foo/.", "foo/.." *

* Importantly, in a true Posix filesystem, if /foo/ is a directory then /foo is as well, however * /foo in S3 cannot be inferred to be a directory. */ boolean isDirectory() { return isDirectoryString(path); } private static boolean isDirectoryString(String path) { return path.isEmpty() || hasTrailingSeparatorString(path) || path.equals(".") || path.equals("..") || path.endsWith(PATH_SEPARATOR_CHAR + ".") || path.endsWith(PATH_SEPARATOR_CHAR + ".."); } boolean hasTrailingSeparator() { return hasTrailingSeparatorString(path); } private static boolean hasTrailingSeparatorString(String path) { if (path.isEmpty()) { return false; } return path.charAt(path.length() - 1) == PATH_SEPARATOR_CHAR; } char[] chars() { return path.toCharArray(); } /** * Returns a string representation of the path representation * * @return a string. */ @Override public String toString() { return path; } List elements() { if (this.isRoot()) { return Collections.emptyList(); } return Arrays.stream(path.split(PATH_SEPARATOR)).filter(s -> !s.trim().isEmpty()) .collect(Collectors.toList()); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } PosixLikePathRepresentation that = (PosixLikePathRepresentation) o; return path.equals(that.path); } @Override public int hashCode() { return Objects.hashCode(path); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy