/** * Copyright 2014 Netflix, Inc. * <p/> * 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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.Preconditions; import com.netflix.servo.util.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; /** * This class is not intended to be used by 3rd parties and should be considered an implementation * detail. */ public class SmallTagMap implements Iterable<Tag> { /** * Max number of tags supported in a tag map. Attempting to add additional tags * will result in a warning logged. */ public static final int MAX_TAGS = 32; /** * Initial size for the map. */ public static final int INITIAL_TAG_SIZE = 8; private static final Logger LOGGER = LoggerFactory.getLogger(SmallTagMap.class); /** * Return a new builder to assist in creating a new SmallTagMap using the default tag size (8). */ public static Builder builder() { return new Builder(INITIAL_TAG_SIZE); } /** * Helper class to build the immutable map. */ public static class Builder { private int actualSize = 0; private int size; private Object[] buf; private void init(int size) { this.size = size; buf = new Object[size * 2]; actualSize = 0; } /** * Create a new builder with the specified capacity. * * @param size Size of the underlying array. */ public Builder(int size) { init(size); } /** * Get the number of entries in this map.. */ public int size() { return actualSize; } /** * True if this builder does not have any tags added to it. */ public boolean isEmpty() { return actualSize == 0; } private void resizeIfPossible(Tag tag) { if (size < MAX_TAGS) { Object[] prevBuf = buf; init(size * 2); for (int i = 1; i < prevBuf.length; i += 2) { Tag t = (Tag) prevBuf[i]; if (t != null) { add(t); } } add(tag); } else { final String msg = String.format( "Cannot add Tag %s - Maximum number of tags (%d) reached.", tag, MAX_TAGS); LOGGER.error(msg); } } /** * Adds a new tag to this builder. */ public Builder add(Tag tag) { String k = tag.getKey(); int pos = (int) (Math.abs((long) k.hashCode()) % size); int i = pos; Object ki = buf[i * 2]; while (ki != null && !ki.equals(k)) { i = (i + 1) % size; if (i == pos) { resizeIfPossible(tag); return this; } ki = buf[i * 2]; } if (ki != null) { buf[i * 2] = k; buf[i * 2 + 1] = tag; } else { if (buf[i * 2] != null) { throw new IllegalStateException("position has already been filled"); } buf[i * 2] = k; buf[i * 2 + 1] = tag; actualSize += 1; } return this; } /** * Adds all tags from the {@link Iterable} tags to this builder. */ public Builder addAll(Iterable<Tag> tags) { for (Tag tag : tags) { add(tag); } return this; } /** * Get the resulting SmallTagMap. */ public SmallTagMap result() { Tag[] tagArray = new Tag[actualSize]; int tagIdx = 0; for (int i = 1; i < buf.length; i += 2) { Object o = buf[i]; if (o != null) { tagArray[tagIdx++] = (Tag) o; } } Arrays.sort(tagArray, (o1, o2) -> { int keyCmp = o1.getKey().compareTo(o2.getKey()); if (keyCmp != 0) { return keyCmp; } return o1.getValue().compareTo(o2.getValue()); }); assert (tagIdx == actualSize); return new SmallTagMap(tagArray); } } private class SmallTagIterator implements Iterator<Tag> { private int i = 0; @Override public boolean hasNext() { return i < tagArray.length; } @Override public Tag next() { if (i < tagArray.length) { return tagArray[i++]; } throw new NoSuchElementException(); } @Override public void remove() { throw new UnsupportedOperationException("SmallTagMaps are immutable"); } } @Override public Iterator<Tag> iterator() { return new SmallTagIterator(); } private int cachedHashCode = 0; private final Tag[] tagArray; /** * Create a new SmallTagMap using the given array and size. * * @param tagArray sorted array of tags */ SmallTagMap(Tag[] tagArray) { this.tagArray = Preconditions.checkNotNull(tagArray, "tagArray"); } static int binarySearch(Tag[] a, String key) { int low = 0; int high = a.length - 1; while (low <= high) { final int mid = (low + high) >>> 1; final Tag midValTag = a[mid]; final String midVal = midValTag.getKey(); final int cmp = midVal.compareTo(key); if (cmp < 0) { low = mid + 1; } else if (cmp > 0) { high = mid - 1; } else { return mid; // tag key found } } return -(low + 1); // tag key not found. } /** * Get the tag associated with a given key. */ public Tag get(String key) { int idx = binarySearch(tagArray, key); if (idx < 0) { return null; } else { return tagArray[idx]; } } /** * {@inheritDoc} */ @Override public int hashCode() { if (cachedHashCode == 0) { cachedHashCode = Arrays.hashCode(tagArray); } return cachedHashCode; } /** * {@inheritDoc} */ @Override public String toString() { return "SmallTagMap{" + Strings.join(",", iterator()) + "}"; } /** * Returns true whether this map contains a Tag with the given key. */ public boolean containsKey(String k) { return get(k) != null; } /** * Returns true if this map has no entries. */ public boolean isEmpty() { return tagArray.length == 0; } /** * Returns the number of Tags stored in this map. */ public int size() { return tagArray.length; } /** * Returns the {@link Set} of tags. * * @deprecated This method will be removed in the next version. This is an expensive method * and not in the spirit of this class which is to be more efficient than the standard * collections library. */ @Deprecated public Set<Tag> tagSet() { return new HashSet<>(Arrays.asList(tagArray)); } @Override /** {@inheritDoc} */ public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || !(obj instanceof SmallTagMap)) { return false; } SmallTagMap that = (SmallTagMap) obj; return Arrays.equals(tagArray, that.tagArray); } }