package org.geotools.data.efeature;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import org.geotools.util.WeakHashSet;
import org.geotools.util.logging.Logging;
/**
*
* @author kengu
*
*/
public abstract class EStructureInfo<T extends EStructureInfo<?>> {
/** The logger for the {@link EStructureInfo} class */
protected static final Logger LOGGER = Logging.getLogger(EStructureInfo.class);
protected boolean isValid = false;
protected boolean isDisposed = false;
protected boolean isAvailable = true;
/**
* Create fair reentrant lock, ensuring that the longest waiting thread
* is granted access once current thread released the object
*/
protected ReentrantLock eLock = new ReentrantLock(true);
/**
* Set of weakly referenced {@link EStructureInfo} listeners
*/
@SuppressWarnings("rawtypes")
protected WeakHashSet<EFeatureListener>
eListeners = new WeakHashSet<EFeatureListener>(EFeatureListener.class);
/**
* Cached {@link EFeatureStatus#SUCCESS success status}.
*/
protected final EFeatureStatus SUCCESS = EFeatureUtils.newStatus(this, EFeatureStatus.SUCCESS,
null, null);
/** Cached hints */
protected EFeatureHints eHints;
/** Cached {@link EFeatureContext} id */
protected String eContextID;
/** Weak reference to {@link EFeatureContextFactory} instance */
protected WeakReference<EFeatureContextFactory> eFactory;
/** Weak reference to {@link EFeatureContext} instance */
protected WeakReference<EFeatureContext> eContext;
// -----------------------------------------------------
// Constructors
// -----------------------------------------------------
/**
* Default constructor
*/
protected EStructureInfo() { /*NOP*/ }
/**
* Structure copy constructor.
* <p>
* This method copies the structure into given context.
* </p>
* <b>NOTE</b>: This method only adds a one-way reference from
* copied instance to given {@link EFeatureContext context}.
* </p>
* @param eStructureInfo - copy from this {@link EStructureInfo} instance
* @param eContextInfo - copy into context of this structure
*/
protected EStructureInfo(EStructureInfo<T> eStructureInfo, EStructureInfo<?> eContextInfo) {
//
// --------------------------------------------------
// Copy states
// --------------------------------------------------
// NOTE: State 'isDisposed' is not copied to allow
// recreation.
// --------------------------------------------------
//
this.isValid = eStructureInfo.isValid;
this.isAvailable = eStructureInfo.isAvailable;
//
// Copy creation hints
//
this.eHints = eStructureInfo.eHints;
//
// Copy context
//
this.eContext = eContextInfo.eContext;
this.eFactory = eContextInfo.eFactory;
this.eContextID = eContextInfo.eContextID;
}
// -----------------------------------------------------
// EStructureInfo methods
// -----------------------------------------------------
/**
* Get {@link EFeatureContext#eContextID() context ID}.
*
* @return a {@link EFeatureContext#eContextID() context ID}.
*/
public final String eContextID() {
return eContextID;
}
/**
* Get {@link EFeatureContext context} which this structure belongs.
*
* @throws IllegalStateException If {@link #isValid() invalid},
* {@link #isDisposed() disposed} or not found.
*/
public final EFeatureContext eContext()
{
return eContext(true);
}
/**
* Check if this is a <i>prototype</i>.
* <p>
* A prototype have an {@link EFeatureContextInfo} as it's
* {@link EStructureInfo#eParentInfo() parent structure}.
*/
public final boolean isPrototype() {
//
// If context is an prototype, all structures are prototypes,
// else, only EFeatureInfo structure can be prototypes when
// the parent structure is the context structure.
//
return eContext(true).isPrototype() ||
(this instanceof EFeatureInfo) &&
(eParentInfo(true) instanceof EFeatureContextInfo);
}
/**
* Get {@link EFeatureContextFactory} instance.
* @throws IllegalStateException If {@link #isValid() invalid},
* {@link #isDisposed() disposed} or not found.
*/
public final EFeatureContextFactory eFactory()
{
return eFactory(true);
}
/**
* Get parent structure.
*/
public final T eParentInfo() {
return eParentInfo(true);
}
/**
* Check if {@link EStructureInfo structure} is available.
* <p>
* A structure is only available if it is valid, not disposed
* and explicitly made available.
* </p>
* @return <code>true</code> if available.
*
* @see {@link #setAvailable(boolean)}
*/
public boolean isAvailable() {
return isAvailable && isValid();
}
/**
* Set available state.
* <p>
*
* @param isAvailable - next available state
*/
public final void setAvailable(boolean isAvailable) {
this.isAvailable = isAvailable;
}
/**
* Get {@link EFeatureHints hints}
*/
public EFeatureHints eHints() {
return eHints;
}
/**
* Check if {@link EStructureInfo structure} is valid.
* <p>
*
* @return <code>true</code> if valid.
*/
public final boolean isValid() {
return isValid && !isDisposed;
}
/**
* Invalidate the structure.
* <p>
* @param deep - if <code>true</code>, this and all it's
* children is invalidated.
*/
public final void invalidate(boolean deep){
isValid = false;
doInvalidate(deep);
}
/**
* Check if {@link EStructureInfo structure} is disposed.
* @return <code>true</code> if disposed.
*/
public boolean isDisposed()
{
return isDisposed;
}
/**
* Dispose this {@link EStructureInfo structure} and all it's children.
*/
protected void dispose() {
isDisposed = true;
doDispose();
eFactory = null;
eContext = null;
eListeners.clear();
eListeners=null;
}
// -----------------------------------------------------
// EStructureInfoListener support methods
// -----------------------------------------------------
/**
* Add {@link EFeatureListener} instance.
* <p>
* <strong>NOTE</strong>: This listener is stored as a {@link WeakReference weak reference},
* allowing the garbage collector to reclaim it when no hard references to it exist. At this
* point, the listener is automatically removed from this object.
* </p>
*/
public final void addListener(EFeatureListener<?> eListener) {
eListeners.add(eListener);
}
/**
* Remove {@link EFeatureListener} instance.
* <p>
* <strong>NOTE</strong>: This listener is stored as a {@link WeakReference weak reference},
* allowing the garbage collector to reclaim it when no hard references to it exist. At this
* point, the listener is automatically removed from this object.
* </p>
*/
public final void removeListener(EFeatureListener<?> eListener) {
eListeners.remove(eListener);
}
public void eNotify(Object source, int property, Object oldValue, Object newValue) {
for(EFeatureListener<Object> it : eListeners) {
it.onChange(source, property, oldValue, newValue);
}
}
public void eLock() {
eLock.lock();
}
public void eUnlock() {
eLock.unlock();
}
// -----------------------------------------------------
// EStructureInfo implementation methods
// -----------------------------------------------------
protected abstract void doDispose();
protected abstract T eParentInfo(boolean checkIsValid);
protected abstract void doInvalidate(boolean deep);
protected void doDetach() { /*NOP*/ }
protected void doAdapt() { /*NOP*/ }
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void fireOnChange(int property, Object oldValue, Object newValue) {
//
// Make a copy of current list, allowing concurrent modifications
//
List<EFeatureListener> eList = new ArrayList<EFeatureListener>(eListeners);
for (EFeatureListener it : eList) {
it.onChange(this, property, oldValue, newValue);
}
}
/**
* Check if a {@link EFeatureContext} instance with given
* {@link EFeatureContext#eContextID() registry id} is cached by this, or
* the EFeatureContext {@link #eFactory() factory}.
* </p>
* @param eContextID - the {@link EFeatureContext#eContextID() registry id}
* @return <code>true</code> if cached.
*/
protected boolean isCached(String eContextID) {
EFeatureContext eThis = null;
if( !(eContext==null || eContext.get()==null) )
{
eThis = eContext.get();
}
if(eThis!=null)
{
return eThis.eContextID().equals(eContextID);
}
return eFactory(false).contains(eContextID);
}
/**
* Get {@link EFeatureContextFactory} instance.
* <p>
* @param checkIsValid - if <code>true</code>, the method verifies that
* this {@link EStructureInfo structure} is valid. If invalid,
* an {@link IllegalStateException} is thrown.
* </p>
* @throws IllegalStateException If {@link #isDisposed() disposed} or not found.
*/
protected EFeatureContextFactory eFactory(boolean checkIsValid)
{
verify(checkIsValid);
if(eFactory==null || eFactory.get()==null)
{
eFactory = new WeakReference<EFeatureContextFactory>(EFeatureContextFactory.eDefault());
if(eFactory.get()==null) {
invalidate(true);
throw new IllegalStateException("EFeatureContextFactory '" + "' not found");
}
}
return eFactory.get();
}
/**
* Get {@link EFeatureContext} instance counterpart.
* <p>
* @param checkIsValid - if <code>true</code>, the method verifies that
* this {@link EFeaturePackageInfo structure} is valid. If invalid,
* an {@link IllegalStateException} is thrown.
* </p>
* @throws IllegalStateException If {@link #isDisposed() disposed} or not found.
*/
protected EFeatureContext eContext(boolean checkIsValid)
{
verify(checkIsValid);
if(eContext==null || eContext.get()==null)
{
eContext = new WeakReference<EFeatureContext>(
eFactory(false).eContext(eContextID));
if(eContext.get()==null) {
invalidate(true);
throw new IllegalStateException("EFeatureContext '"
+ eContextID + "' not found");
}
}
return eContext.get();
}
protected void eAdapt(EStructureInfo<?> eStructure) {
//
// Detach from current context?
//
if(eContext(false)!=eStructure.eContext(false)) {
//
// Forward to implementation
//
doDetach();
}
//
//
// Update context information
//
eContext = eStructure.eContext;
eContextID = eStructure.eContextID;
//
// Forward
//
doAdapt();
}
/**
* Verify that state is available
*/
protected void verify() throws IllegalStateException
{
verify(false);
}
/**
* Verify that state is available
*/
protected void verify(boolean checkIsValid) throws IllegalStateException
{
if(isDisposed)
throw new IllegalStateException(this + " is disposed");
if(checkIsValid && !isValid)
throw new IllegalStateException(this + " is not valid. Please validate the structure.");
}
// -----------------------------------------------------
// Static EStructureInfo helper methods
// -----------------------------------------------------
protected static EFeatureStatus failure(Object source, String context, String message) {
StackTraceElement[] stack = EFeatureUtils.getStackTrace(1);
StackTraceElement trace = stack[0];
message = trace.getClassName() + "[" + context+ "]#" + trace.getMethodName() + "(): " + message;
LOGGER.warning(message);
return EFeatureUtils.newStatus(source, EFeatureStatus.FAILURE, message, stack);
}
protected static final EFeatureStatus failure(Object source,
String context, String message, Throwable cause) {
StackTraceElement[] stack = EFeatureUtils.getStackTrace(1);
StackTraceElement trace = stack[0];
message = trace.getClassName() + "[" + context+ "]#" + trace.getMethodName() + "(): " + message;
LOGGER.warning(message);
if(cause==null) {
return EFeatureUtils.newStatus(source, EFeatureStatus.FAILURE, message, stack);
}
return EFeatureUtils.newStatus(source, EFeatureStatus.FAILURE, message, cause);
}
protected final EFeatureStatus structureIsValid(String context) {
isValid = true;
StackTraceElement trace = EFeatureUtils.getStackTraceElement(0,1);
String msg = "Structure is valid: " + trace.getClassName()
+ "[" + context + "]#" + trace.getMethodName()+"()";
return success(msg);
}
protected final EFeatureStatus success(String message) {
LOGGER.fine(message);
return SUCCESS.clone(message);
}
}