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

org.semanticweb.elk.util.collections.Operations Maven / Gradle / Ivy

The newest version!
/*
 * #%L
 * ELK Reasoner
 * 
 * $Id$
 * $HeadURL$
 * %%
 * Copyright (C) 2011 Department of Computer Science, University of Oxford
 * %%
 * 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.
 * #L%
 */
package org.semanticweb.elk.util.collections;

import java.io.IOException;
import java.io.Writer;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;

/**
 * Some useful static methods for collections
 * 
 * @author "Yevgeny Kazakov"
 * 
 */
public class Operations {

	public static final Multimap EMPTY_MULTIMAP = new Multimap() {

		@Override
		public boolean contains(Object key, Object value) {
			return false;
		}

		@Override
		public boolean add(Object key, Object value) {
			throw new UnsupportedOperationException(
					"The Empty multimap cannot be modified!");
		}

		@Override
		public Collection get(Object key) {
			return Collections.emptySet();
		}

		@Override
		public boolean remove(Object key, Object value) {
			return false;
		}

		@Override
		public boolean isEmpty() {
			return true;
		}

		@Override
		public Set keySet() {
			return Collections.emptySet();
		}

		@Override
		public void clear() {
		}

	};

	@SuppressWarnings("unchecked")
	public static  Multimap emptyMultimap() {
		return (Multimap) EMPTY_MULTIMAP;
	}

	@SuppressWarnings("unchecked")
	public static  Iterable concat(final Iterable a,
			final Iterable b) {
		return concat(Arrays.asList(a, b));
	}

	public static  Iterable singleton(final T element) {
		return new Iterable() {
			@Override
			public Iterator iterator() {
				return new Iterator() {
					boolean hasNext = true;

					@Override
					public boolean hasNext() {
						return hasNext;
					}

					@Override
					public T next() {
						hasNext = false;
						return element;
					}

					@Override
					public void remove() {
						throw new UnsupportedOperationException();
					}
				};
			}
		};
	}

	/**
	 * Concatenates several {@link Iterable}s into one
	 * 
	 * @param input
	 *            the {@link Iterable} of {@link Iterable}s to be concatenated
	 * @return {@link Iterable} consisting of all elements found in input
	 *         {@link Iterable}s
	 */
	public static  Iterable concat(
			final Iterable> input) {
		assert input != null;

		return new Iterable() {

			@Override
			public Iterator iterator() {

				return new Iterator() {
					Iterator> outer = input
							.iterator();

					Iterator inner;
					boolean hasNext = advance();

					@Override
					public boolean hasNext() {
						return hasNext;
					}

					@Override
					public T next() {
						if (hasNext) {
							T result = inner.next();
							hasNext = advance();
							return result;
						}
						throw new NoSuchElementException();
					}

					@Override
					public void remove() {
						throw new UnsupportedOperationException();
					}

					boolean advance() {
						while (true) {
							if (inner != null && inner.hasNext())
								return true;

							if (outer.hasNext())
								inner = outer.next().iterator();
							else
								return false;
						}
					}
				};
			}
		};
	}

	/**
	 * Splits the input {@link Iterable} on batches with at most given number of
	 * elements.
	 * 
	 * @param elements
	 *            the {@link Iterable} to be split
	 * @param batchSize
	 *            the maximal number of elements in batches
	 * @return a {@link Iterable} of batches containing elements from the input
	 *         collection
	 * @see #concat(Iterable)
	 */
	public static  Iterable> split(
			final Iterable elements, final int batchSize) {
		return new Iterable>() {

			@Override
			public Iterator> iterator() {
				return new Iterator>() {

					final Iterator elementsIterator = elements
							.iterator();

					@Override
					public boolean hasNext() {
						return elementsIterator.hasNext();
					}

					@Override
					public ArrayList next() {
						final ArrayList nextBatch = new ArrayList(batchSize);
						int count = 0;
						while (count++ < batchSize
								&& elementsIterator.hasNext()) {
							nextBatch.add(elementsIterator.next());
						}
						return nextBatch;
					}

					@Override
					public void remove() {
						throw new UnsupportedOperationException(
								"Deletion is not supported");
					}
				};
			}
		};
	}

	/**
	 * Splits the input {@link Collection} on batches with at most given number
	 * of elements.
	 * 
	 * @param elements
	 *            the {@link Collection} to be split
	 * @param batchSize
	 *            the maximal number of elements in batches
	 * @return a {@link Collection} of batches containing elements from the
	 *         input collection
	 */
	public static  Collection> split(
			final Collection elements, final int batchSize) {
		return new AbstractCollection>() {

			@Override
			public Iterator> iterator() {
				return split((Iterable) elements, batchSize)
						.iterator();
			}

			@Override
			public int size() {
				// rounding up
				return (elements.size() + batchSize - 1) / batchSize;
			}
		};
	}

	public static  Collection getCollection(final Iterable iterable,
			final int size) {
		return new AbstractCollection() {

			@Override
			public Iterator iterator() {
				return iterable.iterator();
			}

			@Override
			public int size() {
				return size;
			}
		};
	}

	/**
	 * Boolean conditions over some type.
	 * 
	 * @param 
	 *            the type of elements which can be used with this condition
	 * 
	 */
	public interface Condition {
		/**
		 * Checks if the condition holds for an element
		 * 
		 * @param element
		 *            the element for which to check the condition
		 * @return {@code true} if the condition holds for the element and
		 *         otherwise {@code false}
		 */
		public boolean holds(T element);
	}

	/**
	 * 
	 * @param input
	 *            the input iterator
	 * @param condition
	 *            the condition used for filtering
	 * @return the filtered iterator
	 * 
	 */
	public static  Iterable filter(final Iterable input,
			final Condition condition) {
		assert input != null;

		return new Iterable() {

			@Override
			public Iterator iterator() {

				return new Iterator() {
					Iterator i = input.iterator();
					T next;
					boolean hasNext = advance();

					@Override
					public boolean hasNext() {
						return hasNext;
					}

					@Override
					public T next() {
						if (hasNext) {
							T result = next;
							hasNext = advance();
							return result;
						}
						throw new NoSuchElementException();
					}

					@Override
					public void remove() {
						i.remove();
					}

					boolean advance() {
						while (i.hasNext()) {
							next = i.next();
							if (condition.holds(next))
								return true;
						}
						return false;
					}
				};
			}
		};
	}

	@SuppressWarnings("unchecked")
	public static  Iterable filter(final Iterable input,
			final Class type) {

		return (Iterable) filter(input, new Condition() {
			@Override
			public boolean holds(S element) {
				return type.isInstance(element);
			}
		});
	}

	/**
	 * Returns read-only view of the given set consisting of the elements
	 * satisfying a given condition, if the number of such elements is known
	 * 
	 * @param input
	 *            the given set to be filtered
	 * @param condition
	 *            the condition used for filtering the set. Must be consistent
	 *            with equals() for T, that is: a.equals(b) must imply that
	 *            holds(a) == holds(b)
	 * @param size
	 *            the number of elements in the filtered set
	 * @return the set consisting of the elements of the input set satisfying
	 *         the given condition
	 */
	public static  Set filter(final Set input,
			final Condition condition, final int size) {
		return new Set() {

			@Override
			public int size() {
				return size;
			}

			@Override
			public boolean isEmpty() {
				return size == 0;
			}

			@Override
			@SuppressWarnings("unchecked")
			public boolean contains(Object o) {

				if (!input.contains(o))
					return false;

				T elem = null;

				try {
					elem = (T) o;
				} catch (ClassCastException cce) {
					return false;
				}
				/*
				 * here's why the condition must be consistent with equals(): we
				 * check it on the passed element while we really need to check
				 * it on the element which is in the underlying set (and is
				 * equal to o according to equals()). However, as long as the
				 * condition is consistent, the result will be the same.
				 */
				return condition.holds(elem);
			}

			@Override
			public Iterator iterator() {
				return filter(input, condition).iterator();
			}

			@Override
			public Object[] toArray() {
				Object[] result = new Object[size];
				int i = 0;
				for (Object o : filter(input, condition)) {
					result[i++] = o;
				}
				return result;
			}

			@Override
			public  S[] toArray(S[] a) {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean add(T e) {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean remove(Object o) {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean containsAll(Collection c) {
				for (Object o : c) {
					if (contains(o))
						return false;
				}
				return true;
			}

			@Override
			public boolean addAll(Collection c) {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean retainAll(Collection c) {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean removeAll(Collection c) {
				throw new UnsupportedOperationException();
			}

			@Override
			public void clear() {
				throw new UnsupportedOperationException();
			}

		};

	}

	/**
	 * Transformations of input values to output values
	 * 
	 * @param 
	 *            the type of the input of the transformation
	 * @param 
	 *            the type of the output of the transformation
	 */
	public interface Transformation {
		/**
		 * Transforms the input element
		 * 
		 * @param element
		 *            the element to be transformed
		 * @return the result of the transformation
		 */
		public O transform(I element);
	}

	// TODO: get rid of Conditions in favour of transformations
	
	/**
	 * Transforms elements using a given {@link Transformation} the output
	 * elements consist of the result of the transformation in the same order;
	 * if the transformation returns {@code null}, it is not included in the
	 * output
	 * 
	 * @param input
	 *            the input elements
	 * @param transformation
	 *            the transformation for elements
	 * @return the transformed output elements
	 * 
	 */
	public static  Iterable map(final Iterable input,
			final Transformation transformation) {
		assert input != null;

		return new Iterable() {

			@Override
			public Iterator iterator() {

				return new Iterator() {
					Iterator i = input.iterator();
					O next;
					boolean hasNext = advance();

					@Override
					public boolean hasNext() {
						return hasNext;
					}

					@Override
					public O next() {
						if (hasNext) {
							O result = next;
							hasNext = advance();
							return result;
						}
						throw new NoSuchElementException();
					}

					@Override
					public void remove() {
						i.remove();
					}

					boolean advance() {
						while (i.hasNext()) {
							next = transformation.transform(i.next());
							if (next != null)
								return true;
						}
						return false;
					}
				};
			}
		};
	}

	/**
	 * Prints key-value entries present in the first {@link Multimap} but not in
	 * the second {@link Multimap} using the given {@link Writer} and prefixing
	 * all messages with a given prefix.
	 * 
	 * @param first
	 * @param second
	 * @param writer
	 * @param prefix
	 * @throws IOException
	 */
	public static  void dumpDiff(Multimap first,
			Multimap second, Writer writer, String prefix)
			throws IOException {
		for (K key : first.keySet()) {
			Collection firstValues = first.get(key);
			Collection secondValues = second.get(key);
			dumpDiff(firstValues, secondValues, writer, prefix + key + "->");
		}

	}

	/**
	 * Prints the elements present in the first {@link Collection} but not in
	 * the second {@link Collection} using the given {@link Writer} and
	 * prefixing all messages with a given prefix.
	 * 
	 * @param first
	 * @param second
	 * @param writer
	 * @param prefix
	 * @throws IOException
	 */
	public static  void dumpDiff(Collection first, Collection second,
			Writer writer, String prefix) throws IOException {
		for (T element : first)
			if (!second.contains(element))
				writer.append(prefix + element + "\n");
	}

	/**
	 * A simple object that transforms objects of type I to objects of type O
	 * 
	 * @author Pavel Klinov
	 * 
	 *         [email protected]
	 */
	public interface Functor {

		public O apply(I element);
	}
	
	/**
	 * An extension of {@link Functor} which can do the reverse transformation
	 * 
	 * @author Pavel Klinov
	 *
	 * [email protected]
	 */
	public interface FunctorEx extends Functor {
		
		/**
		 * The reason this method takes Objects rather than instances of O is because
		 * it's primarily used for an efficient implementation of
		 * {@link Set#contains(Object)}, which takes an Object
		 * 
		 * @return Can return null if the transformation is not possible
		 */
		public I deapply(Object element);
	}
	
	/**
	 * 
	 * @author Pavel Klinov
	 *
	 * [email protected]
	 */
	private static class MapIterator implements Iterator {
		
		private final Iterator iter_;
		private final Functor functor_;
		
		MapIterator(Iterator iter, Functor functor) {
			iter_ = iter;
			functor_ = functor;
		}

		@Override
		public boolean hasNext() {
			return iter_.hasNext();
		}

		@Override
		public O next() {
			return functor_.apply(iter_.next());
		}

		@Override
		public void remove() {
			iter_.remove();
		}
		
		
	}
	
	/**
	 * A simple second-order map function
	 * 
	 * @param input
	 * @param functor
	 * @return a set which contains the results of applying the functor to the elements in the input set
	 */
	public static  Set map(final Set input, final FunctorEx functor) {
		return new AbstractSet() { 

			@Override
			public Iterator iterator() {
				return new MapIterator(input.iterator(), functor);
			}

			@Override
			public boolean contains(Object o) {
				I element = functor.deapply(o);
				
				return element == null ? false : input.contains(element);
			}

			@Override
			public int size() {
				return input.size();
			}
			
		};
	}
}