/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* This program 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.
* This program 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 com.bc.ceres.core.runtime.internal;
import com.bc.ceres.core.Assert;
import com.bc.ceres.core.CoreException;
import com.bc.ceres.core.runtime.Extension;
import com.bc.ceres.core.runtime.ExtensionPoint;
import com.bc.ceres.core.runtime.Module;
import java.net.URL;
import java.text.MessageFormat;
import java.util.*;
/**
* A registry for {@link ModuleImpl}s and their extension points.
* It defines a context for resolving module dependencies.
*/
public class ModuleRegistry {
private Map<Long, ModuleImpl> idToModuleMap;
private Map<URL, ModuleImpl> locationToModuleMap;
private Map<String, Object> symbolicNameToModulesMap; // maps to either a ModuleImpl or a List<ModuleImpl>
private Map<String, ExtensionPoint> extensionPointMap;
/**
* Construct a new module registry.
*/
public ModuleRegistry() {
this.idToModuleMap = new HashMap<Long, ModuleImpl>(33);
this.locationToModuleMap = new HashMap<URL, ModuleImpl>(33);
this.symbolicNameToModulesMap = new HashMap<String, Object>(33);
this.extensionPointMap = new HashMap<String, ExtensionPoint>(33);
}
/**
* Registers the given module with this registry.
*
* @param module the module to register
*/
public void registerModule(ModuleImpl module) throws CoreException {
Assert.notNull(module, "module");
Assert.notNull(module.getSymbolicName(), "module.getId()");
Assert.notNull(module.getLocation(), "module.getLocation()");
// Make sure that ID is not already used
if (idToModuleMap.containsKey(module.getModuleId())) {
throw new CoreException(MessageFormat.format("Duplicate module identifier [{0}]", module.getModuleId()));
}
// Make sure that symbolicName AND version does not already exist
if (symbolicNameToModulesMap.containsKey(module.getSymbolicName())) {
ModuleImpl[] modules = getModules(module.getSymbolicName());
for (ModuleImpl mod : modules) {
if (mod.getVersion().equals(module.getVersion())) {
throw new CoreException(
MessageFormat.format("Module with symbolic name [{0}] and version [{1}] already registered",
module.getSymbolicName(), module.getVersion()));
}
}
}
// Make sure that loocation is not already used
if (locationToModuleMap.containsKey(module.getLocation())) {
throw new CoreException(MessageFormat.format("Duplicate module location [{0}]", module.getLocation()));
}
// Make sure that extension points are not registered twice within same module
// Note that it is possible that a module with same symbolicName but different version (module update)
// may want to register existing extension points. This will not lead to an exception here, but such
// extension points are not registered.
ExtensionPoint[] extensionPoints = module.getExtensionPoints();
for (ExtensionPoint extensionPoint : extensionPoints) {
ExtensionPoint oldExtensionPoint = extensionPointMap.get(extensionPoint.getQualifiedId());
if (oldExtensionPoint != null && oldExtensionPoint.getDeclaringModule() == module) {
throw new CoreException(String.format("Module [%s]: Duplicate extension point identifier [%s]",
module.getSymbolicName(), extensionPoint.getId()));
}
}
registerModuleId(module);
registerLocation(module);
registerSymbolicName(module);
module.setRegistry(this);
for (ExtensionPoint extensionPoint : extensionPoints) {
ExtensionPoint oldExtensionPoint = extensionPointMap.get(extensionPoint.getQualifiedId());
// Only register extension point if not already done, see comment above.
if (oldExtensionPoint == null) {
extensionPointMap.put(extensionPoint.getQualifiedId(), extensionPoint);
}
}
}
/**
* Gets the module for the given module identifier.
*
* @param moduleId the module identifier
* @return the module or <code>null</code> if not found
*/
public ModuleImpl getModule(long moduleId) {
Assert.argument(moduleId >= 0, "moduleId >= 0");
return idToModuleMap.get(moduleId);
}
/**
* Gets the module for the given module identifier.
*
* @param url the module location
* @return the module or <code>null</code> if not found
*/
public ModuleImpl getModule(URL url) {
Assert.notNull(url, "url");
return locationToModuleMap.get(url);
}
/**
* Gets the module for the given module identifier.
*
* @param symbolicName the module's symbolic name
* @return the module or <code>null</code> if not found
*/
public ModuleImpl[] getModules(String symbolicName) {
Assert.notNull(symbolicName, "symbolicName");
Object o = symbolicNameToModulesMap.get(symbolicName);
if (o == null) {
return new ModuleImpl[0];
} else if (o instanceof ModuleImpl) {
ModuleImpl module = (ModuleImpl) o;
return new ModuleImpl[]{module};
} else if (o instanceof List) {
List<ModuleImpl> modules = (List<ModuleImpl>) o;
return modules.toArray(ModuleImpl.EMPTY_ARRAY);
} else {
throw new IllegalStateException();
}
}
/**
* Gets all modules registered so far.
*
* @return the modules registered so far.
*/
public ModuleImpl[] getModules() {
Collection<ModuleImpl> modules = idToModuleMap.values();
return modules.toArray(ModuleImpl.EMPTY_ARRAY);
}
/**
* Gets the extension point for the given extension point identifier.
*
* @param extensionPointId the qualified extension point identifier in the form <code><moduleId>:<extensionPointId></code>.
* @return the extension point or <code>null</code> if not found
*/
public ExtensionPoint getExtensionPoint(String extensionPointId) {
if (extensionPointId == null) {
throw new NullPointerException("extensionPointId");
}
return extensionPointMap.get(extensionPointId);
}
/**
* Gets the extension point for the given extension point identifier. The declaring module
* is used to search the dependency tree for inherited extension points, if a point with the given fully qualified
* name has not been registered directly.
*
* @param extensionPointId the qualified extension point identifier in the form <code><moduleId>:<extensionPointId></code>.
* @param declaringModule the declaring module
* @return the extension point or <code>null</code> if not found
*/
public ExtensionPoint getExtensionPoint(String extensionPointId, ModuleImpl declaringModule) {
ExtensionPoint point = getExtensionPoint(extensionPointId);
if (point == null) {
String extensionPointIdSimple = extensionPointId;
final int index = extensionPointId.indexOf(':');
if (index > 0) {
extensionPointIdSimple = extensionPointId.substring(index + 1);
}
point = findExtensionPoint(declaringModule, extensionPointIdSimple);
}
return point;
}
/**
* Gets all extension points defined so far.
*
* @return the extension points
*/
public ExtensionPoint[] getExtensionPoints() {
Collection<ExtensionPoint> extensionPoints = extensionPointMap.values();
return extensionPoints.toArray(new ExtensionPoint[0]);
}
/**
* Gets all extensions for the given extension point identifier.
*
* @param extensionPointId the qualified extension point identifier in the form <code><moduleId>:<extensionPointId></code>.
* @return the extension points
*/
public Extension[] getExtensions(String extensionPointId) {
ExtensionPoint extensionPoint = getExtensionPoint(extensionPointId);
ArrayList<Extension> list = new ArrayList<Extension>(16);
if (extensionPoint != null) {
// Declaring module shall contribute first, ...
Module declaringModule = extensionPoint.getDeclaringModule();
collectExtensions(declaringModule, extensionPoint, list);
Module[] modules = getModules();
for (Module module : modules) {
// ... then all other modules contribute.
if (module != declaringModule) {
collectExtensions(module, extensionPoint, list);
}
}
}
return list.toArray(new Extension[0]);
}
private void collectExtensions(Module module, ExtensionPoint extensionPoint, ArrayList<Extension> extensionList) {
Extension[] extensions = module.getExtensions();
for (Extension extension : extensions) {
if (extension.getExtensionPoint() == extensionPoint) {
extensionList.add(extension);
}
}
}
private void registerModuleId(ModuleImpl module) {
idToModuleMap.put(module.getModuleId(), module);
}
private void registerLocation(ModuleImpl module) {
locationToModuleMap.put(module.getLocation(), module);
}
private void registerSymbolicName(ModuleImpl module) {
Object o = symbolicNameToModulesMap.get(module.getSymbolicName());
if (o == null) {
symbolicNameToModulesMap.put(module.getSymbolicName(), module);
} else if (o instanceof ModuleImpl) {
List<Module> arrayList = new ArrayList<Module>(3);
arrayList.add((ModuleImpl) o);
arrayList.add(module);
symbolicNameToModulesMap.put(module.getSymbolicName(), arrayList);
} else if (o instanceof List) {
((List<Module>) o).add(module);
} else {
throw new IllegalStateException();
}
}
private static ExtensionPoint findExtensionPoint(ModuleImpl module, String extensionPointIdSimple) {
final ModuleImpl[] dependencies = module.getModuleDependencies();
if (dependencies == null) {
return null;
}
for (ModuleImpl dependency : dependencies) {
final ExtensionPoint point1 = dependency.getExtensionPoint(extensionPointIdSimple);
if (point1 != null) {
return point1;
}
}
for (ModuleImpl dependency : dependencies) {
final ExtensionPoint point1 = findExtensionPoint(dependency, extensionPointIdSimple);
if (point1 != null) {
return point1;
}
}
return null;
}
}