/*******************************************************************************
* Copyright (c) 2010-2014 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SAP AG - initial API and implementation
*******************************************************************************/
package org.eclipse.skalli.core.tagging;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;
import org.eclipse.skalli.model.EntityBase;
import org.eclipse.skalli.model.ExtensibleEntityBase;
import org.eclipse.skalli.model.ExtensionEntityBase;
import org.eclipse.skalli.model.Taggable;
import org.eclipse.skalli.services.entity.EntityService;
import org.eclipse.skalli.services.tagging.TagCount;
/**
* Cache for tags of a given entity type. This cache is designed to support non-blocking
* read access to the recorded tags while still being thread-safe on occasional updates.
* Tags are provided either sorted by {@link #getByName() tag name}, or by decreasing
* {@link #getByCount() number of occurences}.
*/
public class TagCache {
/** Map that assigns tag names with the entities that have them */
private Map<String,Set<UUID>> byEntity = new HashMap<String,Set<UUID>>();
/** All known tags sorted alphanumerically */
private volatile ConcurrentSkipListMap<String,Integer> byName = new ConcurrentSkipListMap<String,Integer>();
/** All known tags sorted by decreasing number of occurence */
private volatile ConcurrentSkipListSet<TagCount> byCount = new ConcurrentSkipListSet<TagCount>();
SortedMap<String,Integer> getByName() {
return Collections.unmodifiableSortedMap(byName);
}
SortedSet<TagCount> getByCount() {
return Collections.unmodifiableSortedSet(byCount);
}
SortedSet<TagCount> getByCount(int size) {
if (size < 0) {
return Collections.unmodifiableSortedSet(byCount);
}
TreeSet<TagCount> result = new TreeSet<TagCount>();
int i = 0;
Iterator<TagCount> it = byCount.iterator();
while (i < size && it.hasNext()) {
result.add(it.next());
++i;
}
return result;
}
void update(EntityBase entity) {
synchronized (byEntity) {
removeTags(entity);
addTags(entity);
reindexTags();
}
}
void initialize(EntityService<?> entityService) {
synchronized (byEntity) {
for (UUID entityId: entityService.keySet()) {
EntityBase entity = entityService.getByUUID(entityId);
if (entity != null) {
addTags(entity);
}
}
reindexTags();
}
}
private void reindexTags() {
ConcurrentSkipListMap<String,Integer> byName = new ConcurrentSkipListMap<String,Integer>();
for (Entry<String,Set<UUID>> entry: byEntity.entrySet()) {
byName.put(entry.getKey(), entry.getValue().size());
}
ConcurrentSkipListSet<TagCount> byCount = new ConcurrentSkipListSet<TagCount>();
for (Entry<String,Integer> entry: byName.entrySet()) {
byCount.add(new TagCount(entry.getKey(), entry.getValue()));
}
this.byName = byName;
this.byCount = byCount;
}
private void removeTags(EntityBase entity) {
UUID entityId = entity.getUuid();
Iterator<Set<UUID>> it = byEntity.values().iterator();
while (it.hasNext()) {
Set<UUID> next = it.next();
next.remove(entityId);
if (next.isEmpty()) {
it.remove();
}
}
}
private void addTags(EntityBase entity) {
if (!entity.isDeleted()) {
if (entity instanceof Taggable) {
addTags(entity.getUuid(), (Taggable)entity);
} else if (entity instanceof ExtensibleEntityBase) {
for (ExtensionEntityBase ext: ((ExtensibleEntityBase)entity).getAllExtensions()) {
if (ext instanceof Taggable) {
addTags(entity.getUuid(), (Taggable)ext);
}
}
}
}
}
private void addTags(UUID entityId, Taggable taggable) {
Set<String> tags = taggable.getTags();
for (String tag: tags) {
Set<UUID> entityIds = byEntity.get(tag);
if (entityIds == null) {
entityIds = new HashSet<UUID>();
byEntity.put(tag, entityIds);
}
entityIds.add(entityId);
}
}
}