/*
* CoreObject.java
*
* Copyright (c) 2010, Ralf Biedert All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer. Redistributions in binary form must reproduce the
* above copyright notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of the author nor the names of its contributors may be used to endorse or
* promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.jcores.cores;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.jcores.CommonCore;
import net.jcores.interfaces.functions.F0;
import net.jcores.interfaces.functions.F1;
import net.jcores.interfaces.functions.F1Object2Bool;
import net.jcores.interfaces.functions.F1Object2Int;
import net.jcores.interfaces.functions.F2DeltaObjects;
import net.jcores.interfaces.functions.F2ReduceObjects;
import net.jcores.options.MessageType;
import net.jcores.options.Option;
import net.jcores.options.OptionMapType;
import net.jcores.utils.Folder;
import net.jcores.utils.Mapper;
import net.jcores.utils.Staple;
import net.jcores.utils.lang.ObjectUtils;
/**
* The standard core that wraps a number of objects and exposes a number of methods to act on
* them, some of them in parallel. If you implement your own core you should extend this class.
*
* @author Ralf Biedert
* @since 1.0
*
* @param <T> Type of the objects to wrap.
*/
public class CoreObject<T> extends Core {
/** The array we work on. */
protected final T[] t;
/**
* Creates the core object for the given single object.
*
* @param supercore CommonCore to use.
* @param type Type of the object to wrap (in case it is null).
* @param object Object to wrap.
*/
@SuppressWarnings("unchecked")
public CoreObject(CommonCore supercore, Class<?> type, T object) {
super(supercore);
// Check if we have an object. If not, and if there is no type, use an
// empty Object array
if (object != null) {
this.t = (T[]) Array.newInstance(type, 1);
this.t[0] = object;
} else {
this.t = (T[]) new Object[0];
}
}
/**
* Creates the core object for the given array.
*
* @param supercore CommonCore to use.
* @param objects Object to wrap.
*/
public CoreObject(CommonCore supercore, T... objects) {
super(supercore);
this.t = objects;
}
/**
* Returns the core's content as an array of the given type. Elements that don't fit
* into the given target type will be skipped.<br/><br/>
*
* Single-threaded.<br/><br/>
*
* @param in Type of the target array to use.
* @param <N> Type of the array.
*
* @return An array containing the all assignable elements.
*/
@SuppressWarnings("unchecked")
public <N> N[] array(Class<N> in) {
final N[] n = (N[]) Array.newInstance(in, 0);
if (this.t != null)
return (N[]) Arrays.copyOf(this.t, this.t.length, n.getClass());
return (N[]) Array.newInstance(in, 0);
}
/**
* Returns a core that tries to treat all elements as being of the given type. Elements which
* don't match are ignored. Can also be used to load extensions (e.g., <code>somecore.as(CoreString.class)</code>).
* This function should not be called within hot-spots (functions called millions of times a second)
* as it relies heavily on reflection.<br/><br/>
*
* Single-threaded. Heavyweight.<br/><br/>
*
* @param <C> Type of the clazz.
* @param clazz Core to be spawned and returned.
* @return If successful, spawns a core of type <code>clazz</code> and returns it, wrapping all contained elements.
*/
@SuppressWarnings({ "unchecked", "null" })
public <C extends Core> C as(Class<C> clazz) {
try {
final Constructor<?>[] constructors = clazz.getConstructors();
Constructor<C> constructor = null;
// Find a proper constructor!
for (Constructor<?> c : constructors) {
if (c.getParameterTypes().length != 2) continue;
if (!c.getParameterTypes()[0].equals(CommonCore.class)) continue;
// Sanity check.
if (constructor != null)
System.err.println("There should only be one constructor per core! And here comes your exception ... ;-)");
constructor = (Constructor<C>) c;
}
Object newT[] = null;
Class<? extends Object[]> requestedType = null;
// Try to convert our array. If that fails, create an empty one ...
try {
requestedType = (Class<? extends Object[]>) constructor.getParameterTypes()[1];
newT = Arrays.copyOf(this.t, size(), requestedType);
} catch (ArrayStoreException e) {
this.commonCore.report(MessageType.EXCEPTION, "Unable to convert our array " + this.t + " to the requested type " + requestedType + ". Returning empty core.");
newT = (Object[]) Array.newInstance(requestedType.getComponentType(), 0);
}
return constructor.newInstance(this.commonCore, newT);
// NOTE: We do not swallow all execptions, because as() is a bit special and we cannot return
// anyhting that would still be usable.
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NullPointerException e) {
this.commonCore.report(MessageType.EXCEPTION, "No constructor found for " + clazz);
System.err.println("No suitable constructor found!");
}
return null;
}
/**
* Performs a generic call on each element of this core. This function should not be called within
* hot-spots (functions called millions of times a second) as it relies heavily on reflection.<br/><br/>
*
* Multi-threaded. Heavyweight.<br/><br/>
*
* @param string The call to perform, e.g. <code>toString</code>
* @param params Parameters the call takes
*
* @return A CoreObject wrapping the results of each invocation.
*/
@SuppressWarnings("null")
public CoreObject<Object> call(final String string, final Object... params) {
final int len = params == null ? 0 : params.length;
final Class<?>[] types = new Class[len];
// Convert classes.
for (int i = 0; i < len; i++) {
types[i] = params[i].getClass();
}
return new CoreObject<Object>(this.commonCore, map(new F1<T, Object>() {
public Object f(T x) {
try {
final Method method = x.getClass().getMethod(string, types);
return method.invoke(x, params);
} catch (SecurityException e) {
CoreObject.this.commonCore.report(MessageType.EXCEPTION, "SecurityException for " + x + " (method was " + string + ")");
} catch (NoSuchMethodException e) {
CoreObject.this.commonCore.report(MessageType.EXCEPTION, "NoSuchMethodException for " + x + " (method was " + string + ")");
} catch (IllegalArgumentException e) {
CoreObject.this.commonCore.report(MessageType.EXCEPTION, "IllegalArgumentException for " + x + " (method was " + string + ")");
} catch (IllegalAccessException e) {
CoreObject.this.commonCore.report(MessageType.EXCEPTION, "IllegalAccessException for " + x + " (method was " + string + ")");
} catch (InvocationTargetException e) {
CoreObject.this.commonCore.report(MessageType.EXCEPTION, "InvocationTargetException for " + x + " (method was " + string + ")");
}
return null;
}
}).array(Object.class));
}
/**
* Casts all elements to the given type or sets them null if they are not castable.<br/><br/>
*
* Multi-threaded. <br/><br/>
*
* @param <N> Target type.
* @param target Class to cast all elements
* @return A CoreObject wrapping all cast elements.
*/
public <N> CoreObject<N> cast(final Class<N> target) {
return map(new F1<T, N>() {
@SuppressWarnings("unchecked")
@Override
public N f(T x) {
if (target.isAssignableFrom(x.getClass())) return (N) x;
return null;
}
}, new OptionMapType(target));
}
/**
* Returns a compacted core whose underlying array does not
* contain null anymore. <br/><br/>
*
* Single-threaded. <br/><br/>
*
* @return A new CoreObject of the same type, with a (probably) reduced size without any null element.
*/
public CoreObject<T> compact() {
// No size == no fun.
if (size() == 0) return this;
final T[] tmp = Arrays.copyOf(this.t, this.t.length);
int dst = 0;
// Now process our array
for (int i = 0; i < this.t.length; i++) {
if (this.t[i] == null) continue;
tmp[dst++] = this.t[i];
}
return new CoreObject<T>(this.commonCore, Arrays.copyOf(tmp, dst));
}
/**
* Returns true if this core contains the given object. <br/><br/>
*
* Single-threaded. <br/><br/>
*
* @param object The object to search for. A search for null will always return false.
* @return True if the object is there, false if not.
*/
public boolean contains(final T object) {
// TODO: Parallelize me.
for (int i = 0; i < size(); i++) {
if (this.t[i] != null && this.t[i].equals(object)) return true;
}
return false;
}
/**
* Prints a debug output. Useful for figuring out what's going wrong in a
* chain of map() operations.<br/><br/>
*
* Single-threaded. <br/><br/>
*
* @return This object again.
*/
public CoreObject<T> debug() {
String inner = "null";
int ctr = 0;
if (this.t != null) {
for (int i = 0; i < this.t.length; i++) {
if (this.t[i] != null) ctr++;
}
inner = "" + ctr;
}
System.out.println("@(" + getClass().getSimpleName() + "; outerSize:" + size() + "; innerSize:" + inner + ")");
return this;
}
/**
* Returns a core of length size() - 1 consisting of the results of the delta
* function. Delta always takes two adjacent elements and execute stores the
* delta function's output. In contrast to the common map operation this function
* does not ignore <code>null</code> elements. If of two adjacent slots any
* is <code>null</code>, the value <code>null</code> will be stored. <br/><br/>
*
* Multi-threaded. <br/><br/>
*
* @param delta The delta function, taking two elements and return a result.
* @param <R> Type of the result.
* @param options Relevant options: <code>OptionMapType</code>.
*
* @return A core of size n - 1 containing all deltas.
*/
@SuppressWarnings("unchecked")
public <R> CoreObject<R> delta(final F2DeltaObjects<T, R> delta, Option... options) {
Class<?> mapType = null;
// Check options if we have a map type.
for (Option option : options) {
if (option instanceof OptionMapType) {
mapType = ((OptionMapType) option).getType();
}
}
// Create mapper
final Mapper mapper = new Mapper(mapType, size() - 1) {
@Override
public void handle(int i) {
// Get our target-array (if it is already there)
R[] a = (R[]) this.array.get();
// Get the in-value from the source-array
final T ii = CoreObject.this.t[i];
final T jj = CoreObject.this.t[i + 1];
// Convert
if (ii == null || jj == null) return;
final R out = delta.f(ii, jj);
if (out == null) return;
// If we haven't had an in-array, create it now, according to the return type
if (a == null) {
a = (R[]) updateArray(Array.newInstance(out.getClass(), this.size));
}
// Eventually set the out value
a[i] = out;
}
};
// Map ...
map(mapper, options);
// In case we don't have a return array (which happens when the mapper never returned something
// sensible), we create a simple object array of our size, so that the core's size stays
// consistent.
R rval[] = (R[]) mapper.getTargetArray();
if (rval == null) {
rval = (R[]) Array.newInstance(Object.class, Math.max(0, size() - 1));
}
// ... and return result.
return new CoreObject<R>(this.commonCore, rval);
}
/**
* Returns a single object that, if any of its functions is executed, the corresponding function is
* executed on all enclosed elements. Only works if <code>c</code> is an interface and only on
* enclosed elements implementing <code>c</code>. From a performance perspective this method only
* makes sense if the requested operation is complex, as on simple methods the reflection costs will
* outweigh all benefits. Also note that all return values are skipped. <br/><br/>
*
* Multi-threaded. Heavyweight.<br/><br/>
*
* @param c The interface to use.
* @param <X> The interface's type.
*
* @return Something implementing c that acts on each element implementing c.
*/
@SuppressWarnings("unchecked")
public <X> X each(final Class<X> c) {
if (c == null || !c.isInterface()) {
System.err.println("You must pass an interface.");
return null;
}
// Get only assignable classes of our collection
final CoreObject<X> filtered = cast(c);
// Provide an invocation handler
return (X) ObjectUtils.getProxy(new InvocationHandler() {
public Object invoke(Object proxy, final Method method, final Object[] args)
throws Throwable {
filtered.map(new F1<X, Object>() {
public Object f(X x) {
try {
method.invoke(x, args);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
});
return null;
}
}, c);
}
/**
* Expands contained arrays into a single array of the given type. This means, if this core wraps
* a number of collections, lists or arrays, each of which are containing elements on their own,
* <code>expand()</code> will break up all of these lists and return a single CoreObject wrapping
* the union of everything that was previously held in them.<br/><br/>
*
* Single-threaded.<br/><br/>
*
* @param <N> Type of the return core.
* @param class1 Defines the class element of the returned core's array.
* @return A CoreObject wrapping all broken up collections, arrays, ...
*/
@SuppressWarnings("unchecked")
public <N> CoreObject<N> expand(Class<N> class1) {
int length = 0;
if (this.t == null) return new CoreObject<N>(this.commonCore, class1, null);
// Compute overall size
for (T x : this.t) {
if (x == null) continue;
// Is it a collection?
if (x instanceof Collection<?>) {
length += ((Collection<?>) x).size();
continue;
}
// An array?
try {
length += Array.getLength(x);
continue;
} catch (IllegalArgumentException e) {
//
}
// A single object?!
length++;
}
// Generate array
N[] n = (N[]) Array.newInstance(class1, length);
int offset = 0;
// Copy to array
for (T x : this.t) {
if (x == null) continue;
// Is it a collection?
if (x instanceof Collection<?>) {
Object[] array = ((Collection<?>) x).toArray();
System.arraycopy(array, 0, n, offset, array.length);
offset += array.length;
continue;
}
// An array?
try {
int size = Array.getLength(x);
System.arraycopy(x, 0, n, offset, size);
offset += size;
continue;
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
} catch (ArrayStoreException e) {
//
}
// A single element?
Array.set(n, offset, x);
}
return new CoreObject<N>(this.commonCore, n);
}
/**
* Returns a new core with all null elements set to <code>fillValue</code>.<br/><br/>
*
* Single-threaded.<br/><br/>
*
* @param fillValue Value used to fill up all <code>null</code> slots.
*
* @return A filled up CoreObject.
*/
public CoreObject<T> fill(T fillValue) {
if (this.t == null) return this;
final T[] copy = Arrays.copyOf(this.t, size());
for (int i = 0; i < copy.length; i++) {
copy[i] = copy[i] == null ? fillValue : copy[i];
}
return new CoreObject<T>(this.commonCore, copy);
}
/**
* Filters the object using the given function. A compacted array will be returned that
* contains only values for which f returned true.<br/><br/>
*
* Multi-threaded.<br/><br/>
*
* @param f If f returns true the object is kept.
* @param options No options supported right now.
*
* @return A new CoreObject of our type, containing only kept elements.
*/
public CoreObject<T> filter(final F1Object2Bool<T> f, Option... options) {
CoreObject<T> rval = map(new F1<T, T>() {
public T f(T x) {
if (f.f(x)) return x;
return null;
}
});
return rval.compact();
}
/**
* Filters all object by their toString() value using the given regular
* expression. <br/><br/>
*
* Multi-threaded.<br/><br/>
*
* @param regex The regular expression to use.
* @param options Currently none used.
*
* @return A CoreObject containing a filtered subset of our elements.
*/
public CoreObject<T> filter(final String regex, Option... options) {
final Pattern p = Pattern.compile(regex);
return filter(new F1Object2Bool<T>() {
public boolean f(T x) {
final Matcher matcher = p.matcher(x.toString());
return matcher.matches();
}
}, options);
}
/**
* Reduces the given object, multi-threaded version. In contrast to reduce() the
* order in which two element might be reduced is not defined. Note: At present,
* reduce() is much faster for simple operations, as it involves much less synchronization
* overhead. Right now, only use fold for very complex operations. <br/><br/>
*
* Multi-threaded. Heavyweight.<br/><br/>
*
* @param f The reduce function. Takes two elements, returns one.
* @param options Relevant options: <code>OptionMapType</code>.
* @return A CoreObject, containing at most a single element.
*/
@SuppressWarnings("unchecked")
public CoreObject<T> fold(final F2ReduceObjects<T> f, Option... options) {
// In case we only have zero or one elements, don't do anything
if (size() <= 1) return this;
final Folder folder = new Folder(null, size()) {
@Override
public void handle(int i, int j, int destination) {
// Get our target-array (if it is already there)
T[] a = (T[]) this.array.get();
// Get the in-value from the source-array
final T ii = a[i];
final T jj = a[j];
if (ii == null && jj == null) return;
if (ii == null && jj != null) {
a[destination] = jj;
return;
}
if (ii != null && jj == null) {
a[destination] = ii;
return;
}
a[destination] = f.f(ii, jj);
}
};
// Update the target array and fold ...
folder.updateArray(Arrays.copyOf(this.t, size()));
// Now do fold ...
fold(folder, options);
// ... and return result.
return new CoreObject<T>(this.commonCore, Arrays.copyOf((T[]) folder.getTargetArray(), 1));
}
/**
* Return the an element at the the relative position.<br/><br/>
*
* Single-threaded. <br/><br/>
*
* @param percent 0.0 returns the first element, 1.0 the last element, 0.5 returns the element in the
* middle, and so on.
* @return The value at the requested position, or null if there is none.
*/
public T get(double percent) {
if (Double.isNaN(percent)) return null;
if (percent < 0) return null;
if (percent > 1) return null;
return this.t[(int) (percent * size())];
}
/**
* Returns the first element that is an instance of the requested type.
*
* Single-threaded. Heavyweight.<br/><br/>
*
* @param <X> Subtype to request.
* @param request A subclass of our type to request.
* @param dflt The value to return when no class was found.
* @return The first object that is assignable to request, or dflt if there was no such element.
*/
@SuppressWarnings("unchecked")
public <X extends T> X get(Class<X> request, X dflt) {
for (int i = 0; i < size(); i++) {
if (this.t[i] != null && request.isAssignableFrom(this.t[i].getClass()))
return (X) this.t[i];
}
return dflt;
}
/**
* Return the ith element.<br/><br/>
*
* Single-threaded. <br/><br/>
*
* @param i Position to retrieve. Negative indices are treated as values starting at the end (i.e., -1 is the last element, -2 the second-last, ...)
*
* @return The element at the given position.
*/
public T get(int i) {
final int offset = indexToOffset(i);
if (this.t == null || offset < 0) return null;
return this.t[offset];
}
/**
* Return the ith element or dflt if the element if otherwise <code>null</code> had been returned.<br/><br/>
*
* Single-threaded. <br/><br/>
*
* @param i Position to retrieve. Negative indices are treated as values starting at the end (i.e., -1 is the last element, -2 the second-last, ...)
* @param dflt The value to return if null had been returned.
*
* @return Unless dflt is null, this function is guaranteed to return a non-null value.
*/
public T get(int i, T dflt) {
final T rval = get(i);
return rval == null ? dflt : rval;
}
/**
* Returns the first element, or, if there is none, return dflt.<br/><br/>
*
* Single-threaded. <br/><br/>
*
* @param dflt The value to return if get(0) is null.
* @return Unless dflt is null, this function is guaranteed to return a non-null value.
*/
public T get(T dflt) {
return get(0, dflt);
}
/**
* Checks if all elements are not null.<br/><br/>
*
* Single-threaded. <br/><br/>
*
* @return True if all elements are not null, false if a single element was null.
*/
public boolean hasAll() {
// TODO: That's not fast, just lazy ...
return size() == compact().size();
}
/**
* Checks if the element has any element.<br/><br/>
*
* Single-threaded. <br/><br/>
*
* @return True if any element is set. False if all elements are null.
*/
public boolean hasAny() {
// TODO: That's not fast, just lazy ...
return compact().size() > 0;
}
/**
* If all elements are present, execute f0.<br/><br/>
*
* Single-threaded. <br/><br/>
*
* @param f0 The function to execute if all elements are given.
*/
public void ifAll(F0 f0) {
if (hasAll()) f0.f();
}
/**
* Returns the wrapped collection as a list.<br/><br/>
*
* Single-threaded. <br/><br/>
*
* @return A list containing all elements. Null values should be preserved.
*/
public List<T> list() {
if (this.t == null) return new ArrayList<T>();
return Arrays.asList(this.t);
}
/**
* Maps the core's content with the given function and returns the result. This is the most fundamental
* function of this core. Maps all elements using the given mapper function. If the core is of size 0
* nothing is done, if it is of size 1 <code>f</code> is executed directly. <br/><br/>
*
* Multi-threaded.<br/><br/>
*
* @param <R> Return type.
* @param f Mapper function, should be thread-safe.
* @param options Relevant options: <code>OptionMapType</code>.
*
* @return A CoreObject containing the mapped elements in a stable order.
*/
@SuppressWarnings("unchecked")
public <R> CoreObject<R> map(final F1<T, R> f, Option... options) {
Class<?> mapType = null;
// Check options if we have a map type.
for (Option option : options) {
if (option instanceof OptionMapType) {
mapType = ((OptionMapType) option).getType();
}
}
// Create mapper
final Mapper mapper = new Mapper(mapType, size()) {
@Override
public void handle(int i) {
// Get our target-array (if it is already there)
R[] a = (R[]) this.array.get();
// Get the in-value from the source-array
final T in = CoreObject.this.t[i];
// Convert
if (in == null) return;
final R out = f.f(in);
if (out == null) return;
// If we haven't had an in-array, create it now, according to the return type
if (a == null) {
a = (R[]) updateArray(Array.newInstance(out.getClass(), this.size));
}
// Eventually set the out value
a[i] = out;
}
};
// Map ...
map(mapper, options);
// In case we don't have a return array (which happens when the mapper never returned something
// sensible), we create a simple object array of our size, so that the core's size stays
// consistent.
R rval[] = (R[]) mapper.getTargetArray();
if (rval == null) {
rval = (R[]) Array.newInstance(Object.class, size());
}
// ... and return result.
return new CoreObject<R>(this.commonCore, rval);
}
/**
* Maps the core's content with the given function and returns the result.
*
* @param f
* @param options
*
* @deprecated
* @return The mapped elements in a stable order
*/
@Deprecated
public CoreInt map(final F1Object2Int<T> f, Option... options) {
final Mapper mapper = new Mapper(int.class, size()) {
@Override
public void handle(int i) {
int[] a = (int[]) this.array.get();
a[i] = f.f(CoreObject.this.t[i]);
}
};
map(mapper, options);
return new CoreInt(this.commonCore, (int[]) mapper.getTargetArray());
}
/**
* Returns a randomly selected object, including null values.<br/><br/>
*
* Single-threaded.<br/><br/>
*
* @return A randomly selected object from this core.
*/
public T random() {
final int size = size();
if (size == 0) return null;
return this.t[this.commonCore.random().nextInt(size)];
}
/**
* Reduces the given object, single-threaded version. In contrast to fold() the
* order in which two element might be reduced is well defined from left to right. You should
* use <code>reduce()</code> for simple operations and <code>fold()</code> for very
* complex operations.<br/><br/>
*
* Single-threaded.<br/><br/>
*
* @param f The reduce function. Takes two elements, returns one.
* @param options Relevant options: <code>OptionMapType</code>.
* @return A CoreObject, containing at most a single element.
*/
@SuppressWarnings("unchecked")
public CoreObject<T> reduce(final F2ReduceObjects<T> f, Option... options) {
T stack = null;
for (int i = 0; i < size(); i++) {
T current = this.t[i];
// Nothing to do for null elements
if (current == null) continue;
// Init stack with first element found
if (stack == null) {
stack = current;
continue;
}
stack = f.f(stack, current);
}
// We have to use the stack, because we otherwise we might
// create an array of another type, and not the type f returned.
final Class<T> type = stack != null ? (Class<T>) stack.getClass() : null;
return new CoreObject<T>(this.commonCore, type, stack);
}
/**
* Returns how many slots are in this core, counting null elements.<br/><br/>
*
* Single-threaded. <br/><br/>
*
* @see Core#size()
* @return .
*/
@Override
public int size() {
if (this.t == null) return 0;
return this.t.length;
}
/**
* Returns a slice of this core. Element inside this slice start at
* <code>start</code>. If <code>length</code> is positive it is treated
* as a length, if it is negative, it is treated as a (inclusive) end-index.<br/><br/>
*
* Single-threaded. <br/><br/>
*
* @param start The start position.
* @param length If length is positive it is treated as length, if negative as a starting position from the end (-1 equals the last position)
* @return A ObjectCore wrapping all sliced elements.
*/
public CoreObject<T> slice(final int start, final int length) {
if (this.t == null) return this;
final int i = indexToOffset(start);
final int l = length > 0 ? length : indexToOffset(length) - i + 1;
return new CoreObject<T>(this.commonCore, Arrays.copyOfRange(this.t, i, i + l));
}
/**
* Returns a new, sorted core.<br/><br/>
*
* Single-threaded. <br/><br/>
*
* @param c Comparator to use.
* @return A CoreObject with sorted entries.
*/
public CoreObject<T> sort(Comparator<T> c) {
if (this.t == null) return this;
final T[] copyOf = Arrays.copyOf(this.t, size());
Arrays.sort(copyOf, c);
return new CoreObject<T>(this.commonCore, copyOf);
}
/**
* Staples all elements. Assists, for example, in computing the average of a number
* of elements. <code>staple()</code> is similar to <code>reduce()</code>, with the exception that a
* given neutral element is used as a starting point, and some some derived value
* of each contained element might be connected with it. In the end a <code>Staple</code> will
* be returned, containing the stapled value and the actual number of elements used.<br/><br/>
*
* Single-threaded. <br/><br/>
*
* @param neutralElement The initial element.
* @param sumAndNext A reduce function. Left will be the current sum, right will
* be the next element.
*
* @return A <code>Staple</code> object, with the current sum and the size.
*/
public Staple<T> staple(T neutralElement, F2ReduceObjects<T> sumAndNext) {
final int size = size();
if (size == 0) return new Staple<T>(neutralElement, 1);
int count = 0;
T sum = neutralElement;
for (int i = 0; i < size; i++) {
if (this.t[i] == null) continue;
sum = sumAndNext.f(sum, this.t[i]);
count++;
}
return new Staple<T>(sum, count);
}
/**
* Converts all elements to strings.<br/><br/>
*
* Multi-threaded. <br/><br/>
*
* @return A CoreString containing all <code>toString()</code> output.
*/
public CoreString string() {
return map(new F1<T, String>() {
public String f(T x) {
return x.toString();
}
}).as(CoreString.class);
}
/**
* Returns a core containing only unique objects, i.e., object mutually un-<code>equal()</code>.<br/><br/>
*
* Single-threaded. <br/><br/>
*
* @return A CoreObject containing only unique, non-null objects.
*/
public CoreObject<T> unique() {
if (size() == 0) return this;
final T[] copy = Arrays.copyOf(this.t, size());
// Now check for each element
for (int i = 1; i < copy.length; i++) {
// If it is null, no nothing
if (copy[i] == null) continue;
// Check each previous element
for (int j = 0; j < i; j++) {
// If the current element is equal to a previous element,
// this element can be nulled
if (copy[i].equals(copy[j])) {
copy[i] = null;
break;
}
}
}
// Return the new, unique core.
return new CoreObject<T>(this.commonCore, copy).compact();
}
/**
* Converts an index to an offset.
*
* @param index
* @return An index.
*/
protected final int indexToOffset(int index) {
final int size = size();
if (index >= size) return -1;
// We also support negative indices.
if (index < 0) {
if (-index > size) return -1;
return size + index;
}
return index;
}
}