IndexGeneratorHelper.java
- /*
- * The coLAB project
- * Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO
- *
- * Licensed under the MIT License
- */
- package ch.colabproject.colab.api.controller.document;
- import ch.colabproject.colab.api.model.WithIndex;
- import ch.colabproject.colab.generator.model.exceptions.HttpErrorMessage;
- import ch.colabproject.colab.generator.model.exceptions.MessageI18nKey;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Comparator;
- import java.util.List;
- import javax.ejb.LocalBean;
- import javax.ejb.Stateless;
- /**
- * Deal with assigning index in a collection. The index is a field of the object and not the
- * built-in index of a list.
- * <p>
- * The indexes are progressive (from small to big) but the size of the room between two neighboring
- * indexes is not minimal nor has to be regular. In other words two neighboring indexes do not need
- * to be two just-following numbers.
- * <p>
- * The purpose is to be able to easily add an object between others without having to adjust other
- * indexes. In order to do that, we set a room between two neighboring indexes.
- *
- * @author sandra
- * @author maxence
- *
- * @param <T> Type of the items
- */
- @Stateless
- @LocalBean
- public class IndexGeneratorHelper<T extends WithIndex> {
- /**
- * The default value of the smallest index that is suitable
- */
- private final int DEFAULT_MIN_INDEX = 0;
- /**
- * The default value of the biggest index that is suitable
- */
- private final int DEFAULT_MAX_INDEX = Integer.MAX_VALUE;
- /**
- * Default room between two neighboring indexes
- */
- private final int DEFAULT_INDEX_INC = 1000;
- /**
- * Value of the smallest index that is suitable
- */
- private final int minIndex;
- /**
- * Value of the biggest index that is suitable
- */
- private final int maxIndex;
- /**
- * Initial room between two neighboring indexes
- */
- private final int indexIncrement;
- /**
- * Index for the first item in the list
- */
- private final int soloIndex;
- /**
- * Default constructor
- */
- public IndexGeneratorHelper() {
- minIndex = DEFAULT_MIN_INDEX;
- maxIndex = DEFAULT_MAX_INDEX;
- indexIncrement = DEFAULT_INDEX_INC;
- soloIndex = minIndex + indexIncrement;
- }
- /**
- * Constructor with parameters
- *
- * @param minIndex The value of the smallest index that is suitable
- * @param maxIndex The value of the biggest index that is suitable
- * @param indexIncrement The initial room between two neighboring indexes
- */
- public IndexGeneratorHelper(int minIndex, int maxIndex, int indexIncrement) {
- this.minIndex = minIndex;
- this.maxIndex = maxIndex;
- this.indexIncrement = indexIncrement;
- soloIndex = minIndex + indexIncrement;
- }
- // *********************************************************************************************
- //
- // *********************************************************************************************
- /**
- * Move (or set) the given item at the beginning of the collection.
- * <p>
- * That means setting an index to the item so that it is the first when sorting by index.
- *
- * @param item the item waiting for an index to be set
- * @param collection the collection of items
- *
- * @throws HttpErrorMessage if the collection is too big
- */
- public void moveItemToBeginning(T item, Collection<T> collection) {
- if (collection.isEmpty()) {
- item.setIndex(soloIndex);
- } else {
- List<T> sortedList = sortCollection(collection);
- T baseItem = sortedList.get(0);
- int justTooLow = minIndex - 1;
- if (!hasEnoughSpaceBetween(justTooLow, baseItem.getIndex())) {
- reorderIndexes(sortedList);
- }
- int wantedIndex = computeInBetweenIndex(justTooLow, baseItem.getIndex());
- item.setIndex(wantedIndex);
- }
- }
- /**
- * Move the given item one step ahead from where it is now.
- *
- * @param item the item waiting for a new index
- * @param collection the collection of items
- *
- * @throws HttpErrorMessage if the item is not in the collection
- */
- public void moveOneStepAhead(T item, Collection<T> collection) {
- if (!collection.contains(item)) {
- throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
- }
- List<T> sortedList = sortCollection(collection);
- T item1 = getItemBefore(item, sortedList);
- if (item1 == null) {
- // already at the beginning
- return;
- }
- int itemIndex = item.getIndex();
- item.setIndex(item1.getIndex());
- item1.setIndex(itemIndex);
- }
- /**
- * Move (or set) the given item before the baseItem.
- *
- * @param item the item waiting for a new index
- * @param baseItem the item of reference
- * @param collection the collection of items
- *
- * @throws HttpErrorMessage if the baseItem is not in the collection or if the collection is too
- * big
- */
- public void moveItemBefore(T item, T baseItem, Collection<T> collection) {
- if (!collection.contains(baseItem)) {
- throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
- }
- List<T> sortedList = sortCollection(collection);
- T item1 = baseItem;
- T item2 = getItemBefore(baseItem, sortedList);
- if (item2 == null) {
- // set before item1 which is the first item
- moveItemToBeginning(item, collection);
- } else {
- // between two items
- if (!hasEnoughSpaceBetween(item1.getIndex(), item2.getIndex())) {
- reorderIndexes(sortedList);
- }
- int wantedIndex = computeInBetweenIndex(item1.getIndex(), item2.getIndex());
- item.setIndex(wantedIndex);
- }
- }
- /**
- * Move an item one step behind from where it is now.
- *
- * @param item the item waiting for a new index
- * @param collection the collection of items
- *
- * @throws HttpErrorMessage if the item is not in the collection
- */
- public void moveOneStepBehind(T item, Collection<T> collection) {
- if (!collection.contains(item)) {
- throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
- }
- List<T> sortedList = sortCollection(collection);
- T item1 = getItemAfter(item, sortedList);
- if (item1 == null) {
- // already at the end
- return;
- }
- int itemIndex = item.getIndex();
- item.setIndex(item1.getIndex());
- item1.setIndex(itemIndex);
- }
- /**
- * Move (or set) the given item after the baseItem.
- *
- * @param item the item waiting for a new index
- * @param baseItem the item of reference
- * @param collection the collection of items
- *
- * @throws HttpErrorMessage if the baseItem is not in the collection or if the collection is too
- * big
- */
- public void moveItemAfter(T item, T baseItem, Collection<T> collection) {
- if (!collection.contains(baseItem)) {
- throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
- }
- List<T> sortedList = sortCollection(collection);
- T item1 = baseItem;
- T item2 = getItemAfter(baseItem, sortedList);
- if (item2 == null) {
- // set after item1 which is the last item
- moveItemToEnd(item, collection);
- } else {
- if (!hasEnoughSpaceBetween(item1.getIndex(), item2.getIndex())) {
- reorderIndexes(sortedList);
- }
- int wantedIndex = computeInBetweenIndex(item1.getIndex(), item2.getIndex());
- item.setIndex(wantedIndex);
- }
- }
- /**
- * Move (or set) the given item at the end of the collection.
- * <p>
- * That means setting an index to the item so that it is the last when sorting by index.
- *
- * @param item the item waiting for an index to be set
- * @param collection the collection of items
- *
- * @throws HttpErrorMessage if the collection is too big
- */
- public void moveItemToEnd(T item, Collection<T> collection) {
- if (collection.isEmpty()) {
- item.setIndex(soloIndex);
- } else {
- List<T> sortedList = sortCollection(collection);
- T baseItem = sortedList.get(sortedList.size() - 1);
- int justTooBigIndex = maxIndex + 1;
- if (!hasEnoughSpaceBetween(baseItem.getIndex(), justTooBigIndex)) {
- reorderIndexes(sortedList);
- }
- int wantedIndex;
- if ((justTooBigIndex - baseItem.getIndex()) > indexIncrement) {
- wantedIndex = baseItem.getIndex() + indexIncrement;
- } else {
- wantedIndex = computeInBetweenIndex(baseItem.getIndex(), justTooBigIndex);
- }
- item.setIndex(wantedIndex);
- }
- }
- // *********************************************************************************************
- //
- // *********************************************************************************************
- /**
- * Reorder the list so that each neighboring items have items separated by
- * {@link #indexIncrement}.
- * <p>
- * The first item also has a room of {@link #indexIncrement} before it.
- *
- * @param sortedCollection The sorted collection to index again
- *
- * @throws HttpErrorMessage if the collection is too big to be reorder
- */
- private void reorderIndexes(List<T> sortedCollection) {
- if (!canBeReordered(sortedCollection)) {
- throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
- }
- int index = minIndex + indexIncrement;
- for (WithIndex obj : sortedCollection) {
- obj.setIndex(index);
- index += indexIncrement;
- }
- }
- /**
- * Compute if the collection size fits into items indexes separated by {@link #indexIncrement}
- * between {@link #minIndex} and {@link #maxIndex}
- *
- * @param collection
- *
- * @return True if the collection size fits into
- */
- private boolean canBeReordered(Collection<T> collection) {
- return Math.floor(((float) (maxIndex - minIndex)) / indexIncrement) - 1 >= collection
- .size();
- }
- // *********************************************************************************************
- //
- // *********************************************************************************************
- /**
- * Sort the collection by the items index field.
- *
- * @param collection the collection of items to sort
- *
- * @return a list of sorted items by index
- */
- private List<T> sortCollection(Collection<T> collection) {
- List<T> sortedList = new ArrayList<>(collection);
- sortedList.sort(Comparator.comparingInt(obj -> obj.getIndex()));
- // TODO sandra ask Maxence if the old way was more suitable
- // sortedList.sort((a, b) -> {
- // if (a != null) {
- // if (b != null) {
- // return Math.max(a.getIndex(), b.getIndex());
- // } else {
- // // a is not null, b is null
- // return -1;
- // }
- // } else if (b != null) {
- // // a is null, not b
- // return 1;
- // }
- // //both are null
- // return 0;
- return sortedList;
- }
- // *********************************************************************************************
- //
- // *********************************************************************************************
- /**
- * Determine if there is enough space between two indexes so that we can insert something new.
- *
- * @param indexA an index
- * @param indexB another index
- *
- * @return True if there is at least 1 room left between the two indexes
- */
- private boolean hasEnoughSpaceBetween(int indexA, int indexB) {
- return Math.abs(indexB - indexA) > 1;
- }
- /**
- * Determine the new index which will take place between the two given indexes.
- *
- * @param indexA an index
- * @param indexB another index
- *
- * @return An index that fits between the two given indexes
- */
- private int computeInBetweenIndex(int indexA, int indexB) {
- int baseIndex = indexA;
- int delta = indexB - baseIndex;
- int increment = (int) Math.floor(delta / 2.0);
- return baseIndex + increment;
- }
- /**
- * Fetch the item which is just before the given baseItem in the sortedList.
- *
- * @param baseItem the reference item
- * @param sortedList the sorted list of items
- *
- * @return the item just before baseItem
- *
- * @throws HttpErrorMessage if the sorted list does not contain the baseItem
- */
- private T getItemBefore(T baseItem, List<T> sortedList) {
- if (!sortedList.contains(baseItem)) {
- throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
- }
- int baseIndexInList = sortedList.indexOf(baseItem);
- if (baseIndexInList > 0) {
- return sortedList.get(baseIndexInList - 1);
- } else {
- // nothing before baseItem
- return null;
- }
- }
- /**
- * Fetch the item which is just after the given baseItem in the sortedList.
- *
- * @param baseItem the reference item
- * @param sortedList the sorted list of items
- *
- * @return the item just after baseItem
- *
- * @throws HttpErrorMessage if the sorted list does not contain the baseItem
- */
- private T getItemAfter(T baseItem, List<T> sortedList) {
- if (!sortedList.contains(baseItem)) {
- throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
- }
- int baseIndexInList = sortedList.indexOf(baseItem);
- if (baseIndexInList < sortedList.size() - 1) {
- return sortedList.get(baseIndexInList + 1);
- } else {
- // nothing after baseItem
- return null;
- }
- }
- }