/*
* Copyright (c) 2010, SQL Power Group Inc.
*
* This file is part of SQL Power Library.
*
* SQL Power Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* SQL Power Library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ca.sqlpower.object;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.apache.commons.collections.map.MultiValueMap;
import org.apache.log4j.Logger;
/**
* This class keeps track of {@link SPVariableResolver}s that can resolve
* namespaces and register to {@link SPObject} root objects.
*/
public class SPResolverRegistry {
private static final Logger logger = Logger.getLogger(SPResolverRegistry.class);
/**
* Maps a root SPObject to a list of resolvers.
*/
private static Map<String, List<SPVariableResolver>> resolvers = Collections.synchronizedMap(new WeakHashMap<String, List<SPVariableResolver>>());
private static Map<String, TreeListener> listeners = Collections.synchronizedMap(new WeakHashMap<String, TreeListener>());
private SPResolverRegistry() {
// Everything in this class is static.
// No need to create instances of it.
}
/**
* Returns the root {@link SPObject} given a descendant {@link SPObject}.
*
* @param treeElement
* The descendant {@link SPObject}.
* @return The root {@link SPObject}, or null if the descendant
* {@link SPObject} is null.
*/
private static SPObject getRoot(SPObject treeElement) {
if (treeElement == null) {
return null;
}
while (true) {
if (treeElement.getParent() == null) {
return treeElement;
} else {
treeElement = treeElement.getParent();
}
}
}
/**
* Initializes the root of an {@link SPObject} in the resolver and listener
* {@link Map}s by creating a new key (if it does not exist) by UUID, and
* adds a tree listener to that root {@link SPObject}.
*
* @param treeElement
* The {@link SPObject} whose root object is to be initialized.
* @return The root {@link SPObject}, or null if the passed in
* {@link SPObject} is null.
*/
public static SPObject init(SPObject treeElement) {
if (treeElement == null) {
return null;
}
synchronized (resolvers) {
SPObject root = getRoot(treeElement);
// No need to init this tree twice.
if (!resolvers.containsKey(root.getUUID())) {
// Init this placeholder with an empty list
resolvers.put(root.getUUID(), Collections.synchronizedList(new ArrayList<SPVariableResolver>()));
listeners.put(root.getUUID(), new TreeListener());
// Now listen to the hierarchy for UUID change
root.addSPListener(listeners.get(root.getUUID()));
}
return root;
}
}
/**
* Registers an {@link SPVariableResolver} for the root of a given
* {@link SPObject} if it has not already been registered.
*
* @param treeMember
* The {@link SPObject} whose root object the resolver should be
* registered to.
* @param resolver
* The {@link SPVariableResolver} to register.
*/
public static void register(SPObject treeMember, SPVariableResolver resolver) {
if (treeMember != null && resolver != null) {
synchronized (resolvers) {
SPObject root = init(treeMember);
if (!resolvers.get(root.getUUID()).contains(resolver)) {
logger.debug("Registering resolver - Namespace:" + resolver.getNamespace() + " bound to node:" + treeMember.getName());
resolvers.get(root.getUUID()).add(resolver);
}
}
}
}
/**
* Deregisters an {@link SPVariableResolver} from the root of a given
* {@link SPObject}.
*
* @param treeMember
* The {@link SPObject} whose root object the resolver should be
* deregistered from.
* @param resolver
* The {@link SPVariableResolver} to deregister.
*/
public static void deregister(SPObject treeMember, SPVariableResolver resolver) {
if (treeMember != null && resolver != null) {
synchronized (resolvers) {
SPObject root = init(treeMember);
logger.debug("Deregistering resolver - Namespace:" + resolver.getNamespace() + " bound to node:" + treeMember.getName());
resolvers.get(root.getUUID()).remove(resolver);
}
}
}
/**
* Creates a {@link List} of registered {@link SPVariableResolver}s that
* resolves a given namespace for the root of a given {@link SPObject}.
*
* @param treeMember
* The {@link SPObject} whose root to get the resolvers from.
* @param namespace
* The namespace that the resolvers should resolve.
* @return The created {@link List}.
*/
public static List<SPVariableResolver> getResolvers(SPObject treeMember, String namespace) {
if (treeMember == null) {
return Collections.emptyList();
}
synchronized (resolvers) {
SPObject root = init(treeMember);
if (root != null) {
List<SPVariableResolver> registeredResolvers = resolvers.get(root.getUUID());
List<SPVariableResolver> matches = new ArrayList<SPVariableResolver>();
for (SPVariableResolver resolver: registeredResolvers) {
if (resolver.resolvesNamespace(namespace)) {
matches.add(resolver);
}
}
return matches;
} else {
return Collections.emptyList();
}
}
}
/**
* Finds the first {@link SPVariableResolver} that resolves a given
* namespace for the root a given {@link SPObject}.
*
* @param treeMember
* The {@link SPObject} whose root to get the resolver from.
* @param namespace
* The namespace that the resolver should resolve.
* @return The first {@link SPVariableResolver} found.
*/
public static SPVariableResolver getResolver(SPObject treeMember, String namespace) {
if (treeMember == null) {
return null;
}
synchronized (resolvers) {
SPObject root = init(treeMember);
for (SPVariableResolver resolver: resolvers.get(root.getUUID())) {
if (resolver.resolvesNamespace(namespace)) {
return resolver;
}
}
return null;
}
}
/**
* Creates a {@link MultiValueMap} of {@link SPVariableResolver} user
* friendly names to their respective namespace.
*
* @param treeMember
* The {@link SPObject} whose root to get the namespaces from.
* @return The created {@link MultiValueMap}.
*/
public static MultiValueMap getNamespaces(SPObject treeMember) {
if (treeMember == null) {
return new MultiValueMap();
}
synchronized (resolvers) {
MultiValueMap results = new MultiValueMap();
SPObject root = init(treeMember);
for (SPVariableResolver resolver: resolvers.get(root.getUUID())) {
results.put(resolver.getUserFriendlyName(), resolver.getNamespace());
}
return results;
}
}
/**
* This {@link SPListener} listens to {@link SPObject}s whose UUID has
* changed and has {@link SPVariableResolver}s registered to that object. It
* updates the {@link Map}s of {@link SPResolverRegistry#resolvers} and
* {@link SPResolverRegistry#listeners} to this new UUID.
*/
private static class TreeListener extends AbstractPoolingSPListener {
protected void propertyChangeImpl(PropertyChangeEvent e) {
synchronized (resolvers) {
if (e.getPropertyName().equalsIgnoreCase("uuid")
&& resolvers.containsKey(e.getOldValue())) {
// This means that the root object has changed it's UUID.
// Update the maps accordingly.
resolvers.put((String)e.getNewValue(), resolvers.get(e.getOldValue()));
listeners.put((String)e.getNewValue(), listeners.get(e.getOldValue()));
resolvers.remove(e.getOldValue());
listeners.remove(e.getOldValue());
}
}
}
}
}