All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javolution.util.FastCollection Maven / Gradle / Ivy

The newest version!
/*
 * Javolution - Java(TM) Solution for Real-Time and Embedded Systems
 * Copyright (C) 2012 - Javolution (http://javolution.org/)
 * All rights reserved.
 * 
 * Permission to use, copy, modify, and distribute this software is
 * freely granted, provided that this notice is preserved.
 */
package javolution.util;

import static javolution.lang.Realtime.Limit.CONSTANT;
import static javolution.lang.Realtime.Limit.LINEAR;
import static javolution.lang.Realtime.Limit.N_SQUARE;

import java.io.IOException;
import java.io.Serializable;
import java.util.Collection;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.Iterator;

import javolution.lang.Immutable;
import javolution.lang.Parallelizable;
import javolution.lang.Realtime;
import javolution.text.Cursor;
import javolution.text.DefaultTextFormat;
import javolution.text.TextContext;
import javolution.text.TextFormat;
import javolution.util.function.Consumer;
import javolution.util.function.Equalities;
import javolution.util.function.Equality;
import javolution.util.function.Function;
import javolution.util.function.Predicate;
import javolution.util.function.Reducer;
import javolution.util.function.Reducers;
import javolution.util.internal.collection.AtomicCollectionImpl;
import javolution.util.internal.collection.DistinctCollectionImpl;
import javolution.util.internal.collection.FilteredCollectionImpl;
import javolution.util.internal.collection.MappedCollectionImpl;
import javolution.util.internal.collection.ParallelCollectionImpl;
import javolution.util.internal.collection.ReversedCollectionImpl;
import javolution.util.internal.collection.SequentialCollectionImpl;
import javolution.util.internal.collection.SharedCollectionImpl;
import javolution.util.internal.collection.SortedCollectionImpl;
import javolution.util.internal.collection.UnmodifiableCollectionImpl;
import javolution.util.service.CollectionService;

/**
 * 

A closure-based collection supporting numerous views which can be chained. *

* *

Unmodifiable collections are not always immutable. An {@link javolution.lang.Immutable immutable}. * reference (or const reference) can only be {@link #toImmutable() obtained} when the originator * guarantees that the collection source will not be modified even by himself * (the value of the immutable reference being an {@link #unmodifiable unmodifiable} collection). * [code] * Immutable> winners * = new FastTable().addAll("John Deuff", "Otto Graf", "Sim Kamil").toImmutable(); * // Immutability is guaranteed, no reference left on the collection source. * [/code]

* *

Atomic collections use Copy-On-Write * optimizations in order to provide mutex-free read access. Only writes operations are mutually * exclusive. Collections can be optimized to not require the full copy during write operations * (e.g. immutable parts don't need to be copied). Still, when multiple updates are performed, * it is beneficial to group them into one single {@link #update update} operation. * [code] * FastTable tokens = ... * ... * // Replace null with "" in tokens. If tokens is atomic the update is atomic. * // If tokens is parallel, the update is also performed concurrently ! * tokens.update(new Consumer>() { * public void accept(List view) { * for (int i=0, n = view.size(); i < n; i++) * if (view.get(i) == null) view.set(i, ""); * } * } * });[/code]

*

The same code using closure (Java 8). * [code] * tokens.update((List view) -> { * for (int i = 0, n = view.size(); i < n; i++) { * if (view.get(i) == null) view.set(i, ""); * } * });[/code]

* *

Views are similar to * Java 8 streams except that views are themselves collections (virtual collections) * and actions on a view can impact the original collection. Collection views are nothing "new" * since they already existed in the original java.util collection classes (e.g. List.subList(...), * Map.keySet(), Map.values()). Javolution extends to this concept and allows views to be chained * which addresses the concern of class proliferation.

* [code] * FastTable names = new FastTable().addAll("Oscar Thon", "Eva Poret", "Paul Auchon"); * boolean found = names.comparator(Equalities.LEXICAL_CASE_INSENSITIVE).contains("LUC SURIEUX"); * names.subTable(0, n).clear(); // Removes the n first names (see java.util.List.subList). * names.distinct().add("Guy Liguili"); // Adds "Guy Liguili" only if not already present. * names.filtered(isLong).clear(); // Removes all the persons with long names. * names.filtered(isLong).parallel().clear(); // Same as above but performed concurrently ! * ... * Predicate isLong = new Predicate() { * public boolean test(CharSequence csq) { * return csq.length() > 16; * } * });[/code]

* *

Views can of course be used to perform "stream" oriented filter-map-reduce operations with the same benefits: * Parallelism support, excellent memory characteristics (no caching and cost nothing to create), etc. * [code] * String anyLongName = names.filtered(isLong).any(String.class); // Returns any long name. * int nbrChars = names.mapped(toLength).reduce(Reducers.sum()); // Returns the total number of characters. * int maxLength = names.mapped(toLength).parallel().max(); // Finds the longest name in parallel. * ... * Function toLength = new Function() { * public Integer apply(CharSequence csq) { * return csq.length(); * } * }); * * // JDK Class.getEnclosingMethod using Javolution's views and Java 8 (to be compared with the current 20 lines implementation !). * Method matching = new FastTable().addAll(enclosingInfo.getEnclosingClass().getDeclaredMethods()) * .filtered(m -> Equalities.STANDARD.areEqual(m.getName(), enclosingInfo.getName()) * .filtered(m -> Equalities.ARRAY.areEqual(m.getParameterTypes(), parameterClasses)) * .filtered(m -> Equalities.STANDARD.areEqual(m.getReturnType(), returnType)) * .any(Method.class); * if (matching == null) throw new InternalError("Enclosing method not found"); * return matching;[/code]

* *

If a collection (or a map) is shared, derived views are also thread-safe. * Similarly, if a collection is {@link #parallel parallel}, closure-based iterations * on derived views are performed concurrently. * [code] * FastMap tasks = ... * ... * tasks.values().parallel().forEach(new Consumer() { // Executes task concurrently. * public void accept(Runnable task) { * task.run(); * } * });[/code]

*

With Java 8, closures are greatly simplified using lambda expressions. * [code] * tasks.values().parallel().forEach(task -> task.run()); // Same as above. * names.sorted().reversed().forEach(str -> System.out.println(str)); // Prints names in reverse alphabetical order. * [/code]

* * @author Jean-Marie Dautelle * @version 6.0, July 21, 2013 */ @Realtime @DefaultTextFormat(FastCollection.Format.class) public abstract class FastCollection implements Collection, Serializable { private static final long serialVersionUID = 0x600L; // Version. /** * Default constructor. */ protected FastCollection() {} //////////////////////////////////////////////////////////////////////////// // Views. // /** * Returns an atomic view over this collection. All operations that write * or access multiple elements in the collection (such as addAll(), * retainAll()) are atomic. * Iterators on atomic collections are thread-safe * (no {@link ConcurrentModificationException} possible). */ @Parallelizable(mutexFree = true, comment = "Except for write operations, all read operations are mutex-free.") public FastCollection atomic() { return new AtomicCollectionImpl(service()); } /** * Returns a thread-safe view over this collection. The shared view * allows for concurrent read as long as there is no writer. * The default implementation is based on * readers-writers locks giving priority to writers. * Iterators on shared collections are thread-safe * (no {@link ConcurrentModificationException} possible). */ @Parallelizable(mutexFree = false, comment = "Use multiple-readers/single-writer lock.") public FastCollection shared() { return new SharedCollectionImpl(service()); } /** * Returns a parallel collection. Parallel collections affect * only closure-based operations, all others operations behaving the * same. Parallel actions are performed concurrently using Javolution * {@link javolution.context.ConcurrentContext ConcurrentContext}. * The number of parallel views is derived from the context * {@link javolution.context.ConcurrentContext#getConcurrency() * concurrency} ({@code number of parallel views = concurrency + 1}). * Parallel views do not require this collection to be thread-safe * (internal synchronization). * * @see #perform(Consumer) * @see #update(Consumer) * @see #forEach(Consumer) * @see #removeIf(Predicate) * @see #reduce(Reducer) */ public FastCollection parallel() { return new ParallelCollectionImpl(service()); } /** * Returns a sequential view of this collection. Using this view, * all closure-based iterations are performed sequentially. */ public FastCollection sequential() { return new SequentialCollectionImpl(service()); } /** * Returns an unmodifiable view over this collection. Any attempt to * modify the collection through this view will result into * a {@link java.lang.UnsupportedOperationException} being raised. */ public FastCollection unmodifiable() { return new UnmodifiableCollectionImpl(service()); } /** * Returns a view exposing only the elements matching the specified * filter. Adding elements not matching the specified filter has * no effect. If this collection is initially empty, using a filtered * view to add new elements ensure that this collection has only elements * satisfying the filter predicate. */ public FastCollection filtered(Predicate filter) { return new FilteredCollectionImpl(service(), filter); } /** * Returns a view exposing elements through the specified mapping function. * The returned view does not allow new elements to be added. */ public FastCollection mapped( Function function) { return new MappedCollectionImpl(service(), function); } /** * Returns a view exposing elements sorted according to the * collection {@link #comparator() order}. */ public FastCollection sorted() { return new SortedCollectionImpl(service(), comparator()); } /** * Returns a view exposing elements sorted according to the specified * comparator. */ public FastCollection sorted(Comparator cmp) { return new SortedCollectionImpl(service(), cmp); } /** * Returns a view exposing elements in reverse iterative order. */ public FastCollection reversed() { return new ReversedCollectionImpl(service()); } /** * Returns a view exposing only distinct elements (it does not iterate twice * over the {@link #comparator() same} elements). Adding elements already * in the collection through this view has no effect. If this collection is * initially empty, using a distinct view to add new elements ensures that * this collection has no duplicate. */ public FastCollection distinct() { return new DistinctCollectionImpl(service()); } //////////////////////////////////////////////////////////////////////////// // Closure operations. // /** * Executes the specified read-only action on this collection. * That logic may be performed concurrently on sub-collections * if this collection is {@link #parallel() parallel}. * * @param action the read-only action. * @throws UnsupportedOperationException if the action tries to update * this collection and this collection is thread-safe. * @throws ClassCastException if the action type is not compatible with * this collection (e.g. action on set and this is a list). * @see #update(Consumer) */ @SuppressWarnings("unchecked") @Realtime(limit = LINEAR) public void perform(Consumer> action) { service().perform((Consumer>) action, service()); } /** * Executes the specified update action on this collection. * That logic may be performed concurrently on sub-collections * if this collection is {@link #parallel() parallel}. * For {@link #atomic() atomic} collections the update is atomic * (either concurrent readers see the full result of the action or * nothing). * * @param action the update action. * @throws ClassCastException if the action type is not compatible with * this collection (e.g. action on set and this is a list). * @see #perform(Consumer) */ @SuppressWarnings("unchecked") @Realtime(limit = LINEAR) public void update(Consumer> action) { service().update((Consumer>) action, service()); } /** * Iterates over all this collection elements applying the specified * consumer (convenience method). Iterations are performed concurrently * if the collection is {@link #parallel() parallel}. * * @param consumer the functional consumer applied to the collection elements. */ @Realtime(limit = LINEAR) public void forEach(final Consumer consumer) { perform(new Consumer>() { public void accept(Collection view) { Iterator it = view.iterator(); while (it.hasNext()) { consumer.accept(it.next()); } } }); } /** * Removes from this collection all the elements matching the specified * functional predicate (convenience method). Removals are performed * concurrently if this collection is {@link #parallel() parallel} and * atomically if this collection is {@link #atomic() atomic}. * * @param filter a predicate returning {@code true} for elements to be removed. * @return {@code true} if at least one element has been removed; * {@code false} otherwise. */ @Realtime(limit = LINEAR) public boolean removeIf(final Predicate filter) { final boolean[] removed = new boolean[1]; update(new Consumer>() { public void accept(Collection view) { Iterator it = view.iterator(); while (it.hasNext()) { if (filter.test(it.next())) { it.remove(); // Ok mutable iteration. removed[0] = true; } } } }); return removed[0]; } /** * Performs a reduction of the elements of this collection using the * specified reducer. This may not involve iterating over all the * collection elements, for example the reducers: {@link Reducers#any}, * {@link Reducers#and} and {@link Reducers#or} may stop iterating * early. Reduction is performed concurrently if this collection is * {@link #parallel() parallel}. * * @param reducer the collection reducer. * @return the reduction result. */ @Realtime(limit = LINEAR) public E reduce(Reducer reducer) { perform(reducer); return reducer.get(); } //////////////////////////////////////////////////////////////////////////// // Collection operations. // /** Adds the specified element to this collection */ @Override @Realtime(limit = LINEAR, comment = "May have to search the whole collection (e.g. distinct view).") public boolean add(E element) { return service().add(element); } /** Indicates if this collection is empty. */ @Override @Realtime(limit = LINEAR, comment = "May actually iterates the whole collection (e.g. filtered view).") public boolean isEmpty() { return iterator().hasNext(); } /** Returns the size of this collection. */ @Override @Realtime(limit = LINEAR, comment = "Potentially counts the elements.") public int size() { return service().size(); } /** Removes all elements from this collection. */ @Override @Realtime(limit = LINEAR, comment = "Potentially removes elements one at a time.") public void clear() { service().clear(); } /** Indicates if this collection contains the specified element. */ @Override @Realtime(limit = LINEAR, comment = "Potentially searches the whole collection.") public boolean contains(Object searched) { return service().contains(searched); } /** Removes the specified element from this collection. */ @Override @Realtime(limit = LINEAR, comment = "Potentially searches the whole collection.") public boolean remove(Object searched) { return service().remove(searched); } /** * Returns an iterator over this collection elements. For * shared/atomic collections the iterator is immune to * concurrent modifications. In other words the elements iterated over * may or may not reflect the current state of the collection. */ @Override @Realtime(limit = N_SQUARE, comment = "Construction of the iterator may require sorting the elements (e.g. sorted view)") public Iterator iterator() { return service().iterator(); } /** Adds all the specified elements to this collection. */ @Override @Realtime(limit = LINEAR) public boolean addAll(final Collection that) { return service().addAll(that); } /** Indicates if this collection contains all the specified elements. */ @Override @Realtime(limit = N_SQUARE) public boolean containsAll(Collection that) { return service().containsAll(that); } /** Removes all the specified element from this collection. */ @Override @Realtime(limit = N_SQUARE) public boolean removeAll(final Collection that) { return service().removeAll(that); } /** Removes all the elements except those in the specified collection. */ @Override @Realtime(limit = N_SQUARE) public boolean retainAll(final Collection that) { return service().retainAll(that); } /** Returns an array holding this collection elements. */ @Override @Realtime(limit = LINEAR) public Object[] toArray() { return service().toArray(); } /** Returns the specified array holding this collection elements if * enough capacity. */ @Override @Realtime(limit = LINEAR) public T[] toArray(final T[] array) { return service().toArray(array); } //////////////////////////////////////////////////////////////////////////// // Misc. // /** * Returns any non-null element of the specified type (convenience method). * * @param type the element type searched for. * @return {@code reduce(Reducers.any(type))} * @see Reducers#any */ @SuppressWarnings("unchecked") @Realtime(limit = LINEAR) public T any(Class type) { return (T) reduce((Reducer) Reducers.any(type)); } /** * Returns the smallest element of this collection using this * collection {@link #comparator() comparator} (convenience method). * Returns {@code null} if this collection is empty. * * @return {@code reduce(Reducers.min(comparator()))} * @see Reducers#min */ @Realtime(limit = LINEAR) public E min() { return reduce(Reducers.min(comparator())); } /** * Returns the largest element of this collection using this * collection {@link #comparator() comparator} (convenience method). * Returns {@code null} if this collection is empty. * * @return {@code reduce(Reducers.max(comparator()))} * @see Reducers#max */ @Realtime(limit = LINEAR) public E max() { return reduce(Reducers.max(comparator())); } /** * Returns this collection with the specified element added. * * @param elements the elements to be added. * @return {@code this} */ @Realtime(limit = LINEAR) public FastCollection addAll(E... elements) { for (E e : elements) { add(e); } return this; } /** * Returns this collection with the specified collection's elements added * in sequence. */ @Realtime(limit = LINEAR) public FastCollection addAll(FastCollection that) { addAll((Collection) that); return this; } /** * Returns the comparator uses by this collection for equality and/or * ordering if this collection is sorted. */ @Realtime(limit = CONSTANT) public Equality comparator() { return service().comparator(); } /** * Returns an immutable reference over this collection. The immutable * value is an {@link #unmodifiable() unmodifiable} view of this collection. * The caller must guarantees that the original collection is never going * to be updated (e.g. there is no reference left of the original collection). */ @Realtime(limit = CONSTANT) public > Immutable toImmutable() { return new Immutable() { @SuppressWarnings("unchecked") final T value = (T) unmodifiable(); @Override public T value() { return value; } }; } /** * Compares the specified object with this collection for equality. * This method follows the {@link Collection#equals(Object)} specification * if this collection {@link #comparator comparator} is * {@link Equalities#STANDARD} (default). Otherwise, only collections * using the same comparator can be considered equals. * * @param obj the object to be compared for equality with this collection * @return true if both collections are considered equals; * false otherwise. */ @Override @Realtime(limit = LINEAR) public boolean equals(Object obj) { return service().equals(obj); } /** * Returns the hash code of this collection. * This method follows the {@link Collection#hashCode()} specification * if this collection {@link #comparator comparator} is * {@link Equalities#STANDARD}. * * @return this collection hash code. */ @Override @Realtime(limit = LINEAR) public int hashCode() { return service().hashCode(); } /** Returns the string representation of this collection using its * default {@link TextFormat format}. */ @Override @Realtime(limit = LINEAR) public String toString() { return TextContext.getFormat(FastCollection.class).format(this); } /** * Returns the service implementation of this collection (for sub-classes). */ protected abstract CollectionService service(); /** * Returns the service implementation of any fast collection * (for sub-classes). */ protected static CollectionService serviceOf( FastCollection collection) { return collection.service(); } //////////////////////////////////////////////////////////////////////////// // Textual format. // /** * Default text format for fast collections (parsing not supported). * It is the format used when printing standard {@code java.util.Collection} * instances except that the elements themselves are written using * their {@link TextContext TextContext} format. */ @Parallelizable public static class Format extends TextFormat> { @Override public FastCollection parse(CharSequence csq, Cursor cursor) throws IllegalArgumentException { throw new UnsupportedOperationException(); } @Override public Appendable format(FastCollection that, final Appendable dest) throws IOException { dest.append('['); Class elementType = null; TextFormat format = null; for (Object element : that) { if (elementType == null) elementType = Void.class; else dest.append(", "); // Not the first. if (element == null) { dest.append("null"); continue; } Class cls = element.getClass(); if (elementType.equals(cls)) { format.format(element, dest); continue; } elementType = cls; format = TextContext.getFormat(cls); format.format(element, dest); } return dest.append(']'); } } }