All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hadii.stiff.extractor.ComponentRelations Maven / Gradle / Ivy

The newest version!
package com.hadii.stiff.extractor;

import com.hadii.clarpse.reference.ComponentReference;
import com.hadii.clarpse.sourcemodel.OOPSourceModelConstants;
import com.hadii.clarpse.sourcemodel.OOPSourceModelConstants.AccessModifiers;
import com.hadii.clarpse.sourcemodel.OOPSourceModelConstants.ComponentType;
import com.hadii.stiff.diagram.DiagramComponent;
import com.hadii.stiff.diagram.DiagramConstants;
import com.hadii.stiff.diagram.DiagramCodeModel;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Returns a list of binary class relationships from a given source code mergedModel.
 */
public class ComponentRelations {

    private final Map> relationMap = new HashMap<>();

    public ComponentRelations(final DiagramCodeModel sourceCodeModel) {
        final Map components = sourceCodeModel.components();
        for (final Map.Entry entry : components.entrySet()) {
            final DiagramComponent tempClass = entry.getValue();
            ComponentType cmpType = entry.getValue().componentType();
            if (cmpType.isBaseComponent() || cmpType.isMethodComponent() || cmpType == ComponentType.FIELD) {
                collectComponentRelations(tempClass, components);
            }
        }
    }

    public ComponentRelations() {
    }

    public ComponentRelations(ComponentRelations... cmpRelations) {
        for (ComponentRelations cmpRelationsObj : cmpRelations) {
            for (ComponentRelation componentRelation : cmpRelationsObj.relations()) {
                this.addRelation(componentRelation);
            }
        }
    }

    public ComponentRelations(Set cmpRelations) {
        for (ComponentRelation componentRelation : cmpRelations) {
            this.addRelation(componentRelation);
        }
    }

    /**
     * Finds external class links from all the field, method and method params
     * in the given component.
     */
    private void genAssociations(final DiagramComponent component, final Map components) {
        // Only consider non-base components (eg: methods, fields, etc ..)
        if (!component.componentType().isBaseComponent()) {
            // Get the the current component's parent base component
            final DiagramComponent currentBaseComponent = component.parentBaseComponent(components);
            if (currentBaseComponent != null) {
                // Get a list of all the external components referenced
                final Set componentReferences = component.componentInvocations();
                // Remove redundant references..
                filterComponentInvocations(componentReferences, components, currentBaseComponent, currentBaseComponent);
                // Loop through list of references and create component relationships as required..
                for (final ComponentReference componentRelationship : componentReferences) {
                    final String componentReferenceName = componentRelationship.invokedComponent();
                    // Important: We only consider component references that refer to components in the given code base!
                    if (components.containsKey(componentReferenceName)) {
                        final DiagramComponent targetClass = components.get(componentReferenceName);
                        ComponentAssociationMultiplicity relationMultiplicity;
                        DiagramConstants.ComponentAssociation relationAssociation;
                        ComponentRelation externalClassLink;
                        // TODO: Set relation multiplicity appropriately based on component references.
                        relationMultiplicity = new ComponentAssociationMultiplicity(DiagramConstants.DefaultClassMultiplicities.NONE);
                        // create external class link based on calling component type
                        // --> IF INVOCATION SITE IS CLASS FIELD:
                        if (component.componentType() == ComponentType.FIELD) {
                            if (component.modifiers().contains(
                                    OOPSourceModelConstants.getJavaAccessModifierMap().get(AccessModifiers.PRIVATE))
                                    || component.modifiers().contains(OOPSourceModelConstants
                                    .getJavaAccessModifierMap().get(AccessModifiers.PROTECTED))) {
                                relationAssociation = DiagramConstants.ComponentAssociation.COMPOSITION;
                            } else {
                                relationAssociation = DiagramConstants.ComponentAssociation.AGGREGATION;
                            }
                            externalClassLink = new ComponentRelation(currentBaseComponent, targetClass, relationMultiplicity,
                                    relationAssociation);
                            // --> IF INVOCATION SITE IS METHOD
                        } else if ((component.componentType() == ComponentType.METHOD)
                                && !currentBaseComponent.uniqueName().equals(targetClass.uniqueName())) {
                            relationAssociation = DiagramConstants.ComponentAssociation.ASSOCIATION;
                            externalClassLink = new ComponentRelation(currentBaseComponent, targetClass, relationMultiplicity,
                                    relationAssociation);
                            // --> IF INVOCATION SITE IS CONSTRUCTOR
                        } else if ((component.componentType() == ComponentType.CONSTRUCTOR)
                                && !currentBaseComponent.uniqueName().equals(targetClass.uniqueName())) {
                            relationAssociation = DiagramConstants.ComponentAssociation.ASSOCIATION;
                            externalClassLink = new ComponentRelation(currentBaseComponent, targetClass, relationMultiplicity,
                                    relationAssociation);
                        } else {
                            continue;
                        }
                        addRelation(externalClassLink);
                    }
                }
            }
        }
    }

    /**
     * Recursively removes self-referencing implementation and extension relationships.
     */
    private void filterComponentInvocations(Set componentInvocations,
                                            Map components, DiagramComponent filterComponent,
                                            DiagramComponent originalComponent) {
        if (!filterComponent.componentInvocations(OOPSourceModelConstants.TypeReferences.IMPLEMENTATION).isEmpty()) {
            for (ComponentReference ref : filterComponent.componentInvocations(OOPSourceModelConstants.TypeReferences.IMPLEMENTATION)) {
                DiagramComponent invokedComponent = components.get(ref.invokedComponent());
                if (invokedComponent != null && !filterComponent.equals(invokedComponent)) {
                    filterComponentInvocations(componentInvocations, components, invokedComponent, originalComponent);
                }
            }
        }
        if (!filterComponent.componentInvocations(OOPSourceModelConstants.TypeReferences.EXTENSION).isEmpty()) {
            for (ComponentReference ref : filterComponent.componentInvocations(OOPSourceModelConstants.TypeReferences.EXTENSION)) {
                DiagramComponent invokedComponent = components.get(ref.invokedComponent());
                if (invokedComponent != null && !filterComponent.equals(invokedComponent)) {
                    filterComponentInvocations(componentInvocations, components, invokedComponent, originalComponent);
                }
            }
        }
        if (!filterComponent.uniqueName().equals(originalComponent.uniqueName())) {
            removeMatchingInvocations(filterComponent.componentInvocations(), componentInvocations);
        }
    }

    /**
     * Removes all the to-be-removed-invocations from a given list of component
     * invocations.
     */
    private void removeMatchingInvocations(Set invokedComponentsToBeRemoved,
                                           Set externalClassTypeReferences) {
        List invocationsCopy = new ArrayList<>(externalClassTypeReferences);
        for (ComponentReference tmpInvocation : invocationsCopy) {
            for (ComponentReference toBeRemovedInvocation : invokedComponentsToBeRemoved) {
                if (tmpInvocation.invokedComponent().equals(toBeRemovedInvocation.invokedComponent())) {
                    externalClassTypeReferences.remove(tmpInvocation);
                }
            }
        }
    }

    /**
     * Registers a new component relation between two components.
     */
    public void addRelation(final ComponentRelation cmpRelation) {
        if (!cmpRelation.originalComponent().componentType().isBaseComponent()
                || !cmpRelation.targetComponent().componentType().isBaseComponent()) {
            throw new IllegalArgumentException("Relations must exist between base components only!");
        }
        if (cmpRelation.originalComponent().equals(cmpRelation.targetComponent())) {
            // skip
            return;
        }
        if (this.relationMap.containsKey(cmpRelation.originalComponent())) {
            if (this.relationMap.get(cmpRelation.originalComponent()).contains(cmpRelation)) {
                // If a relation already exists between the two components of the incoming relation, take the stronger one.
                int relationIndex = this.relationMap.get(cmpRelation.originalComponent()).indexOf(cmpRelation);
                int currRelationStrength = this.relationMap.get(cmpRelation.originalComponent())
                        .get(relationIndex).associationType().strength();
                if (currRelationStrength < cmpRelation.associationType().strength()) {
                    this.relationMap.get(cmpRelation.originalComponent()).set(relationIndex, cmpRelation);
                }
            } else {
                this.relationMap.get(cmpRelation.originalComponent()).add(cmpRelation);
            }
        } else {
            List relationships = new ArrayList<>();
            relationships.add(cmpRelation);
            this.relationMap.put(cmpRelation.originalComponent(), relationships);
        }
    }

    /**
     * Analyzes the components specialized by the given component.
     */
    private void componentSpecializations(final DiagramComponent component, final Map allComponents) {
        final List superClasses = component.componentInvocations(OOPSourceModelConstants.TypeReferences.EXTENSION);
        if (!superClasses.isEmpty()) {
            for (final ComponentReference superClass : superClasses) {
                if (allComponents.containsKey(superClass.invokedComponent())) {
                    final DiagramComponent targetClass = allComponents.get(superClass.invokedComponent());
                    final ComponentRelation specializationRelation = new ComponentRelation(component,
                            targetClass, new ComponentAssociationMultiplicity(DiagramConstants.DefaultClassMultiplicities.NONE),
                            DiagramConstants.ComponentAssociation.SPECIALIZATION);
                    addRelation(specializationRelation);
                }
            }
        }
    }

    /**
     * @param component          Component to be analyzed for relations
     * @param codeBaseComponents Map of all components in the code base
     */
    private void collectComponentRelations(final DiagramComponent component, final Map codeBaseComponents) {
        // Scan class signature for any classes that have been extended
        componentSpecializations(component, codeBaseComponents);
        // Scan class signature for any classes that have been implemented
        componentRealizations(component, codeBaseComponents);
        // Scan class fields for associations
        genAssociations(component, codeBaseComponents);
    }

    /**
     * Process components realized by the given component.
     */
    private void componentRealizations(final DiagramComponent sourceComponent, final Map allComponents) {
        final List implementedClasses = sourceComponent
                .componentInvocations(OOPSourceModelConstants.TypeReferences.IMPLEMENTATION);
        if (!implementedClasses.isEmpty()) {
            for (final ComponentReference implementedClass : implementedClasses) {
                if (allComponents.containsKey(implementedClass.invokedComponent())) {
                    final DiagramComponent targetClass = allComponents.get(implementedClass.invokedComponent());
                    final ComponentRelation realizationExternalClassLink = new ComponentRelation(sourceComponent,
                            targetClass, new ComponentAssociationMultiplicity(DiagramConstants.DefaultClassMultiplicities.NONE),
                            DiagramConstants.ComponentAssociation.REALIZATION);
                    addRelation(realizationExternalClassLink);
                }
            }
        }
    }

    public final Collection relations() {
        List relations = new ArrayList<>();
        for (Map.Entry> entry : this.relationMap.entrySet()) {
            relations.addAll(entry.getValue());
        }
        return relations;
    }

    public final boolean hasRelation(ComponentRelation componentRelation) {
        return this.relationMap.containsKey(componentRelation.originalComponent())
                && this.relationMap.get(componentRelation.originalComponent()).contains(componentRelation);
    }

    public final boolean hasRelationsforComponent(DiagramComponent component) {
        return this.relationMap.containsKey(component);
    }

    public final boolean hasRelationsforComponent(String cmpUniqueName) {
        return this.relationMap.keySet()
                .stream()
                .filter(diagramComponent -> diagramComponent.uniqueName().equals(cmpUniqueName))
                .collect(Collectors.toSet())
                .size() == 1;
    }

    public final List componentRelations(DiagramComponent component) {
        return this.relationMap.get(component);
    }

    /**
     * Returns a relation going in the opposite direction between the original and target component if it exists.
     * Otherwise, an empty relation is returned.
     */
    public final ComponentRelation reverseRelation(ComponentRelation relation) {
        DiagramComponent componentA = relation.originalComponent();
        DiagramComponent componentB = relation.targetComponent();
        ComponentRelation reverseRelation = new ComponentRelation();
        // Figure out if a component relation in the opposite direction exists
        if (hasRelationsforComponent(componentB)) {
            List targetComponentRelations = new ArrayList<>(componentRelations(componentB));
            targetComponentRelations.removeIf(targetComponentRelation -> !targetComponentRelation.targetComponent().equals(componentA));
            if (targetComponentRelations.size() > 0) {
                reverseRelation = targetComponentRelations.get(0);
            }
        }
        return reverseRelation;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy