Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ static ArrayTagSet create(Tag... tags) {
}

/** Create a new tag set. */
static ArrayTagSet create(Iterable<Tag> tags) {
static ArrayTagSet create(Iterable<? extends Tag> tags) {
return (tags instanceof ArrayTagSet) ? (ArrayTagSet) tags : EMPTY.addAll(tags);
}

Expand Down Expand Up @@ -146,9 +146,9 @@ ArrayTagSet add(Tag tag) {
}

/** Add a collection of tags to the set. */
ArrayTagSet addAll(Iterable<Tag> ts) {
ArrayTagSet addAll(Iterable<? extends Tag> ts) {
if (ts instanceof Collection) {
Collection<Tag> data = (Collection<Tag>) ts;
Collection<? extends Tag> data = (Collection<? extends Tag>) ts;
if (data.isEmpty()) {
return this;
} else {
Expand Down
25 changes: 25 additions & 0 deletions spectator-api/src/main/java/com/netflix/spectator/api/TagList.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;

/**
* Base type for a collection of tags. Allows access to the keys and values without allocations
Expand All @@ -37,6 +39,29 @@ static TagList create(Map<String, String> tags) {
return ArrayTagSet.create(tags);
}

/**
* Returns a new {@code TagList} containing the given tags.
*
* @throws NullPointerException if {@code tags} is null or contains a null tag
*/
static TagList create(Iterable<? extends Tag> tags) {
return ArrayTagSet.create(tags);
}

/**
* Returns the empty {@code TagList}.
*/
static TagList empty() {
return ArrayTagSet.EMPTY;
}

/**
* Returns a {@code Collector} that accumulates the input tags into a new {@code TagList}.
*/
static Collector<Tag, ?, TagList> toTagList() {
return Collectors.collectingAndThen(Collectors.toList(), TagList::create);
}

/** Return the key at the specified index. */
String getKey(int i);

Expand Down
125 changes: 125 additions & 0 deletions spectator-api/src/test/java/com/netflix/spectator/api/TagListTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright 2014-2026 Netflix, Inc.
Comment thread
kilink marked this conversation as resolved.
*
* 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 com.netflix.spectator.api;

import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;

public class TagListTest {
@Test
public void testEmpty() {
TagList empty = TagList.empty();
assertEquals(0, empty.size());
assertEquals(empty, TagList.empty());
assertFalse(empty.iterator().hasNext());
}

@Test
public void testCreateMap() {
Map<String, String> tagMap = new HashMap<>();
tagMap.put("a", "v1");
tagMap.put("b", "v2");
tagMap.put("c", "v3");
TagList tags = TagList.create(tagMap);
assertEquals(3, tags.size());
assertEquals(Tag.of("a", "v1"), tags.getTag(0));
assertEquals(Tag.of("b", "v2"), tags.getTag(1));
assertEquals(Tag.of("c", "v3"), tags.getTag(2));
}

@Test
public void testCreateIterable() {
TagList tags = TagList.create(
Arrays.asList(Tag.of("a", "v1"), Tag.of("b", "v2"), Tag.of("c", "v3")));
assertEquals(3, tags.size());
assertEquals(Tag.of("a", "v1"), tags.getTag(0));
assertEquals(Tag.of("b", "v2"), tags.getTag(1));
assertEquals(Tag.of("c", "v3"), tags.getTag(2));
}

@Test
public void testCreateIterableContravariance() {
List<CustomTag> customTags =
Arrays.asList(CustomTag.of("a", "v1"), CustomTag.of("b", "v2"), CustomTag.of("c", "v3"));
TagList tags = TagList.create(customTags);
assertEquals(3, tags.size());
assertEquals(Tag.of("a", "v1"), tags.getTag(0));
assertEquals(Tag.of("b", "v2"), tags.getTag(1));
assertEquals(Tag.of("c", "v3"), tags.getTag(2));
}

@Test
public void testCollector() {
// Use a mix of Tag implementations to ensure the collector handles a heterogeneous stream.
TagList tags = Stream.<Tag>of(Tag.of("a", "v1"), CustomTag.of("b", "v2"), Tag.of("c", "v3"))
.collect(TagList.toTagList());
assertEquals(3, tags.size());
assertEquals(Tag.of("a", "v1"), tags.getTag(0));
assertEquals(Tag.of("b", "v2"), tags.getTag(1));
assertEquals(Tag.of("c", "v3"), tags.getTag(2));
}

static final class CustomTag implements Tag {

private final String key;
private final String value;

private CustomTag(String key, String value) {
this.key = key;
this.value = value;
}

static CustomTag of(String key, String value) {
return new CustomTag(key, value);
}

@Override
public String key() {
return key;
}

@Override
public String value() {
return value;
}

@Override
public int hashCode() {
return Objects.hash(key, value);
}

@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Tag)) {
return false;
}
Tag other = (Tag) obj;
return Objects.equals(key, other.key()) && Objects.equals(value, other.value());
}
}
}