package net.i2p.client.naming;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.CopyOnWriteArrayList;
import net.i2p.I2PAppContext;
import net.i2p.data.Destination;
/**
* A naming service of multiple naming services.
* Supports .b32.i2p and {b64} lookups.
* Supports caching.
*/
public class MetaNamingService extends DummyNamingService {
private final static String PROP_NAME_SERVICES = "i2p.nameservicelist";
private final static String DEFAULT_NAME_SERVICES =
"net.i2p.client.naming.HostsTxtNamingService";
protected final List<NamingService> _services;
/**
* Adds the services from the i2p.nameservicelist property, in order, as chained services.
*/
public MetaNamingService(I2PAppContext context) {
super(context);
String list = _context.getProperty(PROP_NAME_SERVICES, DEFAULT_NAME_SERVICES);
StringTokenizer tok = new StringTokenizer(list, ",");
_services = new CopyOnWriteArrayList<NamingService>();
while (tok.hasMoreTokens()) {
try {
Class<?> cls = Class.forName(tok.nextToken());
Constructor<?> con = cls.getConstructor(I2PAppContext.class);
addNamingService((NamingService)con.newInstance(), false);
} catch (Exception ex) {
}
}
}
/**
* @param services if non-null, services to be added. If null, this will only handle b32 and b64,
* until addNamingService() is called later.
* @since 0.8.7
*/
public MetaNamingService(I2PAppContext context, List<NamingService> services) {
super(context);
_services = new CopyOnWriteArrayList<NamingService>();
if (services != null) {
for (NamingService ns : services) {
addNamingService(ns, false);
}
}
}
@Override
public boolean addNamingService(NamingService ns, boolean head) {
if (head)
_services.add(0, ns);
else
_services.add(ns);
return true;
}
@Override
public List<NamingService> getNamingServices() {
return Collections.unmodifiableList(_services);
}
@Override
public boolean removeNamingService(NamingService ns) {
return _services.remove(ns);
}
@Override
public void registerListener(NamingServiceListener nsl) {
for (NamingService ns : _services) {
ns.registerListener(nsl);
}
}
@Override
public void unregisterListener(NamingServiceListener nsl) {
for (NamingService ns : _services) {
ns.unregisterListener(nsl);
}
}
@Override
public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) {
// cache check is in super()
Destination d = super.lookup(hostname, null, null);
if (d != null)
return d;
// Base32 failed?
if (hostname.length() == BASE32_HASH_LENGTH + 8 && hostname.toLowerCase(Locale.US).endsWith(".b32.i2p"))
return null;
for (NamingService ns : _services) {
d = ns.lookup(hostname, lookupOptions, storedOptions);
if (d != null) {
putCache(hostname, d);
return d;
}
}
return null;
}
@Override
public String reverseLookup(Destination dest, Properties options) {
for (NamingService ns : _services) {
String host = ns.reverseLookup(dest, options);
if (host != null) {
return host;
}
}
return null;
}
/**
* Stores in the last service
*/
@Override
public boolean put(String hostname, Destination d, Properties options) {
if (_services.isEmpty())
return false;
boolean rv = _services.get(_services.size() - 1).put(hostname, d, options);
// overwrite any previous entry in case it changed
if (rv)
putCache(hostname, d);
return rv;
}
/**
* Stores in the last service
*/
@Override
public boolean putIfAbsent(String hostname, Destination d, Properties options) {
if (_services.isEmpty())
return false;
boolean rv = _services.get(_services.size() - 1).putIfAbsent(hostname, d, options);
if (rv)
putCache(hostname, d);
return rv;
}
/**
* Removes from all services
*/
@Override
public boolean remove(String hostname, Properties options) {
boolean rv = false;
for (NamingService ns : _services) {
if (ns.remove(hostname, options))
rv = true;
}
if (rv)
removeCache(hostname);
return rv;
}
/**
* All services aggregated
*/
@Override
public Map<String, Destination> getEntries(Properties options) {
Map<String, Destination> rv = new HashMap<String, Destination>();
for (NamingService ns : _services) {
rv.putAll(ns.getEntries(options));
}
return rv;
}
/**
* All services aggregated
* @since 0.9.20
*/
@Override
public Map<String, String> getBase64Entries(Properties options) {
Map<String, String> rv = new HashMap<String, String>();
for (NamingService ns : _services) {
rv.putAll(ns.getBase64Entries(options));
}
return rv;
}
/**
* All services aggregated
*/
@Override
public Set<String> getNames(Properties options) {
Set<String> rv = new HashSet<String>();
for (NamingService ns : _services) {
rv.addAll(ns.getNames(options));
}
return rv;
}
/**
* All services aggregated.
* Duplicates not removed (for efficiency)
* @since 0.9.20
*/
public void export(Writer out, Properties options) throws IOException {
for (NamingService ns : _services) {
export(out, options);
}
}
/**
* All services aggregated
*/
@Override
public int size(Properties options) {
int rv = 0;
for (NamingService ns : _services) {
int s = ns.size(options);
if (s > 0)
rv += s;
}
return rv;
}
public void shutdown() {
for (NamingService ns : _services) {
ns.shutdown();
}
}
}