/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2005-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* 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;
* version 2.1 of the License.
*
* 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.
*/
package org.geotoolkit.coverage;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentHashMap;
import org.opengis.coverage.Coverage;
import org.geotoolkit.resources.Errors;
import org.apache.sis.util.Disposable;
import org.apache.sis.util.NullArgumentException;
import org.geotoolkit.internal.ReferenceQueueConsumer;
/**
* Holds {@linkplain WeakReference weak references} to {@linkplain Coverage coverage} instances.
* Every call to the {@link #reference} method with the same {@code Coverage} instance will return
* the same {@code WeakReference} instance. If all weak references to a coverage have been created
* by the same instance of {@code CoverageReferences} (typically the {@link #DEFAULT} one), then it
* is guaranteed that given:
*
* {@preformat java
* Coverage coverageA = ...;
* Coverage coverageB = ...;
*
* WeakReference<Coverage> refA = reference(coverageA);
* WeakReference<Coverage> refB = reference(coverageB);
* }
*
* then testing {@code (refA == refB)} is equivalent to testing {@code (coverageA == coverageB)}
* Comparing the references instead than the coverages can keep comparisons mainfull even after
* the coverages have been garbage collected. This is sometime useful for checking if two results
* of a calculation were done using the same coverage inputs, without preventing garbage-collection
* of those coverages.
* <p>
* Because a weak references created by this class may be shared by many, invoking
* {@link Reference#clear} on them has no effect. Only the garbage-collector can
* clear the references.
*
* {@note This class may be extended in a future version with coverage-specific enhancements.
* For example we could prevent the garbage-collection of a coverage as long as the JAI
* <code>TileCache</code> has not disposed the image tiles. This class is defined in this
* package instead than in the more generic <code>org.geotoolkit.util.collection</code>
* package for that reason.}
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 3.13
*
* @since 2.1
* @module
*/
public class CoverageReferences {
/**
* The default, system-wide weak references for {@linkplain Coverage coverages}.
*/
public static final CoverageReferences DEFAULT = new CoverageReferences();
/**
* The map of coverages references. Keys and values are the same instances.
*/
private final ConcurrentMap<Ref,Ref> pool = new ConcurrentHashMap<>();
/**
* Creates a new coverage cache. This method is given protected access for subclassing
* only. For typical usage, the {@link #DEFAULT} instance should be used instead.
*/
protected CoverageReferences() {
}
/**
* Returns a {@linkplain WeakReference weak reference} to the specified coverage. If such
* reference already exists for the specified coverage, then that reference is returned.
* Otherwise, a new {@code WeakReference} is created and returned.
*
* @param coverage The coverage to reference.
* @return A unique weak reference to the specified coverage.
*/
public Reference<Coverage> reference(final Coverage coverage) {
if (coverage == null) {
throw new NullArgumentException(Errors.format(Errors.Keys.NullArgument_1, "coverage"));
}
Ref ref = pool.get(new Lookup(coverage));
if (ref == null) {
ref = new Ref(coverage);
final Ref old = pool.putIfAbsent(ref, ref);
if (old != null) {
ref = old;
}
}
/*
* We really want identity equality, not Object.equals(...), otherwise we have
* no guarantee that the coverage is not garbage-collected before the user invokes
* Reference.get().
*/
assert ref.get() == coverage;
return ref;
}
/**
* A temporary key used for lookup only. The main feature of this lookup key is to
* be comparable with {@link Ref} objects.
*
* @author Martin Desruisseaux (IRD)
* @version 3.00
*
* @since 3.00
* @module
*/
private static final class Lookup {
/**
* The coverage to test for equality.
*/
private final Coverage coverage;
/**
* Creates a new lookup key for the given coverage.
*/
Lookup(final Coverage coverage) {
this.coverage = coverage;
}
/**
* Returns the hash code value.
*/
@Override
public int hashCode() {
return System.identityHashCode(coverage);
}
/**
* Compares the given object with this lookup key for equality. {@code Lookup} objects are
* aimed to be compared to {@code Ref} objects only. Comparisons with other objects should
* never happen, nevertheless we allow comparisons with other {@code Lookup} instances for
* symmetry with {@link Ref#equals} implementation, which make us more compliant with the
* {@link Object#equals} transitivity contract.
* <p>
* Note: We need to compare coverages for identity equality, not using
* an hypothetical {@link Coverage#equals(Object)} method.
*/
@Override
public boolean equals(final Object object) {
if (object instanceof Ref) {
return coverage == ((Ref) object).get();
}
if (object instanceof Lookup) {
return coverage == ((Lookup) object).coverage;
}
return super.equals(object);
}
}
/**
* A reference to a coverage, to be stored in {@link CoverageReferences#pool}.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 3.13
*
* @since 2.1
* @module
*/
private final class Ref extends WeakReference<Coverage> implements Disposable {
/**
* Hash code value, saved at construction time before
* the coverage reference is nullified.
*/
private final int hash;
/**
* Constructs a reference to the specified coverage.
*/
Ref(final Coverage coverage) {
super(coverage, ReferenceQueueConsumer.DEFAULT.queue);
hash = System.identityHashCode(coverage);
}
/**
* Returns the hash code value for the coverage.
*/
@Override
public int hashCode() {
return hash;
}
/**
* Compares this reference with the specified object for equality. {@code Ref} objects are
* comparable to {@code Lookup} keys and with other {@code Ref} objects. In the later case,
* two {@code Ref} objects are equal if their referent are non-null and equal. If one or
* both referents are null, then the {@code Ref} objects are not considered equal except
* if their is an identity equality ({@code this == other}).
* <p>
* Note: We need to compare coverages for identity equality, not using
* an hypothetical {@link Coverage#equals(Object)} method.
*/
@Override
public boolean equals(final Object object) {
if (object instanceof Lookup) {
return ((Lookup) object).coverage == get();
}
if (object instanceof Ref) {
final Coverage coverage = get();
if (coverage != null) {
return coverage == ((Ref) object).get();
}
// Need to fallback on the identity check below.
}
/*
* Do not unconditionally return 'false' here! The check for identity equality
* is mandatory for proper disposal of references when the referenced Coverage
* has been garbage-collected.
*/
return super.equals(object);
}
/**
* Removes this reference from the pool after it has been cleared by the garbage-collector.
* This method may be invoked either by the user or from the {@link Disposer} thread.
* However explicit invocations do not clear the referent, because if we did we would
* have no guarantee that the reference returned by {@link CoverageReferences#reference}
* has a non-null referent.
*/
@Override
public void clear() {
dispose();
}
/**
* Removes a reference from the map. This method is invoked by
* {@link ReferenceQueueConsumer#process} when the reference has
* been garbage-collected.
*/
@Override
public void dispose() {
// Do not invoke super.clear() - see clear() javadoc.
if (get() == null) {
final Ref old = pool.remove(this);
assert old == this || old == null;
}
}
}
}