package marubinotto.piggydb.model; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import marubinotto.piggydb.model.exception.InvalidTaggingException; import marubinotto.util.Assert; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class MutableClassification implements Classification, Serializable { static Log logger = LogFactory.getLog(MutableClassification.class); private Map<String, Tag> tags = new HashMap<String, Tag>(); private Classifiable classifiable; private Tag targetTag; public MutableClassification() { } public MutableClassification(Collection<Tag> tags) throws InvalidTaggingException { for (Tag tag : tags) addTag(tag); } public MutableClassification(Classifiable classifiable) { Assert.Arg.notNull(classifiable, "classifiable"); this.classifiable = classifiable; if (classifiable instanceof Tag) { this.targetTag = (Tag)classifiable; } } public Classifiable getClassifiable() { return this.classifiable; } public Tag getTargetTag() { return this.targetTag; } public Tag addTag(Tag tag) throws InvalidTaggingException { Assert.Arg.notNull(tag, "tag"); Assert.Arg.notNull(tag.getName(), "tag.getName()"); if (this.targetTag != null) { if (tag.equals(this.targetTag)) { throw new InvalidTaggingException(); } if (tag.getClassification().isSubordinateOf(this.targetTag.getName())) { throw new InvalidTaggingException(); } } Tag removedTag = null; for (Iterator<Tag> i = getTagIterator(); i.hasNext();) { Tag memberTag = i.next(); if (memberTag.getClassification().isInSameHierarchyOf(tag)) { removeTag(memberTag.getName()); removedTag = memberTag; } } this.tags.put(tag.getName(), tag); return removedTag; } public void syncWith(Classification classification) throws InvalidTaggingException { Assert.Arg.notNull(classification, "classification"); clear(); for (Tag tag : classification) addTag(tag); } public void removeTag(String name) { Assert.Arg.notNull(name, "name"); this.tags.remove(name); } public int size() { return this.tags.size(); } public boolean isEmpty() { return this.tags.isEmpty(); } public boolean containsTagName(String name) { Assert.Arg.notNull(name, "name"); return this.tags.containsKey(name); } public boolean containsTagId(long tagId) { for (Tag tag : this.tags.values()) { if (tag.getId() != null && tag.getId().longValue() == tagId) { return true; } } return false; } public boolean containsAny(Collection<Long> tagIds) { Assert.Arg.notNull(tagIds, "tagIds"); for (Long tagId : tagIds) { if (containsTagId(tagId)) { return true; } } return false; } public Tag getTag(String name) { Assert.Arg.notNull(name, "name"); return this.tags.get(name); } public Iterator<Tag> getTagIterator() { SortedSet<Tag> sortedTags = new TreeSet<Tag>(Tag.TAG_NAME_COMPARATOR); sortedTags.addAll(this.tags.values()); return sortedTags.iterator(); } public Iterator<Tag> iterator() { return getTagIterator(); } public Set<String> getTagNames() { return this.tags.keySet(); } public String toCommaSeparated() { StringBuilder tags = new StringBuilder(); for (String tagName : getTagNames()) { if (tags.length() > 0) tags.append(", "); tags.append(tagName); } return tags.toString(); } public Collection<Tag> getTags() { return this.tags.values(); } public boolean isSubordinateOf(String name) { Assert.Arg.notNull(name, "name"); if (containsTagName(name)) { return true; } else { for (Iterator<Tag> i = getTagIterator(); i.hasNext();) { if (i.next().getClassification().isSubordinateOf(name)) { return true; } } } return false; } public boolean isInSameHierarchyOf(Tag tag) { Assert.Arg.notNull(tag, "tag"); Assert.Arg.notNull(tag.getName(), "tag.getName()"); if (this.targetTag != null && tag.equals(this.targetTag)) { return true; } if (isSubordinateOf(tag.getName())) { return true; } if (this.targetTag != null && tag.getClassification().isSubordinateOf(this.targetTag.getName())) { return true; } return false; } public void refreshEachTag(TagRepository tagRepository) throws Exception { Assert.Arg.notNull(tagRepository, "tagRepository"); for (Tag tag : this) { if (tag.getId() != null) { removeTag(tag.getName()); Tag latest = tagRepository.get(tag.getId().longValue()); if (latest != null) addTag(latest); } } } @Override public String toString() { StringBuffer buffer = new StringBuffer(); toStringRecursively(this, buffer); return buffer.toString(); } private static void toStringRecursively(Classification classification, StringBuffer buffer) { if (classification.size() == 0) return; if (buffer.length() > 0) buffer.append(" "); buffer.append("("); boolean first = true; for (Tag tag : classification) { if (first) first = false; else buffer.append(", "); buffer.append(tag.getName()); toStringRecursively(tag.getClassification(), buffer); } buffer.append(")"); } public void clear() { this.tags.clear(); } public List<Set<Long>> expandEach(TagRepository tagRepository) throws Exception { Assert.Arg.notNull(tagRepository, "tagRepository"); List<Set<Long>> expandedTags = new ArrayList<Set<Long>>(); for (Iterator<Tag> i = getTagIterator(); i.hasNext();) { expandedTags.add(i.next().expandToIdsOfSubtree(tagRepository)); } return expandedTags; } public Set<Long> expandAll(TagRepository tagRepository) throws Exception { Assert.Arg.notNull(tagRepository, "tagRepository"); Set<Long> tagIds = new HashSet<Long>(); for (Tag tag : this.tags.values()) { if (tag.getId() == null) { throw new IllegalStateException("Cannot expand for missing ID: " + tag); } tagIds.add(tag.getId()); } tagIds.addAll(tagRepository.getAllSubordinateTagIds(tagIds)); return tagIds; } public boolean isClassifiedByAll(List<Set<Long>> expandedTags) { Assert.Arg.notNull(expandedTags, "expandedTags"); for (Set<Long> tagTree : expandedTags) { if (!containsAny(tagTree)) { return false; } } return true; } }