/* * JBoss, Home of Professional Open Source. * Copyright 2000 - 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.cache; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import static org.jboss.cache.AbstractNode.NodeFlags.*; import org.jboss.cache.marshall.MarshalledValue; import org.jboss.cache.util.FastCopyHashMap; import org.jboss.cache.util.Immutables; import org.jboss.cache.util.concurrent.SelfInitializingConcurrentHashMap; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * Basic data node class. Throws {@link UnsupportedOperationException} for version-specific methods like {@link #getVersion()} and * {@link #setVersion(org.jboss.cache.optimistic.DataVersion)}, defined in {@link org.jboss.cache.NodeSPI}. * * @author Manik Surtani (manik AT jboss DOT org) * @since 2.0.0 */ public class UnversionedNode extends AbstractNode implements InternalNode { /** * Debug log. */ protected static Log log = LogFactory.getLog(UnversionedNode.class); protected static final boolean trace = log.isTraceEnabled(); /** * Map of general data keys to values. */ protected Map data; protected NodeSPI delegate; protected CacheSPI cache; /** * Constructs a new node with an FQN of Root. */ public UnversionedNode() { this.fqn = Fqn.ROOT; initFlags(); } /** * Constructs a new node a given Fqn * * @param fqn fqn of the node */ public UnversionedNode(Fqn fqn) { this.fqn = fqn; initFlags(); } public UnversionedNode(Fqn fqn, CacheSPI cache, boolean lockForChildInsertRemove) { initFlags(); this.cache = cache; setLockForChildInsertRemove(lockForChildInsertRemove); this.fqn = fqn; // if this is a root node, create the child map. if (fqn.isRoot()) { children = new ConcurrentHashMap>(64, .5f, 16); } else { // this always needs to be initialized. The actual cost of the ConcurrentHashMap, however, is deferred. children = new SelfInitializingConcurrentHashMap>(); } } public UnversionedNode(Fqn fqn, CacheSPI cache, boolean lockForChildInsertRemove, Map data) { this(fqn, cache, lockForChildInsertRemove); if (data != null) this.data = new FastCopyHashMap(data); } /** * This method initialises flags on the node, by setting DATA_LOADED to true and VALID to true and all other flags to false. * The flags are defined in the {@link org.jboss.cache.AbstractNode.NodeFlags} enum. */ protected void initFlags() { setFlag(DATA_LOADED); setFlag(VALID); } public NodeSPI getDelegate() { return delegate; } public void setDelegate(NodeSPI delegate) { this.delegate = delegate; } /** * Returns a parent by checking the TreeMap by name. */ public NodeSPI getParent() { if (fqn.isRoot()) { return null; } return cache.peek(fqn.getParent(), true); } // does not need to be synchronized since this will only be accessed by a single thread in MVCC thanks to the write lock. // private void initDataMap() // { // if (data == null) data = new FastCopyHashMap(); // } public CacheSPI getCache() { return cache; } public boolean isChildrenLoaded() { return isFlagSet(CHILDREN_LOADED); } public void setChildrenLoaded(boolean childrenLoaded) { setFlag(CHILDREN_LOADED, childrenLoaded); } public V get(K key) { return data == null ? null : data.get(key); } public Map getData() { if (data == null) return Collections.emptyMap(); return data; } public V put(K key, V value) { if (data == null) { // new singleton map! data = Collections.singletonMap(key, value); return null; } if (data.size() == 1 && data.containsKey(key)) { V oldVal = data.get(key); data = Collections.singletonMap(key, value); return oldVal; } upgradeDataMap(); return data.put(key, value); } @Override public InternalNode getChild(Fqn f) { if (fqn.size() == 1) { return getChild(fqn.getLastElement()); } else { InternalNode currentNode = this; for (int i = 0; i < fqn.size(); i++) { Object nextChildName = fqn.get(i); currentNode = currentNode.getChild(nextChildName); if (currentNode == null) return null; } return currentNode; } } @Override public InternalNode getChild(Object childName) { if (childName == null) return null; return children().get(childName); } @Override public Set> getChildren() { // strip out deleted child nodes... if (children.isEmpty()) return Collections.emptySet(); Set> exclDeleted = new HashSet>(); for (InternalNode n : children().values()) { if (!n.isRemoved()) exclDeleted.add(n); } exclDeleted = Collections.unmodifiableSet(exclDeleted); return exclDeleted; } @Override public Set> getChildren(boolean includeMarkedForRemoval) { if (includeMarkedForRemoval) { if (!children.isEmpty()) { return Immutables.immutableSetConvert(children().values()); } else { return Collections.emptySet(); } } else { return getChildren(); } } @Override public ConcurrentMap> getChildrenMap() { return children(); } @Override public void setChildrenMap(ConcurrentMap> children) { this.children = children; } @Override public void addChild(Object nodeName, InternalNode nodeToAdd) { if (nodeName != null) { children().put(nodeName, nodeToAdd); } } @Override public void addChild(InternalNode child) { addChild(child, false); } @Override public void addChild(InternalNode child, boolean safe) { Fqn childFqn = child.getFqn(); if (safe || childFqn.isDirectChildOf(fqn)) { children().put(childFqn.getLastElement(), child); } else { throw new CacheException("Attempting to add a child [" + childFqn + "] to [" + fqn + "]. Can only add direct children."); } } public V remove(K key) { if (data == null) return null; V value; if (data instanceof FastCopyHashMap) { value = data.remove(key); downgradeDataMapIfNeeded(); } else { // singleton maps cannot remove! value = data.get(key); data = null; } return value; } public void printDetails(StringBuilder sb, int indent) { printDetailsInMap(sb, indent); } /** * Returns a debug string. */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName()); if (!isValid()) sb.append(" (invalid) "); if (isRemoved()) { sb.append(" (deleted) [ ").append(fqn); } else { sb.append("[ ").append(fqn); } if (this instanceof VersionedNode) { sb.append(" version=").append(this.getVersion()); } if (data != null) { if (trace) { sb.append(" data=").append(data.keySet()); } else { sb.append(" data=["); Set keys = data.keySet(); int i = 0; for (Object o : keys) { i++; sb.append(o); if (i == 5) { int more = keys.size() - 5; if (more > 1) { sb.append(", and "); sb.append(more); sb.append(" more"); break; } } else { sb.append(", "); } } sb.append("]"); } } if (!children.isEmpty()) { if (trace) { sb.append(" children=").append(getChildrenNames()); } else { sb.append(" children=["); Set names = getChildrenNames(); int i = 0; for (Object o : names) { i++; sb.append(o); if (i == 5) { int more = names.size() - 5; if (more > 1) { sb.append(", and "); sb.append(more); sb.append(" more"); break; } } else { sb.append(", "); } } sb.append("]"); } } sb.append("]"); return sb.toString(); } public void clear() { data = null; // if (data != null) data.clear(); } @SuppressWarnings("unchecked") public Set getChildrenNames() { return children.isEmpty() ? Collections.emptySet() : Immutables.immutableSetCopy(children.keySet()); } public Set getKeys() { if (data == null) { return Collections.emptySet(); } return Immutables.immutableSetCopy(data.keySet()); } public boolean containsKey(K key) { return data != null && data.containsKey(key); } public boolean removeChild(Object childName) { return children.remove(childName) != null; } public boolean removeChild(Fqn f) { if (f.size() == 1) { return removeChild(f.getLastElement()); } else { NodeSPI child = getChildDirect(f); return child != null && child.getParentDirect().removeChildDirect(f.getLastElement()); } } public void putAll(Map data) { if (data != null) { if (this.data == null) this.data = copyDataMap(data); else if (this.data.size() == 1 && data.size() == 1 && this.data.keySet().iterator().next().equals(data.keySet().iterator().next())) { // replace key! Entry e = data.entrySet().iterator().next(); // These casts are a work-around for an eclipse compiler bug, please don't remove ;) this.data = Collections.singletonMap((K)e.getKey(), (V) e.getValue()); } else { // size. Do we need to update the existing data map to a FCHM? upgradeDataMap(); this.data.putAll(data); } } } protected final void upgradeDataMap() { if (data != null && !(data instanceof FastCopyHashMap)) data = new FastCopyHashMap(data); } protected final void downgradeDataMapIfNeeded() { if (data.size() == 1 && data instanceof FastCopyHashMap) { Entry e = data.entrySet().iterator().next(); data = Collections.singletonMap(e.getKey(), e.getValue()); } } public void removeChildren() { children.clear(); } public void markAsRemoved(boolean marker, boolean recursive) { setFlag(REMOVED, marker); if (recursive) { for (InternalNode child : children().values()) child.markAsRemoved(marker, true); } } protected final void printIndent(StringBuilder sb, int indent) { if (sb != null) { for (int i = 0; i < indent; i++) { sb.append(" "); } } } /** * Returns the name of this node. */ public Fqn getFqn() { return fqn; } public void setFqn(Fqn fqn) { if (trace) { log.trace(getFqn() + " set FQN " + fqn); } this.fqn = fqn; // invoke children for (Map.Entry> me : children().entrySet()) { InternalNode n = me.getValue(); Fqn cfqn = Fqn.fromRelativeElements(fqn, me.getKey()); n.setFqn(cfqn); } } public boolean hasChildren() { return !children.isEmpty(); } /** * Adds details of the node into a map as strings. */ protected void printDetailsInMap(StringBuilder sb, int indent) { printIndent(sb, indent); indent += 2;// increse it sb.append(Fqn.SEPARATOR); if (!fqn.isRoot()) sb.append(fqn.getLastElement()); sb.append(" "); sb.append(data); for (InternalNode n : children().values()) { sb.append("\n"); n.printDetails(sb, indent); } } /** * Returns true if the data was loaded from the cache loader. */ public boolean isDataLoaded() { return isFlagSet(DATA_LOADED); } /** * Sets if the data was loaded from the cache loader. */ public void setDataLoaded(boolean dataLoaded) { setFlag(DATA_LOADED, dataLoaded); } public boolean isValid() { return isFlagSet(VALID); } public void setValid(boolean valid, boolean recursive) { setFlag(VALID, valid); if (trace) log.trace("Marking node " + getFqn() + " as " + (valid ? "" : "in") + "valid"); if (recursive) { for (InternalNode child : children().values()) { child.setValid(valid, recursive); } } } public boolean isLockForChildInsertRemove() { return isFlagSet(LOCK_FOR_CHILD_INSERT_REMOVE); } public void setLockForChildInsertRemove(boolean lockForChildInsertRemove) { setFlag(LOCK_FOR_CHILD_INSERT_REMOVE, lockForChildInsertRemove); } @SuppressWarnings("unchecked") public InternalNode copy() { UnversionedNode n = new UnversionedNode(fqn, cache, isFlagSet(LOCK_FOR_CHILD_INSERT_REMOVE)); n.data = copyDataMap(data); copyInternals(n); return n; } protected void copyInternals(UnversionedNode n) { // direct reference to child map n.children = children; n.delegate = delegate; n.flags = flags; } public void setInternalState(Map state) { if (data == null) { data = copyDataMap(state); } else { // don't bother doing anything here putAll(state); } } protected final Map copyDataMap(Map toCopyFrom) { if (toCopyFrom != null && toCopyFrom.size() > 0) { Map map; if (toCopyFrom instanceof FastCopyHashMap) { map = (FastCopyHashMap) ((FastCopyHashMap) toCopyFrom).clone(); } else if (toCopyFrom.size() == 1) { Entry e = toCopyFrom.entrySet().iterator().next(); map = Collections.singletonMap(e.getKey(), e.getValue()); } else { map = new FastCopyHashMap(toCopyFrom); } return map; } return null; } public Map getInternalState(boolean onlyInternalState) { if (onlyInternalState) return new HashMap(0); // don't bother doing anything here if (data == null) return new HashMap(0); return new HashMap(data); } public void releaseObjectReferences(boolean recursive) { if (recursive) { for (InternalNode child : children().values()) { child.releaseObjectReferences(recursive); } } if (data != null) { for (K key : data.keySet()) { // get the key first, before attempting to serialize stuff since data.get() may deserialize the key if doing // a hashcode() or equals(). Object value = data.get(key); if (key instanceof MarshalledValue) { ((MarshalledValue) key).compact(true, true); } if (value instanceof MarshalledValue) { ((MarshalledValue) value).compact(true, true); } } } } /** * @return genericized version of the child map */ @SuppressWarnings("unchecked") private ConcurrentMap> children() { return children; } }