it.tidalwave.netbeans.util.AsLookupSupport Maven / Gradle / Ivy
/***********************************************************************************************************************
*
* blueBill Core - open source birding
* Copyright (C) 2009-2011 by Tidalwave s.a.s. (http://www.tidalwave.it)
*
***********************************************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
***********************************************************************************************************************
*
* WWW: http://bluebill.tidalwave.it
* SCM: https://kenai.com/hg/bluebill~core-src
*
**********************************************************************************************************************/
package it.tidalwave.netbeans.util;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.Arrays;
import java.io.Serializable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import it.tidalwave.util.As;
import it.tidalwave.util.NotFoundException;
import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;
import it.tidalwave.netbeans.capabilitiesprovider.LookupFactory;
import it.tidalwave.netbeans.capabilitiesprovider.ThreadLookupBinder;
/***********************************************************************************************************************
*
* FIXME: move to OpenBlueSky
*
* @author Fabrizio Giudici
* @version $Id$
*
**********************************************************************************************************************/
public class AsLookupSupport implements As, Lookup.Provider, Serializable
{
private static final Logger log = LoggerFactory.getLogger(AsLookupSupport.class);
private static final long serialVersionUID = 5704964038450988L;
/**
* The cache seems to give some advantage on Android. E.g. blueBill rendering taxa displaynames on a Motorola
* Milestone with Android 2.2 takes about 5msec without cache and 0msec with cache.
* TODO: enable it with a system property, so we can enable it only with Android.
* TODO: theoretically, it is possible to inject a mutable Lookup, so we should set up a listener to the Lookup
* and invalidate the cache upon changes. This is probably too much for many cases in which Lookups are used in
* a static fashion Maybe we could use the listener if another system property is enabled.
*/
private static final boolean USE_CACHE = true;
/**
* The owner is usually the object itself, but it can be another in case this class is used in a composition for
* delegation.
*/
private final Object owner;
/** Those capabilities specific of this instance. */
private final Object[] instanceCapabilities;
/**
* Transient: there's no reason in making lookup serializable as it contains capabilities injected by the
* context that could be not meaningful in the target deserialization context. Injected capabilities will be
* recreated at the first getLookup() call after deserialization).
*/
@CheckForNull
private transient Lookup lookup;
@CheckForNull
private transient Map, Object> cache;
@CheckForNull
private transient Lookup threadLocalLookup;
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public AsLookupSupport()
{
this(new Object[0]);
threadLocalLookup = ThreadLookupBinder.getLookup();
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public AsLookupSupport (final @Nonnull Object[] instanceCapabilities)
{
this.owner = this;
this.instanceCapabilities = instanceCapabilities.clone();
threadLocalLookup = ThreadLookupBinder.getLookup();
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public AsLookupSupport (final @Nonnull Object owner)
{
this(owner, new Object[0]);
threadLocalLookup = ThreadLookupBinder.getLookup();
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public AsLookupSupport (final @Nonnull Object owner, final @Nonnull Object[] instanceCapabilities)
{
this.owner = owner;
this.instanceCapabilities = instanceCapabilities.clone();
threadLocalLookup = ThreadLookupBinder.getLookup();
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
@Nonnull
public final synchronized Lookup getLookup()
{
if (lookup == null)
{
lookup = createLookup();
}
return lookup;
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
@Nonnull
public final T as (final @Nonnull Class type)
{
return as(type, As.Defaults.throwAsException(type));
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
@Nonnull
public T as (final @Nonnull Class type, final @Nonnull NotFoundBehaviour notFoundBehaviour)
{
final long time = System.currentTimeMillis();
try
{
T result = null;
if (!USE_CACHE)
{
result = getLookup().lookup(type);
}
else
{
synchronized (this)
{
if (cache == null)
{
cache = new WeakHashMap, Object>();
}
result = type.cast(cache.get(type));
if (result == null)
{
result = getLookup().lookup(type);
if (result != null)
{
cache.put(type, result);
}
}
}
}
return (result != null) ? result : notFoundBehaviour.run(new NotFoundException("No " + type.getName() + " in " + this));
}
finally
{
log.trace(">>>> {}.as({}) took {} msec.", new Object[]{ toShortString(), type.getSimpleName(), System.currentTimeMillis() - time });
}
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
@Nonnull
protected Lookup createLookup()
{
final long time = System.currentTimeMillis();
Lookup result = LookupFactory.createLookup(owner, Lookups.fixed(instanceCapabilities));
if (threadLocalLookup != null)
{
result = LookupFactory.createLookup(threadLocalLookup, this, result);
threadLocalLookup = null;
}
log.trace(">>>> createLookup() - for {} took {} msec.", toShortString(), System.currentTimeMillis() - time);
return result;
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
@Override @Nonnull
public String toString()
{
return String.format("%s@%x[%s]", getClass().getSimpleName(),
System.identityHashCode(this),
Arrays.toString(instanceCapabilities));
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
@Nonnull
private String toShortString()
{
return String.format("%s@%x", getClass().getSimpleName(), System.identityHashCode(this));
}
}