/*
* Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
* subject to license terms.
*/
package org.jdesktop.beansbinding.ext;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.beans.*;
public final class BeanAdapterFactory {
private static final BeanAdapterFactory INSTANCE = new BeanAdapterFactory();
private final Map<Object, List<VendedAdapter>> vendedAdapters;
private final List<BeanAdapterProvider> providers;
private final Set<ClassLoader> classLoaders;
private final Set<URL> serviceURLs;
public static Object getAdapter(Object source, String property) {
return INSTANCE.getAdapter0(source, property);
}
public static List<PropertyDescriptor> getAdapterPropertyDescriptors(Class<?> type) {
return INSTANCE.getAdapterPropertyDescriptors0(type);
}
public BeanAdapterFactory() {
this.providers = new ArrayList<BeanAdapterProvider>();
classLoaders = new HashSet<ClassLoader>();
serviceURLs = new HashSet<URL>();
vendedAdapters = new WeakHashMap<Object, List<VendedAdapter>>();
}
private void loadProvidersIfNecessary() {
ClassLoader currentLoader = Thread.currentThread().getContextClassLoader();
if (!classLoaders.contains(currentLoader)) {
classLoaders.add(currentLoader);
loadProviders(currentLoader);
}
}
private void loadProviders(ClassLoader classLoader) {
// PENDING: this needs to be rewriten in terms of ServiceLoader
String serviceName = "META-INF/services/" +
BeanAdapterProvider.class.getName();
try {
Enumeration<URL> urls = classLoader.getResources(serviceName);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
if (!serviceURLs.contains(url)) {
serviceURLs.add(url);
addProviders(url);
}
}
} catch (IOException ex) {
}
}
private void addProviders(URL url) {
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = url.openStream();
reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
String line;
while ((line = reader.readLine()) != null) {
try {
providers.add((BeanAdapterProvider)Class.forName(line).newInstance());
} catch (IllegalAccessException ex) {
} catch (InstantiationException ex) {
} catch (ClassNotFoundException ex) {
}
}
} catch (UnsupportedEncodingException ex) {
} catch (IOException ex) {
}
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
}
}
}
public Object getAdapter0(Object source, String property) {
if (source == null || property == null) {
throw new IllegalArgumentException();
}
loadProvidersIfNecessary();
property = property.intern();
BeanAdapterProvider provider = getProvider(source, property);
if (provider != null) {
List<VendedAdapter> adapters = vendedAdapters.get(source);
if (adapters != null) {
for (int i = adapters.size() - 1; i >= 0; i--) {
VendedAdapter vendedAdapter = adapters.get(i);
Object adapter = vendedAdapter.getAdapter();
if (adapter == null) {
vendedAdapters.remove(i);
} else if (vendedAdapter.getProvider() == provider && vendedAdapter.getProperty() == property) {
return adapter;
}
}
} else {
adapters = new ArrayList<VendedAdapter>(1);
vendedAdapters.put(source, adapters);
}
Object adapter = provider.createAdapter(source, property);
adapters.add(new VendedAdapter(property, provider, adapter));
return adapter;
}
return null;
}
private BeanAdapterProvider getProvider(Object source, String property) {
Class<?> type = source.getClass();
for (BeanAdapterProvider provider : providers) {
if (provider.providesAdapter(type, property)) {
return provider;
}
}
return null;
}
private List<FeatureDescriptor> getDescriptors(Class<?> type) {
BeanInfo info = null;
try {
info = Introspector.getBeanInfo(type);
} catch (Exception ex) {
}
if (info == null) {
return Collections.emptyList();
}
ArrayList<FeatureDescriptor> list = new ArrayList<FeatureDescriptor>(
info.getPropertyDescriptors().length);
for (PropertyDescriptor pd: info.getPropertyDescriptors()) {
// PENDING: The following properties come from EL, are they
// needed?
if (pd.getPropertyType() != null) {
pd.setValue("type", pd.getPropertyType());
}
pd.setValue("resolvableAtDesignTime", Boolean.TRUE);
list.add(pd);
}
return list;
}
private static BeanInfo getBeanInfo(Class<?> type) {
try {
return Introspector.getBeanInfo(type);
} catch (IntrospectionException ie) {
return null;
}
}
private List<PropertyDescriptor> getAdapterPropertyDescriptors0(Class<?> type) {
if (type == null) {
throw new IllegalArgumentException("Type must be non-null");
}
loadProvidersIfNecessary();
ArrayList<PropertyDescriptor> des = new ArrayList<PropertyDescriptor>();
for (BeanAdapterProvider provider : providers) {
Class<?> pdType = provider.getAdapterClass(type);
if (pdType != null) {
BeanInfo info = getBeanInfo(pdType);
if (info != null) {
PropertyDescriptor[] pds = info.getPropertyDescriptors();
if (pds != null) {
for (PropertyDescriptor pd : pds) {
if (provider.providesAdapter(type, pd.getName())) {
des.add(pd);
}
}
}
}
}
}
return des;
}
private static final class VendedAdapter {
private final BeanAdapterProvider provider;
private final String property;
private final WeakReference<Object> adapter;
public VendedAdapter(String property, BeanAdapterProvider provider, Object adapter) {
this.property = property;
this.adapter = new WeakReference<Object>(adapter);
this.provider = provider;
}
public Object getAdapter() {
return adapter.get();
}
public String getProperty() {
return property;
}
public BeanAdapterProvider getProvider() {
return provider;
}
}
}