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

org.projectnessie.model.Namespace Maven / Gradle / Ivy

Go to download

nessie-model-jakarta is effectively the same as nessie-model, but it is _not_ a multi-release jar and retains the jakarta annotations in the canonical classes. Please note that this artifact will go away, once Nessie no longer support Java 8 for clients. Therefore, do _not_ refer to this artifact - it is only meant for consumption by Nessie-Quarkus.

The newest version!
/*
 * Copyright (C) 2020 Dremio
 *
 * 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 org.projectnessie.model;

import static org.projectnessie.model.Namespace.Empty.EMPTY_NAMESPACE;
import static org.projectnessie.model.Util.DOT_STRING;
import static org.projectnessie.model.Util.FIRST_ALLOWED_KEY_CHAR;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.validation.constraints.NotNull;
import org.immutables.value.Value;
import org.immutables.value.Value.Derived;

/**
 * For a given table name a.b.c.tableName, the {@link Namespace} would be the prefix
 * a.b.c, since the last element tableName always represents the name of the actual
 * table and is not included in the {@link Namespace} itself. Therefore, the {@link Namespace} is
 * always consisting of the first N-1 elements.
 */
@Value.Immutable
@JsonSerialize(as = ImmutableNamespace.class)
@JsonDeserialize(as = ImmutableNamespace.class)
@JsonTypeName("NAMESPACE")
public abstract class Namespace extends Content {

  static final String ERROR_MSG_TEMPLATE =
      "'%s' is not a valid namespace identifier (should not end with '.')";

  /** This separate static class is needed to prevent class loader deadlocks. */
  public static final class Empty {
    private Empty() {}

    public static final Namespace EMPTY_NAMESPACE = builder().build();
  }

  /**
   * Refactor all code references to this constant to use {@link Empty#EMPTY_NAMESPACE}, there's a
   * non-zero risk of causing a class loader deadlock.
   */
  @Deprecated
  public static final Namespace EMPTY = builder().elements(Collections.emptyList()).build();

  public static ImmutableNamespace.Builder builder() {
    return ImmutableNamespace.builder();
  }

  @Override
  public Type getType() {
    return Type.NAMESPACE;
  }

  @NotNull
  @jakarta.validation.constraints.NotNull
  @Derived
  @JsonIgnore
  public String name() {
    return toPathString();
  }

  @JsonIgnore
  @Value.Redacted
  public boolean isEmpty() {
    return getElements().isEmpty();
  }

  @NotNull
  @jakarta.validation.constraints.NotNull
  public abstract List getElements();

  @JsonIgnore
  @Value.Redacted
  public String[] getElementsArray() {
    return getElements().toArray(new String[0]);
  }

  @JsonIgnore
  @Value.Redacted
  public int getElementCount() {
    return getElements().size();
  }

  @JsonIgnore
  @Value.Redacted
  public Namespace getParent() {
    List elements = getElements();
    if (elements.size() <= 1) {
      throw new IllegalArgumentException("Namespace has no parent");
    }
    return Namespace.of(elements.subList(0, elements.size() - 1));
  }

  @JsonIgnore
  @Value.Redacted
  public Namespace getParentOrEmpty() {
    List elements = getElements();
    if (elements.size() <= 1) {
      return EMPTY_NAMESPACE;
    }
    return Namespace.of(elements.subList(0, elements.size() - 1));
  }

  @NotNull
  @jakarta.validation.constraints.NotNull
  @JsonInclude(Include.NON_EMPTY)
  public abstract Map getProperties();

  @Override
  public abstract Namespace withId(String id);

  /**
   * Builds a {@link Namespace} using the elements of the given content key.
   *
   * @param contentKey key with the elements for the namespace
   * @return new namespace
   */
  public static Namespace of(ContentKey contentKey) {
    return Namespace.of(contentKey.getElementsArray());
  }

  /**
   * Builds a {@link Namespace} instance for the given elements.
   *
   * @param elements The elements to build the namespace from.
   * @return The constructed {@link Namespace} instance. If elements is empty, then {@link
   *     Namespace#name()} will be an empty string.
   */
  public static Namespace of(String... elements) {
    return Namespace.of(Collections.emptyMap(), elements);
  }

  /**
   * Builds a {@link Namespace} instance for the given elements and the given properties.
   *
   * @param elements The elements to build the namespace from.
   * @param properties The namespace properties.
   * @return The constructed {@link Namespace} instance. If elements is empty, then {@link
   *     Namespace#name()} will be an empty string.
   */
  public static Namespace of(Map properties, String... elements) {
    Objects.requireNonNull(elements, "elements must be non-null");
    if (elements.length == 0 || (elements.length == 1 && "".equals(elements[0]))) {
      return EMPTY_NAMESPACE;
    }

    for (String e : elements) {
      if (e == null) {
        throw new IllegalArgumentException(
            String.format(
                "Namespace '%s' must not contain a null element.", Arrays.toString(elements)));
      }
      if (e.isEmpty()) {
        throw new IllegalArgumentException(
            String.format(
                "Namespace '%s' must not contain an empty element.", Arrays.toString(elements)));
      }
      if (e.chars().anyMatch(i -> i < FIRST_ALLOWED_KEY_CHAR)) {
        throw new IllegalArgumentException(
            String.format(
                "Namespace '%s' must not contain characters less than 0x%2h.",
                Arrays.toString(elements), FIRST_ALLOWED_KEY_CHAR));
      }
    }

    if (DOT_STRING.equals(elements[elements.length - 1])) {
      throw new IllegalArgumentException(
          String.format(ERROR_MSG_TEMPLATE, Arrays.toString(elements)));
    }

    return ImmutableNamespace.builder()
        .elements(Arrays.asList(elements))
        .properties(properties)
        .build();
  }

  /**
   * Builds a {@link Namespace} instance for the given elements.
   *
   * @param elements The elements to build the namespace from.
   * @return The constructed {@link Namespace} instance. If elements is empty, then {@link
   *     Namespace#name()} will be an empty string.
   */
  public static Namespace of(List elements) {
    Objects.requireNonNull(elements, "elements must be non-null");
    return Namespace.of(elements.toArray(new String[0]));
  }

  /**
   * Builds a {@link Namespace} instance for the given elements and the given properties.
   *
   * @param elements The elements to build the namespace from.
   * @param properties The properties of the namespace.
   * @return The constructed {@link Namespace} instance. If elements is empty, then {@link
   *     Namespace#name()} will be an empty string.
   */
  public static Namespace of(List elements, Map properties) {
    Objects.requireNonNull(elements, "elements must be non-null");
    return Namespace.of(properties, elements.toArray(new String[0]));
  }

  /**
   * Builds a {@link Namespace} instance for the given elements split by the . (dot)
   * character.
   *
   * @param identifier The identifier to build the namespace from.
   * @return Splits the given identifier by . and returns a {@link Namespace}
   *     instance. If identifier is empty, then {@link Namespace#name()} will be an empty
   *     string.
   */
  public static Namespace parse(String identifier) {
    Objects.requireNonNull(identifier, "identifier must be non-null");
    if (identifier.isEmpty()) {
      return EMPTY_NAMESPACE;
    }
    if (identifier.endsWith(DOT_STRING)) {
      throw new IllegalArgumentException(String.format(ERROR_MSG_TEMPLATE, identifier));
    }
    return Namespace.of(Util.fromPathString(identifier));
  }

  /**
   * Checks whether the current {@link Namespace} is the same as or a sub-element of the given
   * {@link Namespace} instance by comparing each canonical element.
   *
   * @param parent {@link Namespace} instance to compare with.
   * @return true if the current {@link Namespace} is the same as or a sub-element of
   *     the given {@link Namespace} instance by comparing each canonical element, false
   *      otherwise.
   */
  public boolean isSameOrSubElementOf(Namespace parent) {
    Objects.requireNonNull(parent, "namespace must be non-null");
    if (getElementCount() < parent.getElementCount()) {
      return false;
    }
    for (int i = 0; i < parent.getElementCount(); i++) {
      // elements must match exactly
      if (!getElements().get(i).equals(parent.getElements().get(i))) {
        return false;
      }
    }
    return true;
  }

  /**
   * Convert from path encoded string to normal string.
   *
   * @param encoded Path encoded string
   * @return Actual key.
   */
  public static Namespace fromPathString(String encoded) {
    return parse(encoded);
  }

  /**
   * Convert this namespace to a URL encoded path string.
   *
   * @return String encoded for path use.
   */
  public String toPathString() {
    return Util.toPathString(getElements());
  }

  @Override
  public String toString() {
    return name();
  }

  public ContentKey toContentKey() {
    return ContentKey.of(getElements());
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy