org.teamapps.universaldb.model.DatabaseModel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of universal-db Show documentation
Show all versions of universal-db Show documentation
Ultra fast TeamApps database
The newest version!
/*-
* ========================LICENSE_START=================================
* UniversalDB
* ---
* Copyright (C) 2014 - 2024 TeamApps.org
* ---
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =========================LICENSE_END==================================
*/
package org.teamapps.universaldb.model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.teamapps.commons.util.collections.ByKeyComparisonResult;
import org.teamapps.commons.util.collections.CollectionUtil;
import org.teamapps.message.protocol.utils.MessageUtils;
import org.teamapps.universaldb.generator.PojoCodeGenerator;
import java.io.*;
import java.lang.invoke.MethodHandles;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
public class DatabaseModel {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final static int DATABASE_MODEL_VERSION = 1;
private final String name;
private final String title;
private final String namespace;
private final String modelClassName;
private final List enums = new ArrayList<>();
private final List tables = new ArrayList<>();
private final List views = new ArrayList<>(); //todo remove!
private long pojoBuildTime;
private int version;
private int dateCreated;
private int dateModified;
public DatabaseModel(String title) {
this(title, title, "org.teamapps.model");
}
public DatabaseModel(String name, String title, String namespace) {
this(name, title, namespace, NamingUtils.createName(name) + "Model");
}
public DatabaseModel(String name, String title, String namespace, String modelClassName) {
this.name = NamingUtils.createName(name);
this.title = NamingUtils.createTitle(title);
this.namespace = namespace;
this.pojoBuildTime = System.currentTimeMillis();
this.modelClassName = NamingUtils.createName(modelClassName);
}
public DatabaseModel(byte[] bytes) throws IOException {
this(new DataInputStream(new ByteArrayInputStream(bytes)));
}
public DatabaseModel(DataInputStream dis) throws IOException {
int modelVersion = dis.readInt();
name = MessageUtils.readString(dis);
title = MessageUtils.readString(dis);
namespace = MessageUtils.readString(dis);
modelClassName = MessageUtils.readString(dis);
pojoBuildTime = dis.readLong();
version = dis.readInt();
dateCreated = dis.readInt();
dateModified = dis.readInt();
int enumCount = dis.readInt();
for (int i = 0; i < enumCount; i++) {
enums.add(new EnumModel(dis));
}
int tableCount = dis.readInt();
List> resolveFunctions = new ArrayList<>();
for (int i = 0; i < tableCount; i++) {
TableModel tableModel = new TableModel(dis, resolveFunctions, this);
tables.add(tableModel);
}
for (Function resolveFunction : resolveFunctions) {
if (!resolveFunction.apply(this)) {
throw new RuntimeException("Db model mapping error");
}
}
int views = dis.readInt();
}
public String getModelCode() {
try {
return new PojoCodeGenerator().createModelProviderCode(this);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String getModelClassCode() {
try {
return new PojoCodeGenerator().createModelProviderClassCode(this);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void write(DataOutputStream dos) throws IOException {
dos.writeInt(DATABASE_MODEL_VERSION);
MessageUtils.writeString(dos, name);
MessageUtils.writeString(dos, title);
MessageUtils.writeString(dos, namespace);
MessageUtils.writeString(dos, modelClassName);
dos.writeLong(pojoBuildTime);
dos.writeInt(version);
dos.writeInt(dateCreated);
dos.writeInt(dateModified);
dos.writeInt(enums.size());
for (EnumModel enumModel : enums) {
enumModel.write(dos);
}
dos.writeInt(tables.size());
for (TableModel table : tables) {
table.write(dos);
}
dos.writeInt(views.size());
for (ViewModel view : views) {
view.write(dos);
}
}
public void initialize() {
if (version != 0) {
return;
}
version = 1;
int timestamp = (int) (System.currentTimeMillis() / 1000);
dateCreated = timestamp;
AtomicInteger idGenerator = new AtomicInteger();
for (EnumModel enumModel : enums) {
enumModel.setVersionCreated(version);
enumModel.setDateCreated(timestamp);
}
for (TableModel table : tables) {
table.setTableId(idGenerator.incrementAndGet());
table.setVersionCreated(version);
table.setDateCreated(timestamp);
for (FieldModel field : table.getFields()) {
field.setFieldId(idGenerator.incrementAndGet());
field.setVersionCreated(version);
field.setDateCreated(timestamp);
}
}
for (ViewModel view : views) {
view.setVersionCreated(version);
view.setDateCreated(timestamp);
//todo add view fields with meta data!
}
}
public void initialize(Function tableIdProvider, BiFunction fieldIdProvider) {
if (version != 0) {
return;
}
version = 1;
int timestamp = (int) (System.currentTimeMillis() / 1000);
dateCreated = timestamp;
for (EnumModel enumModel : enums) {
enumModel.setVersionCreated(version);
enumModel.setDateCreated(timestamp);
}
for (TableModel table : tables) {
table.setTableId(tableIdProvider.apply(table));
table.setVersionCreated(version);
table.setDateCreated(timestamp);
for (FieldModel field : table.getFields()) {
field.setFieldId(fieldIdProvider.apply(table, field));
field.setVersionCreated(version);
field.setDateCreated(timestamp);
}
}
}
public void initializeWithForeignModel(DatabaseModel model) {
if (version != 0) {
return;
}
version = model.getVersion();
dateCreated = model.getDateCreated();
dateModified = model.getDateModified();
for (EnumModel enumModel : enums) {
EnumModel referenceModel = model.getEnumModel(enumModel.getName());
if (referenceModel != null) {
enumModel.setVersionCreated(referenceModel.getVersionCreated());
enumModel.setDateCreated(referenceModel.getDateCreated());
enumModel.setDeprecated(referenceModel.isDeprecated());
} else {
logger.warn("Missing enum:" + enumModel.getName());
}
}
for (TableModel table : tables) {
TableModel referenceTable = model.getTable(table.getName());
if (referenceTable != null) {
table.setTableId(referenceTable.getTableId());
table.setVersionCreated(referenceTable.getVersionCreated());
table.setVersionModified(referenceTable.getVersionModified());
table.setDateModified(referenceTable.getDateModified());
table.setDateCreated(referenceTable.getDateCreated());
table.setDeprecated(referenceTable.isDeprecated());
table.setDeleted(referenceTable.isDeleted());
for (FieldModel field : table.getFields()) {
FieldModel referenceField = referenceTable.getField(field.getName());
if (referenceField != null) {
field.setFieldId(referenceField.getFieldId());
field.setVersionCreated(referenceField.getVersionCreated());
field.setVersionModified(referenceField.getVersionModified());
field.setDateModified(referenceField.getDateModified());
field.setDateCreated(referenceField.getDateCreated());
field.setDeprecated(referenceField.isDeprecated());
field.setDeleted(referenceField.isDeleted());
} else {
logger.warn("Missing field:" + table.getName() + "." + field.getName());
}
}
} else {
logger.warn("Missing table:" + table.getName());
}
}
AtomicInteger idGenerator = new AtomicInteger(getMaxId());
int timestamp = (int) (System.currentTimeMillis() / 1000);
//sanity check
for (TableModel table : getTables()) {
if (table.getTableId() == 0) {
table.setTableId(idGenerator.incrementAndGet());
table.setVersionCreated(version);
table.setDateCreated(timestamp);
logger.warn("WARNING: table without id on sanity check - create id:" + table.getName());
}
for (FieldModel field : table.getFields()) {
if (field.getFieldId() == 0) {
System.out.println("WARNING: fields without id on sanity check - create id:" + table.getName() + ", field:" + field.getName());
field.setFieldId(idGenerator.incrementAndGet());
field.setVersionCreated(version);
field.setDateCreated(timestamp);
}
}
}
//id duplicates check:
Set existingIdSet = new HashSet<>();
for (TableModel table : getTables()) {
if (existingIdSet.contains(table.getTableId())) {
throw new RuntimeException("Error: duplicate table id:" + table.getName());
}
existingIdSet.add(table.getTableId());
for (FieldModel field : table.getFields()) {
if (existingIdSet.contains(field.getFieldId())) {
throw new RuntimeException("Error: duplicate field id:" + table.getName() + "." + field.getFieldId());
}
existingIdSet.add(field.getFieldId());
}
}
}
public void mergeModel(DatabaseModel model) {
if (!model.isValid()) {
throw new RuntimeException("Invalid model for merge");
}
if (!isCompatible(model)) {
throw new RuntimeException("Incompatible model for merge");
}
int version = getVersion() + 1;
int timestamp = (int) (System.currentTimeMillis() / 1000);
setVersion(version);
setDateModified(timestamp);
AtomicInteger idGenerator = new AtomicInteger(getMaxId());
ByKeyComparisonResult enumCompare = CollectionUtil.compareByKey(enums, model.getEnums(), EnumModel::getName, EnumModel::getName, true);
//existing enums
for (EnumModel enumModel : enumCompare.getBEntriesInA()) {
EnumModel existingEnum = enumCompare.getA(enumModel);
if (enumModel.getEnumNames().size() > existingEnum.getEnumNames().size()) {
existingEnum.updateValues(enumModel.getEnumNames(), enumModel.getEnumTitles());
existingEnum.setVersionModified(version);
existingEnum.setDateModified(version);
}
}
//new enums
for (EnumModel enumModel : enumCompare.getBEntriesNotInA()) {
enumModel.setVersionCreated(version);
enumModel.setDateCreated(timestamp);
addEnum(enumModel);
}
//removed enums
for (EnumModel existingEnum : enumCompare.getAEntriesNotInB().stream().filter(e -> !e.isDeleted() && !e.isDeprecated()).toList()) {
existingEnum.setDeprecated(true);
existingEnum.setVersionModified(version);
existingEnum.setDateModified(timestamp);
}
ByKeyComparisonResult tableCompare = CollectionUtil.compareByKey(getTables(), model.getTables(), TableModel::getName, TableModel::getName, true);
//existing tables
for (TableModel table : tableCompare.getBEntriesInA()) {
TableModel existingTable = tableCompare.getA(table);
if (existingTable.isRemoteTable()) {
if ((existingTable.getRemoteDatabaseNamespace() == null && table.getRemoteDatabaseNamespace() != null) || (existingTable.getRemoteDatabaseNamespace() != null && !existingTable.getRemoteDatabaseNamespace().equals(table.getRemoteDatabaseNamespace()))) {
//todo table change
//todo set table namespace
}
}
ByKeyComparisonResult fieldResult = CollectionUtil.compareByKey(existingTable.getFields(), table.getFields(), FieldModel::getName, FieldModel::getName, true);
//existing fields
for (FieldModel field : fieldResult.getBEntriesInA()) {
boolean fieldIsUpdated = false;
FieldModel existingField = fieldResult.getA(field);
if (existingField.getTitle().equals(field.getTitle())) {
existingField.setTitle(field.getTitle());
fieldIsUpdated = true;
}
if (fieldIsUpdated) {
field.setVersionModified(version);
field.setDateModified(timestamp);
}
}
//new fields
for (FieldModel field : fieldResult.getBEntriesNotInA()) {
existingTable.addFieldModel(field);
if (field.getFieldId() == 0) {
field.setFieldId(idGenerator.incrementAndGet());
field.setVersionCreated(version);
field.setDateCreated(timestamp);
}
}
//removed fields
for (FieldModel existingField : fieldResult.getAEntriesNotInB().stream().filter(f -> !f.isDeleted() && !f.isDeprecated()).toList()) {
existingField.setDeprecated(true);
existingField.setVersionModified(version);
existingField.setDateModified(timestamp);
}
}
//new tables
for (TableModel table : tableCompare.getBEntriesNotInA()) {
addTable(table);
if (table.getTableId() == 0) {
table.setTableId(idGenerator.incrementAndGet());
table.setVersionCreated(version);
table.setDateCreated(timestamp);
}
for (FieldModel field : table.getFields()) {
if (field.getFieldId() == 0) {
field.setFieldId(idGenerator.incrementAndGet());
field.setVersionCreated(version);
field.setDateCreated(timestamp);
}
}
}
//removed tables
for (TableModel existingTable : tableCompare.getAEntriesNotInB().stream().filter(t -> !t.isDeleted() && !t.isDeprecated()).toList()) {
existingTable.setDeprecated(true);
existingTable.setVersionModified(version);
existingTable.setDateModified(timestamp);
}
//sanity check
for (TableModel table : getTables()) {
if (table.getTableId() == 0) {
table.setTableId(idGenerator.incrementAndGet());
table.setVersionCreated(version);
table.setDateCreated(timestamp);
logger.warn("WARNING: table without id on sanity check - create id:" + table.getName());
}
for (FieldModel field : table.getFields()) {
if (field.getFieldId() == 0) {
System.out.println("WARNING: fields without id on sanity check - create id:" + table.getName() + ", field:" + field.getName());
field.setFieldId(idGenerator.incrementAndGet());
field.setVersionCreated(version);
field.setDateCreated(timestamp);
}
}
}
//id duplicates check:
Set existingIdSet = new HashSet<>();
for (TableModel table : getTables()) {
if (existingIdSet.contains(table.getTableId())) {
throw new RuntimeException("Error: duplicate table id:" + table.getName());
}
existingIdSet.add(table.getTableId());
for (FieldModel field : table.getFields()) {
if (existingIdSet.contains(field.getFieldId())) {
throw new RuntimeException("Error: duplicate field id:" + table.getName() + "." + field.getFieldId());
}
existingIdSet.add(field.getFieldId());
}
}
}
public boolean isCompatible(DatabaseModel model) {
return checkCompatibilityErrors(model).isEmpty();
}
public List checkCompatibilityErrors(DatabaseModel newModel) {
List errors = new ArrayList<>();
if (!newModel.isValid()) {
errors.add("Model not valid!");
}
if (!name.equals(newModel.getName())) {
errors.add("Wrong model name: " + name + " vs: " + newModel.getName());
}
ByKeyComparisonResult enumCompare = CollectionUtil.compareByKey(enums, newModel.getEnums(), EnumModel::getName, EnumModel::getName, true);
for (EnumModel enumModel : enumCompare.getBEntriesInA()) {
EnumModel existingEnum = enumCompare.getA(enumModel);
if (existingEnum.getEnumNames().size() > enumModel.getEnumNames().size()) {
errors.add("Wrong config for enum " + enumModel.getName() + ", size:" + existingEnum.getEnumNames().size() + "->" + enumModel.getEnumNames().size());
} else {
for (int i = 0; i < existingEnum.getEnumNames().size(); i++) {
if (!existingEnum.getEnumNames().get(i).equals(enumModel.getEnumNames().get(i))) {
errors.add("Wrong config for enum " + enumModel.getName() + ", " + existingEnum.getEnumNames().get(i) + "->" + enumModel.getEnumNames().get(i));
}
}
if (existingEnum.isRemoteEnum() != enumModel.isRemoteEnum()) {
errors.add("Incompatible enum locality (local vs. remote):" + existingEnum.getName());
}
if (existingEnum.isRemoteEnum() && !existingEnum.getRemoteDatabase().equals(enumModel.getRemoteDatabase())) {
errors.add("Wrong database for enum " + existingEnum.getName() + ", " + existingEnum.getRemoteDatabase() + "->" + enumModel.getRemoteDatabase());
}
if (existingEnum.isRemoteEnum() && !existingEnum.getRemoteDatabaseNamespace().equals(enumModel.getRemoteDatabaseNamespace())) {
errors.add("Wrong namespace for enum " + existingEnum.getName() + ", " + existingEnum.getRemoteDatabaseNamespace() + "->" + enumModel.getRemoteDatabaseNamespace());
}
}
}
for (TableModel table : newModel.getTables()) {
TableModel existingTable = getTable(table.getName());
if (existingTable != null) {
if (existingTable.isVersioning() != table.isVersioning() ||
existingTable.isRecoverableRecords() != table.isRecoverableRecords() ||
existingTable.isTrackModifications() != table.isTrackModifications() ||
existingTable.isRemoteTable() != table.isRemoteTable() ||
(existingTable.isRemoteTable() && !existingTable.getRemoteDatabase().equals(table.getRemoteDatabase()))
) {
errors.add("Wrong config for table " + table.getName() + "." + table.getName() + ", versioning/recoverable/modifications/remoteTable: " +
existingTable.isVersioning() + "/" + existingTable.isRecoverableRecords() + "/" + existingTable.isTrackModifications() + "/" + existingTable.isRemoteTable() + "->" +
table.isVersioning() + "/" + table.isRecoverableRecords() + "/" + table.isTrackModifications() + "/" + table.isRemoteTable()
);
}
for (FieldModel field : table.getFields()) {
FieldModel existingField = existingTable.getField(field.getName());
if (existingField != null) {
if (existingField.getFieldType() != field.getFieldType()) {
errors.add("Wrong config for field " + table.getName() + "." + field.getName() + ", type:" + existingField.getFieldType() + "->" + field.getFieldType());
} else {
if (existingField.getFieldType().isReference()) {
ReferenceFieldModel existingReference = (ReferenceFieldModel) existingField;
ReferenceFieldModel referenceField = (ReferenceFieldModel) field;
if (existingReference.isMultiReference() != referenceField.isMultiReference() ||
existingReference.isCascadeDelete() != referenceField.isCascadeDelete() ||
!existingReference.getReferencedTable().getName().equals(referenceField.getReferencedTable().getName()) ||
Objects.isNull(existingReference.getReverseReferenceField()) != Objects.isNull(referenceField.getReverseReferenceField()) ||
(existingReference.getReverseReferenceField() != null && !existingReference.getReverseReferenceField().getName().equals(referenceField.getReverseReferenceField().getName()))
) {
errors.add("Wrong config for field " + table.getName() + "." + field.getName() + ", multi-ref/cascade/ref-table/ref-field: " +
existingReference.isMultiReference() + "/" + existingReference.isCascadeDelete() + "/" + existingReference.getReferencedTable().getName() + "/" + getRefFieldPathOrNull(existingReference.getReverseReferenceField()) + "->" +
referenceField.isMultiReference() + "/" + referenceField.isCascadeDelete() + "/" + referenceField.getReferencedTable().getName() + "/" + getRefFieldPathOrNull(referenceField.getReverseReferenceField())
);
}
} else if (existingField.getFieldType() == FieldType.ENUM) {
EnumFieldModel existingEnum = (EnumFieldModel) existingField;
EnumFieldModel enumField = (EnumFieldModel) field;
if (!existingEnum.getEnumModel().getName().equals(enumField.getEnumModel().getName())) {
errors.add("Wrong enum model for field " + field.getName());
}
}
}
}
}
}
}
ByKeyComparisonResult viewCompare = CollectionUtil.compareByKey(views, newModel.getViews(), ViewModel::getName, ViewModel::getName, true);
for (ViewModel viewModel : viewCompare.getBEntriesInA()) {
ViewModel existingView = viewCompare.getA(viewModel);
if (!existingView.getTable().getName().equals(viewModel.getTable().getName())) {
errors.add("Wrong table for view " + viewModel.getName());
}
}
return errors;
}
private String getRefFieldPathOrNull(ReferenceFieldModel field) {
if (field == null) {
return "null";
} else {
return field.getTableModel().getName() + "." + field.getName();
}
}
public boolean isValid() {
return checkErrors().isEmpty();
}
public List checkErrors() {
List errors = new ArrayList<>();
Set tableNames = new HashSet<>();
enums.stream()
.collect(Collectors.groupingBy(f -> f.getName().toLowerCase(), Collectors.counting()))
.entrySet().stream()
.filter(e -> e.getValue() > 1)
.map(Map.Entry::getKey)
.forEach(name -> errors.add("Duplicate enum name:" + name));
tables.stream()
.collect(Collectors.groupingBy(f -> f.getName().toLowerCase(), Collectors.counting()))
.entrySet().stream()
.filter(e -> e.getValue() > 1)
.map(Map.Entry::getKey)
.forEach(name -> errors.add("Duplicate table name:" + name));
views.stream()
.collect(Collectors.groupingBy(f -> f.getName().toLowerCase(), Collectors.counting()))
.entrySet().stream()
.filter(e -> e.getValue() > 1)
.map(Map.Entry::getKey)
.forEach(name -> errors.add("Duplicate view name:" + name));
for (EnumModel enumModel : enums) {
if (enumModel.getEnumNames().isEmpty() || enumModel.getEnumNames().size() != enumModel.getEnumTitles().size()) {
errors.add("Wrong enum values:" + enumModel.getName());
}
if (enumModel.getEnumNames().stream().anyMatch(v -> v == null || v.strip().isBlank())) {
errors.add("Empty enum value:" + enumModel.getName());
}
if (enumModel.getEnumTitles().stream().anyMatch(v -> v == null || v.strip().isBlank())) {
errors.add("Empty enum title:" + enumModel.getName());
}
}
for (TableModel table : tables) {
if (tableNames.contains(table.getName().toLowerCase())) {
errors.add("Duplicate table name:" + table.getName());
}
tableNames.add(table.getName().toLowerCase());
table.getFields().stream()
.collect(Collectors.groupingBy(f -> f.getName().toLowerCase(), Collectors.counting()))
.entrySet().stream()
.filter(e -> e.getValue() > 1)
.map(Map.Entry::getKey)
.forEach(fieldName -> errors.add("Duplicate field name '" + fieldName + "' in table " + table.getName()));
table.getReferenceFields().stream()
.filter(f -> f.getReverseReferenceField() != null)
.filter(f -> !f.equals(f.getReverseReferenceField().getReverseReferenceField()))
.forEach(f -> errors.add("Wrong reverse reference field " + f.getName() + " in table " + table.getName()));
table.getEnumFields().stream()
.filter(f -> getEnumModel(f.getEnumModel().getName()) == null)
.forEach(f -> errors.add("Wrong enum field " + f.getName() + " in table " + table.getName()));
}
for (ViewModel view : views) {
if (tableNames.contains(view.getName().toLowerCase())) {
errors.add("Duplicate table/view name:" + view.getName());
}
tableNames.add(view.getName().toLowerCase());
view.getFields().stream()
.collect(Collectors.groupingBy(f -> f.getName().toLowerCase(), Collectors.counting()))
.entrySet().stream()
.filter(e -> e.getValue() > 1)
.map(Map.Entry::getKey)
.forEach(fieldName -> errors.add("Duplicate field " + fieldName + " in view " + view.getName()));
String tableName = view.getTable().getName();
view.getFields().stream()
.filter(f -> !f.getTableModel().getName().equals(tableName))
.forEach(f -> errors.add("View with field from other table, field:" + f.getName() + ", view:" + view.getName()));
}
return errors;
}
public boolean checkIds() {
for (TableModel table : tables) {
if (table.getTableId() <= 0) return false;
for (FieldModel field : table.getFields()) {
if (field.getFieldId() <= 0) return false;
}
}
return true;
}
public boolean isSameModel(DatabaseModel model) {
if (!name.equals(model.getName())) return false;
if (!title.equals(model.getTitle())) return false;
if (!namespace.equals(model.getNamespace())) return false;
ByKeyComparisonResult enumCompare = CollectionUtil.compareByKey(enums, model.getEnums(), EnumModel::getName, EnumModel::getName, true);
if (enumCompare.isDifferent()) return false;
for (EnumModel enumModel : enumCompare.getBEntriesInA()) {
EnumModel existingModel = enumCompare.getA(enumModel);
if (!existingModel.getName().equals(enumModel.getName())) return false;
if (!existingModel.getTitle().equals(enumModel.getTitle())) return false;
if (CollectionUtil.compareByKey(existingModel.getEnumNames(), enumModel.getEnumNames(), s -> s, s -> s).isDifferent()) return false;
if (CollectionUtil.compareByKey(existingModel.getEnumTitles(), enumModel.getEnumTitles(), s -> s, s -> s).isDifferent()) return false;
if (existingModel.isRemoteEnum() != enumModel.isRemoteEnum()) return false;
if (existingModel.isRemoteEnum() && !existingModel.getRemoteDatabase().equals(enumModel.getRemoteDatabase())) return false;
if (existingModel.isRemoteEnum() && !existingModel.getRemoteDatabaseNamespace().equals(enumModel.getRemoteDatabaseNamespace())) return false;
}
ByKeyComparisonResult tableCompare = CollectionUtil.compareByKey(tables, model.getTables(), TableModel::getName, TableModel::getName, true);
if (tableCompare.isDifferent()) return false;
for (TableModel tableModel : tableCompare.getBEntriesInA()) {
TableModel existingModel = tableCompare.getA(tableModel);
if (!existingModel.getName().equals(tableModel.getName())) return false;
if (!existingModel.getTitle().equals(tableModel.getTitle())) return false;
if (existingModel.isRemoteTable() != tableModel.isRemoteTable()) return false;
if (existingModel.getRemoteDatabaseNamespace() == null && tableModel.getRemoteDatabaseNamespace() != null) return false;
if (existingModel.getRemoteDatabaseNamespace() != null && !existingModel.getRemoteDatabaseNamespace().equals(tableModel.getRemoteDatabaseNamespace())) return false;
if (existingModel.isTrackModifications() != tableModel.isTrackModifications()) return false;
if (existingModel.isVersioning() != tableModel.isVersioning()) return false;
if (existingModel.isRecoverableRecords() != tableModel.isRecoverableRecords()) return false;
if (existingModel.getRemoteDatabase() != null && !existingModel.getRemoteDatabase().equals(tableModel.getRemoteDatabase())) return false;
ByKeyComparisonResult fieldCompare = CollectionUtil.compareByKey(existingModel.getFields(), tableModel.getFields(), FieldModel::getName, FieldModel::getName, true);
if (fieldCompare.isDifferent()) return false;
for (FieldModel fieldModel : fieldCompare.getBEntriesInA()) {
FieldModel existingField = fieldCompare.getA(fieldModel);
if (!existingField.getName().equals(fieldModel.getName())) return false;
if (!existingField.getTitle().equals(fieldModel.getTitle())) return false;
if (existingField.getFieldType() != fieldModel.getFieldType()) return false;
if (fieldModel.getFieldType().isFile()) {
FileFieldModel fileFieldModel = (FileFieldModel) fieldModel;
FileFieldModel existingFileModel = (FileFieldModel) existingField;
if (existingFileModel.isIndexContent() != fileFieldModel.isIndexContent()) return false;
if (existingFileModel.isDetectLanguage() != fileFieldModel.isDetectLanguage()) return false;
if (existingFileModel.getMaxIndexContentLength() != fileFieldModel.getMaxIndexContentLength()) return false;
} else if (fieldModel.getFieldType().isReference()) {
ReferenceFieldModel referenceFieldModel = (ReferenceFieldModel) fieldModel;
ReferenceFieldModel existingReferenceModel = (ReferenceFieldModel) existingField;
if (existingReferenceModel.isMultiReference() != referenceFieldModel.isMultiReference()) return false;
if (existingReferenceModel.isCascadeDelete() != referenceFieldModel.isCascadeDelete()) return false;
if (!existingReferenceModel.getReferencedTable().getName().equals(referenceFieldModel.getReferencedTable().getName())) return false;
if ((existingReferenceModel.getReverseReferenceField() == null) != (referenceFieldModel.getReverseReferenceField() == null)) return false;
if (existingReferenceModel.getReverseReferenceField() != null && !existingReferenceModel.getReverseReferenceField().getName().equals(referenceFieldModel.getReverseReferenceField().getName())) return false;
}
}
}
return true;
}
private int getMaxId() {
int tableId = tables.stream().mapToInt(TableModel::getTableId).max().orElse(0);
int fieldId = tables.stream().flatMap(t -> t.getFields().stream()).mapToInt(FieldModel::getFieldId).max().orElse(0);
return Math.max(tableId, fieldId);
}
public TableModel createTable(String title) {
return createTable(title, title, true, true, true);
}
public TableModel createTable(String name, String title) {
return createTable(name, title, true, true, true);
}
public TableModel createTable(String name, String title, boolean trackModifications, boolean versioning, boolean recoverableRecords) {
TableModel tableModel = new TableModel(this, name, title, false, null, null, trackModifications, versioning, recoverableRecords);
return addTable(tableModel);
}
public TableModel createRemoteTable(String title, String databaseName) {
return createRemoteTable(title, title, databaseName);
}
public TableModel createRemoteTable(String name, String title, String databaseName) {
TableModel tableModel = new TableModel(this, name, title, true, databaseName, null, false, false, false);
return addTable(tableModel);
}
public TableModel createRemoteTable(String name, String title, String databaseName, String namespace) {
TableModel tableModel = new TableModel(this, name, title, true, databaseName, namespace, false, false, false);
return addTable(tableModel);
}
public TableModel createRemoteTable(String name, String title, String tableName, String databaseName, String namespace) {
TableModel tableModel = new TableModel(this, name, title, true, tableName, databaseName, namespace, false, false, false);
return addTable(tableModel);
}
public EnumModel createEnum(String title, String... values) {
return createEnum(title, Arrays.asList(values));
}
public EnumModel createEnum(String title, List enumTitles) {
return createEnum(title, title, enumTitles, enumTitles);
}
public EnumModel createEnum(String name, String title, List enumTitles) {
return createEnum(name, title, enumTitles, enumTitles);
}
public EnumModel createEnum(String name, String title, List enumNames, List enumTitles) {
EnumModel enumModel = new EnumModel(name, title, enumNames, enumTitles);
return addEnum(enumModel);
}
public EnumModel createRemoteEnum(String name, String title, List enumNames, List enumTitles, String remoteDatabase, String remoteDatabaseNamespace) {
EnumModel enumModel = new EnumModel(name, title, enumNames, enumTitles, true, remoteDatabase, remoteDatabaseNamespace);
return addEnum(enumModel);
}
private EnumModel addEnum(EnumModel enumModel) {
if (enums.stream().anyMatch(e -> e.getName().equalsIgnoreCase(enumModel.getName()))) {
throw new RuntimeException("Error: enum with name " + enumModel.getName() + " already exists!");
}
enums.add(enumModel);
return enumModel;
}
public EnumModel getEnumModel(String enumModelName) {
return enums.stream()
.filter(e -> e.getName().equals(enumModelName))
.findAny()
.orElse(null);
}
public List getEnums() {
return new ArrayList<>(enums);
}
private TableModel addTable(TableModel tableModel) {
if (tables.stream().anyMatch(e -> e.getName().equalsIgnoreCase(tableModel.getName()))) {
throw new RuntimeException("Error: table with name " + tableModel.getName() + " already exists!");
}
tables.add(tableModel);
return tableModel;
}
public void addReverseReferenceField(TableModel tableA, String fieldA, TableModel tableB, String fieldB) {
ReferenceFieldModel referenceFieldA = tableA.getReferenceField(fieldA);
ReferenceFieldModel referenceFieldB = tableB.getReferenceField(fieldB);
if (referenceFieldA == null || referenceFieldB == null) {
throw new RuntimeException("Error missing reference fields for model:" + fieldA + ", " + fieldB);
}
referenceFieldA.setReverseReferenceField(referenceFieldB);
}
public String getName() {
return name;
}
public String getTitle() {
return title;
}
public String getNamespace() {
return namespace;
}
public String getModelClassName() {
return modelClassName;
}
public String getFullNameSpace() {
return getNamespace() + "." + getName().toLowerCase();
}
public List getTables() {
return new ArrayList<>(tables);
}
public List getLocalTables() {
return tables.stream()
.filter(t -> !t.isRemoteTable())
//.filter(t -> !t.isDeleted())
.toList();
}
public List getRemoteTables() {
return tables.stream()
.filter(TableModel::isRemoteTable)
//.filter(t -> !t.isDeleted())
.toList();
}
public List getRemoteTableNamespaces() {
return getRemoteTables().stream()
.filter(t -> t.getRemoteDatabaseNamespace() != null)
.map(t -> t.getRemoteDatabaseNamespace() + "." + t.getRemoteDatabase().toLowerCase())
.distinct()
.toList();
}
public TableModel getTable(String name) {
return tables.stream()
.filter(t -> t.getName().equals(name))
.findAny()
.orElse(null);
}
public FieldModel getField(String tableName, String fieldName) {
TableModel table = getTable(tableName);
return table != null ? table.getField(fieldName) : null;
}
public ReferenceFieldModel getReferenceField(String tableName, String fieldName) {
TableModel table = getTable(tableName);
return table != null ? table.getReferenceField(fieldName) : null;
}
public List getViews() {
return new ArrayList<>(views);
}
public List getImportedTables() {
return tables.stream()
.filter(TableModel::isRemoteTable)
.toList();
}
public List getImportedDatabases() {
return getImportedTables().stream()
.map(TableModel::getRemoteDatabase)
.distinct()
.toList();
}
public int getVersion() {
return version;
}
private void setVersion(int version) {
this.version = version;
}
public int getDateCreated() {
return dateCreated;
}
private void setDateCreated(int dateCreated) {
this.dateCreated = dateCreated;
}
public int getDateModified() {
return dateModified;
}
private void setDateModified(int dateModified) {
this.dateModified = dateModified;
}
public long getPojoBuildTime() {
return pojoBuildTime;
}
public void setPojoBuildTime(long pojoBuildTime) {
this.pojoBuildTime = pojoBuildTime;
}
public byte[] toBytes() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
write(dos);
dos.close();
return bos.toByteArray();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Database model: ").append(name).append(", ").append(title).append(", ns:").append(namespace).append(", build:").append(pojoBuildTime).append(", version:").append(version).append("\n");
sb.append("Tables: ").append(tables.size()).append("\n");
for (TableModel table : tables) {
String remoteTable = table.isRemoteTable() ? ", [REMOTE]" : "";
sb.append("\t").append(table.getName()).append(", ").append(table.getTitle()).append(remoteTable).append(", (").append(table.getTableId()).append(")").append("\n");
for (FieldModel field : table.getFields()) {
sb.append("\t").append("\t").append(field.getName()).append(", ").append(field.getTitle()).append(", ").append(field.getFieldType()).append(", (").append(field.getFieldId()).append(")").append("\n");
}
}
return sb.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy