blob: 8e49a34050e54b74808d4681fad3505546a822bc [file] [log] [blame]
/*********************************************************************************
* Copyright (c) 2020-2021 Robert Bosch GmbH and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Robert Bosch GmbH - initial API and implementation
********************************************************************************
*/
package org.eclipse.app4mc.visualization.ui.registry;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import org.apache.commons.lang.ClassUtils;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Registry that collects all {@link Visualization} components.
*/
@Component(service=ModelVisualizationRegistry.class)
public class ModelVisualizationRegistry {
private static final Logger LOGGER = LoggerFactory.getLogger(ModelVisualizationRegistry.class);
private ConcurrentHashMap<String, List<ModelVisualization>> visualizationRegistry = new ConcurrentHashMap<>();
@Reference(
cardinality = ReferenceCardinality.MULTIPLE,
policy = ReferencePolicy.DYNAMIC,
policyOption = ReferencePolicyOption.GREEDY)
void bindModelVisualization(Visualization visualization, Map<String, Object> properties) {
String modelClassName = getModelClassName(visualization, properties);
if (modelClassName != null) {
List<ModelVisualization> visualizations =
visualizationRegistry.computeIfAbsent(modelClassName, key -> new CopyOnWriteArrayList<>());
String id = (String) properties.getOrDefault("id", visualization.getClass().getName());
if (visualizations.stream().noneMatch(v -> v.getId().equals(id))) {
visualizations.add(new ModelVisualization(
id,
visualization,
(String) properties.getOrDefault("name", visualization.getClass().getSimpleName()),
(String) properties.getOrDefault("description", null),
modelClassName));
} else {
LOGGER.error("A visualization with the ID {} already exists!", id);
}
} else {
LOGGER.error("Unable to extract model class name for Visualization {}", visualization.getClass().getName());
}
}
void unbindModelVisualization(Visualization visualization, Map<String, Object> properties) {
String modelClassName = getModelClassName(visualization, properties);
if (modelClassName != null) {
visualizationRegistry.remove(modelClassName);
}
}
/**
* Get all {@link ModelVisualization} objects that are registered for the given classes.
*
* @param classes The classes for which the {@link ModelVisualization}s are requested.
* @return All {@link ModelVisualization} objects that are registered for the
* given classes.
*/
@SuppressWarnings("unchecked")
public List<ModelVisualization> getVisualizations(List<Class<?>> classes) {
HashSet<String> classNames = new LinkedHashSet<>();
for (Class<?> clazz : classes) {
if (clazz != null) {
classNames.add(clazz.getName());
List<Class<?>> allInterfaces = ClassUtils.getAllInterfaces(clazz);
classNames.addAll(allInterfaces.stream().map(Class::getName).collect(Collectors.toList()));
}
}
return classNames.stream()
.filter(Objects::nonNull)
.flatMap(name -> visualizationRegistry.getOrDefault(name, new ArrayList<>()).stream())
.collect(Collectors.toList());
}
/**
* Extracts the model class name for which the {@link Visualization} should be
* registered. First checks the <i>modelClass</i> component property. If it is
* not set the type of the first parameter of the method annotated with
* {@link PostConstruct} will be returned.
*
* @param visualization The {@link Visualization} for which the model class name
* should be returned.
* @param properties The component properties map of the
* {@link Visualization} object.
* @return The model class name for which the {@link Visualization} should be
* registered.
*/
private String getModelClassName(Visualization visualization, Map<String, Object> properties) {
// check if property for modelClass is set
String modelClassName = (String) properties.getOrDefault("modelClass", null);
if (modelClassName == null) {
// else find method annotated with @PostConstruct
Class<? extends Visualization> visuClass = visualization.getClass();
Method[] methods = visuClass.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(PostConstruct.class)) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length > 0) {
if (Collection.class.isAssignableFrom(parameterTypes[0])) {
// extract generic information for List support
Type[] genericParameterTypes = method.getGenericParameterTypes();
if (genericParameterTypes[0] instanceof ParameterizedType) {
Type[] typeArguments = ((ParameterizedType)genericParameterTypes[0]).getActualTypeArguments();
modelClassName = typeArguments.length > 0 ? typeArguments[0].getTypeName() : null;
}
} else {
modelClassName = parameterTypes[0].getName();
}
break;
}
}
}
}
return modelClassName;
}
}