org.openide.util.lookup.MetaInfServicesLookup Maven / Gradle / Ivy
/*
* 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.lookup;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openide.util.Lookup;
import org.openide.util.lookup.implspi.SharedClassObjectBridge;
/**
* @author Jaroslav Tulach, Jesse Glick
* @see Lookups#metaInfServices(ClassLoader,String)
* @see "#14722"
*/
final class MetaInfServicesLookup extends AbstractLookup {
static final Logger LOGGER = Logger.getLogger(MetaInfServicesLookup.class.getName());
private static final MetaInfCache CACHE = new MetaInfCache(512);
private static Reference RP = new WeakReference(null);
static synchronized Executor getRP() {
Executor res = RP.get();
if (res == null) {
try {
Class> seek = Class.forName("org.openide.util.RequestProcessor");
res = (Executor)seek.newInstance();
} catch (Throwable t) {
try {
res = Executors.newSingleThreadExecutor();
} catch (Throwable t2) {
res = new Executor() {
@Override
public void execute(Runnable command) {
command.run();
}
};
}
}
RP = new SoftReference(res);
}
return res;
}
/** A set of all requested classes.
* Note that classes that we actually succeeded on can never be removed
* from here because we hold a strong reference to the loader.
* However we also hold classes which are definitely not loadable by
* our loader.
*/
private final Map,Object> classes = new WeakHashMap,Object>();
/** class loader to use */
private final ClassLoader loader;
/** prefix to prepend */
private final String prefix;
/** Create a lookup reading from a specified classloader.
*/
@SuppressWarnings("LeakingThisInConstructor")
public MetaInfServicesLookup(ClassLoader loader, String prefix) {
this.loader = loader;
this.prefix = prefix;
LOGGER.log(Level.FINE, "Created: {0}", this);
}
@Override
public String toString() {
return "MetaInfServicesLookup[" + loader + "]"; // NOI18N
}
/** Initialize soon, before result's listeners are activated
*/
@Override
void beforeLookupResult(Template> template) {
beforeLookup(template);
}
/* Tries to load appropriate resources from manifest files.
*/
@Override
protected final void beforeLookup(Lookup.Template> t) {
Class> c = t.getType();
Collection> toAdd = null;
synchronized (this) {
if (classes.get(c) == null) { // NOI18N
toAdd = new ArrayList>();
} else {
// ok, nothing needs to be done
return;
}
}
if (toAdd != null) {
Set> all = new HashSet>();
for (Class type : allSuper(c, all)) {
search(type, toAdd);
}
}
HashSet listeners = null;
synchronized (this) {
if (classes.put(c, "") == null) { // NOI18N
// Added new class, search for it.
LinkedHashSet> lhs = getPairsAsLHS();
List- arr = new ArrayList
- ();
for (Pair> lh : lhs) {
arr.add((Item)lh);
}
for (Pair> p : toAdd) {
insertItem((Item) p, arr);
}
listeners = setPairsAndCollectListeners(arr);
}
}
if (listeners != null) {
notifyIn(getRP(), listeners);
}
}
private Set
> allSuper(Class> clazz, Set> all) {
all.add(clazz);
Class> sup = clazz.getSuperclass();
if (sup != null && sup != Object.class) {
all.add(sup);
}
for (Class> c : clazz.getInterfaces()) {
allSuper(c, all);
}
return all;
}
/** Finds all pairs and adds them to the collection.
*
* @param clazz class to find
* @param result collection to add Pair to
*/
private void search(Class> clazz, Collection> result) {
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.log(Level.FINER, "Searching for {0} in {1} from {2}", new Object[] {clazz.getName(), clazz.getClassLoader(), this});
}
String res = prefix + clazz.getName(); // NOI18N
Enumeration en;
try {
en = loader.getResources(res);
} catch (IOException ioe) {
// do not use ErrorManager because we are in the startup code
// and ErrorManager might not be ready
ioe.printStackTrace();
return;
}
// Do not create multiple instances in case more than one JAR
// has the same entry in it (and they load to the same class).
// Probably would not happen, assuming JARs only list classes
// they own, but just in case...
List- foundClasses = new ArrayList
- ();
Collection
> removeClasses = new ArrayList>();
boolean foundOne = false;
while (en.hasMoreElements()) {
if (!foundOne) {
foundOne = true;
// Double-check that in fact we can load the *interface* class.
// For example, say class I is defined in two JARs, J1 and J2.
// There is also an implementation M1 defined in J1, and another
// implementation M2 defined in J2.
// Classloaders C1 and C2 are made from J1 and J2.
// A MetaInfServicesLookup is made from C1. Then the user asks to
// lookup I as loaded from C2. J1 has the services line and lists
// M1, and we can in fact make it. However it is not of the desired
// type to be looked up. Don't do this check, which could be expensive,
// unless we expect to be getting some results, however.
Class> realMcCoy = null;
try {
realMcCoy = loader.loadClass(clazz.getName());
} catch (ClassNotFoundException cnfe) {
// our loader does not know about it, OK
}
if (realMcCoy != clazz) {
// Either the interface class is not available at all in our loader,
// or it is not the same version as we expected. Don't provide results.
if (realMcCoy != null) {
LOGGER.log(Level.WARNING, "{0} is not the real McCoy! Actually found it in {1} (from {2}) but searched for from {3}",
new Object[] {clazz.getName(), realMcCoy.getClassLoader(), loader, clazz.getClassLoader()}); // NOI18N
} else {
LOGGER.log(Level.WARNING, "{0} could not be found in {1}", new Object[] {clazz.getName(), loader}); // NOI18N
}
return;
}
}
URL url = en.nextElement();
Item currentItem = null;
try {
InputStream is = url.openStream();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
// XXX consider using ServiceLoaderLine instead
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
line = line.trim();
// is it position attribute?
if (line.startsWith("#position=")) {
if (currentItem == null) {
LOGGER.log(Level.INFO, "Found line ''{0}'' in {1} but there is no item to associate it with", new Object[] {line, url});
continue;
}
try {
currentItem.position = Integer.parseInt(line.substring(10));
} catch (NumberFormatException e) {
// do not use ErrorManager because we are in the startup code
// and ErrorManager might not be ready
e.printStackTrace();
}
}
if (currentItem != null) {
insertItem(currentItem, foundClasses);
currentItem = null;
}
// Ignore blank lines and comments.
if (line.length() == 0) {
continue;
}
boolean remove = false;
if (line.charAt(0) == '#') {
if ((line.length() == 1) || (line.charAt(1) != '-')) {
continue;
}
// line starting with #- is a sign to remove that class from lookup
remove = true;
line = line.substring(2);
}
Class> inst = null;
try {
Object ldr = url.getContent(new Class[] { ClassLoader.class });
if (ldr instanceof ClassLoader) {
inst = Class.forName(line, false, (ClassLoader)ldr);
}
} catch (LinkageError err) {
// go on
} catch (ClassNotFoundException ex) {
LOGGER.log(Level.FINER, "No class found in " + url, ex);
} catch (IOException ex) {
LOGGER.log(Level.FINER, "URL does not support classloader protocol " + url, ex);
}
if (inst == null) try {
// Most lines are fully-qualified class names.
inst = Class.forName(line, false, loader);
} catch (LinkageError err) {
if (remove) {
continue;
}
throw new ClassNotFoundException(err.getMessage(), err);
} catch (ClassNotFoundException cnfe) {
if (remove) {
// if we are removing somthing and the something
// cannot be found it is ok to do nothing
continue;
} else {
// but if we are not removing just rethrow
throw cnfe;
}
}
if (!clazz.isAssignableFrom(inst)) {
throw new ClassNotFoundException(clazzToString(inst) + " not a subclass of " + clazzToString(clazz)); // NOI18N
}
if (remove) {
removeClasses.add(inst);
} else {
// create new item here, but do not put it into
// foundClasses array yet because following line
// might specify its position
currentItem = new Item(inst);
}
}
if (currentItem != null) {
insertItem(currentItem, foundClasses);
currentItem = null;
}
} finally {
is.close();
}
} catch (ClassNotFoundException ex) {
LOGGER.log(Level.INFO, null, ex);
} catch (IOException ex) {
LOGGER.log(Level.INFO, null, ex);
}
}
LOGGER.log(Level.FINER, "Found impls of {0}: {1} and removed: {2} from: {3}", new Object[] {clazz.getName(), foundClasses, removeClasses, this});
/* XXX makes no sense, wrong types:
foundClasses.removeAll(removeClasses);
*/
for (Item item : foundClasses) {
if (removeClasses.contains(item.clazz())) {
continue;
}
result.add(item);
}
}
private static String clazzToString(Class> clazz) {
String loc = null;
try {
if (clazz.getProtectionDomain() != null && clazz.getProtectionDomain().getCodeSource() != null) {
loc = clazz.getProtectionDomain().getCodeSource().getLocation().toString();
}
} catch (Throwable ex) {
loc = ex.getMessage();
}
return clazz.getName() + "@" + clazz.getClassLoader() + ":" + loc; // NOI18N
}
/**
* Insert item to the list according to item.position value.
*/
private void insertItem(Item item, List- list) {
// no position? -> add it to the end
if (item.position == -1) {
if (!list.contains(item)) {
list.add(item);
}
return;
}
int foundIndex = -1;
int index = -1;
for (Item i : list) {
if (i.equals(item)) {
return;
}
index++;
if (foundIndex < 0) {
if (i.position == -1 || i.position > item.position) {
foundIndex = index;
}
}
}
if (foundIndex < 0) {
list.add(item); // add to the end
} else {
list.add(foundIndex, item); // insert at found index
}
}
static Item createPair(Class> clazz) {
return new Item(clazz);
}
/** Pair that holds name of a class and maybe the instance.
*/
private static final class Item extends AbstractLookup.Pair
© 2015 - 2024 Weber Informatics LLC | Privacy Policy