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

org.junit.platform.engine.UniqueId Maven / Gradle / Ivy

/*
 * Copyright 2015-2020 the original author or authors.
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License v2.0 which
 * accompanies this distribution and is available at
 *
 * https://www.eclipse.org/legal/epl-v20.html
 */

package org.junit.platform.engine;

import static java.util.Collections.singletonList;
import static java.util.Collections.unmodifiableList;
import static org.apiguardian.api.API.Status.STABLE;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import org.apiguardian.api.API;
import org.junit.platform.commons.JUnitException;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.ToStringBuilder;

/**
 * {@code UniqueId} encapsulates the creation, parsing, and display of unique IDs
 * for {@link TestDescriptor TestDescriptors}.
 *
 * 

Instances of this class have value semantics and are immutable. * * @since 1.0 */ @API(status = STABLE, since = "1.0") public class UniqueId implements Cloneable, Serializable { private static final long serialVersionUID = 1L; private static final String ENGINE_SEGMENT_TYPE = "engine"; /** * Parse a {@code UniqueId} from the supplied string representation using the * default format. * * @param uniqueId the string representation to parse; never {@code null} or blank * @return a properly constructed {@code UniqueId} * @throws JUnitException if the string cannot be parsed */ public static UniqueId parse(String uniqueId) throws JUnitException { Preconditions.notBlank(uniqueId, "Unique ID string must not be null or blank"); return UniqueIdFormat.getDefault().parse(uniqueId); } /** * Create an engine's unique ID from its {@code engineId} using the default * format. * *

The engine ID will be stored in a {@link Segment} with * {@link Segment#getType type} {@code "engine"}. * * @param engineId the engine ID; never {@code null} or blank * @see #root(String, String) */ public static UniqueId forEngine(String engineId) { Preconditions.notBlank(engineId, "engineId must not be null or blank"); return root(ENGINE_SEGMENT_TYPE, engineId); } /** * Create a root unique ID from the supplied {@code segmentType} and * {@code value} using the default format. * * @param segmentType the segment type; never {@code null} or blank * @param value the value; never {@code null} or blank * @see #forEngine(String) */ public static UniqueId root(String segmentType, String value) { return new UniqueId(UniqueIdFormat.getDefault(), new Segment(segmentType, value)); } private final UniqueIdFormat uniqueIdFormat; private final List segments; // lazily computed private transient int hashCode; // lazily computed private transient String toString; private UniqueId(UniqueIdFormat uniqueIdFormat, Segment segment) { this.uniqueIdFormat = uniqueIdFormat; this.segments = singletonList(segment); } /** * Initialize a {@code UniqueId} instance. * * @implNote A defensive copy of the segment list is not created by * this implementation. All callers should immediately drop the reference * to the list instance that they pass into this constructor. */ UniqueId(UniqueIdFormat uniqueIdFormat, List segments) { this.uniqueIdFormat = uniqueIdFormat; this.segments = unmodifiableList(segments); } final Optional getRoot() { return this.segments.isEmpty() ? Optional.empty() : Optional.of(this.segments.get(0)); } /** * Get the engine ID stored in this {@code UniqueId}, if available. * * @see #forEngine(String) */ public final Optional getEngineId() { return getRoot().filter(segment -> segment.getType().equals(ENGINE_SEGMENT_TYPE)).map(Segment::getValue); } /** * Get the immutable list of {@linkplain Segment segments} that make up this * {@code UniqueId}. */ public final List getSegments() { return this.segments; } /** * Construct a new {@code UniqueId} by appending a new {@link Segment}, based * on the supplied {@code segmentType} and {@code value}, to the end of this * {@code UniqueId}. * *

This {@code UniqueId} will not be modified. * *

Neither the {@code segmentType} nor the {@code value} may contain any * of the special characters used for constructing the string representation * of this {@code UniqueId}. * * @param segmentType the type of the segment; never {@code null} or blank * @param value the value of the segment; never {@code null} or blank */ public final UniqueId append(String segmentType, String value) { return append(new Segment(segmentType, value)); } /** * Construct a new {@code UniqueId} by appending a new {@link Segment} to * the end of this {@code UniqueId}. * *

This {@code UniqueId} will not be modified. * * @param segment the segment to be appended; never {@code null} * * @since 1.1 */ @API(status = STABLE, since = "1.1") public final UniqueId append(Segment segment) { Preconditions.notNull(segment, "segment must not be null"); List baseSegments = new ArrayList<>(this.segments); baseSegments.add(segment); return new UniqueId(this.uniqueIdFormat, baseSegments); } /** * Determine if the supplied {@code UniqueId} is a prefix for this * {@code UniqueId}. * * @param potentialPrefix the {@code UniqueId} to be checked; never {@code null} * * @since 1.1 */ @API(status = STABLE, since = "1.1") public boolean hasPrefix(UniqueId potentialPrefix) { Preconditions.notNull(potentialPrefix, "potentialPrefix must not be null"); int size = this.segments.size(); int prefixSize = potentialPrefix.segments.size(); return size >= prefixSize && this.segments.subList(0, prefixSize).equals(potentialPrefix.segments); } /** * Construct a new {@code UniqueId} and removing the last {@link Segment} of * this {@code UniqueId}. * *

This {@code UniqueId} will not be modified. * * @throws org.junit.platform.commons.PreconditionViolationException * if this {@code UniqueId} contains a single segment * @return a new {@code UniqueId}; never {@code null} * @since 1.5 */ @API(status = STABLE, since = "1.5") public UniqueId removeLastSegment() { Preconditions.condition(this.segments.size() > 1, "Cannot remove last remaining segment"); return new UniqueId(uniqueIdFormat, new ArrayList<>(segments.subList(0, segments.size() - 1))); } /** * Get the last {@link Segment} of this {@code UniqueId}. * * @return the last {@code Segment}; never {@code null} * @since 1.5 */ @API(status = STABLE, since = "1.5") public Segment getLastSegment() { return this.segments.get(this.segments.size() - 1); } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } UniqueId that = (UniqueId) o; return this.segments.equals(that.segments); } @Override public int hashCode() { int value = this.hashCode; if (value == 0) { value = this.segments.hashCode(); if (value == 0) { // handle the edge case of the computed hashCode being 0 value = 1; } // this is a benign race like String#hash // we potentially read and write values from multiple threads // without a happens-before relationship // however the JMM guarantees us that we only ever see values // that were valid at one point, either 0 or the hash code // so we might end up not seeing a value that a different thread // has computed or multiple threads writing the same value this.hashCode = value; } return value; } /** * Generate the unique, formatted string representation of this {@code UniqueId} * using the configured {@link UniqueIdFormat}. */ @Override public String toString() { String s = this.toString; if (s == null) { s = this.uniqueIdFormat.format(this); // this is a benign race like String#hash // we potentially read and write values from multiple threads // without a happens-before relationship // however the JMM guarantees us that we only ever see values // that were valid at one point, either null or the toString value // so we might end up not seeing a value that a different thread // has computed or multiple threads writing the same value this.toString = s; } return s; } /** * A segment of a {@link UniqueId} comprises a type and a * value. */ @API(status = STABLE, since = "1.0") public static class Segment implements Serializable { private static final long serialVersionUID = 1L; private final String type; private final String value; /** * Create a new {@code Segment} using the supplied {@code type} and * {@code value}. * * @param type the type of this segment * @param value the value of this segment */ Segment(String type, String value) { Preconditions.notBlank(type, "type must not be null or blank"); Preconditions.notBlank(value, "value must not be null or blank"); this.type = type; this.value = value; } /** * Get the type of this segment. */ public String getType() { return this.type; } /** * Get the value of this segment. */ public String getValue() { return this.value; } @Override public int hashCode() { return Objects.hash(this.type, this.value); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Segment that = (Segment) o; return Objects.equals(this.type, that.type) && Objects.equals(this.value, that.value); } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("type", this.type) .append("value", this.value) .toString(); // @formatter:on } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy