/* XXL: The eXtensible and fleXible Library for data processing Copyright (C) 2000-2011 Prof. Dr. Bernhard Seeger Head of the Database Research Group Department of Mathematics and Computer Science University of Marburg Germany This library 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 3 of the License, or (at your option) any later version. This library 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 library; If not, see <http://www.gnu.org/licenses/>. http://code.google.com/p/xxl/ */ package xxl.core.util.metaData; import java.io.Serializable; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * A composite metadata provides a generic metadata object that can be used to * merge different metadata object, so called <i>metadata fragments</i>, to a * single one that can be handled by the <code>getMetaData</code> method of the * interface <code>MetaDataProvider</code>. The different metadata fragments * describing an object can easily be plugged together to a composite metadata. * In order to retrieve the separate metadata fragments from the composite * metadata, they must be associated to unique identifiers, that are published * to composite metadata when a new metadata fragment is plugged in. * <code>MetaDataException</code> are used to indicate an inappropriate use of * identifiers, i.e., when adding a new metadata fragment associated with an * identifier that is already published to the composite metadata or removing a * metadata fragment associated with an identifier that is unknown. * * <p><b>Note:</b> When trying to plug in a composite metadata to another * composite metadata using the <code>addAll</code> method, the metadata * fragments of the first composite metadata will be plugged in the second * composite metadata associated with the identifiers used in the first * composite metadata.</p> * * @param <I> the type of the identifiers. * @param <M> the type of the metadata fragments. * @see xxl.core.util.metaData.MetaDataProvider#getMetaData() * @see xxl.core.util.metaData.MetaDataException */ public class CompositeMetaData<I, M> implements Iterable<Entry<I, M>>, Cloneable, Serializable { private static final long serialVersionUID = -5321284277751775898L; /** * A concurrent hash map that is internally used to store the metadata * fragments this composite metadata is built up of. */ protected ConcurrentHashMap<I, M> metaDataMap; /** * The reentrant read/write lock is used to synchronize the access of the * internally used concurrent hash map. */ protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); /** * Creates a new composite metadata that does not contain any metadata * fragments. */ public CompositeMetaData() { this.metaDataMap = new ConcurrentHashMap<I, M>(); } /** * Adds the given metadata fragment associated the specified identifier to * this composite metadata. If the composite metadata already contains a * metadata fragment for the specified identifier, an exception is thrown. * * @param identifier the identifier with which the specified metadata * fragment is to be associated. * @param metaData the metadata fragment to be plugged to this composite * metadata associated with the specified identifier. * @throws MetaDataException if the composite metadata already contains a * metadata fragment associated with the specified identifier. */ public void add(I identifier, M metaData) throws MetaDataException { lock.writeLock().lock(); try { if (contains(identifier)) throw new MetaDataException("composite metadata already contains specified metadata fragment \'" + identifier + "\'"); metaDataMap.put(identifier, metaData); } finally { lock.writeLock().unlock(); } } /** * Adds the whole given composite metadata to this one by adding every * metadata fragment of it. The metadata fragments of the given composite * metadata are associated with their original identifiers. If the calling * composite metadata already contains a metadata fragment for one of the * given composite metadata's identifier, the remaining metadata fragments * are added to it and afterwards an exception is thrown. * * @param <J> the type of the given composite metadata's identifiers. * @param compositeMetaData the composite metadata whose metadata fragments * should be plugged to the calling composite metadata associated * with their original identifier. * @throws MetaDataException if the composite metadata already contains a * metadata fragment associated with one of the given composite * matedata's identifiers. */ public <J extends I> void addAll(CompositeMetaData<J, ? extends M> compositeMetaData) throws MetaDataException { String message = ""; for (Entry<J, ? extends M> entry : compositeMetaData) { try { add(entry.getKey(), entry.getValue()); } catch (MetaDataException mde) { message += '\n' + mde.getMessage(); } } if (message.length() > 0) throw new MetaDataException("some metadata fragments cannot be added to composite metadata:" + message); } /** * Puts the given metadata fragment associated the specified identifier * into this composite metadata and returns the metadata fragment * previously associated with the specified identifier by the composite * metadata or <code>null</code> if there is no such metadata fragment. * * @param identifier the identifier with which the specified metadata * fragment is to be associated. * @param metaData the metadata fragment to be plugged to this composite * metadata associated with the specified identifier. * @return the metadata fragment previously associated with the specified * identifier by this composite metadata or <code>null</code> if * there is no such metadata fragment. */ public M put(I identifier, M metaData) { lock.writeLock().lock(); try { return metaDataMap.put(identifier, metaData); } finally { lock.writeLock().unlock(); } } /** * Puts the whole given composite metadata into this one by putting every * metadata fragment of it. The metadata fragments of the given composite * metadata are associated with their original identifiers. This method * returns an iteration over the metadata fragments previously associated * with the given composite metadata's identifiers by the calling composite * metadata. If an identifier was not associated with a metadata fragment * within the calling composite metadata the corresponding position of the * returned iteration will be <code>null</code>. * * @param <J> the type of the given composite metadata's identifiers. * @param compositeMetaData the composite metadata whose metadata fragments * should be plugged to the calling composite metadata associated * with their original identifier. * @return an iteration over the metadata fragments previously associated * with the given composite metadata's identifiers by the calling * composite metadata. If an identifier was not associated with a * metadata fragment within the calling composite metadata the * corresponding position of the returned iteration will be * <code>null</code>. */ public <J extends I> Iterator<Entry<J, M>> putAll(CompositeMetaData<J, ? extends M> compositeMetaData) { HashMap<J, M> map = new HashMap<J, M>(compositeMetaData.size()); String message = ""; for (Entry<J, ? extends M> entry : compositeMetaData) { try { map.put(entry.getKey(), put(entry.getKey(), entry.getValue())); } catch (MetaDataException mde) { message += '\n' + mde.getMessage(); } } if (message.length() > 0) throw new MetaDataException("some metadata fragments cannot be added to composite metadata:" + message); return map.entrySet().iterator(); } /** * Replaces the given metadata fragment associated the specified identifier * in this composite metadata and returns the metadata fragment previously * associated with the specified identifier by the composite metadata. If * the composite metadata does not contain a metadata fragment for the * specified identifier, an exception is thrown. * * @param identifier the identifier with which the specified metadata * fragment is to be associated. * @param metaData the metadata fragment to be plugged to this composite * metadata associated with the specified identifier. * @return the metadata fragment previously associated with the specified * identifier by this composite metadata. * @throws MetaDataException if the composite metadata does not contain a * metadata fragment associated with the specified identifier. */ public M replace(I identifier, M metaData) throws MetaDataException { lock.writeLock().lock(); try { if (!contains(identifier)) throw new MetaDataException("composite meta data does not contain specified meta data \'" + identifier + "\'"); return metaDataMap.put(identifier, metaData); } finally { lock.writeLock().unlock(); } } /** * Replaces the whole given composite metadata in this one by replacing * every metadata fragment of it. The metadata fragments of the given * composite metadata are associated with their original identifiers. This * method returns an iteration over the metadata fragments previously * associated with the given composite metadata's identifiers by the * calling composite metadata. If the calling composite metadata does not * contain a metadata fragment for every identifier of the given composite * metadata, the remaining metadata fragments are replaced and afterwards * an exception is thrown. * * @param <J> the type of the given composite metadata's identifiers. * @param compositeMetaData the composite metadata whose metadata fragments * should be plugged to the calling composite metadata associated * with their original identifier. * @return an iteration over the metadata fragments previously associated * with the given composite metadata's identifiers by the calling * composite metadata. * @throws MetaDataException if the composite metadata does not contain a * metadata fragment for every identifier of the given composite * metadata. */ public <J extends I> Iterator<Entry<J, M>> replaceAll(CompositeMetaData<J, ? extends M> compositeMetaData) throws MetaDataException { HashMap<J, M> map = new HashMap<J, M>(compositeMetaData.size()); String message = ""; for (Entry<J, ? extends M> entry : compositeMetaData) { try { map.put(entry.getKey(), replace(entry.getKey(), entry.getValue())); } catch (MetaDataException mde) { message += '\n' + mde.getMessage(); } } if (message.length() > 0) throw new MetaDataException("some metadata fragments cannot be added to composite metadata:" + message); return map.entrySet().iterator(); } /** * Returns <code>true</code> if this composite metadata contains a metadata * fragment for the specified identifier. * * @param identifier the identifier whose presence in this composite * metadata is to be tested. * @return <code>true</code> if this composite metadata contains a metadata * fragment for the specified identifier. */ public boolean contains(I identifier) { return metaDataMap.containsKey(identifier); } /** * Returns the metadata fragment the specified identifier is associated * with by this composite metadata. A return value of <code>null</code> * indicates that <code>null</code> is explicitly associated with the given * identifier by the composite metadata. If the composite metadata does not * contain a metadata fragment for the specified identifier, an exception * is thrown. * * @param identifier the identifier whose associated metadata fragment is * to be returned. * @return the metadata fragment that is associated with the given * identifier by this composite metadata. * @throws MetaDataException if the composite metadata does not contain a * metadata fragment associated with the specified identifier. */ public M get(I identifier) throws MetaDataException { lock.readLock().lock(); try { if (!contains(identifier)) throw new MetaDataException("composite meta data does not contain specified meta data \'" + identifier + "\'"); return metaDataMap.get(identifier); } finally { lock.readLock().unlock(); } } /** * Removes the metadata fragment for this identifier from this composite * metadata. If the composite metadata does not contain a metadata fragment * for the specified identifier, an exception is thrown. * * @param identifier the identifier whose metadata fragment is to be * removed from the composite metadata. * @return the previous metadata fragment associated with specified * identifier. A <code>null</code> return indicates that the * composite metadata previously associated <code>null</code> with * the specified identifier. * @throws MetaDataException if the composite metadata does not contain a * metadata fragment associated with the specified identifier. */ public M remove(I identifier) throws MetaDataException { lock.writeLock().lock(); try { if (!contains(identifier)) throw new MetaDataException("composite meta data does not contain specified meta data \'" + identifier + "\'"); return metaDataMap.remove(identifier); } finally { lock.writeLock().unlock(); } } /** * Removes all metadata fragment from this composite metadata. */ public void clear() { lock.writeLock().lock(); try { metaDataMap.clear(); } finally { lock.writeLock().unlock(); } } /** * Returns the number of metadata fragment this composite metadata is built * up of. * * @return the number of metadata fragments this composite metadata is * built up of. */ public int size() { return metaDataMap.size(); } /** * Returns an iteration over the identifiers the metadata fragments this * composite metadata is built up of are associated with. The iteration is * backed by the composite metadata, so changes performed on the iteration * are reflected in the composite metadata. * * @return an iteration over the identifiers the metadata fragments this * composite metadata is built up of are associated with. */ public Iterator<I> identifiers() { return metaDataMap.keySet().iterator(); } /** * Returns an iteration over the metadata fragments this composite metadata * is built up of. The iteration is backed by the composite metadata, so * changes performed on the iteration are reflected in the composite * metadata. * * @return an iteration over the metadata fragments this composite metadata * is built up of. */ public Iterator<M> fragments() { return metaDataMap.values().iterator(); } /** * Returns an iteration over the identifier/metadata fragment pairs this * composite metadata is built up of. The iteration is backed by the * composite metadata, so changes performed on the iteration are reflected * in the composite metadata. * * @return an iteration over the identifier/metadata fragment pairs this * composite metadata is built up of. */ public Iterator<Entry<I, M>> iterator() { return metaDataMap.entrySet().iterator(); } /** * Returns the hash code value for this composite metadata. The hash code * of a composite metadata is defined to be the sum of the hash codes of * each stored metadata fragment. This ensures that * <code>t1.equals(t2)</code> implies that * <code>t1.hashCode()==t2.hashCode()</code> for any two composite * metadata objects <code>t1</code> and <code>t2</code>, as required by the * general contract of {@link Object#hashCode() Object.hashCode}. * * <p>This implementation simply returns the hash code of the internally * used hash map.</p> * * @return the hash code value for this composite metadata. * @see Object#hashCode() * @see #equals(Object) */ @Override public int hashCode() { return metaDataMap.hashCode(); } /** * Indicates whether some other object is "equal to" this composite * metadata. * * <p>The <code>equals</code> method implements an equivalence relation: * <ul> * <li> * It is <i>reflexive</i>: for any reference value <code>x</code>, * <code>x.equals(x)</code> should return <code>true</code>. * </li> * <li> * It is <i>symmetric</i>: for any reference values <code>x</code> * and <code>y</code>, <code>x.equals(y)</code> should return * <code>true</code> if and only if <code>y.equals(x)</code> * returns <code>true</code>. * </li> * <li> * It is <i>transitive</i>: for any reference values * <code>x</code>, <code>y</code>, and <code>z</code>, if * <code>x.equals(y)</code> returns <code>true</code> and * <code>y.equals(z)</code> returns <code>true</code>, then * <code>x.equals(z)</code> should return <code>true</code>. * </li> * <li> * It is <i>consistent</i>: for any reference values <code>x</code> * and <code>y</code>, multiple invocations of * <code>x.equals(y)</code> consistently return <code>true</code> * or consistently return <code>false</code>, provided no * information used in <code>equals</code> comparisons on the * object is modified. * </li> * <li> * For any non-<code>null</code> reference value <code>x</code>, * <code>x.equals(null)</code> should return <code>false</code>. * </li> * </ul></p> * * <p>The current <code>equals</code> method returns true if and only if * the given object: * <ul> * <li> * is this composite metadata or * </li> * <li> * is an instance of the type <code>CompositeMetaData</code> and * the internally used hash maps of this and the specified object * are equal. * </li> * </ul></p> * * @param object the reference object with which to compare. * @return <code>true</code> if this object is the same as the specified * object; <code>false</code> otherwise. * @see #hashCode() */ @Override public boolean equals(Object object) { if (object == null) return false; if (this == object) return true; return object instanceof CompositeMetaData && metaDataMap.equals(((CompositeMetaData<?, ?>)object).metaDataMap); } /** * Returns a shallow copy of this composite metadata instance: the * identifiers and metadata fragments themselves are not cloned. * * @return a shallow copy of this composite metadata. * @throws CloneNotSupportedException if the instance cannot be cloned. */ @Override @SuppressWarnings("unchecked") // clone methods must create correct type public Object clone() throws CloneNotSupportedException { CompositeMetaData<I, M> clone = (CompositeMetaData<I, M>)super.clone(); clone.metaDataMap.putAll(metaDataMap); return clone; } /** * Returns a string representation of the composite metadata. In general, * the <code>toString</code> method returns a string that "textually * represents" this object. The result should be a concise but informative * representation that is easy for a person to read. * * <p>The <code>toString</code> method for this class simply returns the * textually representation of its internally user hash map.</p> * * @return a string representation of the composite metadata. */ @Override public String toString() { return metaDataMap.toString(); } }