/*
 * Copyright 2015-2017 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
 *
 * http://www.eclipse.org/legal/epl-v20.html
 */

package org.junit.platform.engine;

import static org.apiguardian.api.API.Status.STABLE;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Objects;

import org.apiguardian.api.API;
import org.junit.platform.commons.util.PreconditionViolationException;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.StringUtils;

/**
 * Immutable value object for a <em>tag</em> that is assigned to a test or
 * container.
 *
 * @since 1.0
 * @see #isValid(String)
 * @see #create(String)
 */
@API(status = STABLE, since = "1.0")
public final class TestTag implements Serializable {

	private static final long serialVersionUID = 1L;

	private final String name;

	/**
	 * <em>Reserved characters</em> that are not permissible as part of a tag name.
	 *
	 * <ul>
	 * <li>{@code ","}</li>
	 * <li>{@code "("}</li>
	 * <li>{@code ")"}</li>
	 * <li>{@code "&"}</li>
	 * <li>{@code "|"}</li>
	 * <li>{@code "!"}</li>
	 * </ul>
	 */
	public static final String[] RESERVED_CHARACTERS = { ",", "(", ")", "&", "|", "!" };

	/**
	 * Determine if the supplied tag name is valid with regard to the supported
	 * syntax for tags.
	 *
	 * <h3>Syntax Rules for Tags</h3>
	 * <ul>
	 * <li>A tag must not be {@code null}.</li>
	 * <li>A tag must not be blank.</li>
	 * <li>A trimmed tag must not contain whitespace.</li>
	 * <li>A trimmed tag must not contain ISO control characters.</li>
	 * <li>A trimmed tag must not contain {@linkplain #RESERVED_CHARACTERS
	 * reserved characters}.</li>
	 * </ul>
	 *
	 * <p>If this method returns {@code true} for a given name, it is then a
	 * valid candidate for the {@link TestTag#create(String) create()} factory
	 * method.
	 *
	 * @param name the name of the tag to validate; may be {@code null} or blank
	 * @return {@code true} if the supplied tag name conforms to the supported
	 * syntax for tags
	 * @see StringUtils#isNotBlank(String)
	 * @see String#trim()
	 * @see StringUtils#doesNotContainWhitespace(String)
	 * @see StringUtils#doesNotContainIsoControlCharacter(String)
	 * @see #RESERVED_CHARACTERS
	 * @see TestTag#create(String)
	 */
	public static boolean isValid(String name) {
		if (name == null) {
			return false;
		}
		name = name.trim();

		return !name.isEmpty() && //
				StringUtils.doesNotContainWhitespace(name) && //
				StringUtils.doesNotContainIsoControlCharacter(name) && //
				doesNotContainReservedCharacter(name);
	}

	private static boolean doesNotContainReservedCharacter(String str) {
		return Arrays.stream(RESERVED_CHARACTERS).noneMatch(str::contains);
	}

	/**
	 * Create a {@code TestTag} from the supplied {@code name}.
	 *
	 * <p>Consider checking whether the syntax of the supplied {@code name}
	 * is {@linkplain #isValid(String) valid} before attempting to create a
	 * {@code TestTag} using this factory method.
	 *
	 * <p>Note: the supplied {@code name} will be {@linkplain String#trim() trimmed}.
	 *
	 * @param name the name of the tag; must be syntactically <em>valid</em>
	 * @throws PreconditionViolationException if the supplied tag name is not
	 * syntactically <em>valid</em>
	 * @see TestTag#isValid(String)
	 */
	public static TestTag create(String name) throws PreconditionViolationException {
		return new TestTag(name);
	}

	private TestTag(String name) {
		Preconditions.condition(TestTag.isValid(name),
			() -> String.format("Tag name [%s] must be syntactically valid", name));
		this.name = name.trim();
	}

	/**
	 * Get the name of this tag.
	 *
	 * @return the name of this tag; never {@code null} or blank
	 */
	public String getName() {
		return this.name;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof TestTag) {
			TestTag that = (TestTag) obj;
			return Objects.equals(this.name, that.name);
		}
		return false;
	}

	@Override
	public int hashCode() {
		return this.name.hashCode();
	}

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

}

@@ -468,23 +468,101 @@
 va.util.
-Objects
+Collections;%0Aimport java.util.HashSet;%0Aimport java.util.Objects;%0Aimport java.util.Set
 ;%0A%0Aimpor
@@ -1389,16 +1389,19 @@
 nal 
+Set%3C
 String
-%5B%5D
+%3E
  RES
@@ -1423,10 +1423,67 @@
 S = 
-%7B 
+Collections.unmodifiableSet(%0A%09%09new HashSet%3C%3E(Arrays.asList(
 %22,%22,
@@ -1510,10 +1510,11 @@
  %22!%22
- %7D
+)))
 ;%0A%0A%09
@@ -2962,22 +2962,8 @@
 urn 
-Arrays.stream(
 RESE
@@ -2977,16 +2977,24 @@
 ARACTERS
+.stream(
 ).noneMa

Make reserved character collection immutable