package org.gridkit.jvmtool.stacktrace;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.gridkit.jvmtool.event.SimpleTagCollection;
import org.gridkit.jvmtool.event.TagCollection;
public class TagDictionary {
private static final TagSet EMPTY = new TagSet(new SimpleTagCollection());
private Map<TagSet, Integer> tagSetDic = new LinkedHashMap<TagSet, Integer>();
private int limit;
public TagDictionary(int limit) {
this.limit = limit;
tagSetDic.put(EMPTY, 0); // empty entry
}
public int intern(TagCollection tags, TagSetEncoder encoder) {
TagSet ts = new TagSet(tags);
Integer id = tagSetDic.remove(ts);
if (id != null) {
tagSetDic.put(ts, id); // TODO garbage hotspot
return id;
}
else if (tagSetDic.size() < limit) {
id = tagSetDic.size();
encodeTag(id, ts, encoder);
return id;
}
else {
id = evict();
encodeTag(id, ts, encoder);
return id;
}
}
protected int evict() {
Integer id;
Iterator<Integer> it = tagSetDic.values().iterator();
id = it.next();
if (id == 0) {
// never evict empty tag set
id = it.next();
}
it.remove();
return id;
}
private void encodeTag(int id, TagSet ts, TagSetEncoder encoder) {
int baseRef = 0;
TagSet baseSet = EMPTY;
int minDistance = distance(EMPTY, ts, encoder);
for(Entry<TagSet, Integer> e: tagSetDic.entrySet()) {
int d = distance(e.getKey(), ts, encoder);
if (d < minDistance) {
baseSet = e.getKey();
baseRef = e.getValue();
minDistance = d;
}
}
touch(baseSet, baseRef);
encoder.startTagSet(id, baseRef);
encode(baseSet, ts, encoder);
encoder.finishTag();
tagSetDic.put(ts, id);
}
private void touch(TagSet baseSet, int baseRef) {
if (baseRef != 0) {
tagSetDic.remove(baseSet);
tagSetDic.put(baseSet, baseRef);
}
}
private int distance(TagSet base, TagSet set, TagSetEncoder encoder) {
int cost = 0;
int nb = 0;
int ns = 0;
while(nb < base.tags.length && ns < set.tags.length) {
int c = base.tags[nb].compareTo(set.tags[ns]);
if (c == 0) {
++nb;
++ns;
}
else if (c < 0) {
cost += encoder.cost(base.tags[nb].key, base.tags[nb].tag);
++nb;
}
else {
cost += encoder.cost(set.tags[ns].key, set.tags[ns].tag);
++ns;
}
}
while(nb < base.tags.length) {
cost += encoder.cost(base.tags[nb].key, base.tags[nb].tag);
++nb;
}
while(ns < set.tags.length) {
cost += encoder.cost(set.tags[ns].key, set.tags[ns].tag);
++ns;
}
return cost;
}
private void encode(TagSet base, TagSet set, TagSetEncoder encoder) {
int nb = 0;
int ns = 0;
while(nb < base.tags.length && ns < set.tags.length) {
int c = base.tags[nb].compareTo(set.tags[ns]);
if (c == 0) {
++nb;
++ns;
}
else if (c < 0) {
encodeRemoveTag(base, encoder, nb);
++nb;
}
else {
encoder.append(set.tags[ns].key, set.tags[ns].tag);
++ns;
}
}
while(nb < base.tags.length) {
encodeRemoveTag(base, encoder, nb);
++nb;
}
while(ns < set.tags.length) {
encoder.append(set.tags[ns].key, set.tags[ns].tag);
++ns;
}
}
protected void encodeRemoveTag(TagSet base, TagSetEncoder encoder, int nb) {
boolean multikey = false;
if (nb > 0 && base.tags[nb - 1].key.equals(base.tags[nb].key)) {
multikey = true;
}
if (nb + 1 < base.tags.length && base.tags[nb + 1].key.equals(base.tags[nb].key)) {
multikey = true;
}
if (multikey) {
encoder.remove(base.tags[nb].key, base.tags[nb].tag);
}
else {
// short remove
encoder.remove(base.tags[nb].key);
}
}
// private int cost(Tag tag) {
// return 2 + tag.key.length() + tag.tag.length();
// }
//
public interface TagSetEncoder {
public void startTagSet(int setId, int baseId);
public void append(String key, String tag);
public void remove(String key);
public void remove(String key, String tag);
public int cost(String key, String tag);
public void finishTag();
}
private static class TagSet {
final Tag[] tags;
final int hash;
public TagSet(TagCollection col) {
List<Tag> buf = new ArrayList<TagDictionary.Tag>();
for(String key: col) {
for(String tag: col.tagsFor(key)) {
buf.add(new Tag(key, tag));
}
}
tags = buf.toArray(new Tag[buf.size()]);
Arrays.sort(tags);
hash = Arrays.hashCode(tags);
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TagSet other = (TagSet) obj;
if (hash != other.hash)
return false;
if (!Arrays.equals(tags, other.tags))
return false;
return true;
}
@Override
public String toString() {
return Arrays.toString(tags);
}
}
private static class Tag implements Comparable<Tag> {
final String key;
final String tag;
public Tag(String key, String tag) {
this.key = key;
this.tag = tag;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((key == null) ? 0 : key.hashCode());
result = prime * result + ((tag == null) ? 0 : tag.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Tag other = (Tag) obj;
if (key == null) {
if (other.key != null)
return false;
} else if (!key.equals(other.key))
return false;
if (tag == null) {
if (other.tag != null)
return false;
} else if (!tag.equals(other.tag))
return false;
return true;
}
@Override
public int compareTo(Tag o) {
int n = key.compareTo(o.key);
if (n == 0) {
n = tag.compareTo(o.tag);
}
return n;
}
@Override
public String toString() {
return key + ":" + tag;
}
}
}