DatabaseIRef.java
/*
* Copyright (c) 2021 Mārtiņš Avots (Martins Avots) and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0, or the MIT License,
* which is available at https://spdx.org/licenses/MIT.html.
*
* SPDX-License-Identifier: EPL-2.0 OR MIT
*/
package net.splitcells.gel.data.database;
import net.splitcells.dem.data.set.list.List;
import net.splitcells.dem.object.Discoverable;
import net.splitcells.dem.resource.communication.interaction.LogLevel;
import net.splitcells.gel.data.table.Line;
import net.splitcells.gel.data.table.attribute.Attribute;
import net.splitcells.gel.data.table.column.ColumnView;
import java.util.Collection;
import static java.util.stream.IntStream.range;
import static net.splitcells.dem.data.atom.Bools.require;
import static net.splitcells.dem.environment.config.StaticFlags.ENFORCING_UNIT_CONSISTENCY;
import static net.splitcells.dem.environment.config.StaticFlags.TRACING;
import static net.splitcells.dem.lang.Xml.*;
import static net.splitcells.dem.resource.communication.log.Domsole.domsole;
import static net.splitcells.dem.resource.communication.interaction.LogLevel.DEBUG;
import static net.splitcells.gel.common.Language.*;
import static org.assertj.core.api.Assertions.assertThat;
/**
* TODO Make this an aspect in order to make it usable for other implementations of {@link Database}.
* <p/>
* TODO Require the usage of a non empty name during construction.
* <p/>
* TODO Invalidate Lines pointing to an index where values are already replaced.
* <p/>
* TODO PERFORMANCE Abstract Database implementation with generic storage in order to
* simplify implementation and maintenance row and column based Databases.
* <p/>
* TODO Test consistency of meta data.
* <p/>
* TODO IDEA Implement Java collection interface.
*/
public class DatabaseIRef extends DatabaseI {
@Deprecated
protected DatabaseIRef(List<Attribute<? extends Object>> attribute) {
super(attribute);
}
protected DatabaseIRef(String name, Discoverable parent, List<Attribute<Object>> header) {
super(name, parent, header);
assert this.attributes.size() == columns.size();
header.requireUniqueness();
}
@Deprecated
protected DatabaseIRef(List<Attribute<?>> header, Collection<List<Object>> linesValues) {
super(header, linesValues);
}
protected DatabaseIRef(String name, Discoverable parent, Attribute<? extends Object>... header) {
super(name, parent, header);
}
@Deprecated
protected DatabaseIRef(Attribute<?>... header) {
super(header);
}
/**
* TODO PERFORMANCE Cache list views in Order to minimize number of objects.
* <p/>
* TODO Return an unmodifiable view of the column.
*
* @param attribute
* @param <T>
* @return
*/
@Override
public <T> ColumnView<T> columnView(Attribute<T> attribute) {
if (ENFORCING_UNIT_CONSISTENCY) {
assertThat(attributes.contains(attribute))
.describedAs(attributes.stream()
.map(a -> a.name() + ", ")
.reduce((a, b) -> a + b)
.orElse("")
+ ": " + attribute.name())
.isTrue();
assertThat(typed_column_index.containsKey(attribute))
.describedAs(attribute.name() + " is not present in "
+ typed_column_index.keySet().stream()
.map(a -> a.name())
.reduce((a, b) -> a + ", " + b)
.get()
)
.isTrue();
}
return super.columnView(attribute);
}
/**
* TODO PERFORMANCE No copies have to be created, as it is guaranteed that a Line
* does not change its content during its life cycle. This is important for
* constraints.
* <p/>
* TODO Test whether the line is added to the correct place.
* <p/>
* TODO FIX Why does List<?> not work?
* <p/>
* TODO PERFORMANCE Reduce the high number of copies.
*
* @param line
* @return
*/
@Override
public Line add(Line line) {
if (ENFORCING_UNIT_CONSISTENCY) {
assert attributes.size() == line.context().headerView().size() : path() + "" + line.context().path();
assert !lines.contains(line);
assert line.index() >= rawLines.size() || rawLines.get(line.index()) == null : path().toString() + line.index();
}
range(0, attributes.size()).forEach(i -> {
assert attributes.get(i).equals(line.context().headerView().get(i));
});
return super.add(line);
}
/**
* @param lineValues TODO Support {@link net.splitcells.dem.lang.dom.Domable#toDom} for logging.
*/
protected Line addTranslated(List<Object> lineValues, int index) {
if (TRACING) {
domsole().append(
event("addTranslatingAt." + Database.class.getSimpleName()
, path().toString()
, elementWithChildren("index", textNode("" + index))
, elementWithChildren("line-values", textNode(lineValues.toString()))
)
, this
, DEBUG
);
}
if (ENFORCING_UNIT_CONSISTENCY) {
assertThat(lineValues.size()).isEqualTo(attributes.size());
require(indexesOfFree.contains(index) || index >= rawLines.size());
range(0, lineValues.size()).forEach(i -> attributes.get(i).isInstanceOf(lineValues.get(i)).required());
}
return super.addTranslated(lineValues, index);
}
/**
* TODO REMOVE Code duplication of {@link DatabaseIRef#addTranslated} methods.
*/
@Override
public Line addTranslated(List<? extends Object> lineValues) {
if (ENFORCING_UNIT_CONSISTENCY) {
assertThat(lineValues.size()).isEqualTo(attributes.size());
/**
* TODO Check for {@link Attribute} compatibility and not Class compatibility.
*/
lineValues.stream().forEach(e ->
assertThat(e).as("<%s> should not contain nulls.", lineValues)
.isNotNull());
range(0, lineValues.size()).forEach(i -> attributes.get(i).isInstanceOf(lineValues.get(i)).required());
}
final var translatedAddition = super.addTranslated(lineValues);
if (TRACING) {
domsole().append(
event("addTranslating." + Database.class.getSimpleName()
, path().toString()
, elementWithChildren("index", textNode("" + translatedAddition.index()))
, elementWithChildren("line-values", textNode(lineValues.toString())))
, this, DEBUG
);
}
return translatedAddition;
}
@Override
public void remove(int lineIndex) {
if (ENFORCING_UNIT_CONSISTENCY) {
assertThat(indexesOfFree).doesNotContain(lineIndex);
assertThat(lineIndex).isNotNegative();
assert lineIndex < rawLines.size() : lineIndex + ":" + rawLines.size() + path();
assertThat(rawLines.get(lineIndex)).isNotNull();
lines.hasOnlyOnce(rawLines.get(lineIndex));
columns.forEach(column -> {
assert lineIndex < column.size();
assert rawLines.size() == column.size();
});
assert lineIndex < rawLines.size();
}
super.remove(lineIndex);
}
@Override
public void remove(Line line) {
if (TRACING) {
domsole().append(event(REMOVE.value()
+ PATH_ACCESS_SYMBOL.value()
+ Database.class.getSimpleName()
, path().toString()
, elementWithChildren(LINE.value(), line.toDom()))
, this, LogLevel.DEBUG);
}
super.remove(line);
}
}