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

cdc.mf.ea.EaDumpToMfImpl Maven / Gradle / Ivy

The newest version!
package cdc.mf.ea;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import cdc.issues.Issue;
import cdc.issues.IssuesCollector;
import cdc.issues.locations.Location;
import cdc.issues.rules.Rule;
import cdc.mf.Config;
import cdc.mf.ea.checks.EaAttributeIdMustBeValid;
import cdc.mf.ea.checks.EaAttributeIgnored;
import cdc.mf.ea.checks.EaAttributeNameMustBeValid;
import cdc.mf.ea.checks.EaAttributeTypeMustBeValid;
import cdc.mf.ea.checks.EaConnectorIdMustBeValid;
import cdc.mf.ea.checks.EaConnectorIgnored;
import cdc.mf.ea.checks.EaConnectorMustBeValid;
import cdc.mf.ea.checks.EaConnectorTypeIgnored;
import cdc.mf.ea.checks.EaCreateMissingType;
import cdc.mf.ea.checks.EaError;
import cdc.mf.ea.checks.EaInfo;
import cdc.mf.ea.checks.EaObjectIdMustBeValid;
import cdc.mf.ea.checks.EaObjectIgnored;
import cdc.mf.ea.checks.EaSubstituteTypeBuiltin;
import cdc.mf.ea.checks.EaSubstituteTypeRisky;
import cdc.mf.ea.checks.EaSubstituteTypeSafe;
import cdc.mf.model.MfAnnotation;
import cdc.mf.model.MfAnnotationOwner;
import cdc.mf.model.MfCardinality;
import cdc.mf.model.MfClass;
import cdc.mf.model.MfConnector;
import cdc.mf.model.MfConnectorOwner;
import cdc.mf.model.MfConstraint;
import cdc.mf.model.MfConstraintOwner;
import cdc.mf.model.MfDependency;
import cdc.mf.model.MfDependencyOwner;
import cdc.mf.model.MfElement;
import cdc.mf.model.MfFactory;
import cdc.mf.model.MfImplementation;
import cdc.mf.model.MfImplementationOwner;
import cdc.mf.model.MfInterface;
import cdc.mf.model.MfMemberOwner;
import cdc.mf.model.MfMetasItem;
import cdc.mf.model.MfModel;
import cdc.mf.model.MfNameItem;
import cdc.mf.model.MfPackage;
import cdc.mf.model.MfProperty;
import cdc.mf.model.MfSpecialization;
import cdc.mf.model.MfSpecializationOwner;
import cdc.mf.model.MfTag;
import cdc.mf.model.MfTagOwner;
import cdc.mf.model.MfTip;
import cdc.mf.model.MfType;
import cdc.mf.model.MfVisibility;
import cdc.office.ss.SheetLoader;
import cdc.office.ss.WorkbookKind;
import cdc.office.tables.Header;
import cdc.office.tables.HeaderMapper;
import cdc.office.tables.Row;
import cdc.util.lang.Checks;
import cdc.util.strings.StringUtils;
import cdc.util.time.Chronometer;

/**
 * Class used to analyze the EA database dump and generate a memory model.
 * 

* Only used (by S-Series) or relevant data is extracted.
* Several tables are used: *

    *
  • t_object: it contains common data for different kinds of objects (packages, classes, interfaces, constraints, ...)
    * Almost all objects have an object_id. Root packages don't seem to have one.
    * All objects have a guid. * *
  • t_package: it contains packages and their composition relationships.
    * Packages have a package_id (different from object_id).
    * Reading this table seems necessary because t_object does not contain the package_id of packages. * *
  • t_attribute: it contains attributes of objects.
    * The type of each attribute is sometimes defined by its object_id in the classifier column.
    * It is always designated by its type name in the type column.
    * There are cases where classifier does not correspond to any declared object.
    * In such case, we use type to find the corresponding type.
    * It is possible that no types with that name exist, or that several types exist.
    * In former case we must create one. In latter case, we must select one. * *
  • t_objectproperties: it contains tagged values of objects. * *
  • t_attributetag: it contains tagged values of attributes. * *
  • t_connector: *
* Note: it seems that EA does not use many foreign key constraints, which make things more complicated. *

* WARNING: it is possible that a better solution exists to mine EA data base, this one is purely * based on reverse engineering. * * @author Damien Carbonne */ public final class EaDumpToMfImpl { private static final Logger LOGGER = LogManager.getLogger(EaDumpToMfImpl.class); public static final String META_EA_FILE = "ea-file"; public static final String META_FIX_DIRECTION = "fix-direction"; public static final String META_GENERATION_DATE = "generation-date"; public static final String META_GENERATOR = "generator"; private static final String TYPE_CLASS = "Class"; private static final String TYPE_CONSTRAINT = "Constraint"; private static final String TYPE_INTERFACE = "Interface"; private static final String TYPE_PACKAGE = "Package"; private static final String CTYPE_ASSOCIATION = "Association"; private static final String CTYPE_AGGREGATION = "Aggregation"; private static final String CTYPE_DEPENDENCY = "Dependency"; private static final String CTYPE_GENERALIZATION = "Generalization"; private static final String CTYPE_REALISATION = "Realisation"; private static final String CTYPE_NOTE_LINK = "NoteLink"; private static final String CSUBTYPE_STRONG = "Strong"; private static final String CSUBTYPE_WEAK = "Weak"; private static final String CSUBTYPE_CLASS = "Class"; private static final String CDIR_BI_DIRECTIONAL = "Bi-Directional"; private static final String CDIR_DST_SRC = "Destination -> Source"; private static final String CDIR_SRC_DST = "Source -> Destination"; private static final String CDIR_UNSPECIFIED = "Unspecified"; private static final String VIS_PUBLIC = "Public"; private static final String VIS_PROTECTED = "Protected"; private static final String VIS_PRIVATE = "Private"; private static final String TABLE_AT = "TABLE@"; private static final String IGNORE_ROW = "\nIgnore row."; private static final String BUILTIN = "builtin"; private static final String DB_SCHEMA = "PUBLIC-PUBLIC-"; /** Table of objects attributes. */ private static final String ATTRIBUTE = "t_attribute"; /** Object identifier of the containing object. */ private static final String ATTRIBUTE_OBJECT_ID = "Object_ID"; /** Attribute name. */ private static final String ATTRIBUTE_NAME = "Name"; /** Attribute visibility. */ private static final String ATTRIBUTE_VISIBILITY = "Scope"; /** Attribute stereotype. */ private static final String ATTRIBUTE_STEREOTYPE = "Stereotype"; private static final String ATTRIBUTE_LOWER_BOUND = "LowerBound"; private static final String ATTRIBUTE_UPPER_BOUND = "UpperBound"; /** Attribute notes. */ private static final String ATTRIBUTE_NOTES = "Notes"; /** Attribute identifier. */ private static final String ATTRIBUTE_ID = "ID"; /** Attribute GUID. */ private static final String ATTRIBUTE_GUID = "ea_guid"; /** Attribute pos. */ private static final String ATTRIBUTE_POS = "Pos"; /** Object identifier of the attribute type. */ private static final String ATTRIBUTE_TYPE_ID = "Classifier"; /** Name of the attribute type. */ private static final String ATTRIBUTE_TYPE_NAME = "Type"; private static final Header ATTRIBUTE_HEADER = Header.builder() .names(ATTRIBUTE_OBJECT_ID, ATTRIBUTE_NAME, ATTRIBUTE_VISIBILITY, ATTRIBUTE_STEREOTYPE, ATTRIBUTE_LOWER_BOUND, ATTRIBUTE_UPPER_BOUND, ATTRIBUTE_NOTES, ATTRIBUTE_ID, ATTRIBUTE_GUID, ATTRIBUTE_POS, ATTRIBUTE_TYPE_ID, ATTRIBUTE_TYPE_NAME) .build(); // TODO there is an ID column. Does it correspond to attribute ID? // If so, use it // No GUID /** Table of attribute constraints. */ private static final String ATTRIBUTE_CONSTRAINT = "t_attributeconstraints"; /** Object ID. **/ private static final String ATTRIBUTE_CONSTRAINT_OBJECT_ID = "Object_ID"; /** Attribute name. **/ private static final String ATTRIBUTE_CONSTRAINT_ATT_NAME = "AttName"; /** Constraint value. */ private static final String ATTRIBUTE_CONSTRAINT_SPECIFICATION = "Constraint"; /** Constraint type. */ private static final String ATTRIBUTE_CONSTRAINT_TYPE = "Type"; /** Constraint notes. */ private static final String ATTRIBUTE_CONSTRAINT_NOTES = "Notes"; private static final Header ATTRIBUTE_CONSTRAINT_HEADER = Header.builder() .names(ATTRIBUTE_CONSTRAINT_OBJECT_ID, ATTRIBUTE_CONSTRAINT_ATT_NAME, ATTRIBUTE_CONSTRAINT_SPECIFICATION, ATTRIBUTE_CONSTRAINT_TYPE, ATTRIBUTE_CONSTRAINT_NOTES) .build(); /** Table of attributes tags. */ private static final String ATTRIBUTE_TAG = "t_attributetag"; /** Tag identifier. */ private static final String ATTRIBUTE_TAG_ID = "PropertyID"; /** Attribute tag GUID. */ private static final String ATTRIBUTE_TAG_GUID = "ea_guid"; /** Identifier of the attribute containing the tag. **/ private static final String ATTRIBUTE_TAG_PARENT_ID = "ElementID"; /** Tag name. */ private static final String ATTRIBUTE_TAG_NAME = "Property"; /** Tag value. **/ private static final String ATTRIBUTE_TAG_VALUE = "VALUE"; /** Tag notes. **/ private static final String ATTRIBUTE_TAG_NOTES = "NOTES"; private static final Header ATTRIBUTE_TAG_HEADER = Header.builder() .names(ATTRIBUTE_TAG_ID, ATTRIBUTE_TAG_GUID, ATTRIBUTE_TAG_PARENT_ID, ATTRIBUTE_TAG_NAME, ATTRIBUTE_TAG_VALUE, ATTRIBUTE_TAG_NOTES) .build(); /** Table of objects (Class, Interface, Package, Constraint, Note, ...). */ private static final String OBJECT = "t_object"; /** Object identifier. */ private static final String OBJECT_OBJECT_ID = "Object_ID"; /** Object type (Class, Interface, ...). */ private static final String OBJECT_OBJECT_TYPE = "Object_Type"; /** Containing package identifier. */ private static final String OBJECT_PACKAGE_ID = "Package_ID"; /** Object name. */ private static final String OBJECT_NAME = "Name"; /** Object Visibiulity. */ private static final String OBJECT_VISIBILITY = "Visibility"; /** Object notes. */ private static final String OBJECT_NOTE = "Note"; /** Object stereotype. */ private static final String OBJECT_STEREOTYPE = "Stereotype"; /** Object author. */ private static final String OBJECT_AUTHOR = "Author"; /** Object version. */ private static final String OBJECT_VERSION = "Version"; /** Object is abstract. */ private static final String OBJECT_ABSTRACT = "Abstract"; /** Object GUID. */ private static final String OBJECT_GUID = "ea_guid"; private static final Header OBJECT_HEADER = Header.builder() .names(OBJECT_OBJECT_ID, OBJECT_OBJECT_TYPE, OBJECT_PACKAGE_ID, OBJECT_NAME, OBJECT_VISIBILITY, OBJECT_NOTE, OBJECT_STEREOTYPE, OBJECT_AUTHOR, OBJECT_VERSION, OBJECT_ABSTRACT, OBJECT_GUID) .build(); /** Table of packages. */ private static final String PACKAGE = "t_package"; /** Package identifier. */ private static final String PACKAGE_PACKAGE_ID = "Package_ID"; /** Package name. */ private static final String PACKAGE_NAME = "Name"; /** Parent package package identifier. */ private static final String PACKAGE_PARENT_ID = "Parent_ID"; /** Package notes. */ private static final String PACKAGE_NOTES = "Notes"; /** Package GUID. */ private static final String PACKAGE_GUID = "ea_guid"; private static final Header PACKAGE_HEADER = Header.builder() .names(PACKAGE_PACKAGE_ID, PACKAGE_NAME, PACKAGE_PARENT_ID, PACKAGE_NOTES, PACKAGE_GUID) .build(); // No GUID /** Table of object constraints. */ private static final String OBJECT_CONSTRAINT = "t_objectconstraint"; /** Object ID. **/ private static final String OBJECT_CONSTRAINT_OBJECT_ID = "Object_ID"; /** Constraint value. */ private static final String OBJECT_CONSTRAINT_SPECIFICATION = "Constraint"; /** Constraint type. */ private static final String OBJECT_CONSTRAINT_TYPE = "ConstraintType"; /** Constraint notes. */ private static final String OBJECT_CONSTRAINT_NOTES = "Notes"; private static final Header OBJECT_CONSTRAINT_HEADER = Header.builder() .names(OBJECT_CONSTRAINT_OBJECT_ID, OBJECT_CONSTRAINT_SPECIFICATION, OBJECT_CONSTRAINT_TYPE, OBJECT_CONSTRAINT_NOTES) .build(); /** Table of objects tags. */ private static final String OBJECT_TAG = "t_objectproperties"; /** Tag identifier. */ private static final String OBJECT_TAG_ID = "PropertyID"; /** Tag GUID. */ private static final String OBJECT_TAG_GUID = "ea_guid"; /** Identifier of the object containing the tag. **/ private static final String OBJECT_TAG_PARENT_ID = "Object_ID"; /** Tag name. */ private static final String OBJECT_TAG_NAME = "Property"; /** Tag value. */ private static final String OBJECT_TAG_VALUE = "Value"; /** Tag notes. **/ private static final String OBJECT_TAG_NOTES = "Notes"; private static final Header OBJECT_TAG_HEADER = Header.builder() .names(OBJECT_TAG_ID, OBJECT_TAG_GUID, OBJECT_TAG_PARENT_ID, OBJECT_TAG_NAME, OBJECT_TAG_VALUE, OBJECT_TAG_NOTES) .build(); /** Table of connectors. */ private static final String CONNECTOR = "t_connector"; /** Connector identifier. */ private static final String CONNECTOR_ID = "Connector_ID"; /** Connector GUID. */ private static final String CONNECTOR_GUID = "ea_guid"; /** Connector direction. */ private static final String CONNECTOR_DIRECTION = "Direction"; /** Connector name. */ private static final String CONNECTOR_NAME = "Name"; /** Connector notes. */ private static final String CONNECTOR_NOTES = "Notes"; /** Connector type. */ private static final String CONNECTOR_TYPE = "Connector_Type"; /** Connector subtype. */ private static final String CONNECTOR_SUBTYPE = "SubType"; /** Connector source cardinality. */ private static final String CONNECTOR_SRC_CARD = "SourceCard"; /** Connector source role. */ private static final String CONNECTOR_SRC_ROLE = "SourceRole"; /** Connector source role notes. */ private static final String CONNECTOR_SRC_ROLE_NOTE = "SourceRoleNote"; /** Connector source is aggregate. */ private static final String CONNECTOR_SRC_IS_AGGREGATE = "SourceIsAggregate"; /** Connector source is ordered. */ private static final String CONNECTOR_SRC_IS_ORDERED = "SourceIsOrdered"; /** Connector source is navigable. */ private static final String CONNECTOR_SRC_IS_NAVIGABLE = "SourceIsNavigable"; /** Start object id. */ private static final String CONNECTOR_START_OBJECT_ID = "Start_Object_ID"; /** Connector destination cardinality. */ private static final String CONNECTOR_DST_CARD = "DestCard"; /** Connector destination role. */ private static final String CONNECTOR_DST_ROLE = "DestRole"; /** Connector destination role notes. */ private static final String CONNECTOR_DST_ROLE_NOTE = "DestRoleNote"; /** Connector destination is aggregate. */ private static final String CONNECTOR_DST_IS_AGGREGATE = "DestIsAggregate"; /** Connector destination is ordered. */ private static final String CONNECTOR_DST_IS_ORDERED = "DestIsOrdered"; /** Connector destination is navigable. */ private static final String CONNECTOR_DST_IS_NAVIGABLE = "DestIsNavigable"; /** End object id. */ private static final String CONNECTOR_END_OBJECT_ID = "End_Object_ID"; private static final Header CONNECTOR_HEADER = Header.builder() .names(CONNECTOR_ID, CONNECTOR_GUID, CONNECTOR_DIRECTION, CONNECTOR_NAME, CONNECTOR_NOTES, CONNECTOR_TYPE, CONNECTOR_SUBTYPE, CONNECTOR_SRC_CARD, CONNECTOR_SRC_ROLE, CONNECTOR_SRC_ROLE_NOTE, CONNECTOR_SRC_IS_AGGREGATE, CONNECTOR_SRC_IS_ORDERED, CONNECTOR_SRC_IS_NAVIGABLE, CONNECTOR_START_OBJECT_ID, CONNECTOR_DST_CARD, CONNECTOR_DST_ROLE, CONNECTOR_DST_ROLE_NOTE, CONNECTOR_DST_IS_AGGREGATE, CONNECTOR_DST_IS_ORDERED, CONNECTOR_DST_IS_NAVIGABLE, CONNECTOR_END_OBJECT_ID) .build(); // No GUID /** Table of connector constraints. */ private static final String CONNECTOR_CONSTRAINT = "t_connectorconstraint"; /** Connector ID. **/ private static final String CONNECTOR_CONSTRAINT_CONNECTOR_ID = "ConnectorID"; /** Constraint value. */ private static final String CONNECTOR_CONSTRAINT_SPECIFICATION = "Constraint"; /** Constraint type. */ private static final String CONNECTOR_CONSTRAINT_TYPE = "ConstraintType"; /** Constraint notes. */ private static final String CONNECTOR_CONSTRAINT_NOTES = "Notes"; private static final Header CONNECTOR_CONSTRAINT_HEADER = Header.builder() .names(CONNECTOR_CONSTRAINT_CONNECTOR_ID, CONNECTOR_CONSTRAINT_SPECIFICATION, CONNECTOR_CONSTRAINT_TYPE, CONNECTOR_CONSTRAINT_NOTES) .build(); /** Table of connectors tags. */ private static final String CONNECTOR_TAG = "t_connectortag"; /** Tag identifier. */ private static final String CONNECTOR_TAG_ID = "PropertyID"; /** Connector tag GUID. */ private static final String CONNECTOR_TAG_GUID = "ea_guid"; /** Identifier of the connector containing the tag. **/ private static final String CONNECTOR_TAG_PARENT_ID = "ElementID"; /** Tag name. */ private static final String CONNECTOR_TAG_NAME = "Property"; /** Tag value. */ private static final String CONNECTOR_TAG_VALUE = "VALUE"; /** Tag notes. **/ private static final String CONNECTOR_TAG_NOTES = "NOTES"; private static final Header CONNECTOR_TAG_HEADER = Header.builder() .names(CONNECTOR_TAG_ID, CONNECTOR_TAG_PARENT_ID, CONNECTOR_TAG_NAME, CONNECTOR_TAG_VALUE, CONNECTOR_TAG_NOTES) .build(); /** EAP file that was used to generate dumps. This is used to locate issues. */ private final File eapFile; /** Directory where the EAP database was dumped. */ private final File dumpDir; private final WorkbookKind kind; private final boolean verbose; private final boolean fixDirection; private final String language; private final MfModel model; private final String basename; private final SheetLoader loader = new SheetLoader(); private final IssuesCollector issues = new IssuesCollector<>(); /** The actual packages header. */ private Header packagesHeader; /** The data rows for packages. */ private final List packagesData = new ArrayList<>(); /** (packageId, guid) map/. */ private final Map packageIdToGuid = new HashMap<>(); /** The actual objects header. */ private Header objectsHeader; /** The data rows for objects. */ private final List objectsData = new ArrayList<>(); /** (guid, objectId) map/. */ private final Map guidToObjectId = new HashMap<>(); private final MfPackage builtin; private int nextObjectId = -1000; public int newObjectId() { nextObjectId--; return nextObjectId + 1; } private void info(String description, Location location) { issues.issue(Issue.builder() .rule(EaInfo.RULE) .project(eapFile.getName()) .description(description) .location(location) .build()); } private void info(String description) { issues.issue(Issue.builder() .rule(EaInfo.RULE) .project(eapFile.getName()) .description(description) .build()); } private void error(String description, Location location) { issues.issue(Issue.builder() .rule(EaError.RULE) .project(eapFile.getName()) .description(description) .location(location) .build()); } private EaDumpToMfImpl(Builder builder) { final MfFactory factory = MfFactory.builder().build(); this.language = builder.language; final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); this.model = factory.model() .id(toModelId(1)) // No GUID .meta(MfMetasItem.AUTHOR, builder.author) .meta(META_EA_FILE, builder.eapFile.getName()) .meta(META_FIX_DIRECTION, Boolean.toString(builder.fixDirection)) .meta(META_GENERATION_DATE, format.format(new Date())) .meta(META_GENERATOR, EaDumpToMf.class.getSimpleName() + "-" + Config.VERSION) .name(builder.name) .build(); this.builtin = this.model.pack().id(toObjectId(newObjectId())) .name("Builtin") .meta(MfMetasItem.AUTHOR, "CDC") .stereotype(BUILTIN) .build(); setNotes(this.builtin, "Package containing builtin types that are automatically created when loading the model."); this.eapFile = builder.eapFile; this.dumpDir = builder.dumpDir; this.kind = builder.kind; this.basename = builder.basename; this.verbose = builder.verbose; this.fixDirection = builder.fixDirection; } /** * @param tableName The table name. * @return The file corresponding to dumped table named {@code name}. */ private File getFile(String tableName) { if (StringUtils.isNullOrEmpty(basename)) { return new File(dumpDir, DB_SCHEMA + tableName + "." + kind.getExtension()); } else { return new File(dumpDir, basename + "-" + DB_SCHEMA + tableName + "." + kind.getExtension()); } } /** * Loads the table dumps and fills the model. *

* WARNING: this should be called once. * * @throws IOException When an IO error occurs. */ public void loadAndBuildModel() throws IOException { final Chronometer chrono = new Chronometer(); chrono.start(); log("Load dumps"); preloadPackages(); preloadObjects(); createPackages(); createPackagedItems(); loadObjectsConstraints(); loadObjectsTags(); loadAttributes(); loadAttributesConstraints(); loadAttributesTags(); loadConnectors(); loadConnectorsConstraints(); loadConnectorsTags(); chrono.suspend(); log("Loaded dumps (" + chrono + ")"); } private void log(String message) { if (verbose) { LOGGER.info(message); } info(message); } /** * @return The model. */ public MfModel getModel() { return model; } /** * @return The issues collector. */ public IssuesCollector getIssuesCollector() { return issues; } private static void assertNotEmptyRows(File file, List rows) { Checks.assertFalse(rows.isEmpty(), "Empty {}", file); } private static String toModelId(int id) { return "M@" + id; } private static String toAttributeId(int id) { return "A@" + id; } private static String toAttributeTagId(int id) { return "AT@" + id; } private static String toObjectId(int id) { return "O@" + id; } private static String toObjectTagId(int id) { return "OT@" + id; } private static String toConnectorId(int id) { return "C@" + id; } private static String toConnectorTagId(int id) { return "CT@" + id; } private static String toNotesId(MfElement parent) { return getIdPrefix(parent.getId()) + "N@" + getIdNumber(parent.getId()); } private static String toSourceTipId(MfConnector connector) { return getIdPrefix(connector.getId()) + "SRC@" + getIdNumber(connector.getId()); } private static String toTargetTipId(MfConnector connector) { return getIdPrefix(connector.getId()) + "TGT@" + getIdNumber(connector.getId()); } private static String toConstraintId(MfConstraintOwner parent) { return getIdPrefix(parent.getId()) + "C@" + getIdNumber(parent.getId()); } private static String toAnnotationId(MfConstraint parent) { return getIdPrefix(parent.getId()) + "A@" + getIdNumber(parent.getId()); } private static int getIdNumber(String id) { final int pos = id.indexOf('@'); return Integer.valueOf(id.substring(pos + 1)); } private static String getIdPrefix(String id) { final int pos = id.indexOf('@'); return id.substring(0, pos); } private void setNotes(MfElement element, String notes) { if (!StringUtils.isNullOrEmpty(notes)) { element.documentation() .id(toNotesId(element)) .text(notes) .language(language) .build(); } } private static void assertMandatory(Header mandatory, Header actual) { final HeaderMapper mapper = HeaderMapper.builder() .mandatory(mandatory) .actual(actual) .build(); Checks.assertTrue(mapper.hasAllMandatoryCells(), "Missing header columns: {}", mapper.getMissingMandatoryCells()); } private void preloadPackages() throws IOException { final File file = getFile(PACKAGE); log("Preload packages " + file); final List rows = loader.load(file, null, 0); assertNotEmptyRows(file, rows); packagesHeader = Header.builder().names(rows.get(0)).build(); assertMandatory(PACKAGE_HEADER, packagesHeader); this.packagesData.addAll(rows.subList(1, rows.size())); for (final Row data : packagesData) { final String guid = data.getValue(packagesHeader.getMatchingIndex(PACKAGE_GUID)); final int packageId = data.getValueAsInteger(packagesHeader.getMatchingIndex(PACKAGE_PACKAGE_ID), -1); packageIdToGuid.put(packageId, guid); info("Preloaded package " + packageId); } } private void preloadObjects() throws IOException { final File file = getFile(OBJECT); log("Preload objects " + file); final List rows = loader.load(file, null, 0); assertNotEmptyRows(file, rows); objectsHeader = Header.builder().names(rows.get(0)).build(); assertMandatory(OBJECT_HEADER, objectsHeader); this.objectsData.addAll(rows.subList(1, rows.size())); for (final Row data : objectsData) { final String guid = data.getValue(objectsHeader.getMatchingIndex(OBJECT_GUID)); final int objectId = data.getValueAsInteger(objectsHeader.getMatchingIndex(OBJECT_OBJECT_ID), -1); guidToObjectId.put(guid, objectId); info("Preloaded object " + objectId); } } private void createPackages() { log("Create packages"); // Stereotype and Author are not filled in table of packages. We must retrieve them in table of objects. // So we build a map from object id to package rows final Map oidToRow = new HashMap<>(); for (final Row data : objectsData) { final String objectType = data.getValue(objectsHeader.getMatchingIndex(OBJECT_OBJECT_TYPE)); if (TYPE_PACKAGE.equals(objectType)) { final int objectId = data.getValueAsInteger(objectsHeader.getMatchingIndex(OBJECT_OBJECT_ID), -1); oidToRow.put(objectId, data); } } final List todo = new ArrayList<>(packagesData); while (!todo.isEmpty()) { final List done = new ArrayList<>(); for (int index = 0; index < todo.size(); index++) { final Row data = todo.get(index); final int packageId = data.getValueAsInteger(packagesHeader.getMatchingIndex(PACKAGE_PACKAGE_ID), -1); final String name = data.getValue(packagesHeader.getMatchingIndex(PACKAGE_NAME)); final int parentId = data.getValueAsInteger(packagesHeader.getMatchingIndex(PACKAGE_PARENT_ID), -1); final String guid = data.getValue(packagesHeader.getMatchingIndex(PACKAGE_GUID)); final String notes = data.getValue(packagesHeader.getMatchingIndex(PACKAGE_NOTES)); if (parentId == 0) { // This package is not declared in objects file final int oid = newObjectId(); guidToObjectId.put(guid, oid); packageIdToGuid.put(packageId, guid); final MfPackage p = model.pack() .id(toObjectId(oid)) .guid(guid) .name(name) .build(); setNotes(p, notes); done.add(index); info("Created package " + p.getId()); } else { // Could return null, but should not final String parentPackageGuid = packageIdToGuid.get(parentId); // The parent package may not yet exist, and the map may return null, which can not be converted to int final int parentPackageObjectId = guidToObjectId.getOrDefault(parentPackageGuid, -1); final Optional parent = model.getItemWithId(toObjectId(parentPackageObjectId), MfPackage.class); if (parent.isPresent()) { final int packageObjectId = guidToObjectId.get(guid); final String stereotype = oidToRow.get(packageObjectId).getValue(objectsHeader.getMatchingIndex(OBJECT_STEREOTYPE)); final String author = oidToRow.get(packageObjectId).getValue(objectsHeader.getMatchingIndex(OBJECT_AUTHOR)); final String version = oidToRow.get(packageObjectId).getValue(objectsHeader.getMatchingIndex(OBJECT_VERSION)); final MfPackage p = parent.get() .pack() .id(toObjectId(packageObjectId)) .guid(guid) .name(name) .stereotype(stereotype) .meta(MfMetasItem.AUTHOR, author) .meta(MfMetasItem.VERSION, version) .build(); setNotes(p, notes); done.add(index); info("Created package " + p.getId()); } } } Collections.reverse(done); for (final int index : done) { todo.remove(index); } } } private void createPackagedItems() { log("Create packaged items"); for (final Row data : objectsData) { final String name = data.getValue(objectsHeader.getMatchingIndex(OBJECT_NAME)); final String objectType = data.getValue(objectsHeader.getMatchingIndex(OBJECT_OBJECT_TYPE)); final int objectId = data.getValueAsInteger(objectsHeader.getMatchingIndex(OBJECT_OBJECT_ID), -1); final String guid = data.getValue(objectsHeader.getMatchingIndex(OBJECT_GUID), null); final int packageId = data.getValueAsInteger(objectsHeader.getMatchingIndex(OBJECT_PACKAGE_ID), -1); final String note = data.getValue(objectsHeader.getMatchingIndex(OBJECT_NOTE)); final int objectAbstract = data.getValueAsInteger(objectsHeader.getMatchingIndex(OBJECT_ABSTRACT), 0); final String stereotype = data.getValue(objectsHeader.getMatchingIndex(OBJECT_STEREOTYPE)); final String author = data.getValue(objectsHeader.getMatchingIndex(OBJECT_AUTHOR)); final String version = data.getValue(objectsHeader.getMatchingIndex(OBJECT_VERSION)); final String sVisibility = data.getValue(objectsHeader.getMatchingIndex(OBJECT_VISIBILITY)); final MfVisibility visibility = toMfVisibility(sVisibility); final String packageGuid = packageIdToGuid.get(packageId); final int packageObjectId = guidToObjectId.get(packageGuid); final MfPackage parentPackage = model.getItemWithId(toObjectId(packageObjectId), MfPackage.class).orElseThrow(); final Location location = Location.builder() .path(TABLE_AT + OBJECT) .anchor(OBJECT_OBJECT_ID + "@" + objectId) .build(); if (TYPE_CLASS.equals(objectType)) { final MfClass cls = parentPackage.cls() .id(toObjectId(objectId)) .guid(guid) .name(name) .visibility(visibility) .isAbstract(objectAbstract != 0) .stereotype(stereotype) .meta(MfMetasItem.AUTHOR, author) .meta(MfMetasItem.VERSION, version) .build(); setNotes(cls, note); info("Created class " + cls.getId(), location); } else if (TYPE_INTERFACE.equals(objectType)) { final MfInterface xfce = parentPackage.xface() .id(toObjectId(objectId)) .guid(guid) .name(name) .visibility(visibility) .stereotype(stereotype) .meta(MfMetasItem.AUTHOR, author) .meta(MfMetasItem.VERSION, version) .build(); setNotes(xfce, note); info("Created interface " + xfce.getId(), location); } else if (TYPE_CONSTRAINT.equals(objectType)) { // The notes are the constraint specification final MfConstraint constraint = parentPackage.constraint() .id(toObjectId(objectId)) .guid(guid) .name(name) .stereotype(stereotype) .meta(MfMetasItem.AUTHOR, author) .meta(MfMetasItem.VERSION, version) .specification(note) .build(); info("Created constraint " + constraint.getId(), location); } else if (TYPE_PACKAGE.equals(objectType)) { // Ignore: already loaded info("Ignored package", location); } else { issues.issue(Issue.builder() .rule(EaObjectIgnored.RULE) .project(eapFile.getName()) .description("This object type " + objectType + " is not supported.") .location(location) .build()); } } } private void loadObjectsConstraints() throws IOException { final File file = getFile(OBJECT_CONSTRAINT); log("Load objects constraints " + file); final List rows = loader.load(file, null, 0); assertNotEmptyRows(file, rows); final Header actual = Header.builder().names(rows.get(0)).build(); assertMandatory(OBJECT_CONSTRAINT_HEADER, actual); final List constraintsData = rows.subList(1, rows.size()); for (final Row data : constraintsData) { final int objectId = data.getValueAsInteger(actual.getMatchingIndex(OBJECT_CONSTRAINT_OBJECT_ID), -1); final String specification = data.getValue(actual.getMatchingIndex(OBJECT_CONSTRAINT_SPECIFICATION)); final String type = data.getValue(actual.getMatchingIndex(OBJECT_CONSTRAINT_TYPE)); final String notes = data.getValue(actual.getMatchingIndex(OBJECT_CONSTRAINT_NOTES)); final Optional owner = model.getItemWithId(toObjectId(objectId), MfConstraintOwner.class); final Location location = Location.builder() .path(TABLE_AT + OBJECT_CONSTRAINT) .anchor(OBJECT_CONSTRAINT_OBJECT_ID + "@" + objectId) .build(); if (owner.isEmpty()) { issues.issue(Issue.builder() .rule(EaObjectIdMustBeValid.RULE) .project(eapFile.getName()) .description("Cannot find object with id " + objectId + " for constraint '" + specification + "'." + IGNORE_ROW) .location(location) .build()); } else { // We expect constraints to be only associated to classes and interfaces // It is really true? final MfType context = (MfType) owner.get(); final MfConstraint constraint = context.constraint() .id(toConstraintId(context)) .specification(specification) .stereotype(type) .build(); constraint.annotation() .id(toAnnotationId(constraint)) .target(context) .build(); setNotes(constraint, notes); info("Created object constraint " + constraint.getId(), location); } } } private void loadObjectsTags() throws IOException { final File file = getFile(OBJECT_TAG); log("Load objects tags " + file); final List rows = loader.load(file, null, 0); assertNotEmptyRows(file, rows); final Header actual = Header.builder().names(rows.get(0)).build(); assertMandatory(OBJECT_TAG_HEADER, actual); final List tagsData = rows.subList(1, rows.size()); for (final Row data : tagsData) { final int id = data.getValueAsInteger(actual.getMatchingIndex(OBJECT_TAG_ID), -1); final String guid = data.getValue(actual.getMatchingIndex(OBJECT_TAG_GUID), null); final int parentId = data.getValueAsInteger(actual.getMatchingIndex(OBJECT_TAG_PARENT_ID), -1); final String name = data.getValue(actual.getMatchingIndex(OBJECT_TAG_NAME)); final String value = data.getValue(actual.getMatchingIndex(OBJECT_TAG_VALUE)); final String notes = data.getValue(actual.getMatchingIndex(OBJECT_TAG_NOTES)); final Optional parent = model.getItemWithId(toObjectId(parentId), MfTagOwner.class); final Location location = Location.builder() .path(TABLE_AT + OBJECT_TAG) .anchor(OBJECT_TAG_ID + "@" + id) .build(); if (parent.isEmpty()) { issues.issue(Issue.builder() .rule(EaObjectIdMustBeValid.RULE) .project(eapFile.getName()) .description("Cannot find object with id " + parentId + " for tagged value '" + name + "'." + IGNORE_ROW) .location(location) .build()); } else { final MfTag tag = parent.get() .tag() .id(toObjectTagId(id)) .guid(guid) .name(name) .value(value) .build(); setNotes(tag, notes); info("Created object tag " + tag.getId(), location); } } } /** * Tries to return a type known by its id and/or name. *

* If a type with matching id is found, it is returned.
* Otherwise name is used: *

    *
  • If one type with that name exists, it is returned. *
  • If zero type with that name exists, a class is create din the Others package. *
  • If several types with that name exist, if a type with that name exists in the same root package than parent, it is * returned. *
  • Otherwise ... *
* * @param parent The object that needs the type. * @param attributeId The attribute id. Used for issues. * @param attributeName The attribute name. Used for issues. * @param typeId The type id. * @param typeName The type name. * @return The corresponding type or null. */ private MfType getAttributeType(MfMemberOwner parent, int attributeId, String attributeName, int typeId, String typeName) { // Try to find a type with the same id MfType type = model.getItemWithId(toObjectId(typeId), MfType.class).orElse(null); if (type == null) { // Didn't find a matching type using id // Try with name, if meaningful if (StringUtils.isNullOrEmpty(typeName)) { // There is no name final StringBuilder desc = new StringBuilder(); desc.append("Attribute " + parent.getQName() + "@" + attributeName + " has no type."); issues.issue(Issue.builder() .rule(EaAttributeTypeMustBeValid.RULE) .project(eapFile.getName()) .description(desc.toString()) .location(Location.builder() .path(TABLE_AT + ATTRIBUTE) .anchor(ATTRIBUTE_ID + "@" + attributeId) .build()) .build()); } else { // There is a name final Set types = new HashSet<>(model.collect(MfType.class, MfNameItem.named(typeName))); final String qualif; final Rule rule; if (types.size() == 1) { // One possible solution, use it type = types.iterator().next(); qualif = "the only possible "; if (getIdNumber(type.getId()) < 0) { rule = EaSubstituteTypeBuiltin.RULE; } else { rule = EaSubstituteTypeSafe.RULE; } } else if (types.isEmpty()) { // Not solution, create one type = builtin.cls() .id(toObjectId(newObjectId())) .name(typeName) .stereotype(BUILTIN) .meta(MfMetasItem.AUTHOR, "CDC") .build(); issues.issue(Issue.builder() .rule(EaCreateMissingType.RULE) .project(eapFile.getName()) .description("Create missing type " + type.getId() + " " + type.getQName() + ".") .location(Location.builder() .path("PACKAGE@" + builtin.getId() + "[" + builtin.getName() + "]") .build()) .build()); qualif = ""; rule = EaSubstituteTypeBuiltin.RULE; } else { // Too many solutions, which one should we select? final MfPackage root = parent.getRootPackage(); int count = 0; for (final MfType t : types) { if (t.getRootPackage() == root) { type = t; count++; } } if (count > 1) { type = null; } if (type == null) { // Could we find a better choice ? type = types.iterator().next(); qualif = "the randomly selected "; } else { qualif = "the selected "; } rule = EaSubstituteTypeRisky.RULE; } final StringBuilder desc = new StringBuilder(); desc.append("Used " + qualif + "type [" + type.getId() + " " + type.getQName() + "]\n" + "instead of [" + typeId + " " + typeName + "]\n" + "for attribute " + parent.getQName() + "@" + attributeName + "."); if (types.size() > 1) { desc.append("\nPossible types:"); for (final MfType t : types) { desc.append("\n " + t.getQName()); } } issues.issue(Issue.builder() .rule(rule) .project(eapFile.getName()) .description(desc.toString()) .location(Location.builder() .path(TABLE_AT + ATTRIBUTE) .anchor(ATTRIBUTE_ID + "@" + attributeId) .build()) .build()); } } return type; } private void loadAttributes() throws IOException { final File file = getFile(ATTRIBUTE); log("Load attributes " + file); final List rows = loader.load(file, null, 0); assertNotEmptyRows(file, rows); final Header actual = Header.builder().names(rows.get(0)).build(); assertMandatory(ATTRIBUTE_HEADER, actual); final int posIndex = actual.getMatchingIndex(ATTRIBUTE_POS); final Comparator posComparator = Comparator.comparingInt((Row row) -> row.getValueAsInteger(posIndex, -1)); final List attributesData = rows.subList(1, rows.size()); // Sort attributes using their pos Collections.sort(attributesData, posComparator); for (final Row data : attributesData) { final int id = data.getValueAsInteger(actual.getMatchingIndex(ATTRIBUTE_ID), -1); final String guid = data.getValue(actual.getMatchingIndex(OBJECT_TAG_GUID), null); final String name = data.getValue(actual.getMatchingIndex(ATTRIBUTE_NAME)); final String sVisibility = data.getValue(actual.getMatchingIndex(ATTRIBUTE_VISIBILITY)); final String stereotype = data.getValue(actual.getMatchingIndex(ATTRIBUTE_STEREOTYPE)); final int objectId = data.getValueAsInteger(actual.getMatchingIndex(ATTRIBUTE_OBJECT_ID), -1); final int typeId = data.getValueAsInteger(actual.getMatchingIndex(ATTRIBUTE_TYPE_ID), -1); final String typeName = data.getValue(actual.getMatchingIndex(ATTRIBUTE_TYPE_NAME)); final String lowerBound = data.getValue(actual.getMatchingIndex(ATTRIBUTE_LOWER_BOUND)); final String upperBound = data.getValue(actual.getMatchingIndex(ATTRIBUTE_UPPER_BOUND)); final String notes = data.getValue(actual.getMatchingIndex(ATTRIBUTE_NOTES)); final MfElement rawParent = model.getItemWithId(toObjectId(objectId)).orElseThrow(); final MfMemberOwner parent = rawParent instanceof final MfMemberOwner x ? x : null; final MfType type = getAttributeType(parent, id, name, typeId, typeName); final MfCardinality cardinality = MfCardinality.of(lowerBound, upperBound); final MfVisibility visibility = toMfVisibility(sVisibility); final Location location = Location.builder() .path(TABLE_AT + ATTRIBUTE) .anchor(ATTRIBUTE_ID + "@" + id) .build(); if (parent == null) { if (rawParent != null) { issues.issue(Issue.builder() .rule(EaAttributeIgnored.RULE) .project(eapFile.getName()) .description("Attaching an attribute to " + rawParent.getKind() + " is not supported.") .location(location) .build()); } else { issues.issue(Issue.builder() .rule(EaObjectIdMustBeValid.RULE) .project(eapFile.getName()) .description("Cannot find object with id " + objectId + " as parent of attribute [" + id + " " + name + "].") .location(location) .build()); } } else { final MfProperty att = parent.property() .name(name) .visibility(visibility) .id(toAttributeId(id)) .guid(guid) .cardinality(cardinality) .type(type) .stereotype(stereotype) .build(); setNotes(att, notes); info("Created attribute " + att.getId(), location); } } } private void loadAttributesConstraints() throws IOException { final File file = getFile(ATTRIBUTE_CONSTRAINT); log("Load attributes constraints " + file); final List rows = loader.load(file, null, 0); assertNotEmptyRows(file, rows); final Header actual = Header.builder().names(rows.get(0)).build(); assertMandatory(ATTRIBUTE_CONSTRAINT_HEADER, actual); final List tagsData = rows.subList(1, rows.size()); for (final Row data : tagsData) { final int objectId = data.getValueAsInteger(actual.getMatchingIndex(ATTRIBUTE_CONSTRAINT_OBJECT_ID), -1); final String attName = data.getValue(actual.getMatchingIndex(ATTRIBUTE_CONSTRAINT_ATT_NAME)); final String specification = data.getValue(actual.getMatchingIndex(ATTRIBUTE_CONSTRAINT_SPECIFICATION)); final String type = data.getValue(actual.getMatchingIndex(ATTRIBUTE_CONSTRAINT_TYPE)); final String notes = data.getValue(actual.getMatchingIndex(ATTRIBUTE_CONSTRAINT_NOTES)); final MfMemberOwner owner = model.getItemWithId(toObjectId(objectId), MfMemberOwner.class).orElse(null); final Optional att = owner == null ? null : owner.getProperty(attName); final Location location = Location.builder() .path(TABLE_AT + ATTRIBUTE_CONSTRAINT) .anchor(OBJECT_CONSTRAINT_OBJECT_ID + "@" + objectId) .build(); if (owner == null) { issues.issue(Issue.builder() .rule(EaObjectIdMustBeValid.RULE) .project(eapFile.getName()) .description("Cannot find object with id " + objectId + " for attribute constraint '" + specification + "'." + IGNORE_ROW) .location(location) .build()); } else if (att.isEmpty()) { issues.issue(Issue.builder() .rule(EaAttributeNameMustBeValid.RULE) .project(eapFile.getName()) .description("Cannot find attribute with name " + attName + " for attribute constraint '" + specification + "'." + IGNORE_ROW) .location(location) .build()); } else { final MfConstraint constraint = att.get() .constraint() .id(toConstraintId(att.orElseThrow())) .specification(specification) .stereotype(type) .build(); constraint.annotation() .id(toAnnotationId(constraint)) .target(att.get()) .build(); setNotes(constraint, notes); info("Created attribute constraint " + constraint.getId(), location); } } } private void loadAttributesTags() throws IOException { final File file = getFile(ATTRIBUTE_TAG); log("Load attributes tags " + file); final List rows = loader.load(file, null, 0); assertNotEmptyRows(file, rows); final Header actual = Header.builder().names(rows.get(0)).build(); assertMandatory(ATTRIBUTE_TAG_HEADER, actual); final List tagsData = rows.subList(1, rows.size()); for (final Row data : tagsData) { final int id = data.getValueAsInteger(actual.getMatchingIndex(ATTRIBUTE_TAG_ID), -1); final String guid = data.getValue(actual.getMatchingIndex(ATTRIBUTE_TAG_GUID), null); final int parentId = data.getValueAsInteger(actual.getMatchingIndex(ATTRIBUTE_TAG_PARENT_ID), -1); final String name = data.getValue(actual.getMatchingIndex(ATTRIBUTE_TAG_NAME)); final String value = data.getValue(actual.getMatchingIndex(ATTRIBUTE_TAG_VALUE)); final String notes = data.getValue(actual.getMatchingIndex(ATTRIBUTE_TAG_NOTES)); final MfProperty parent = model.getItemWithId(toAttributeId(parentId), MfProperty.class).orElse(null); final Location location = Location.builder() .path(TABLE_AT + ATTRIBUTE_TAG) .anchor(ATTRIBUTE_TAG_ID + "@" + id) .build(); if (parent == null) { issues.issue(Issue.builder() .rule(EaAttributeIdMustBeValid.RULE) .project(eapFile.getName()) .description("Cannot find attribute with id " + parentId + "\n" + "for tag [" + id + " " + name + " " + value + "]." + IGNORE_ROW) .location(location) .build()); } else { final MfTag tag = parent.tag() .id(toAttributeTagId(id)) .guid(guid) .name(name) .value(value) .build(); setNotes(tag, notes); info("Created attribute tag " + tag.getId(), location); } } } private static EaConnectorType toEaConnectorType(String type) { if (type == null) { return null; } else { switch (type) { case CTYPE_AGGREGATION: return EaConnectorType.AGGREGATION; case CTYPE_ASSOCIATION: return EaConnectorType.ASSOCIATION; case CTYPE_DEPENDENCY: return EaConnectorType.DEPENDENCY; case CTYPE_GENERALIZATION: return EaConnectorType.GENERALIZATION; case CTYPE_NOTE_LINK: return EaConnectorType.ANNOTATION; case CTYPE_REALISATION: return EaConnectorType.IMPLEMENTATION; default: return null; } } } private static EaConnectorSubtype toEaConnectorSubtype(String subtype) { if (subtype == null) { return null; } else { switch (subtype) { case CSUBTYPE_CLASS: return EaConnectorSubtype.CLASS; case CSUBTYPE_STRONG: return EaConnectorSubtype.STRONG; case CSUBTYPE_WEAK: return EaConnectorSubtype.WEAK; default: return null; } } } private static boolean toOrdered(int ordered) { return ordered == 1; } private static EaConnectorDirection toEaConnectorDirection(String s) { if (CDIR_SRC_DST.equals(s)) { return EaConnectorDirection.FORWARD; } else if (CDIR_DST_SRC.equals(s)) { return EaConnectorDirection.BACKWARD; } else if (CDIR_BI_DIRECTIONAL.equals(s)) { return EaConnectorDirection.BOTH; } else if (CDIR_UNSPECIFIED.equals(s) || StringUtils.isNullOrEmpty(s)) { return EaConnectorDirection.NONE; } else { throw new IllegalArgumentException(s); } } private static MfVisibility toMfVisibility(String s) { if (VIS_PUBLIC.equals(s)) { return MfVisibility.PUBLIC; } else if (VIS_PROTECTED.equals(s)) { return MfVisibility.PROTECTED; } else if (VIS_PRIVATE.equals(s)) { return MfVisibility.PRIVATE; } else if (StringUtils.isNullOrEmpty(s)) { return null; } else { throw new IllegalArgumentException("Cannot decode " + s + " to a visibility."); } } private void loadConnectors() throws IOException { final File file = getFile(CONNECTOR); log("Load connectors " + file); final List rows = loader.load(file, null, 0); assertNotEmptyRows(file, rows); final Header actual = Header.builder().names(rows.get(0)).build(); assertMandatory(CONNECTOR_HEADER, actual); final int idIndex = actual.getMatchingIndex(CONNECTOR_ID); final Comparator idComparator = Comparator.comparingInt((Row row) -> row.getValueAsInteger(idIndex, -1)); final List connectorsData = rows.subList(1, rows.size()); Collections.sort(connectorsData, idComparator); for (final Row data : connectorsData) { // Do not use SourceIsNavigable or TargetIsNavigable // They seem to be a disagreement with Direction which seems to produce the correct values. // Note that by default in EA an aggregation target (whole side) is navigable. // This is the opposite of what I would expect. final int id = data.getValueAsInteger(actual.getMatchingIndex(CONNECTOR_ID), -1); final String guid = data.getValue(actual.getMatchingIndex(CONNECTOR_GUID), null); final String sDirection = data.getValue(actual.getMatchingIndex(CONNECTOR_DIRECTION)); final String name = data.getValue(actual.getMatchingIndex(CONNECTOR_NAME)); final String notes = data.getValue(actual.getMatchingIndex(CONNECTOR_NOTES)); final String sType = data.getValue(actual.getMatchingIndex(CONNECTOR_TYPE)); final String sSubtype = data.getValue(actual.getMatchingIndex(CONNECTOR_SUBTYPE)); final String srcCard = data.getValue(actual.getMatchingIndex(CONNECTOR_SRC_CARD)); final String srcRole = data.getValue(actual.getMatchingIndex(CONNECTOR_SRC_ROLE)); final String srcRoleNote = data.getValue(actual.getMatchingIndex(CONNECTOR_SRC_ROLE_NOTE)); final int srcIsOrdered = data.getValueAsInteger(actual.getMatchingIndex(CONNECTOR_SRC_IS_ORDERED), 0); final String dstCard = data.getValue(actual.getMatchingIndex(CONNECTOR_DST_CARD)); final String dstRole = data.getValue(actual.getMatchingIndex(CONNECTOR_DST_ROLE)); final String dstRoleNote = data.getValue(actual.getMatchingIndex(CONNECTOR_DST_ROLE_NOTE)); final int dstIsOrdered = data.getValueAsInteger(actual.getMatchingIndex(CONNECTOR_DST_IS_ORDERED), 0); final int startObjectId = data.getValueAsInteger(actual.getMatchingIndex(CONNECTOR_START_OBJECT_ID), -1); final int endObjectId = data.getValueAsInteger(actual.getMatchingIndex(CONNECTOR_END_OBJECT_ID), -1); final EaConnectorType type = toEaConnectorType(sType); final EaConnectorSubtype subtype = toEaConnectorSubtype(sSubtype); final EaConnectorDirection direction; if (type == EaConnectorType.AGGREGATION && fixDirection) { direction = EaConnectorDirection.BACKWARD; } else { direction = toEaConnectorDirection(sDirection); } final boolean srcIsNavigable = direction == EaConnectorDirection.BACKWARD || direction == EaConnectorDirection.BOTH; final boolean dstIsNavigable = direction == EaConnectorDirection.FORWARD || direction == EaConnectorDirection.BOTH; final MfCardinality srcCardinality = MfCardinality.of(srcCard); final MfCardinality dstCardinality = MfCardinality.of(dstCard); final MfElement startObject = model.getItemWithId(toObjectId(startObjectId)).orElse(null); final MfElement endObject = model.getItemWithId(toObjectId(endObjectId)).orElse(null); final String path = TABLE_AT + CONNECTOR; final String anchor = CONNECTOR_ID + "=" + id; final Location location = Location.builder() .path(path) .anchor(anchor) .build(); if (type == null) { LOGGER.warn("Unrecognized connector type {}", sType); issues.issue(Issue.builder() .rule(EaConnectorTypeIgnored.RULE) .project(eapFile.getName()) .description("Cannot recognize connector type " + sType + " for connector " + id + "." + IGNORE_ROW) .location(location) .build()); } if (startObject == null) { issues.issue(Issue.builder() .rule(EaObjectIdMustBeValid.RULE) .project(eapFile.getName()) .description("Cannot find start object with id " + startObjectId + " for connector " + id + "." + IGNORE_ROW) .location(location) .build()); } if (endObject == null) { issues.issue(Issue.builder() .rule(EaObjectIdMustBeValid.RULE) .project(eapFile.getName()) .description("Cannot find end object with id " + startObjectId + " for connector " + id + "." + IGNORE_ROW) .location(location) .build()); } if (type != null && startObject != null && endObject != null) { // Are start and end objects compliant, without changing direction, with connector type? final boolean isRawCompliant = type.isCompliantWith(startObject.getClass(), endObject.getClass()); // Adapt connector direction so that: // EXTENDS is the source, EXTENDED_BY is target // IMPLEMENTS is the source, IMPLEMENTED_BY is target // AGGREGATES is the source, AGGREGATED_BY is target // ANNOTATES is the source, ANNOTATED_BY is the target // ... // Cases where raw direction is kept unchanged. // With annotation, direction has no real semantic, but technically we // fix direction from the annotation to the annotated object. final boolean forward = type == EaConnectorType.GENERALIZATION || type == EaConnectorType.IMPLEMENTATION || type == EaConnectorType.DEPENDENCY || (type == EaConnectorType.ANNOTATION && isRawCompliant) || type == EaConnectorType.ASSOCIATION; final MfElement sourceObject = forward ? startObject : endObject; final MfElement targetObject = forward ? endObject : startObject; if (!type.isCompliantWith(sourceObject.getClass(), targetObject.getClass())) { issues.issue(Issue.builder() .rule(EaConnectorMustBeValid.RULE) .project(eapFile.getName()) .description("Cannot connect " + sourceObject.getClass().getSimpleName() + " to " + targetObject.getClass().getSimpleName() + " when type is " + type + " for connector " + id + "." + IGNORE_ROW) .location(location) .build()); } else if (!type.isCompliantWith(subtype)) { issues.issue(Issue.builder() .rule(EaConnectorIgnored.RULE) .project(eapFile.getName()) .description("This connector " + type + " with " + subtype + " is not supported " + " for connector " + id + "." + IGNORE_ROW) .location(location) .build()); } else { if (type == EaConnectorType.AGGREGATION || type == EaConnectorType.ASSOCIATION) { final MfConnector connector; if (type == EaConnectorType.ASSOCIATION) { connector = ((MfConnectorOwner) sourceObject).association() .id(toConnectorId(id)) .guid(guid) .name(name) .build(); info("Created association " + connector.getId(), location); } else if (subtype == EaConnectorSubtype.STRONG) { connector = ((MfConnectorOwner) sourceObject).composition() .id(toConnectorId(id)) .guid(guid) .name(name) .build(); info("Created composition " + connector.getId(), location); } else if (subtype == EaConnectorSubtype.WEAK || subtype == null) { connector = ((MfConnectorOwner) sourceObject).aggregation() .id(toConnectorId(id)) .guid(guid) .name(name) .build(); info("Created aggregation " + connector.getId(), location); } else { connector = null; error("Implementation error, ignore connector", location); } if (connector != null) { setNotes(connector, notes); final MfTip src = connector.source() .id(toSourceTipId(connector)) .name(forward ? srcRole : dstRole) .type((MfType) sourceObject) .cardinality(forward ? srcCardinality : dstCardinality) .isOrdered(forward ? toOrdered(srcIsOrdered) : toOrdered(dstIsOrdered)) .isNavigable(forward ? srcIsNavigable : dstIsNavigable) .build(); setNotes(src, forward ? srcRoleNote : dstRoleNote); final MfTip tgt = connector.target() .id(toTargetTipId(connector)) .name(forward ? dstRole : srcRole) .type((MfType) targetObject) .cardinality(forward ? dstCardinality : srcCardinality) .isOrdered(forward ? toOrdered(dstIsOrdered) : toOrdered(srcIsOrdered)) .isNavigable(forward ? dstIsNavigable : srcIsNavigable) .build(); setNotes(tgt, forward ? dstRoleNote : srcRoleNote); } } else if (type == EaConnectorType.GENERALIZATION) { final MfSpecialization x = ((MfSpecializationOwner) sourceObject).specialization() .id(toConnectorId(id)) .guid(guid) .generalType((MfType) targetObject) .build(); setNotes(x, notes); info("Created specialization " + x.getId(), location); } else if (type == EaConnectorType.IMPLEMENTATION) { final MfImplementation x = ((MfImplementationOwner) sourceObject).implementation() .id(toConnectorId(id)) .guid(guid) .generalType((MfInterface) targetObject) .build(); setNotes(x, notes); info("Created implementation " + x.getId(), location); } else if (type == EaConnectorType.ANNOTATION) { final MfAnnotation x = ((MfAnnotationOwner) sourceObject).annotation() .id(toConnectorId(id)) .guid(guid) .target(targetObject) .build(); setNotes(x, notes); info("Created annotation " + x.getId(), location); } else if (type == EaConnectorType.DEPENDENCY) { final MfDependency x = ((MfDependencyOwner) sourceObject).dependency() .id(toConnectorId(id)) .guid(guid) .target(targetObject) .build(); setNotes(x, notes); info("Created dependency " + x.getId(), location); } else { issues.issue(Issue.builder() .rule(EaConnectorIgnored.RULE) .project(eapFile.getName()) .description("This connector type " + type + " is not supported.") .location(location) .build()); } } } } } private void loadConnectorsConstraints() throws IOException { final File file = getFile(CONNECTOR_CONSTRAINT); log("Load connectors constraints " + file); final List rows = loader.load(file, null, 0); assertNotEmptyRows(file, rows); final Header actual = Header.builder().names(rows.get(0)).build(); assertMandatory(CONNECTOR_CONSTRAINT_HEADER, actual); final List tagsData = rows.subList(1, rows.size()); for (final Row data : tagsData) { final int connectorId = data.getValueAsInteger(actual.getMatchingIndex(CONNECTOR_CONSTRAINT_CONNECTOR_ID), -1); final String specification = data.getValue(actual.getMatchingIndex(CONNECTOR_CONSTRAINT_SPECIFICATION)); final String type = data.getValue(actual.getMatchingIndex(CONNECTOR_CONSTRAINT_TYPE)); final String notes = data.getValue(actual.getMatchingIndex(CONNECTOR_CONSTRAINT_NOTES)); final Optional owner = model.getItemWithId(toConnectorId(connectorId), MfConstraintOwner.class); if (owner.isEmpty()) { issues.issue(Issue.builder() .rule(EaConnectorIdMustBeValid.RULE) .project(eapFile.getName()) .description("Cannot find connector with id " + connectorId + " for constraint '" + specification + "'." + IGNORE_ROW) .location(Location.builder() .path(TABLE_AT + CONNECTOR_CONSTRAINT) .anchor(CONNECTOR_CONSTRAINT_CONNECTOR_ID + "@" + connectorId) .build()) .build()); } else { final MfConstraint constraint = owner.get() .constraint() .id(toConstraintId(owner.orElseThrow())) .specification(specification) .stereotype(type) .build(); constraint.annotation() .id(toAnnotationId(constraint)) .target(owner.get()) .build(); setNotes(constraint, notes); info("Created connector constraint" + constraint.getId(), Location.builder() .path(TABLE_AT + CONNECTOR_CONSTRAINT) .anchor(CONNECTOR_CONSTRAINT_CONNECTOR_ID + "@" + connectorId) .build()); } } } private void loadConnectorsTags() throws IOException { final File file = getFile(CONNECTOR_TAG); log("Load connectors tags " + file); final List rows = loader.load(file, null, 0); assertNotEmptyRows(file, rows); final Header actual = Header.builder().names(rows.get(0)).build(); assertMandatory(CONNECTOR_TAG_HEADER, actual); final List tagsData = rows.subList(1, rows.size()); for (final Row data : tagsData) { final int id = data.getValueAsInteger(actual.getMatchingIndex(CONNECTOR_TAG_ID), -1); final String guid = data.getValue(actual.getMatchingIndex(CONNECTOR_TAG_GUID), null); final int parentId = data.getValueAsInteger(actual.getMatchingIndex(CONNECTOR_TAG_PARENT_ID), -1); final String name = data.getValue(actual.getMatchingIndex(CONNECTOR_TAG_NAME)); final String value = data.getValue(actual.getMatchingIndex(CONNECTOR_TAG_VALUE)); final String notes = data.getValue(actual.getMatchingIndex(CONNECTOR_TAG_NOTES)); final Optional parent = model.getItemWithId(toConnectorId(parentId), MfTagOwner.class); if (parent.isEmpty()) { issues.issue(Issue.builder() .rule(EaConnectorIdMustBeValid.RULE) .project(eapFile.getName()) .description("Cannot find connector with id " + parentId + "\n" + "for tag [" + id + " " + name + " " + value + "]." + IGNORE_ROW) .location(Location.builder() .path(TABLE_AT + CONNECTOR_TAG) .anchor(CONNECTOR_TAG_ID + "@" + id) .build()) .build()); } else { final MfTag tag = parent.get() .tag() .id(toConnectorTagId(id)) .guid(guid) .name(name) .value(value) .build(); setNotes(tag, notes); info("Created connector tag " + tag.getId()); } } } public static Builder builder() { return new Builder(); } public static final class Builder { private String author = "???"; private File eapFile; private File dumpDir; private WorkbookKind kind = WorkbookKind.XLSX; private String basename; private String name; private boolean verbose = false; private boolean fixDirection = false; private String language; private Builder() { } public Builder author(String author) { this.author = author; return this; } public Builder eapFile(File eapFile) { this.eapFile = eapFile; return this; } public Builder dumpDir(File dumpDir) { this.dumpDir = dumpDir; return this; } public Builder kind(WorkbookKind kind) { this.kind = kind; return this; } public Builder basename(String basename) { this.basename = basename; return this; } public Builder name(String name) { this.name = name; return this; } public Builder verbose(boolean verbose) { this.verbose = verbose; return this; } public Builder fixDirection(boolean fixDirection) { this.fixDirection = fixDirection; return this; } public Builder language(String language) { this.language = language; return this; } public Builder language(Locale language) { this.language = language.getLanguage(); return this; } public EaDumpToMfImpl build() { return new EaDumpToMfImpl(this); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy