/* * Copyright 2014 Netflix, Inc. * * 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.servo.tag; import com.netflix.servo.util.Iterables; import com.netflix.servo.util.Preconditions; import com.netflix.servo.util.Strings; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; /** * Immutable tag list. */ public final class BasicTagList implements TagList { /** * An empty tag list. */ public static final TagList EMPTY = new BasicTagList(Collections.<Tag>emptySet()); private final SmallTagMap tagMap; private SortedMap<String, String> sortedTaglist; /** * Create a BasicTagList from a {@link SmallTagMap}. */ public BasicTagList(SmallTagMap tagMap) { this.tagMap = tagMap; } /** * Creates a new instance with a fixed set of tags. * * @param entries entries to include in this tag list */ public BasicTagList(Iterable<Tag> entries) { SmallTagMap.Builder builder = SmallTagMap.builder(); builder.addAll(entries); tagMap = builder.result(); } /** * {@inheritDoc} */ public Tag getTag(String key) { return tagMap.get(key); } /** * {@inheritDoc} */ public String getValue(String key) { final Tag t = tagMap.get(key); return (t == null) ? null : t.getValue(); } /** * {@inheritDoc} */ public boolean containsKey(String key) { return tagMap.containsKey(key); } /** * {@inheritDoc} */ public boolean isEmpty() { return tagMap.isEmpty(); } /** * {@inheritDoc} */ public int size() { return tagMap.size(); } /** * {@inheritDoc} */ public Iterator<Tag> iterator() { return tagMap.iterator(); } /** * {@inheritDoc} */ public Map<String, String> asMap() { if (sortedTaglist != null) { return sortedTaglist; } SortedMap<String, String> tagMap = new TreeMap<>(); for (Tag tag : this.tagMap) { tagMap.put(tag.getKey(), tag.getValue()); } sortedTaglist = Collections.unmodifiableSortedMap(tagMap); return sortedTaglist; } /** * Returns a new tag list with additional tags from {@code tags}. If there * is a conflict with tag keys the tag from {@code tags} will be used. */ public BasicTagList copy(TagList tags) { return concat(this, tags); } /** * Returns a new tag list with an additional tag. If {@code key} is * already present in this tag list the value will be overwritten with * {@code value}. */ public BasicTagList copy(String key, String value) { return concat(this, Tags.newTag(key, value)); } /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { return this == obj || (obj instanceof BasicTagList) && tagMap.equals(((BasicTagList) obj).tagMap); } /** * {@inheritDoc} */ @Override public int hashCode() { return tagMap.hashCode(); } /** * {@inheritDoc} */ @Override public String toString() { return Strings.join(",", tagMap.iterator()); } /** * Returns a tag list containing the union of {@code t1} and {@code t2}. * If there is a conflict with tag keys, the tag from {@code t2} will be * used. */ public static BasicTagList concat(TagList t1, TagList t2) { return new BasicTagList(Iterables.concat(t1, t2)); } /** * Returns a tag list containing the union of {@code t1} and {@code t2}. * If there is a conflict with tag keys, the tag from {@code t2} will be * used. */ public static BasicTagList concat(TagList t1, Tag... t2) { return new BasicTagList(Iterables.concat(t1, Arrays.asList(t2))); } /** * Returns a tag list from the list of key values passed. * <p/> * Example: * <p/> * <code> * BasicTagList tagList = BasicTagList.of("id", "someId", "class", "someClass"); * </code> */ public static BasicTagList of(String... tags) { Preconditions.checkArgument(tags.length % 2 == 0, "tags must be a sequence of key,value pairs"); final SmallTagMap.Builder builder = SmallTagMap.builder(); for (int i = 0; i < tags.length; i += 2) { Tag t = Tags.newTag(tags[i], tags[i + 1]); builder.add(t); } return new BasicTagList(builder.result()); } /** * Returns a tag list from the tags. */ public static BasicTagList of(Tag... tags) { return new BasicTagList(Arrays.asList(tags)); } /** * Returns a tag list that has a copy of {@code tags}. * * @deprecated Use {@link #of(Tag...)} */ @Deprecated public static BasicTagList copyOf(Tag... tags) { return new BasicTagList(Arrays.asList(tags)); } /** * Returns a tag list that has a copy of {@code tags}. Each tag value * is expected to be a string parseable using {@link BasicTag#parseTag}. * * @deprecated Use {@link #of(String...)} with separate key, values instead. */ @Deprecated public static BasicTagList copyOf(String... tags) { return copyOf(Arrays.asList(tags)); } /** * Returns a tag list that has a copy of {@code tags}. Each tag value * is expected to be a string parseable using {@link BasicTag#parseTag}. */ public static BasicTagList copyOf(Iterable<String> tags) { SmallTagMap.Builder builder = SmallTagMap.builder(); for (String tag : tags) { builder.add(Tags.parseTag(tag)); } return new BasicTagList(builder.result()); } /** * Returns a tag list that has a copy of {@code tags}. */ public static BasicTagList copyOf(Map<String, String> tags) { SmallTagMap.Builder builder = SmallTagMap.builder(); for (Map.Entry<String, String> tag : tags.entrySet()) { builder.add(Tags.newTag(tag.getKey(), tag.getValue())); } return new BasicTagList(builder.result()); } }