cdc.mf.ea.EaDumpToMfImpl Maven / Gradle / Ivy
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);
}
}
}