Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.arcadedb.query.sql.executor.MultiValue Maven / Gradle / Ivy
/*
* Copyright © 2021-present Arcade Data Ltd ([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.
*
* SPDX-FileCopyrightText: 2021-present Arcade Data Ltd ([email protected] )
* SPDX-License-Identifier: Apache-2.0
*/
package com.arcadedb.query.sql.executor;
import com.arcadedb.database.Identifiable;
import com.arcadedb.log.LogManager;
import com.arcadedb.utility.Callable;
import com.arcadedb.utility.CollectionUtils;
import com.arcadedb.utility.IterableObject;
import com.arcadedb.utility.IterableObjectArray;
import com.arcadedb.utility.MultiIterator;
import com.arcadedb.utility.ResettableIterator;
import java.lang.reflect.*;
import java.util.*;
import java.util.logging.*;
/**
* Handles Multi-value types such as Arrays, Collections and Maps. It recognizes special Arcade collections.
*
* @author Luca Garulli (l.garulli--(at)--gmail.com)
*/
@SuppressWarnings("unchecked")
public class MultiValue {
/**
* Checks if a class is a multi-value type.
*
* @param iType Class to check
*
* @return true if it's an array, a collection or a map, otherwise false
*/
public static boolean isMultiValue(final Class> iType) {
return Collection.class.isAssignableFrom(iType) || Map.class.isAssignableFrom(iType) || MultiIterator.class.isAssignableFrom(iType) || (
Iterable.class.isAssignableFrom(iType) && !Identifiable.class.isAssignableFrom(iType)) || iType.isArray() || ResultSet.class.isAssignableFrom(iType);
}
/**
* Checks if the object is a multi-value type.
*
* @param iObject Object to check
*
* @return true if it's an array, a collection or a map, otherwise false
*/
public static boolean isMultiValue(final Object iObject) {
return iObject != null && isMultiValue(iObject.getClass());
}
public static boolean isIterable(final Object iObject) {
return iObject instanceof Iterable> || iObject instanceof Iterator>;
}
/**
* Returns the estimated size of the multi-value object.
*
* @param object Multi-value object (array, collection or map)
*
* @return the size of the multi value object
*/
public static int getSize(final Object object) {
if (object == null)
return 0;
if (!isMultiValue(object))
return 0;
if (object instanceof Collection>)
return ((Collection) object).size();
else if (object instanceof Map, ?>)
return ((Map, Object>) object).size();
else if (object.getClass().isArray())
return Array.getLength(object);
else if ((object instanceof ResettableIterator))
return (int) ((ResettableIterator>) object).countEntries();
else if ((object instanceof Iterable)) {
int i = 0;
for (final Object o : (Iterable) object) {
i++;
}
return i;
} else if ((object instanceof Iterator)) {
return (int) CollectionUtils.countEntries((Iterator) object);
}
return 0;
}
/**
* Returns the size of the multi-value object only if the size is available without computing.
*
* @param object Multi-value object (array, collection or map)
*
* @return the size of the multi value object, -1 if the size is not available without computing
*/
public static int getSizeIfAvailable(final Object object) {
if (object == null)
return 0;
if (object instanceof Collection>)
return ((Collection) object).size();
else if (object instanceof Map, ?>)
return ((Map, Object>) object).size();
else if (object.getClass().isArray())
return Array.getLength(object);
return -1;
}
/**
* Returns the first item of the Multi-value object (array, collection or map)
*
* @param object Multi-value object (array, collection or map)
*
* @return The first item if any
*/
public static Object getFirstValue(final Object object) {
if (object == null)
return null;
if (!isMultiValue(object))
return null;
if (getSizeIfAvailable(object) == 0)
return null;
try {
if (object instanceof List>)
return ((List) object).get(0);
else if (object instanceof Iterable>)
return ((Iterable) object).iterator().next();
else if (object instanceof Map, ?>)
return ((Map, Object>) object).values().iterator().next();
else if (object.getClass().isArray())
return Array.get(object, 0);
} catch (final RuntimeException e) {
// IGNORE IT
LogManager.instance().log(object, Level.FINE, "Error on reading the first item of the Multi-value field '%s'", null, object, e);
}
return null;
}
/**
* Returns the last item of the Multi-value object (array, collection or map)
*
* @param object Multi-value object (array, collection or map)
*
* @return The last item if any
*/
public static Object getLastValue(final Object object) {
if (object == null)
return null;
if (!isMultiValue(object))
return null;
if (getSizeIfAvailable(object) == 0)
return null;
try {
if (object instanceof List>)
return ((List) object).get(((List) object).size() - 1);
else if (object instanceof Iterable>) {
Object last = null;
for (final Object o : (Iterable) object)
last = o;
return last;
} else if (object instanceof Map, ?>) {
Object last = null;
for (final Object o : ((Map, Object>) object).values())
last = o;
return last;
} else if (object.getClass().isArray())
return Array.get(object, Array.getLength(object) - 1);
} catch (final RuntimeException e) {
// IGNORE IT
LogManager.instance().log(object, Level.FINE, "Error on reading the last item of the Multi-value field '%s'", null, object, e);
}
return null;
}
/**
* Returns the iIndex item of the Multi-value object (array, collection or map)
*
* @param iObject Multi-value object (array, collection or map)
* @param iIndex integer as the position requested
*
* @return The first item if any
*/
public static Object getValue(final Object iObject, final int iIndex) {
if (iObject == null)
return null;
if (!isMultiValue(iObject))
return null;
final int size = MultiValue.getSizeIfAvailable(iObject);
if (size > -1 && !(iObject instanceof Iterator) && iIndex > size)
return null;
try {
if (iObject instanceof List>)
return ((List>) iObject).get(iIndex);
else if (iObject instanceof Set>) {
int i = 0;
for (final Object o : ((Set>) iObject)) {
if (i++ == iIndex) {
return o;
}
}
} else if (iObject instanceof Map, ?>) {
int i = 0;
for (final Object o : ((Map, ?>) iObject).values()) {
if (i++ == iIndex) {
return o;
}
}
} else if (iObject.getClass().isArray())
return Array.get(iObject, iIndex);
else if (iObject instanceof Iterator> || iObject instanceof Iterable>) {
final Iterator it = (iObject instanceof Iterable>) ? ((Iterable) iObject).iterator() : (Iterator) iObject;
if (it.hasNext()) {
for (int i = 0; it.hasNext(); ++i) {
final Object o = it.next();
if (i == iIndex) {
if (it instanceof ResettableIterator)
((ResettableIterator) it).reset();
return o;
}
}
if (it instanceof ResettableIterator)
((ResettableIterator) it).reset();
}
}
} catch (final RuntimeException e) {
// IGNORE IT
LogManager.instance().log(iObject, Level.FINE, "Error on reading the first item of the Multi-value field '%s'", null, iObject, e);
}
return null;
}
/**
* Sets the value of the Multi-value object (array or collection) at iIndex
*
* @param iObject Multi-value object (array, collection)
* @param iValue The value to set at this specified index.
* @param iIndex integer as the position requested
*/
public static void setValue(final Object iObject, final Object iValue, final int iIndex) {
if (iObject instanceof List>) {
((List) iObject).set(iIndex, iValue);
} else if (iObject.getClass().isArray()) {
Array.set(iObject, iIndex, iValue);
} else {
throw new IllegalArgumentException("Can only set positional indices for Lists and Arrays");
}
}
/**
* Returns an {@literal Iterable} object to browse the multi-value instance (array, collection or map).
*
* @param iObject Multi-value object (array, collection or map)
*/
public static Iterable getMultiValueIterable(final Object iObject) {
if (iObject == null)
return null;
if (iObject instanceof Iterable>)
return (Iterable) iObject;
else if (iObject instanceof Collection>)
return ((Collection) iObject);
else if (iObject instanceof Map, ?>)
return ((Map, Object>) iObject).values();
else if (iObject.getClass().isArray())
return new IterableObjectArray<>(iObject);
else if (iObject instanceof Iterator>) {
final List temp = new ArrayList<>();
for (final Iterator it = (Iterator) iObject; it.hasNext(); )
temp.add(it.next());
return temp;
}
return new IterableObject<>(iObject);
}
/**
* Returns an {@literal Iterable} object to browse the multi-value instance (array, collection or map).
*
* @param iObject Multi-value object (array, collection or map)
* @param iForceConvertRecord allow to force settings to convert RIDs to records while browsing.
*/
public static Iterable getMultiValueIterable(final Object iObject, final boolean iForceConvertRecord) {
if (iObject == null)
return null;
if (iObject instanceof Iterable>)
return (Iterable) iObject;
else if (iObject instanceof Collection>)
return ((Collection) iObject);
else if (iObject instanceof Map, ?>)
return ((Map, Object>) iObject).values();
else if (iObject.getClass().isArray())
return new IterableObjectArray<>(iObject);
else if (iObject instanceof Iterator>) {
final List temp = new ArrayList<>();
for (final Iterator it = (Iterator) iObject; it.hasNext(); )
temp.add(it.next());
return temp;
}
return new IterableObject<>(iObject);
}
/**
* Returns an {@literal Iterator} object to browse the multi-value instance (array, collection or map)
*
* @param iObject Multi-value object (array, collection or map)
* @param iForceConvertRecord allow to force settings to convert RIDs to records while browsing.
*/
public static Iterator getMultiValueIterator(final Object iObject, final boolean iForceConvertRecord) {
if (iObject == null)
return null;
if (iObject instanceof Iterator>)
return (Iterator) iObject;
if (iObject instanceof Iterable>)
return ((Iterable) iObject).iterator();
if (iObject instanceof Map, ?>)
return ((Map, Object>) iObject).values().iterator();
if (iObject.getClass().isArray())
return new IterableObjectArray<>(iObject).iterator();
return new IterableObject<>(iObject);
}
/**
* Returns an {@literal Iterator} object to browse the multi-value instance (array, collection or map)
*
* @param iObject Multi-value object (array, collection or map)
*/
public static Iterator getMultiValueIterator(final Object iObject) {
if (iObject == null)
return null;
if (iObject instanceof Iterator>)
return (Iterator) iObject;
if (iObject instanceof Iterable>)
return ((Iterable) iObject).iterator();
if (iObject instanceof Map, ?>)
return ((Map, Object>) iObject).values().iterator();
if (iObject.getClass().isArray())
return new IterableObjectArray<>(iObject).iterator();
return new IterableObject<>(iObject);
}
/**
* Returns a stringified version of the multi-value object.
*
* @param iObject Multi-value object (array, collection or map)
*
* @return a stringified version of the multi-value object.
*/
public static String toString(final Object iObject) {
final StringBuilder sb = new StringBuilder(2048);
if (iObject instanceof Iterable>) {
final Iterable coll = (Iterable) iObject;
sb.append('[');
for (final Iterator it = coll.iterator(); it.hasNext(); ) {
try {
final Object e = it.next();
sb.append(e == iObject ? "(this Collection)" : e);
if (it.hasNext())
sb.append(", ");
} catch (final NoSuchElementException ignore) {
// IGNORE THIS
}
}
return sb.append(']').toString();
} else if (iObject instanceof Map, ?>) {
final Map map = (Map) iObject;
Map.Entry e;
sb.append('{');
for (final Iterator> it = map.entrySet().iterator(); it.hasNext(); ) {
try {
e = it.next();
sb.append(e.getKey());
sb.append(":");
sb.append(e.getValue() == iObject ? "(this Map)" : e.getValue());
if (it.hasNext())
sb.append(", ");
} catch (final NoSuchElementException ignore) {
// IGNORE THIS
}
}
return sb.append('}').toString();
}
return iObject.toString();
}
/**
* Utility function that add a value to the main object. It takes care about collections/array and single values.
*
* @param iObject MultiValue where to add value(s)
* @param iToAdd Single value, array of values or collections of values. Map are not supported.
*
* @return
*/
public static Object add(final Object iObject, final Object iToAdd) {
if (iObject != null) {
if (iObject instanceof Collection>) {
final Collection coll = (Collection) iObject;
if (!(iToAdd instanceof Map) && isMultiValue(iToAdd)) {
// COLLECTION - COLLECTION
for (final Object o : getMultiValueIterable(iToAdd, false)) {
if (!(o instanceof Map) && isMultiValue(o))
add(coll, o);
else
coll.add(o);
}
} else if (iToAdd != null && iToAdd.getClass().isArray()) {
// ARRAY - COLLECTION
for (int i = 0; i < Array.getLength(iToAdd); ++i) {
final Object o = Array.get(iToAdd, i);
if (!(o instanceof Map) && isMultiValue(o))
add(coll, o);
else
coll.add(o);
}
} else if (iToAdd instanceof Map, ?>) {
// MAP
coll.add(iToAdd);
} else if (iToAdd instanceof Iterator>) {
// ITERATOR
for (final Iterator> it = (Iterator>) iToAdd; it.hasNext(); )
coll.add(it.next());
} else
coll.add(iToAdd);
} else if (iObject.getClass().isArray()) {
// ARRAY - ?
final Object[] copy;
if (iToAdd instanceof Collection>) {
// ARRAY - COLLECTION
final int tot = Array.getLength(iObject) + ((Collection) iToAdd).size();
copy = Arrays.copyOf((Object[]) iObject, tot);
final Iterator it = ((Collection) iToAdd).iterator();
for (int i = Array.getLength(iObject); i < tot; ++i)
copy[i] = it.next();
} else if (iToAdd != null && iToAdd.getClass().isArray()) {
// ARRAY - ARRAY
final int tot = Array.getLength(iObject) + Array.getLength(iToAdd);
copy = Arrays.copyOf((Object[]) iObject, tot);
System.arraycopy(iToAdd, 0, iObject, Array.getLength(iObject), Array.getLength(iToAdd));
} else {
copy = Arrays.copyOf((Object[]) iObject, Array.getLength(iObject) + 1);
copy[copy.length - 1] = iToAdd;
}
return copy;
}
}
return iObject;
}
/**
* Utility function that remove a value from the main object. It takes care about collections/array and single values.
*
* @param iObject MultiValue where to add value(s)
* @param iToRemove Single value, array of values or collections of values. Map are not supported.
* @param iAllOccurrences True if the all occurrences must be removed or false of only the first one (Like
* java.util.Collection.remove())
*
* @return
*/
public static Object remove(Object iObject, Object iToRemove, final boolean iAllOccurrences) {
if (iObject != null) {
if (iObject instanceof MultiIterator>) {
final Collection list = new LinkedList<>();
for (final Object o : ((MultiIterator>) iObject))
list.add(o);
iObject = list;
}
if (iToRemove instanceof MultiIterator>) {
// TRANSFORM IN SET ONCE TO OPTIMIZE LOOPS DURING REMOVE
final Set set = new HashSet<>();
for (final Object o : ((MultiIterator>) iToRemove))
set.add(o);
iToRemove = set;
}
if (iObject instanceof Collection>) {
final Collection coll = (Collection) iObject;
if (iToRemove instanceof Collection>) {
// COLLECTION - COLLECTION
for (final Object o : (Collection) iToRemove) {
if (isMultiValue(o))
remove(coll, o, iAllOccurrences);
else
removeFromOCollection(iObject, coll, o, iAllOccurrences);
}
} else if (iToRemove != null && iToRemove.getClass().isArray()) {
// ARRAY - COLLECTION
for (int i = 0; i < Array.getLength(iToRemove); ++i) {
final Object o = Array.get(iToRemove, i);
if (isMultiValue(o))
remove(coll, o, iAllOccurrences);
else
removeFromOCollection(iObject, coll, o, iAllOccurrences);
}
} else if (iToRemove instanceof Map, ?>) {
// MAP
for (final Map.Entry entry : ((Map) iToRemove).entrySet())
coll.remove(entry.getKey());
} else if (iToRemove instanceof Iterator>) {
// ITERATOR
if (iToRemove instanceof MultiIterator>)
((MultiIterator>) iToRemove).reset();
if (iAllOccurrences) {
final Collection collection = (Collection) iObject;
final MultiIterator> it = (MultiIterator>) iToRemove;
batchRemove(collection, it);
} else {
final Iterator> it = (Iterator>) iToRemove;
if (it.hasNext()) {
final Object itemToRemove = it.next();
coll.remove(itemToRemove);
}
}
} else
removeFromOCollection(iObject, coll, iToRemove, iAllOccurrences);
} else if (iObject.getClass().isArray()) {
// ARRAY - ?
final Object[] copy;
if (iToRemove instanceof Collection>) {
// ARRAY - COLLECTION
final int sourceTot = Array.getLength(iObject);
final int tot = sourceTot - ((Collection) iToRemove).size();
copy = new Object[tot];
int k = 0;
for (int i = 0; i < sourceTot; ++i) {
final Object o = Array.get(iObject, i);
if (o != null) {
boolean found = false;
for (final Object toRemove : (Collection) iToRemove) {
if (o.equals(toRemove)) {
// SKIP
found = true;
break;
}
}
if (!found)
copy[k++] = o;
}
}
} else if (iToRemove != null && iToRemove.getClass().isArray()) {
throw new UnsupportedOperationException("Cannot execute remove() against an array");
} else {
throw new UnsupportedOperationException("Cannot execute remove() against an array");
}
return copy;
} else if (iObject instanceof Map) {
((Map) iObject).remove(iToRemove);
} else
throw new IllegalArgumentException("Object " + iObject + " is not a multi value");
}
return iObject;
}
protected static void removeFromOCollection(final Object iObject, final Collection coll, final Object iToRemove, final boolean iAllOccurrences) {
if (iAllOccurrences && !(iObject instanceof Set)) {
// BROWSE THE COLLECTION ONE BY ONE TO REMOVE ALL THE OCCURRENCES
coll.removeIf(iToRemove::equals);
} else
coll.remove(iToRemove);
}
private static void batchRemove(final Collection coll, final Iterator> it) {
int approximateRemainingSize = -1;
while (it.hasNext()) {
final Set> batch = prepareBatch(it, approximateRemainingSize);
coll.removeAll(batch);
approximateRemainingSize -= batch.size();
}
}
private static Set> prepareBatch(final Iterator> it, final int approximateRemainingSize) {
final HashSet batch;
if (approximateRemainingSize > -1) {
if (approximateRemainingSize > 10000)
batch = new HashSet<>(13400);
else
batch = new HashSet<>((int) (approximateRemainingSize / 0.75));
} else {
batch = new HashSet<>();
}
int count = 0;
while (count < 10000 && it.hasNext()) {
batch.add(it.next());
count++;
}
return batch;
}
public static Object[] array(final Object iValue) {
return array(iValue, Object.class);
}
public static T[] array(final Object iValue, final Class extends T> iClass) {
return array(iValue, iClass, null);
}
public static T[] array(final Object iValue, final Class extends T> iClass, final Callable iCallback) {
if (iValue == null)
return null;
final T[] result;
if (isMultiValue(iValue)) {
// CREATE STATIC ARRAY AND FILL IT
result = (T[]) Array.newInstance(iClass, getSize(iValue));
int i = 0;
for (final Iterator it = (Iterator) getMultiValueIterator(iValue, false); it.hasNext(); ++i)
result[i] = (T) convert(it.next(), iCallback);
} else if (isIterable(iValue)) {
// SIZE UNKNOWN: USE A LIST AS TEMPORARY OBJECT
final List temp = new ArrayList<>();
for (final Iterator it = (Iterator) getMultiValueIterator(iValue, false); it.hasNext(); )
temp.add((T) convert(it.next(), iCallback));
if (iClass.equals(Object.class))
result = (T[]) temp.toArray();
else
// CONVERT THEM
result = temp.toArray((T[]) Array.newInstance(iClass, getSize(iValue)));
} else {
result = (T[]) Array.newInstance(iClass, 1);
result[0] = (T) convert(iValue, iCallback);
}
return result;
}
public static Object convert(final Object iObject, final Callable iCallback) {
return iCallback != null ? iCallback.call(iObject) : iObject;
}
public static boolean equals(final Collection col1, final Collection col2) {
if (col1.size() != col2.size())
return false;
return col1.containsAll(col2) && col2.containsAll(col1);
}
public static boolean contains(final Object iObject, final Object iItem) {
if (iObject == null)
return false;
if (iObject instanceof Collection)
return ((Collection) iObject).contains(iItem);
else if (iObject.getClass().isArray()) {
final int size = Array.getLength(iObject);
for (int i = 0; i < size; ++i) {
final Object item = Array.get(iObject, i);
if (item != null && item.equals(iItem))
return true;
}
}
return false;
}
public static int indexOf(final Object iObject, final Object iItem) {
if (iObject == null)
return -1;
if (iObject instanceof List)
return ((List) iObject).indexOf(iItem);
else if (iObject.getClass().isArray()) {
final int size = Array.getLength(iObject);
for (int i = 0; i < size; ++i) {
final Object item = Array.get(iObject, i);
if (item != null && item.equals(iItem))
return i;
}
}
return -1;
}
public static Object toSet(final Object o) {
if (o instanceof Set>)
return o;
else if (o instanceof Collection>)
return new HashSet((Collection>) o);
else if (o instanceof Map, ?>) {
final Collection values = ((Map) o).values();
return values instanceof Set ? values : new HashSet(values);
} else if (o.getClass().isArray()) {
final HashSet set = new HashSet();
final int tot = Array.getLength(o);
for (int i = 0; i < tot; ++i) {
set.add(Array.get(o, i));
}
return set;
} else if (o instanceof MultiValue) {
} else if (o instanceof Iterator>) {
final HashSet set = new HashSet();
while (((Iterator>) o).hasNext()) {
set.add(((Iterator>) o).next());
}
return set;
} else if (o instanceof Iterable && !(o instanceof Identifiable)) {
final Iterator iterator = ((Iterable) o).iterator();
final Set result = new HashSet();
while (iterator.hasNext()) {
result.add(iterator.next());
}
return result;
}
final HashSet set = new HashSet(1);
set.add(o);
return set;
}
public static List getSingletonList(final T item) {
final List list = new ArrayList<>(1);
list.add(item);
return list;
}
}