
io.vertigo.vega.engines.webservice.json.AbstractUiListModifiable Maven / Gradle / Ivy
The newest version!
/*
* vertigo - application development platform
*
* Copyright (C) 2013-2024, Vertigo.io, [email protected]
*
* 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.
*/
package io.vertigo.vega.engines.webservice.json;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
import io.vertigo.core.lang.Assertion;
import io.vertigo.core.node.definition.DefinitionId;
import io.vertigo.core.util.ClassUtil;
import io.vertigo.datamodel.data.definitions.DataDefinition;
import io.vertigo.datamodel.data.model.DataObject;
import io.vertigo.datamodel.data.model.DtList;
import io.vertigo.datamodel.data.model.DtListState;
import io.vertigo.datamodel.data.util.DataModelUtil;
import io.vertigo.vega.webservice.model.DtListDelta;
import io.vertigo.vega.webservice.model.UiList;
import io.vertigo.vega.webservice.model.UiObject;
import io.vertigo.vega.webservice.validation.DtObjectValidator;
import io.vertigo.vega.webservice.validation.UiMessageStack;
/**
* Version modifiable des UiList.
* @author npiedeloup
* @param Type d'objet
*/
public abstract class AbstractUiListModifiable extends AbstractList> implements UiList, Serializable {
private static final long serialVersionUID = 1L;
private final DefinitionId dataDefinitionId;
private final Class objectType;
private final String inputKey;
// Index
private final Map, D> dtoByUiObject = new HashMap<>();
// Buffer
private final UiListDelta uiListDelta;
private final List> bufferUiObjects;
// Data
private final DtListDelta dtListDelta;
private final DtList dtList;
/**
* Constructor.
* @param dtList Inner DtList
*/
protected AbstractUiListModifiable(final DtList dtList, final String inputKey) {
this(dtList, inputKey, nop -> {
});
}
public > AbstractUiListModifiable(final DtList dtList, final String inputKey, final Consumer postInit) {
Assertion.check().isNotNull(dtList);
//-----
this.dtList = dtList;
this.inputKey = inputKey;
final DataDefinition dataDefinition = dtList.getDefinition();
dataDefinitionId = dataDefinition.id();
this.objectType = (Class) ClassUtil.classForName(dataDefinition.getClassCanonicalName());
// ---
uiListDelta = new UiListDelta<>(objectType, new HashMap<>(), new HashMap<>(), new HashMap<>());
dtListDelta = new DtListDelta<>(new DtList<>(dataDefinition), new DtList<>(dataDefinition), new DtList<>(dataDefinition));
bufferUiObjects = new ArrayList<>(dtList.size());
postInit.accept((U) this);
rebuildBuffer();
}
/* (non-Javadoc)
* @see io.vertigo.vega.webservice.model.UiList#getObjectType()
*/
@Override
public Class getObjectType() {
return objectType;
}
protected abstract UiObject createUiObject(final D dto);
private void rebuildBuffer() {
uiListDelta.getCreatesMap().clear();
uiListDelta.getUpdatesMap().clear();
uiListDelta.getDeletesMap().clear();
// ---
dtoByUiObject.clear();
bufferUiObjects.clear();
int index = 0;
for (final D dto : dtList) {
final UiObject uiObjects = createUiObject(dto);
uiObjects.setInputKey(toContextKey(inputKey, index));
bufferUiObjects.add(uiObjects);
dtoByUiObject.put(uiObjects, dto);
index++;
}
}
/**
* @return DtDefinition de l'objet métier
*/
@Override
public DataDefinition getDtDefinition() {
return dataDefinitionId.get();
}
private String findContextKey(final UiObject uiObject) {
Assertion.check().isNotNull(uiObject);
final int index = indexOfUiObject(uiObject);
Assertion.check().isTrue(index >= 0, "UiObjet {0} not found in UiList with key {1}", uiObject, inputKey);
// ---
return toContextKey(inputKey, index);
}
protected String toContextKey(final String locInputKey, final int index) {
return locInputKey + ".get(" + index + ")";
}
/**
* @param dto Element to removed
* @return If element was removed
*/
public boolean remove(final UiObject dto) {
final boolean result = bufferUiObjects.remove(dto);
if (result) {
if (uiListDelta.getCreatesMap().containsValue(dto)) {
//Si on supprime (remove) un objet déjà ajouté (add),
//alors il suffit de l'enlever de la liste des éléments ajoutés.
uiListDelta.getCreatesMap().remove(dto.getInputKey());
} else {
//Sinon on l'ajoute à la liste des éléments supprimés.
uiListDelta.getDeletesMap().put(dto.getInputKey(), dto);
}
//CHECKME : les inputKey des objets suivant ne sont plus bon
}
return result;
}
/** {@inheritDoc} */
@Override
public UiObject remove(final int index) {
final UiObject dto = get(index);
final boolean result = remove(dto);
Assertion.check().isTrue(result, "Erreur de suppression i={0}", index);
return dto;
}
/**
* @param dto Element to add
* @return true (as specified by Collection.add)
*/
public boolean add(final D dto) {
return add(createUiObject(dto));
}
/**
* @param uiObject Element to add
* @return true (as specified by Collection.add)
*/
@Override
public boolean add(final UiObject uiObject) {
final boolean result = bufferUiObjects.add(uiObject);
uiObject.setInputKey(findContextKey(uiObject));
if (result) {
if (uiListDelta.getDeletesMap().containsValue(uiObject)) {
//Si on ajoute (add) un objet précédemment supprimé (remove),
//alors il suffit de l'enlever de la liste des éléments supprimés.
uiListDelta.getDeletesMap().remove(uiObject.getInputKey());
} else {
uiListDelta.getCreatesMap().put(uiObject.getInputKey(), uiObject);
}
}
return result;
}
/**
* @param uiObject Element to add at index
* @return true (as specified by List.add)
*/
@Override
public void add(final int index, final UiObject uiObject) {
bufferUiObjects.add(index, uiObject);
if (uiObject != null) {
if (uiListDelta.getDeletesMap().containsValue(uiObject)) {
//Si on ajoute (add) un objet précédemment supprimé (remove),
//alors il suffit de l'enlever de la liste des éléments supprimés.
uiListDelta.getDeletesMap().remove(uiObject.getInputKey());
} else {
uiListDelta.getCreatesMap().put(uiObject.getInputKey(), uiObject);
}
}
}
/**
* @return DtListDelta
*/
public DtListDelta getDtListDelta() {
Assertion.check().isNotNull(dtListDelta);
//
return dtListDelta;
}
/** {@inheritDoc} */
@Override
public UiObject get(final int row) {
//id>=0 : par index dans la UiList (pour boucle, uniquement dans la même request)
Assertion.check()
.isTrue(row >= 0, "Le getteur utilisé n'est pas le bon")
.isTrue(row <= DtListState.DEFAULT_MAX_ROWS, "UiListModifiable is limited to " + DtListState.DEFAULT_MAX_ROWS + " elements");
//SKE MLA : lazy initialisation of buffer uiObjects for size changing uiListModifiable
final DataDefinition dataDefinition = dataDefinitionId.get();
for (int i = bufferUiObjects.size(); i < row + 1; i++) {
add((D) DataModelUtil.createDataObject(dataDefinition));
}
final UiObject uiObject = bufferUiObjects.get(row);
Assertion.check().isNotNull(uiObject);
return uiObject;
}
/** {@inheritDoc} */
@Override
public int indexOf(final Object o) {
if (o instanceof DataObject) {
return indexOfDtObject((DataObject) o);
} else if (o instanceof UiObject) {
return indexOfUiObject((UiObject) o);
}
return super.indexOf(o);
}
/**
* @param dtObject DtObject recherché
* @return index de l'objet dans la liste
*/
private int indexOfDtObject(final DataObject dtObject) {
Assertion.check().isNotNull(dtObject);
//-----
for (int i = 0; i < bufferUiObjects.size(); i++) {
if (dtObject.equals(bufferUiObjects.get(i).getServerSideObject())) {
return i;
}
}
return -1;
}
/**
* @param uiObject UiObject recherché
* @return index de l'objet dans la liste
*/
private int indexOfUiObject(final UiObject uiObject) {
Assertion.check().isNotNull(uiObject);
//-----
return bufferUiObjects.indexOf(uiObject);
}
/** {@inheritDoc} */
@Override
public int size() {
return bufferUiObjects.size();
}
/** {@inheritDoc} */
@Override
public boolean checkFormat(final UiMessageStack uiMessageStack) {
//1. check Error => KUserException
//on valide les éléments internes
boolean isValid = true;
for (final UiObject uiObject : bufferUiObjects) {
uiObject.setInputKey(findContextKey(uiObject));
isValid = isValid && uiObject.checkFormat(uiMessageStack);
}
return isValid;
}
/** {@inheritDoc} */
@Override
public DtList mergeAndCheckInput(final List> validators, final UiMessageStack uiMessageStack) {
checkFormat(uiMessageStack);
dtListDelta.getDeleted().clear();
dtListDelta.getCreated().clear();
dtListDelta.getUpdated().clear();
//1. check Error => KUserException
//on valide les éléments internes
int index = 0;
for (final UiObject uiObject : bufferUiObjects) {
if (uiObject.isModified()) {
final D validatedDto = uiObject.mergeAndCheckInput(validators, uiMessageStack);
if (!uiListDelta.getCreatesMap().containsValue(uiObject)) {
dtListDelta.getUpdated().add(validatedDto);
final int prevIndex = dtList.indexOf(validatedDto);
if (prevIndex >= 0 && prevIndex != index) {
dtList.remove(prevIndex);
dtList.add(index, validatedDto); //on le déplace si besoin
}
} else {
dtListDelta.getCreated().add(validatedDto);
dtList.add(index, validatedDto); //on l'ajoute au bon endroit
}
} else if (uiListDelta.getCreatesMap().containsValue(uiObject)) {
dtListDelta.getCreated().add(uiObject.getServerSideObject()); //on ne force pas la validation
}
index++;
}
//2. Opérations
for (final UiObject uiObject : uiListDelta.getDeletesMap().values()) {
final D dto = dtoByUiObject.get(uiObject);
if (dto != null) {//on ne garde que les dto qui ETAIENT dans la dtc
dtListDelta.getDeleted().add(dto);
//on ne supprime pas tout de suite de la dtc, car cela invalidera les index de originIndexByUiObject
}
}
//on vérifie avant s'il y a des elements pour le cas des listes non modifiable
//il faudrait plutot que la DtListInput soit non modifiable aussi
if (!dtListDelta.getDeleted().isEmpty()) {
dtList.removeAll(dtListDelta.getDeleted());
}
//-----
Assertion.check().isTrue(bufferUiObjects.size() == dtList.size(), "bufferList.size() <> dtList.size() : mauvaise synchronisation dtList / bufferList");
//3. On reconstruit buffer et indexes
rebuildBuffer();
return dtList;
}
/** {@inheritDoc} */
@Override
public Iterator> iterator() {
return new UiListModifiableIterator();
}
/** {@inheritDoc} */
@Override
public boolean equals(final Object o) {
if (o == this) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
final AbstractUiListModifiable other = AbstractUiListModifiable.class.cast(o);
return bufferUiObjects.equals(other.bufferUiObjects);
}
/** {@inheritDoc} */
@Override
public int hashCode() {
return bufferUiObjects.hashCode();
}
/** innerclass, volontairement non static */
class UiListModifiableIterator implements Iterator> {
private int expectedSize; //count removed elements
private int currentIndex; //init a 0
/**
* Constructor.
*/
UiListModifiableIterator() {
expectedSize = size();
}
/** {@inheritDoc} */
@Override
public boolean hasNext() {
checkForComodification();
return currentIndex < size();
}
/** {@inheritDoc} */
@Override
public UiObject next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
checkForComodification();
final UiObject next = get(currentIndex);
currentIndex++;
return next;
}
/** {@inheritDoc} */
@Override
public void remove() {
AbstractUiListModifiable.this.remove(get(currentIndex - 1));
expectedSize--;
}
private void checkForComodification() {
if (expectedSize != size()) {
throw new ConcurrentModificationException();
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy