blob: 018bc84af7e11fad44eaa44381ff9b95d097e6eb [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.openide.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.modules.openide.util.GlobalLookup;
import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ProxyLookup;
import org.openide.util.lookup.ServiceProvider;
/**
* A general registry permitting clients to find instances of services
* (implementation of a given interface).
* This class is inspired by the
* <a href="http://www.jini.org/">Jini</a>
* registration and lookup mechanism. The difference is that the methods do
* not throw checked exceptions (as they usually work only locally and not over the network)
* and that the Lookup API concentrates on the lookup, not on the registration
* (although {@link Lookup#getDefault} is strongly encouraged to support
* {@link Lookups#metaInfServices} for registration in addition to whatever
* else it decides to support).
* <p>
* For a general talk about the idea behind the lookup pattern please see
* <UL>
* <LI><a href="lookup/doc-files/index.html">The Solution to Communication Between Components</a>
* page
* <LI>the introduction to the <a href="lookup/doc-files/lookup-api.html">lookup API via
* use cases</a>
* <LI>the examples of <a href="lookup/doc-files/lookup-spi.html">how to write your own lookup</a>
* </UL>
*
* @see org.openide.util.lookup.AbstractLookup
* @see Lookups
* @see LookupListener
* @see LookupEvent
* @author Jaroslav Tulach
*/
public abstract class Lookup {
private static final Logger LOG = Logger.getLogger("org.openide.util.lookup.init"); // NOI18N
/** A dummy lookup that never returns any results.
*/
public static final Lookup EMPTY = new Empty();
/** default instance */
private static Lookup defaultLookup;
/**
* Default instance's provider
*/
private static Lookup.Provider defaultLookupProvider;
/** Empty constructor for use by subclasses. */
public Lookup() {
}
/** Static method to obtain the global lookup in the whole system.
* The actual returned implementation can be different in different
* systems, but the default one is based on
* {@link org.openide.util.lookup.Lookups#metaInfServices}
* with the context classloader of the first caller. Each system is
* adviced to honor this and include some form of <code>metaInfServices</code>
* implementation in the returned lookup as usage of <code>META-INF/services</code>
* is a JDK standard.
*
* @return the global lookup in the system
* @see ServiceProvider
*/
public static synchronized Lookup getDefault() {
Lookup gLpk = GlobalLookup.current();
if (gLpk != null) {
return gLpk;
}
if (defaultLookup != null || defaultLookupProvider != null) {
if (defaultLookupProvider != null) {
Lookup lkp = defaultLookupProvider.getLookup();
if (lkp != null) {
return lkp;
}
}
return defaultLookup;
}
LOG.log(Level.FINER, "About to initialize Lookup@{0}.getDefault() by {1}",
new Object[] { Lookup.class.getClassLoader(), Thread.currentThread() }
);
// You can specify a Lookup impl using a system property if you like.
String className = System.getProperty("org.openide.util.Lookup"); // NOI18N
LOG.log(Level.FINER, "Specified by property? Value: {0}", className);
if ("-".equals(className)) { // NOI18N
// Suppress even MetaInfServicesLookup.
return EMPTY;
}
ClassLoader l = Thread.currentThread().getContextClassLoader();
LOG.log(Level.FINER, "Searching in classloader {0}", l);
try {
if (className != null) {
Object o = Class.forName(className, true, l).newInstance();
defaultLookup = (Lookup)o;
// set the global global Lookuo
GlobalLookup.setSystemLookup(defaultLookup);
LOG.log(Level.FINE, "Default lookup initialized {0}", defaultLookup);
// for testing purposes, tests may setup a class implementing both interfaces
if (o instanceof Lookup.Provider) {
defaultLookupProvider = (Lookup.Provider)o;
Lookup lkp = defaultLookupProvider.getLookup();
if (lkp != null) {
return lkp;
}
}
return defaultLookup;
}
} catch (Exception e) {
LOG.log(Level.WARNING, "Constuction of " + className + " via " + l + " failed", e);
}
// OK, none specified (successfully) in a system property.
// Try MetaInfServicesLookup as a default, which may also
// have a org.openide.util.Lookup line specifying the lookup.
Lookup misl = Lookups.metaInfServices(l);
defaultLookup = misl.lookup(Lookup.class);
LOG.log(Level.FINER, "Searching for {0} in {1} yields {2}", new Object[]{Lookup.class, misl, defaultLookup});
if (defaultLookup != null) {
if (defaultLookup instanceof Lookup.Provider) {
defaultLookupProvider = (Lookup.Provider)defaultLookup;
Lookup lkp = defaultLookupProvider.getLookup();
if (lkp != null) {
return lkp;
}
}
LOG.log(Level.FINE, "Default lookup initialized {0}", defaultLookup);
return defaultLookup;
}
// You may also specify a Lookup.Provider.
Lookup.Provider prov = misl.lookup(Lookup.Provider.class);
LOG.log(Level.FINER, "Searching for {0} in {1} yields {2}", new Object[]{Lookup.Provider.class, misl, defaultLookup});
if (prov != null) {
defaultLookup = Lookups.proxy(prov);
LOG.log(Level.FINE, "Default lookup initialized {0}", defaultLookup);
return defaultLookup;
}
DefLookup def = new DefLookup();
def.init(l, misl, false);
defaultLookup = def;
def.init(l, misl, true);
LOG.log(Level.FINE, "Default lookup initialized {0}", defaultLookup);
return defaultLookup;
}
private static final class DefLookup extends ProxyLookup {
public DefLookup() {
super(new Lookup[0]);
}
public void init(ClassLoader loader, Lookup metaInfLookup, boolean addPath) {
// Had no such line, use simple impl.
// It does however need to have ClassLoader available or many things will break.
// Use the thread context classloader in effect now.
Lookup clLookup = Lookups.singleton(loader);
List<Lookup> arr = new ArrayList<Lookup>();
arr.add(metaInfLookup);
arr.add(clLookup);
String paths = System.getProperty("org.openide.util.Lookup.paths"); // NOI18N
if (addPath && paths != null) {
LOG.log(Level.FINE, "Adding search paths {0}", paths);
for (String p : paths.split(":")) { // NOI18N
arr.add(Lookups.forPath(p));
}
}
LOG.log(Level.FINER, "Setting DefLookup delegates {0}", arr);
setLookups(arr.toArray(new Lookup[0]));
}
}
/** Called from MockServices to reset default lookup in case services change
*/
private static synchronized void resetDefaultLookup() {
if (defaultLookup == null || defaultLookup instanceof DefLookup) {
DefLookup def = (DefLookup)defaultLookup;
ClassLoader l = Thread.currentThread().getContextClassLoader();
Lookup misl = Lookups.metaInfServices(l);
if (def == null) {
def = new DefLookup();
def.init(l, misl, false);
defaultLookup = def;
}
def.init(l, misl, true);
}
}
/** Look up an object matching a given interface.
* This is the simplest method to use.
* If more than one object matches, the first will be returned.
* The template class may be a class or interface; the instance is
* guaranteed to be assignable to it.
*
* @param clazz class of the object we are searching for
* @return an object implementing the given class or <code>null</code> if no such
* implementation is found
*/
public abstract <T> T lookup(Class<T> clazz);
/** The general lookup method. Callers can get list of all instances and classes
* that match the given <code>template</code>, request more info about
* them in form of {@link Lookup.Item} and attach a listener to
* this be notified about changes. The general interface does not
* specify whether subsequent calls with the same template produce new
* instance of the {@link Lookup.Result} or return shared instance. The
* prefered behaviour however is to return shared one.
*
* @param template a template describing the services to look for
* @return an object containing the results
*/
public abstract <T> Result<T> lookup(Template<T> template);
/** Look up the first item matching a given template.
* Includes not only the instance but other associated information.
* @param template the template to check
* @return a matching item or <code>null</code>
*
* @since 1.8
*/
public <T> Item<T> lookupItem(Template<T> template) {
Result<T> res = lookup(template);
Iterator<? extends Item<T>> it = res.allItems().iterator();
return it.hasNext() ? it.next() : null;
}
/**
* Find a result corresponding to a given class.
* Equivalent to calling {@link #lookup(Lookup.Template)} but slightly more convenient.
* Subclasses may override this method to produce the same semantics more efficiently.
* @param clazz the supertype of the result
* @return a live object representing instances of that type
* @since org.openide.util 6.10
*/
public <T> Lookup.Result<T> lookupResult(Class<T> clazz) {
return lookup(new Lookup.Template<T>(clazz));
}
/**
* Find all instances corresponding to a given class.
* Equivalent to calling {@link #lookupResult} and asking for {@link Lookup.Result#allInstances} but slightly more convenient.
* Subclasses may override this method to produce the same semantics more efficiently.
* <p>Example usage:</p>
* {@codesnippet org.openide.util.lookup.SampleLookupUsages#iterate}
* @param clazz the supertype of the result
* @return all currently available instances of that type
* @since org.openide.util 6.10
*/
public <T> Collection<? extends T> lookupAll(Class<T> clazz) {
return lookupResult(clazz).allInstances();
}
/**
* Objects implementing interface Lookup.Provider are capable of
* and willing to provide a lookup (usually bound to the object).
* @since 3.6
*/
public interface Provider {
/**
* Returns lookup associated with the object.
* @return fully initialized lookup instance provided by this object
*/
Lookup getLookup();
}
/*
* I expect this class to grow in the future, but for now, it is
* enough to start with something simple.
*/
/** Template defining a pattern to filter instances by.
*/
public static final class Template<T> extends Object {
/** cached hash code */
private int hashCode;
/** type of the service */
private Class<T> type;
/** identity to search for */
private String id;
/** instance to search for */
private T instance;
/** General template to find all possible instances.
* @deprecated Use <code>new Template (Object.class)</code> which
* is going to be better typed with JDK1.5 templates and should produce
* the same result.
*/
@Deprecated
public Template() {
this(null);
}
/** Create a simple template matching by class.
* @param type the class of service we are looking for (subclasses will match)
*/
public Template(Class<T> type) {
this(type, null, null);
}
/** Constructor to create new template.
* @param type the class of service we are looking for or <code>null</code> to leave unspecified
* @param id the ID of the item/service we are looking for or <code>null</code> to leave unspecified
* @param instance a specific known instance to look for or <code>null</code> to leave unspecified
*/
public Template(Class<T> type, String id, T instance) {
this.type = extractType(type);
this.id = id;
this.instance = instance;
}
@SuppressWarnings("unchecked")
private Class<T> extractType(Class<T> type) {
return (type == null) ? (Class<T>)Object.class : type;
}
/** Get the class (or superclass or interface) to search for.
* If it was not specified in the constructor, <code>Object</code> is used as
* this will match any instance.
* @return the class to search for
*/
public Class<T> getType() {
return type;
}
/** Get the persistent identifier being searched for, if any.
* @return the ID or <code>null</code>
* @see Lookup.Item#getId
*
* @since 1.8
*/
public String getId() {
return id;
}
/** Get the specific instance being searched for, if any.
* Most useful for finding an <code>Item</code> when the instance
* is already known.
*
* @return the object to find or <code>null</code>
*
* @since 1.8
*/
public T getInstance() {
return instance;
}
/* Computes hashcode for this template. The hashcode is cached.
* @return hashcode
*/
@Override
public int hashCode() {
if (hashCode != 0) {
return hashCode;
}
hashCode = ((type == null) ? 1 : type.hashCode()) + ((id == null) ? 2 : id.hashCode()) +
((instance == null) ? 3 : 0);
return hashCode;
}
/* Checks whether two templates represent the same query.
* @param obj another template to check
* @return true if so, false otherwise
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Template)) {
return false;
}
Template t = (Template) obj;
if (hashCode() != t.hashCode()) {
// this is an optimalization - the hashCodes should have been
// precomputed
return false;
}
if (type != t.type) {
return false;
}
if (id == null) {
if (t.id != null) {
return false;
}
} else {
if (!id.equals(t.id)) {
return false;
}
}
if (instance == null) {
return (t.instance == null);
} else {
return instance.equals(t.instance);
}
}
/* for debugging */
@Override
public String toString() {
return "Lookup.Template[type=" + type + ",id=" + id + ",instance=" + instance + "]"; // NOI18N
}
}
/** Result of a lookup request.
* Allows access to all matching instances at once.
* Also permits listening to changes in the result.
* Result can contain duplicate items.
*/
public abstract static class Result<T> extends Object {
/** Registers a listener that is invoked when there is a possible
* change in this result.
* <p>
* <div class="nonnormative">
* Sometimes people report that their listener is not receiving
* events (for example <a href="https://netbeans.org/bugzilla/show_bug.cgi?id=191471">IZ 191471</a>)
* or that the listener receives few events, but then it <em>stops</em>
* listening.
* Such behavior is often caused by not keeping strong reference to
* the {@link Result} object. When it gets garbage collected
* it can no longer deliver events. Thus remember to keep reference
* to the object you are attaching listener to.
* </div>
*
* @param l the listener to add
*/
public abstract void addLookupListener(LookupListener l);
/** Unregisters a listener previously added.
* @param l the listener to remove
*/
public abstract void removeLookupListener(LookupListener l);
/** Get all instances in the result. The return value is an
* unmodifiable list (hence the type
* should be {@link List} as the order matters, but the {@link Collection}
* is kept for compatibility reasons) of all instances present in
* the {@link Result} right now that will never change its content.
* <p></p>
* <div class="nonnormative">
* While the returned collection never changes its content, some
* implementation like {@link ProxyLookup} may
* <a href="@TOP@/apichanges.html#lazy.proxy.lookup">compute the content
* lazily</a>. At least
* <a href="https://github.com/apache/netbeans/pull/1739">once</a>
* such behavior resulted in a deadlock.
* </div>
* @return unmodifiable collection of all instances that will never change its content
*/
public abstract Collection<? extends T> allInstances();
/** Get all classes represented in the result.
* That is, the set of concrete classes
* used by instances present in the result.
* All duplicate classes will be omitted.
* @return unmodifiable set of <code>Class</code> objects that will never change its content
*
* @since 1.8
*/
public Set<Class<? extends T>> allClasses() {
return Collections.emptySet();
}
/** Get all registered items.
* This includes all pairs of instances together
* with their classes, {@link Item#getId() IDs}, and so on.
* The return value is an unmodifiable list (hence the type
* should be {@link List} as the order matters, but the {@link Collection}
* is kept for compatibility reasons) of all {@link Item items} present in
* the {@link Result} right now that will never change its content.
* <p></p>
* <div class="nonnormative">
* While the returned collection never changes its content, some
* implementation like {@link ProxyLookup} may
* <a href="@TOP@/apichanges.html#lazy.proxy.lookup">compute the content
* lazily</a>.
* </div>
*
* @return unmodifiable collection of {@link Lookup.Item} that will never change its content
*
* @since 1.8
*/
public Collection<? extends Item<T>> allItems() {
return Collections.emptyList();
}
}
/** A single item in a lookup result.
* This wrapper provides unified access to not just the instance,
* but its class, a possible persistent identifier, and so on.
*
* @since 1.25
*/
public abstract static class Item<T> extends Object {
/** Get the instance itself.
* @return the instance or null if the instance cannot be created
*/
public abstract T getInstance();
/** Get the implementing class of the instance.
* @return the class of the item
*/
public abstract Class<? extends T> getType();
// XXX can it be null??
/** Get a persistent identifier for the item.
* This identifier should uniquely represent the item
* within its containing lookup (and if possible within the
* global lookup as a whole). For example, it might represent
* the source of the instance as a file name. The ID may be
* persisted and in a later session used to find the same instance
* as was encountered earlier, by means of passing it into a
* lookup template.
*
* @return a string ID of the item
*/
public abstract String getId();
/** Get a human presentable name for the item.
* This might be used when summarizing all the items found in a
* lookup result in some part of a GUI.
* @return the string suitable for presenting the object to a user
*/
public abstract String getDisplayName();
/* show ID for debugging */
@Override
public String toString() {
return getId();
}
}
//
// Implementation of the default lookup
//
private static final class Empty extends Lookup {
private static final Result NO_RESULT = new Result() {
public void addLookupListener(LookupListener l) {
}
public void removeLookupListener(LookupListener l) {
}
public Collection allInstances() {
return Collections.EMPTY_SET;
}
};
Empty() {
}
public <T> T lookup(Class<T> clazz) {
return null;
}
@SuppressWarnings("unchecked")
public <T> Result<T> lookup(Template<T> template) {
return NO_RESULT;
}
}
}