/* 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.util.Iterator; import java.util.Map.Entry; import xxl.core.cursors.Cursors; import xxl.core.cursors.filters.Filter; import xxl.core.cursors.identities.UnmodifiableCursor; import xxl.core.cursors.mappers.Mapper; import xxl.core.cursors.unions.Sequentializer; import xxl.core.functions.AbstractFunction; import xxl.core.predicates.AbstractPredicate; /** * A composite metadata object that wraps another one in a transparent way. The * metadata fragments of the wrapped composite metadata are fully visible from * the transparent composite metadata object, but changes to the transparent * composite metadata's fragment will only be done locally. I. e., new metadata * fragments will be added/put/replaced/removed in the transparent composite * metadata while the the wrapped composite metadata stays unchanged. * * <p>Imagine the following situation. The transparent composite metadata * <tt>T</tt> has locally stored the following IDs and fragments * <tt>T {id<sub>1</sub> frag<sub>1</sub>, id<sub>2</sub> frag<sub>2</sub>}</tt> * and wraps a composite metadata <tt>C</tt> that stores the following IDs and * fragments * <tt>C {id<sub>2</sub> frag<sub>2</sub>, id<sub>3</sub> frag<sub>3</sub>}</tt>. * Then <tt>T</tt> will globally look like this * <tt>T {id<sub>1</sub> frag<sub>1</sub>, id<sub>2</sub> frag<sub>2</sub>, id<sub>3</sub> frag<sub>3</sub>}</tt> * and the following calls will produce the listed results. * <table frame="box"> * <tr> * <th>call</th> * <th>result</th> * <th>reason/effect</th> * </tr> * <tr> * <td><tt>T.add(id<sub>1</sub>, frag)</tt></td> * <td><tt>MetaDataException</tt></td> * <td><tt>T</tt> already contains <tt>id<sub>1</sub></tt></td> * </tr> * <tr> * <td><tt>T.add(id<sub>2</sub>, frag)</tt></td> * <td><tt>MetaDataException</tt></td> * <td><tt>T</tt> already contains <tt>id<sub>2</sub></tt></td> * </tr> * <tr> * <td><tt>T.add(id<sub>3</sub>, frag)</tt></td> * <td><tt>MetaDataException</tt></td> * <td><tt>C</tt> already contains <tt>id<sub>3</sub></tt></td> * </tr> * <tr> * <td><tt>T.add(id<sub>4</sub>, frag)</tt></td> * <td></td> * <td><tt>id<sub>4</sub> frag</tt> is inserted into <tt>T</tt></td> * </tr> * <tr> * <td><tt>T.put(id<sub>1</sub>, frag)</tt></td> * <td><tt>frag<sub>1</sub></tt></td> * <td> * <tt>id<sub>1</sub> frag<sub>1</sub></tt> is replaced by * <tt>id<sub>1</sub> frag</tt> in <tt>T</tt> * </td> * </tr> * <tr> * <td><tt>T.put(id<sub>2</sub>, frag)</tt></td> * <td><tt>frag<sub>2</sub></tt></td> * <td> * <tt>id<sub>2</sub> frag<sub>2</sub></tt> is replaced by * <tt>id<sub>2</sub> frag</tt> in <tt>T</tt> * </td> * </tr> * <tr> * <td><tt>T.put(id<sub>3</sub>, frag)</tt></td> * <td><tt>frag<sub>3</sub></tt></td> * <td> * <tt>id<sub>3</sub> frag</tt> is inserted into <tt>T</tt> (and * hides <tt>id<sub>3</sub> frag<sub>3</sub></tt> from <tt>C</tt>) * </td> * </tr> * <tr> * <td><tt>T.put(id<sub>4</sub>, frag)</tt></td> * <td><tt>null</tt></td> * <td><tt>id<sub>3</sub> frag</tt> is inserted into <tt>T</tt></td> * </tr> * <tr> * <td><tt>T.replace(id<sub>1</sub>, frag)</tt></td> * <td><tt>frag<sub>1</sub></tt></td> * <td> * <tt>id<sub>1</sub> frag<sub>1</sub></tt> is replaced by * <tt>id<sub>1</sub> frag</tt> in <tt>T</tt> * </td> * </tr> * <tr> * <td><tt>T.replace(id<sub>2</sub>, frag)</tt></td> * <td><tt>frag<sub>2</sub></tt></td> * <td> * <tt>id<sub>2</sub> frag<sub>2</sub></tt> is replaced by * <tt>id<sub>2</sub> frag</tt> in <tt>T</tt> * </td> * </tr> * <tr> * <td><tt>T.replace(id<sub>3</sub>, frag)</tt></td> * <td><tt>frag<sub>3</sub></tt></td> * <td> * <tt>id<sub>3</sub> frag</tt> is inserted into <tt>T</tt> (and * hides <tt>id<sub>3</sub> frag<sub>3</sub></tt> from <tt>C</tt>) * </td> * </tr> * <tr> * <td><tt>T.replace(id<sub>4</sub>, frag)</tt></td> * <td><tt>MetaDataException</tt></td> * <td><tt>T</tt> and <tt>C</tt> do not contain <tt>id<sub>4</sub></tt></td> * </tr> * <tr> * <td><tt>T.contains(id<sub>1</sub>)</tt></td> * <td><tt>true</tt></td> * <td><tt>T</tt> contains <tt>id<sub>1</sub></tt></td> * </tr> * <tr> * <td><tt>T.contains(id<sub>2</sub>)</tt></td> * <td><tt>true</tt></td> * <td><tt>T</tt> contains <tt>id<sub>2</sub></tt></td> * </tr> * <tr> * <td><tt>T.contains(id<sub>3</sub>)</tt></td> * <td><tt>true</tt></td> * <td><tt>C</tt> contains <tt>id<sub>3</sub></tt></td> * </tr> * <tr> * <td><tt>T.contains(id<sub>4</sub>)</tt></td> * <td><tt>false</tt></td> * <td><tt>T</tt> and <tt>C</tt> do not contain <tt>id<sub>4</sub></tt></td> * </tr> * <tr> * <td><tt>T.get(id<sub>1</sub>)</tt></td> * <td><tt>frag<sub>1</sub></tt></td> * <td><tt>T</tt> contains <tt>id<sub>1</sub></tt></td> * </tr> * <tr> * <td><tt>T.get(id<sub>2</sub>)</tt></td> * <td><tt>frag<sub>2</sub></tt></td> * <td><tt>T</tt> contains <tt>id<sub>2</sub></tt></td> * </tr> * <tr> * <td><tt>T.get(id<sub>3</sub>)</tt></td> * <td><tt>frag<sub>3</sub></tt></td> * <td><tt>C</tt> contains <tt>id<sub>3</sub></tt></td> * </tr> * <tr> * <td><tt>T.get(id<sub>4</sub>)</tt></td> * <td><tt>MetaDataException</tt></td> * <td><tt>T</tt> and <tt>C</tt> do not contain <tt>id<sub>4</sub></tt></td> * </tr> * <tr> * <td><tt>T.remove(id<sub>1</sub>)</tt></td> * <td><tt>frag<sub>1</sub></tt></td> * <td> * <tt>id<sub>1</sub> frag<sub>1</sub></tt> is removed from * <tt>T</tt> * </td> * </tr> * <tr> * <td><tt>T.remove(id<sub>2</sub>)</tt></td> * <td><tt>frag<sub>2</sub></tt></td> * <td> * <tt>id<sub>2</sub> frag<sub>2</sub></tt> is removed from * <tt>T</tt> (but still visible from <tt>C</tt>) * </td> * </tr> * <tr> * <td><tt>T.remove(id<sub>3</sub>)</tt></td> * <td><tt>MetaDataException</tt></td> * <td><tt>C</tt> cannot be changed</td> * </tr> * <tr> * <td><tt>T.remove(id<sub>4</sub>)</tt></td> * <td><tt>MetaDataException</tt></td> * <td><tt>T</tt> and <tt>C</tt> do not contain <tt>id<sub>4</sub></tt></td> * </tr> * </table></p> * * <p><b>Note</b>, the above-mentioned behaviour shows differences between * composite metadata and transparent composite metadata. First, when the * transparent composite metadata as well as the wrapped composite metadata * contains the same ID then the removal of it will return the removed fragment * but the ID stays in the transparent composite metadata (because it is also * part of the wrapped composite metadata). Second, when an ID is only part of * the wrapped composite metadata but not of the surrounding transparent * composite metadata then the removal of it will be impossible.</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 TransparentCompositeMetaData<I, M> extends CompositeMetaData<I, M> { /** * The wrapped composite metadata whose metadata fragments are made visible * from the surrounding transparent composite metadata, but cannot be * changed. */ protected CompositeMetaData<I, M> compositeMetaData; /** * Creates a new transparent composite metadata that wraps the given * composite metadata. The wrapped composite metadata's fragments are made * visible from the surrounding transparent composite metadata, but cannot * be changed. * * @param compositeMetaData the composite metadata to be wrapped. */ public TransparentCompositeMetaData(CompositeMetaData<I, M> compositeMetaData) { this.compositeMetaData = compositeMetaData; } /** * Creates a new transparent composite metadata that wraps an empty * composite metadata. */ public TransparentCompositeMetaData() { this(new CompositeMetaData<I, M>()); } /** * Returns the wrapped composite metadata. * * @return the wrapped composite metadata. */ public CompositeMetaData<I, M> getCompositeMetaData() { return compositeMetaData; } /** * Sets the wrapped composite metadata to the given one. * * @param compositeMetaData the composite metadata that should be wrapped * by this transparent composite metadata. */ public void setCompositeMetaData(CompositeMetaData<I, M> compositeMetaData) { lock.writeLock().lock(); try { this.compositeMetaData = compositeMetaData; } finally { lock.writeLock().unlock(); } } /** * 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. */ @Override public M put(I identifier, M metaData) { lock.writeLock().lock(); try { if (metaDataMap.containsKey(identifier) || !compositeMetaData.contains(identifier)) return metaDataMap.put(identifier, metaData); metaDataMap.put(identifier, metaData); return compositeMetaData.get(identifier); } finally { lock.writeLock().unlock(); } } /** * 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. */ @Override public M replace(I identifier, M metaData) throws MetaDataException { lock.writeLock().lock(); try { if (metaDataMap.containsKey(identifier)) return metaDataMap.put(identifier, metaData); if (compositeMetaData.contains(identifier)) { metaDataMap.put(identifier, metaData); return compositeMetaData.get(identifier); } throw new MetaDataException("composite meta data does not contain specified meta data \'" + identifier + "\'"); } finally { lock.writeLock().unlock(); } } /** * 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. */ @Override public boolean contains(I identifier) { lock.readLock().lock(); try { return metaDataMap.containsKey(identifier) || compositeMetaData.contains(identifier); } finally { lock.readLock().unlock(); } } /** * 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. */ @Override public M get(I identifier) throws MetaDataException { lock.readLock().lock(); try { if (metaDataMap.containsKey(identifier)) return metaDataMap.get(identifier); if (compositeMetaData.contains(identifier)) return compositeMetaData.get(identifier); throw new MetaDataException("composite meta data does not contain specified meta data \'" + identifier + "\'"); } finally { lock.readLock().unlock(); } } /** * Removes the metadata fragment for this identifer 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. */ @Override public M remove(I identifier) throws MetaDataException { lock.writeLock().lock(); try { if (metaDataMap.containsKey(identifier)) return metaDataMap.remove(identifier); if (compositeMetaData.contains(identifier)) throw new MetaDataException("transparent composite meta data does not contain specified meta data \'" + identifier + "\' as a direct fragment, hence it cannot be removed"); throw new MetaDataException("composite meta data does not contain specified meta data \'" + identifier + "\'"); } 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. */ @Override public int size() { lock.readLock().lock(); try { return Cursors.count(iterator()); } finally { lock.readLock().unlock(); } } /** * 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. */ @Override public Iterator<I> identifiers() { return new Mapper<Entry<I, M>, I>( new AbstractFunction<Entry<I, M>, I>() { @Override public I invoke(Entry<I, M> entry) { return entry.getKey(); } }, 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. */ @Override public Iterator<M> fragments() { return new Mapper<Entry<I, M>, M>( new AbstractFunction<Entry<I, M>, M>() { @Override public M invoke(Entry<I, M> entry) { return entry.getValue(); } }, 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. */ @Override public Iterator<Entry<I, M>> iterator() { return new Sequentializer<Entry<I, M>>( metaDataMap.entrySet().iterator(), new Filter<Entry<I, M>>( new UnmodifiableCursor<Entry<I, M>>( compositeMetaData.iterator() ), new AbstractPredicate<Entry<I, M>>() { @Override public boolean invoke(Entry<I, M> entry) { return !metaDataMap.containsKey(entry.getKey()); } } ) ); } /** * 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() { int hashCode = 0; for (Entry<I, M> entry : this) hashCode += entry.hashCode(); return 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 TransparentCompositeMetaData && metaDataMap.equals(((TransparentCompositeMetaData<?, ?>)object).metaDataMap) && compositeMetaData.equals(((TransparentCompositeMetaData<?, ?>)object).compositeMetaData); } /** * 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 { TransparentCompositeMetaData<I, M> clone = (TransparentCompositeMetaData<I, M>)super.clone(); clone.metaDataMap.putAll(metaDataMap); clone.setCompositeMetaData(compositeMetaData); 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() { StringBuffer string = new StringBuffer().append('{'); I identifier; M metaData; for (Entry<I, M> entry : metaDataMap.entrySet()) { if (string.length() > 1) string.append(", "); string.append((identifier = entry.getKey()) == this ? "(this Map)" : identifier).append('=').append((metaData = entry.getValue()) == this ? "(this Map)" : metaData); } return string.append('}').toString(); } }