/** * Licensed to the Austrian Association for Software Tool Integration (AASTI) * under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright * ownership. The AASTI licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.openengsb.core.ekb.transformation.wonderland.internal; import java.lang.reflect.Field; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.openengsb.core.api.model.ModelDescription; import org.openengsb.core.ekb.api.ModelRegistry; import org.openengsb.core.ekb.api.transformation.TransformationDescription; import org.openengsb.core.ekb.api.transformation.TransformationStep; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The PropertyConnectionCalculator class contains the logic to calculate the property connections based on * transformation descriptions. */ public class PropertyConnectionCalculator { private static final Logger LOGGER = LoggerFactory.getLogger(PropertyConnectionCalculator.class); private ModelRegistry registry; public PropertyConnectionCalculator(ModelRegistry registry) { this.registry = registry; } /** * Calculates the connections of properties of the source and target model of a transformation description. Returns * a map where the keys are the properties of the source model and the values are a set of property names which are * influenced if the key property is changed. */ public Map<String, Set<String>> getPropertyConnections(TransformationDescription description) { Map<String, Set<String>> propertyMap = getSourceProperties(description.getSourceModel()); fillPropertyMap(propertyMap, description); resolveTemporaryProperties(propertyMap); deleteTemporaryProperties(propertyMap); return propertyMap; } /** * Returns a map where the keys are the properties of the model described by the given model description. The values * are empty sets. */ private Map<String, Set<String>> getSourceProperties(ModelDescription description) { Map<String, Set<String>> result = new LinkedHashMap<String, Set<String>>(); try { Class<?> sourceModel = registry.loadModel(description); while (sourceModel != null && !sourceModel.equals(Object.class)) { for (Field field : sourceModel.getDeclaredFields()) { result.put(field.getName(), new HashSet<String>()); } sourceModel = sourceModel.getSuperclass(); } } catch (ClassNotFoundException e) { LOGGER.error("Unable to load model {}", description); return result; } return result; } /** * Fills the given map based on the transformation steps given in the transformation description. It analyzes the * transformation step and adds the target of the transformation step to the set of properties which are influenced * by the source property. */ private void fillPropertyMap(Map<String, Set<String>> map, TransformationDescription description) { for (TransformationStep step : description.getTransformingSteps()) { if (step.getSourceFields() == null) { LOGGER.debug("Step {} is ignored since no source properties are defined"); continue; } String targetField = step.getTargetField(); if (!map.containsKey(targetField) && isTemporaryProperty(targetField)) { LOGGER.debug("Add new property entry for field {}", targetField); map.put(targetField, new HashSet<String>()); } for (String sourceField : step.getSourceFields()) { if (sourceField == null) { continue; } String[] result = StringUtils.split(sourceField, "."); String mapValue = result[0]; Set<String> targets = map.get(mapValue); if (targets != null) { targets.add(targetField); } else { LOGGER.error("Accessed property with the name {} which isn't existing", mapValue); } } } } /** * Resolves the temporary properties of the given property map. It replaces the temporary properties in the values * of the given map with the values of the temporary property it replaces. This procedure is done until there are no * more temporary fields present in the values of the map. */ private void resolveTemporaryProperties(Map<String, Set<String>> map) { boolean temporaryPresent = false; do { temporaryPresent = false; for (Map.Entry<String, Set<String>> entry : map.entrySet()) { Set<String> newProperties = new HashSet<String>(); Iterator<String> properties = entry.getValue().iterator(); while (properties.hasNext()) { String property = properties.next(); if (isTemporaryProperty(property)) { LOGGER.debug("Resolve temporary field {} for property {}", entry.getKey(), property); temporaryPresent = true; newProperties.addAll(map.get(property)); properties.remove(); } } entry.getValue().addAll(newProperties); } } while (temporaryPresent); } /** * Iterates over the map entries and removes all temporary properties so that a clean map can be returned to the * user which is interested in the property connections. */ private void deleteTemporaryProperties(Map<String, Set<String>> map) { Iterator<String> iterator = map.keySet().iterator(); while (iterator.hasNext()) { String key = iterator.next(); if (isTemporaryProperty(key)) { LOGGER.debug("Delete temporary field {} from the connection result", key); iterator.remove(); } } } /** * Returns true if the given property name is a temporary property, returns false if not. */ private boolean isTemporaryProperty(String propertyName) { return propertyName.startsWith("#"); } }