package com.github.phantomthief.concurrent;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly;
import static java.lang.System.nanoTime;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;

import java.time.Duration;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.function.Supplier;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.phantomthief.util.ThrowableFunction;
import com.github.phantomthief.util.ThrowableRunnable;
import com.google.common.util.concurrent.AbstractFuture;
import com.google.common.util.concurrent.ExecutionError;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.google.common.util.concurrent.UncheckedTimeoutException;

/**
 * @author w.vela
 * Created on 2018-06-25.
 */
public class MoreFutures {

    private static final Logger logger = LoggerFactory.getLogger(MoreFutures.class);

    /**
     * @throws UncheckedTimeoutException if timeout occurred.
     * @throws java.util.concurrent.CancellationException if task was canceled.
     * @throws ExecutionError if a {@link Error} occurred.
     * @throws UncheckedExecutionException if a normal Exception occurred.
     */
    public static <T> T getUnchecked(@Nonnull Future<? extends T> future,
            @Nonnull Duration duration) {
        checkNotNull(duration);
        return getUnchecked(future, duration.toNanos(), NANOSECONDS);
    }

    /**
     * @throws UncheckedTimeoutException if timeout occurred.
     * @throws java.util.concurrent.CancellationException if task was canceled.
     * @throws ExecutionError if a {@link Error} occurred.
     * @throws UncheckedExecutionException if a normal Exception occurred.
     */
    public static <T> T getUnchecked(@Nonnull Future<? extends T> future, @Nonnegative long timeout,
            @Nonnull TimeUnit unit) {
        return getUnchecked(future, timeout, unit, false);
    }

    /**
     * @throws UncheckedTimeoutException if timeout occurred.
     * @throws java.util.concurrent.CancellationException if task was canceled.
     * @throws ExecutionError if a {@link Error} occurred.
     * @throws UncheckedExecutionException if a normal Exception occurred.
     */
    public static <T> T getUnchecked(@Nonnull Future<? extends T> future, @Nonnegative long timeout,
            @Nonnull TimeUnit unit, boolean cancelOnTimeout) {
        checkArgument(timeout > 0);
        checkNotNull(future);
        try {
            return getUninterruptibly(future, timeout, unit);
        } catch (ExecutionException e) {
            Throwable cause = e.getCause();
            if (cause instanceof Error) {
                throw new ExecutionError((Error) cause);
            } else {
                throw new UncheckedExecutionException(cause);
            }
        } catch (TimeoutException e) {
            if (cancelOnTimeout) {
                future.cancel(false);
            }
            throw new UncheckedTimeoutException(e);
        }
    }

    /**
     * @see #tryWait(Iterable, long, TimeUnit)
     *
     * @throws TryWaitFutureUncheckedException if not all calls are successful.
     */
    @Nonnull
    public static <F extends Future<V>, V> Map<F, V> tryWait(@Nonnull Iterable<F> futures,
            @Nonnull Duration duration) throws TryWaitFutureUncheckedException {
        checkNotNull(futures);
        checkNotNull(duration);
        return tryWait(futures, duration.toNanos(), NANOSECONDS);
    }

    /**
     * A typical usage:
     * {@code

     *  // a fail-safe example
     *  List<Future<User>> list = doSomeAsyncTasks();
     *  Map<Future<User>, User> success;
     *  try {
     *    success = tryWait(list, 1, SECONDS);
     *  } catch (TryWaitUncheckedException e) {
     *    success = e.getSuccess(); // there are still some success
     *  }
     *
     *  // a fail-fast example
     *  List<Future<User>> list = doSomeAsyncTasks();
     *  // don't try/catch the exception it throws.
     *  Map<Future<User>, User> success; = tryWait(list, 1, SECONDS);
     *
}
     *
     * @throws TryWaitUncheckedException if not all calls are successful.
     */
    @Nonnull
    public static <F extends Future<V>, V> Map<F, V> tryWait(@Nonnull Iterable<F> futures,
            @Nonnegative long timeout, @Nonnull TimeUnit unit)
            throws TryWaitFutureUncheckedException {
        checkNotNull(futures);
        checkArgument(timeout > 0);
        checkNotNull(unit);
        return tryWait(futures, timeout, unit, it -> it, TryWaitFutureUncheckedException::new);
    }

    /**
     * @see #tryWait(Iterable, long, TimeUnit, ThrowableFunction)
     *
     * @throws TryWaitUncheckedException if not all calls are successful.
     */
    @Nonnull
    public static <K, V, X extends Throwable> Map<K, V> tryWait(@Nonnull Iterable<K> keys,
            @Nonnull Duration duration, @Nonnull ThrowableFunction<K, Future<V>, X> asyncFunc)
            throws X, TryWaitUncheckedException {
        checkNotNull(keys);
        checkNotNull(duration);
        checkNotNull(asyncFunc);
        return tryWait(keys, duration.toNanos(), NANOSECONDS, asyncFunc);
    }

    /**
     * A typical usage:
     * {@code

     *  // a fail-safe example
     *  List<Integer> list = getSomeIds();
     *  Map<Integer, User> success;
     *  try {
     *    success = tryWait(list, 1, SECONDS, id -> executor.submit(() -> retrieve(id)));
     *  } catch (TryWaitUncheckedException e) {
     *    success = e.getSuccess(); // there are still some success
     *  }
     *
     *  // a fail-fast example
     *  List<Integer> list = getSomeIds();
     *  // don't try/catch the exception it throws.
     *  Map<Integer, User> success = tryWait(list, 1, SECONDS, id -> executor.submit(() -> retrieve(id)));
     *
}
     *
     * @throws TryWaitUncheckedException if not all calls are successful.
     */
    @Nonnull
    public static <K, V, X extends Throwable> Map<K, V> tryWait(@Nonnull Iterable<K> keys,
            @Nonnegative long timeout, @Nonnull TimeUnit unit,
            @Nonnull ThrowableFunction<K, Future<V>, X> asyncFunc)
            throws X, TryWaitUncheckedException {
        return tryWait(keys, timeout, unit, asyncFunc, TryWaitUncheckedException::new);
    }

    @Nonnull
    private static <K, V, X extends Throwable> Map<K, V> tryWait(@Nonnull Iterable<K> keys,
            @Nonnegative long timeout, @Nonnull TimeUnit unit,
            @Nonnull ThrowableFunction<K, Future<V>, X> asyncFunc,
            @Nonnull Function<TryWaitResult, RuntimeException> throwing) throws X {
        checkNotNull(keys);
        checkArgument(timeout > 0);
        checkNotNull(unit);
        checkNotNull(asyncFunc);

        Map<Future<? extends V>, V> successMap = new LinkedHashMap<>();
        Map<Future<? extends V>, Throwable> failMap = new LinkedHashMap<>();
        Map<Future<? extends V>, TimeoutException> timeoutMap = new LinkedHashMap<>();
        Map<Future<? extends V>, CancellationException> cancelMap = new LinkedHashMap<>();

        long remainingNanos = unit.toNanos(timeout);
        long end = nanoTime() + remainingNanos;

        Map<Future<? extends V>, K> futureKeyMap = new IdentityHashMap<>();
        for (K key : keys) {
            checkNotNull(key);
            Future<V> future = asyncFunc.apply(key);
            checkNotNull(future);
            futureKeyMap.put(future, key);
            if (remainingNanos <= 0) {
                waitAndCollect(successMap, failMap, timeoutMap, cancelMap, future, 1L);
                continue;
            }
            waitAndCollect(successMap, failMap, timeoutMap, cancelMap, future, remainingNanos);
            remainingNanos = end - nanoTime();
        }

        TryWaitResult<K, V> result = new TryWaitResult<>(successMap, failMap, timeoutMap, cancelMap,
                futureKeyMap);

        if (failMap.isEmpty() && timeoutMap.isEmpty() && cancelMap.isEmpty()) {
            return result.getSuccess();
        } else {
            throw throwing.apply(result);
        }
    }

    private static <T> void waitAndCollect(Map<Future<? extends T>, T> successMap,
            Map<Future<? extends T>, Throwable> failMap,
            Map<Future<? extends T>, TimeoutException> timeoutMap,
            Map<Future<? extends T>, CancellationException> cancelMap, Future<? extends T> future,
            long thisWait) {
        try {
            T t = getUninterruptibly(future, thisWait, NANOSECONDS);
            successMap.put(future, t);
        } catch (CancellationException e) {
            cancelMap.put(future, e);
        } catch (TimeoutException e) {
            timeoutMap.put(future, e);
        } catch (ExecutionException e) {
            failMap.put(future, e.getCause());
        } catch (Throwable e) {
            failMap.put(future, e);
        }
    }

    /**
     * @param task any exception throwing would cancel the task. user should swallow exceptions by self.
     * @param executor all task would be stopped after executor has been marked shutting down.
     * @return a future that can cancel the task.
     */
    public static Future<?> scheduleWithDynamicDelay(@Nonnull ScheduledExecutorService executor,
            @Nullable Duration initDelay, @Nonnull Scheduled task) {
        checkNotNull(executor);
        checkNotNull(task);
        AtomicBoolean canceled = new AtomicBoolean(false);
        AbstractFuture<?> future = new AbstractFuture<Object>() {

            @Override
            public boolean cancel(boolean mayInterruptIfRunning) {
                canceled.set(true);
                return super.cancel(mayInterruptIfRunning);
            }
        };
        executor.schedule(new ScheduledTaskImpl(executor, task, canceled),
                initDelay == null ? 0 : initDelay.toMillis(), MILLISECONDS);
        return future;
    }

    /**
     * @param task any exception throwing would be ignore and logged, task would not cancelled.
     * @param executor all task would be stopped after executor has been marked shutting down.
     * @return a future that can cancel the task.
     */
    public static Future<?> scheduleWithDynamicDelay(@Nonnull ScheduledExecutorService executor,
            @Nonnull Supplier<Duration> delay, @Nonnull ThrowableRunnable<Throwable> task) {
        checkNotNull(delay);
        return scheduleWithDynamicDelay(executor, delay.get(), () -> {
            try {
                task.run();
            } catch (Throwable e) {
                logger.error("", e);
            }
            return delay.get();
        });
    }

    public interface Scheduled {

        /**
         * @return a delay for next run. {@code null} means stop.
         */
        @Nullable
        Duration run();
    }

    private static class ScheduledTaskImpl implements Runnable {

        private final ScheduledExecutorService executorService;
        private final Scheduled scheduled;
        private final AtomicBoolean canceled;

        private ScheduledTaskImpl(ScheduledExecutorService executorService, Scheduled scheduled,
                AtomicBoolean canceled) {
            this.executorService = executorService;
            this.scheduled = scheduled;
            this.canceled = canceled;
        }

        @Override
        public void run() {
            if (canceled.get()) {
                return;
            }
            try {
                Duration delay = scheduled.run();
                if (!canceled.get() && delay != null) {
                    executorService.schedule(this, delay.toMillis(), MILLISECONDS);
                }
            } catch (Throwable e) {
                logger.error("", e);
            }
        }
    }
}


package org.semanticweb.elk.util.concurrent.computation;

/*-
 * #%L
 * ELK Utilities for Concurrency
 * $Id:$
 * $HeadURL:$
 * %%
 * Copyright (C) 2011 - 2016 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%
 */

import java.util.concurrent.TimeUnit;

/**
 * A collection of convenience methods for creating {@link ConcurrentExecutor}s
 *
 * @author Yevgeny Kazakov
 *
 */
public class ConcurrentExecutors {

	private static ConcurrentExecutor DEFAULT_ = create("elk-worker");

	/**
	 * Creates a {@link ConcurrentExecutor} with the given name (used as a
	 * prefix for creating thread), and given timeout for running threads. If a
	 * thread did not process a job within the given timeout, the thread is
	 * terminated.
	 *
	 * @param name
	 * @param timeout
	 * @param unit
	 * @return the {@link ConcurrentExecutor} associated with the given
	 *         parameters
	 */
	public static ConcurrentExecutor create(String name, long timeout,
			TimeUnit unit) {
		return new ConcurrentExecutorImpl(name, timeout, unit);
	}

	/**
	 * Creates a {@link ConcurrentExecutor} with the given name (used as a
	 * prefix for creating thread), and the timeout of 1 second for running
	 * threads. If a thread did not process a job within the given timeout, the
	 * thread is terminated.
	 *
	 * @param name
	 * @return the {@link ConcurrentExecutor} associated with the given
	 *         parameters
	 */
	public static ConcurrentExecutor create(String name) {
		return create(name, 1L, TimeUnit.SECONDS);
	}

	/**
	 * @return the default {@link ConcurrentExecutor} with the timeout of 1
	 *         second for running threads.
	 */
	public static ConcurrentExecutor getDefault() {
		return DEFAULT_;
	}

}


/*
 * #%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.logging;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.semanticweb.elk.util.hashing.HashGenerator;
import org.slf4j.Logger;

/**
 * Class for keeping CPU and system times.
 *
 * @author Markus Kroetzsch
 */
public class ElkTimer {

	/** Flag for indicating that no times should be taken (just count runs). */
	public static final int RECORD_NONE = 0x00000000;
	/** Flag for indicating that CPU time should be taken. */
	public static final int RECORD_CPUTIME = 0x00000001;
	/** Flag for indicating that wall clock time should be taken. */
	public static final int RECORD_WALLTIME = 0x00000002;
	/** Flag for indicating that all supported times should be taken. */
	public static final int RECORD_ALL = RECORD_CPUTIME | RECORD_WALLTIME;

	static final ThreadMXBean tmxb = ManagementFactory.getThreadMXBean();

	static final ConcurrentHashMap<ElkTimer, ElkTimer> registeredTimers = new ConcurrentHashMap<ElkTimer, ElkTimer>();

	protected final String name;
	protected final long threadId;
	protected final int todoFlags;

	protected long currentStartCpuTime = -1;
	protected long currentStartWallTime = -1;
	protected boolean isRunning = false;
	protected long totalCpuTime = 0;
	protected long totalWallTime = 0;
	protected int measurements = 0;
	protected int threadCount = 0;

	/**
	 * Constructor. Every timer is identified by three things: a string name, an
	 * integer for flagging its tasks (todos), and a thread id (long).
	 *
	 * Tasks can be flagged by a disjunction of constants like RECORD_CPUTIME
	 * and RECORD_WALLTIME. Only times for which an according flag is set will
	 * be recorded.
	 *
	 * The thread id can be the actual id of the thread that is measured, or 0
	 * (invalid id) to not assign the timer to any thread. In this case, no CPU
	 * time measurement is possible since Java does not allow us to measure the
	 * total CPU time across all threads.
	 *
	 * @param name
	 * @param todoFlags
	 * @param threadId
	 */
	public ElkTimer(String name, int todoFlags, long threadId) {
		this.name = name;
		this.todoFlags = todoFlags;
		this.threadId = threadId;

		if (!tmxb.isThreadCpuTimeEnabled()) {
			tmxb.setThreadCpuTimeEnabled(true);
		}
	}

	/**
	 *
	 * @param name
	 * @param todoFlags
	 * @return a new {@link ElkTimer} for the current thread
	 */
	static public ElkTimer getTimerForCurrentThread(String name, int todoFlags) {
		return new ElkTimer(name, todoFlags, Thread.currentThread().getId());
	}

	/**
	 * Get the total recorded CPU time in nanoseconds.
	 *
	 * @return recorded CPU time in nanoseconds
	 */
	public long getTotalCpuTime() {
		return totalCpuTime;
	}

	public long getAvgCpuTime() {
		return totalCpuTime > 0 && measurements > 0 ? totalCpuTime
				/ measurements : -1;
	}

	/**
	 * Get the string name of the timer.
	 *
	 * @return string name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Get the ID of the thread for which this timer was created.
	 *
	 * @return thread ID
	 */
	public long getThreadId() {
		return threadId;
	}

	/**
	 * Get the total recorded wall clock time in nanoseconds.
	 *
	 * @return recorded wall time in nanoseconds
	 */
	public long getTotalWallTime() {
		return totalWallTime;
	}

	public long getAvgWallTime() {
		return totalWallTime > 0 && measurements > 0 ? totalWallTime
				/ measurements : -1;
	}

	/**
	 * Return true if the timer is running.
	 *
	 * @return true if running
	 */
	public boolean isRunning() {
		return isRunning;
	}

	/**
	 * Start the timer.
	 */
	public synchronized void start() {
		if ((todoFlags & RECORD_CPUTIME) != 0) {
			currentStartCpuTime = getThreadCpuTime(threadId);
		} else {
			currentStartCpuTime = -1;
		}
		if ((todoFlags & RECORD_WALLTIME) != 0) {
			currentStartWallTime = System.nanoTime();
		} else {
			currentStartWallTime = -1;
		}
		isRunning = true;
	}

	/**
	 * Stop the timer (if running) and reset all recorded values.
	 */
	public synchronized void reset() {
		currentStartCpuTime = -1;
		currentStartWallTime = -1;
		totalCpuTime = 0;
		totalWallTime = 0;
		measurements = 0;
		isRunning = false;
		threadCount = 0;
	}

	/**
	 * Stop the timer and return the CPU time that has passed since it had last
	 * been started. The total time (both system and CPU) of all start-stop
	 * cycles is recorded with the timer.
	 *
	 * @return CPU time that the timer was running, or -1 if timer not running
	 *         or CPU time unavailable for other reasons
	 */
	public synchronized long stop() {
		long totalTime = -1;

		if ((todoFlags & RECORD_CPUTIME) != 0 && (currentStartCpuTime != -1)) {
			long cpuTime = getThreadCpuTime(threadId);
			if (cpuTime != -1) { // may fail if thread already dead
				totalTime = cpuTime - currentStartCpuTime;
				totalCpuTime += totalTime;
			}
		}

		if ((todoFlags & RECORD_WALLTIME) != 0 && (currentStartWallTime != -1)) {
			long wallTime = System.nanoTime();
			totalWallTime += wallTime - currentStartWallTime;
		}

		if (isRunning) {
			measurements += 1;
			isRunning = false;
		}

		currentStartWallTime = -1;
		currentStartCpuTime = -1;

		return totalTime;
	}

	/**
	 * Print logging information for the timer using debug priority.
	 *
	 * @see org.semanticweb.elk.util.logging.ElkTimer#log(Logger,Priority) log()
	 * @param logger
	 */
	public void log(Logger logger) {
		log(logger, LogLevel.DEBUG);
	}

	/**
	 * Print logging information for the timer. The log only shows the recorded
	 * time of the completed start-stop cycles. If the timer is still running,
	 * then it will not be stopped to add the currently measured time to the
	 * output but a warning will be logged.
	 *
	 * @param logger
	 * @param priority
	 */
	public void log(Logger logger, LogLevel priority) {
		if (LoggerWrap.isEnabledFor(logger, priority)) {
			String timerLabel;
			if (threadId != 0) {
				timerLabel = name + " (thread " + threadId + ")";
			} else if (threadCount > 1) {
				timerLabel = name + " (over " + threadCount + " threads)";
			} else {
				timerLabel = name;
			}

			if (todoFlags == RECORD_NONE) {
				LoggerWrap.log(logger, priority, "Timer " + timerLabel + " recorded "
						+ measurements + " run(s), no times taken");
			} else {
				String labels = "";
				String values = "";
				String separator;

				if ((todoFlags & RECORD_CPUTIME) != 0 && threadId != 0) {
					labels += "CPU";
					values += totalCpuTime / 1000000;
					separator = "/";
				} else {
					separator = "";
				}
				if ((todoFlags & RECORD_WALLTIME) != 0) {
					labels += separator + "Wall";
					values += separator + totalWallTime / 1000000;
				}
				if ((todoFlags & RECORD_CPUTIME) != 0 && threadId != 0) {
					labels += "/CPU avg";
					values += "/" + (float) (totalCpuTime) / measurements
							/ 1000000;
				}
				if ((todoFlags & RECORD_WALLTIME) != 0) {
					labels += "/Wall avg";
					values += "/" + (float) (totalWallTime) / measurements
							/ 1000000;
				}
				if (threadCount > 1) {
					if ((todoFlags & RECORD_CPUTIME) != 0 && threadId != 0) {
						labels += "/CPU per thread";
						values += "/" + (float) (totalCpuTime) / threadCount
								/ 1000000;
					}
					if ((todoFlags & RECORD_WALLTIME) != 0) {
						labels += "/Wall per thread";
						values += "/" + (float) (totalWallTime) / threadCount
								/ 1000000;
					}
				}

				LoggerWrap.log(logger, priority, "Time for " + timerLabel + " for "
						+ measurements + " run(s) " + labels + " (ms): "
						+ values);
			}

			if (isRunning) {
				logger.warn("Timer " + timerLabel
						+ " logged while it was still running");
			}
		}
	}

	/**
	 * Start a timer of the given string name for all todos and the current
	 * thread. If no such timer exists yet, then it will be newly created.
	 *
	 * @param timerName
	 *            the name of the timer
	 */
	public static void startNamedTimer(String timerName) {
		getNamedTimer(timerName).start();
	}

	/**
	 * Start a timer of the given string name for the current thread. If no such
	 * timer exists yet, then it will be newly created.
	 *
	 * @param timerName
	 *            the name of the timer
	 * @param todoFlags
	 */
	public static void startNamedTimer(String timerName, int todoFlags) {
		getNamedTimer(timerName, todoFlags).start();
	}

	/**
	 * Start a timer of the given string name for the current thread. If no such
	 * timer exists yet, then it will be newly created.
	 *
	 * @param timerName
	 *            the name of the timer
	 * @param todoFlags
	 * @param threadId
	 *            of the thread to track, or 0 if only system clock should be
	 *            tracked
	 */
	public static void startNamedTimer(String timerName, int todoFlags,
			long threadId) {
		getNamedTimer(timerName, todoFlags, threadId).start();
	}

	/**
	 * Stop a timer of the given string name for all todos and the current
	 * thread. If no such timer exists, -1 will be returned. Otherwise the
	 * return value is the CPU time that was measured.
	 *
	 * @param timerName
	 *            the name of the timer
	 * @return CPU time if timer existed and was running, and -1 otherwise
	 */
	public static long stopNamedTimer(String timerName) {
		return stopNamedTimer(timerName, RECORD_ALL, Thread.currentThread()
				.getId());
	}

	/**
	 * Stop a timer of the given string name for the current thread. If no such
	 * timer exists, -1 will be returned. Otherwise the return value is the CPU
	 * time that was measured.
	 *
	 * @param timerName
	 *            the name of the timer
	 * @param todoFlags
	 * @return CPU time if timer existed and was running, and -1 otherwise
	 */
	public static long stopNamedTimer(String timerName, int todoFlags) {
		return stopNamedTimer(timerName, todoFlags, Thread.currentThread()
				.getId());
	}

	/**
	 * Stop a timer of the given string name for the given thread. If no such
	 * timer exists, -1 will be returned. Otherwise the return value is the CPU
	 * time that was measured.
	 *
	 * @param timerName
	 *            the name of the timer
	 * @param todoFlags
	 * @param threadId
	 *            of the thread to track, or 0 if only system clock should be
	 *            tracked
	 * @return CPU time if timer existed and was running, and -1 otherwise
	 */
	public static long stopNamedTimer(String timerName, int todoFlags,
			long threadId) {
		ElkTimer key = new ElkTimer(timerName, todoFlags, threadId);
		if (registeredTimers.containsKey(key)) {
			return registeredTimers.get(key).stop();
		} else {
			return -1;
		}
	}

	/**
	 * Reset a timer of the given string name for all todos and the current
	 * thread. If no such timer exists yet, then it will be newly created.
	 *
	 * @param timerName
	 *            the name of the timer
	 */
	public static void resetNamedTimer(String timerName) {
		getNamedTimer(timerName).reset();
	}

	/**
	 * Reset a timer of the given string name for the current thread. If no such
	 * timer exists yet, then it will be newly created.
	 *
	 * @param timerName
	 *            the name of the timer
	 * @param todoFlags
	 */
	public static void resetNamedTimer(String timerName, int todoFlags) {
		getNamedTimer(timerName, todoFlags).reset();
	}

	/**
	 * Reset a timer of the given string name for the given thread. If no such
	 * timer exists yet, then it will be newly created.
	 *
	 * @param timerName
	 *            the name of the timer
	 * @param todoFlags
	 * @param threadId
	 *            of the thread to track, or 0 if only system clock should be
	 *            tracked
	 */
	public static void resetNamedTimer(String timerName, int todoFlags,
			long threadId) {
		getNamedTimer(timerName, todoFlags, threadId).reset();
	}

	/**
	 * Get a timer of the given string name that takes all possible times
	 * (todos) for the current thread. If no such timer exists yet, then it will
	 * be newly created.
	 *
	 * @param timerName
	 *            the name of the timer
	 * @return timer
	 */
	public static ElkTimer getNamedTimer(String timerName) {
		return getNamedTimer(timerName, RECORD_ALL, Thread.currentThread()
				.getId());
	}

	/**
	 * Returns all registered timers
	 *
	 * @return an iterable collection of named timers
	 */
	public static Iterable<ElkTimer> getNamedTimers() {
		return registeredTimers.keySet();
	}

	/**
	 * Get a timer of the given string name and todos for the current thread. If
	 * no such timer exists yet, then it will be newly created.
	 *
	 * @param timerName
	 *            the name of the timer
	 * @param todoFlags
	 * @return timer
	 */
	public static ElkTimer getNamedTimer(String timerName, int todoFlags) {
		return getNamedTimer(timerName, todoFlags, Thread.currentThread()
				.getId());
	}

	/**
	 * Get a timer of the given string name for the given thread. If no such
	 * timer exists yet, then it will be newly created.
	 *
	 * @param timerName
	 *            the name of the timer
	 * @param todoFlags
	 * @param threadId
	 *            of the thread to track, or 0 if only system clock should be
	 *            tracked
	 * @return timer
	 */
	public static ElkTimer getNamedTimer(String timerName, int todoFlags,
			long threadId) {
		ElkTimer key = new ElkTimer(timerName, todoFlags, threadId);
		ElkTimer previous = registeredTimers.putIfAbsent(key, key);
		if (previous != null) {
			return previous;
		}
		// else
		return key;
	}

	/**
	 * Collect the total times measured by all known named timers of the given
	 * name.
	 *
	 * @param timerName
	 * @return timer
	 */
	public static ElkTimer getNamedTotalTimer(String timerName) {
		long totalCpuTime = 0;
		long totalSystemTime = 0;
		int measurements = 0;
		int threadCount = 0;
		int todoFlags = RECORD_NONE;
		ElkTimer previousTimer = null;
		for (Map.Entry<ElkTimer, ElkTimer> entry : registeredTimers.entrySet()) {
			if (entry.getValue().name.equals(timerName)) {
				previousTimer = entry.getValue();
				threadCount += 1;
				totalCpuTime += previousTimer.totalCpuTime;
				totalSystemTime += previousTimer.totalWallTime;
				measurements += previousTimer.measurements;
				todoFlags |= previousTimer.todoFlags;
			}
		}

		if (threadCount == 1) {
			return previousTimer;
		} else {
			ElkTimer result = new ElkTimer(timerName, todoFlags, 0);
			result.totalCpuTime = totalCpuTime;
			result.totalWallTime = totalSystemTime;
			result.measurements = measurements;
			result.threadCount = threadCount;
			return result;
		}
	}

	public static void logAllNamedTimers(String timerName, Logger logger) {
		logAllNamedTimers(timerName, logger, LogLevel.DEBUG);
	}

	public static void logAllNamedTimers(String timerName, Logger logger,
			LogLevel priority) {
		for (Map.Entry<ElkTimer, ElkTimer> entry : registeredTimers.entrySet()) {
			if (entry.getValue().name.equals(timerName)) {
				entry.getValue().log(logger, priority);
			}
		}
	}

	@Override
	public int hashCode() {
		int nameHash = name.hashCode();
		int threadIdHash = new Long(threadId).hashCode();
		int todoFlagsHash = new Integer(todoFlags).hashCode();
		return HashGenerator.combineListHash(nameHash, todoFlagsHash,
				threadIdHash);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		} else if (obj == null) {
			return false;
		} else if (getClass() != obj.getClass()) {
			return false;
		} else if (threadId == ((ElkTimer) obj).threadId
				&& todoFlags == ((ElkTimer) obj).todoFlags
				&& name.equals(((ElkTimer) obj).name)) {
			return true;
		} else {
			return false;
		}
	}

	protected static long getThreadCpuTime(long threadId) {
		if (threadId == 0) { // generally invalid
			return 0;
		} else {
			return tmxb.getThreadCpuTime(threadId);
		}
	}

}


/*
 * #%L
 * ELK Reasoner
 *
 * $Id$
 * $HeadURL$
 * %%
 * Copyright (C) 2011 - 2012 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.reasoner.saturation;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import org.semanticweb.elk.reasoner.indexing.model.IndexedContextRoot;
import org.semanticweb.elk.reasoner.saturation.conclusions.model.ClassConclusion;
import org.semanticweb.elk.reasoner.saturation.context.Context;
import org.semanticweb.elk.reasoner.saturation.rules.factories.RuleApplicationFactory;
import org.semanticweb.elk.reasoner.saturation.rules.factories.RuleApplicationInput;
import org.semanticweb.elk.util.concurrent.computation.InputProcessor;
import org.semanticweb.elk.util.concurrent.computation.InputProcessorFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The factory for engines that use a {@link RuleApplicationFactory} to process
 * the corresponding {@link SaturationState} (
 * {@link RuleApplicationFactory#getSaturationState()}). The input for
 * processing is submitted in the form of a {@link SaturationJob} that are
 * forwarded to the corresponding engine of the {@link RuleApplicationFactory}
 * (see
 * {@link RuleApplicationFactory#getEngine(ContextCreationListener, ContextModificationListener)}
 * ). The jobs are submitted using the {@link Engine#submit(SaturationJob)}, and
 * all currently submitted jobs are processed using the {@link Engine#process()}
 * . To every {@link ClassExpressionSaturationFactory} it is possible to attach
 * a {@link ClassExpressionSaturationListener}, which can implement hook methods
 * that perform certain actions during the processing, e.g., notifying when the
 * jobs are finished.
 *
 * The implementation relies heavily on the Java's concurrency package and uses
 * several atomic integer to monitor the progress of the computation.
 *
 * @author Frantisek Simancik
 * @author "Yevgeny Kazakov"
 *
 * @param <J>
 *            the type of the saturation jobs that can be processed by this
 *            saturation engine
 * @see ClassExpressionSaturationListener
 *
 */
public class ClassExpressionSaturationFactory<J extends SaturationJob<? extends IndexedContextRoot>>
		implements
		InputProcessorFactory<J, ClassExpressionSaturationFactory<J>.Engine> {

	// logger for this class
	private static final Logger LOGGER_ = LoggerFactory
			.getLogger(ClassExpressionSaturationFactory.class);

	// TODO: switch to listener factory
	/**
	 * The listener object implementing callback functions for this engine
	 */
	private final ClassExpressionSaturationListener<J> listener_;
	/**
	 * The rule application engine used internally for execution of the
	 * saturation rules.
	 */
	private final RuleApplicationFactory<?, RuleApplicationInput> ruleApplicationFactory_;
	/**
	 * The cached {@link SaturationState} used by the
	 * {@link RuleApplicationFactory}
	 */
	private final SaturationState<?> saturationState_;
	/**
	 * The buffer for jobs that need to be processed, i.e., those for which the
	 * method {@link Engine#submit(SaturationJob)} was executed but processing
	 * of jobs has not been started yet.
	 */
	private final Queue<J> jobsToDo_;
	/**
	 * The buffer for jobs in progress, i.e., those for which processing has
	 * started but the method
	 * {@link ClassExpressionSaturationListener#notifyFinished(Object)} was not
	 * executed yet.
	 */
	private final Queue<J> jobsInProgress_;
	/**
	 * The upper bound on the number of submitted jobs, i.e., those for which
	 * the method {@link Engine#submit(SaturationJob)} was executed.
	 */
	private final AtomicInteger countJobsSubmittedUpper_ = new AtomicInteger(0);
	/**
	 * The lower number of processed jobs, i.e., for which saturation is already
	 * computed
	 */
	private final AtomicInteger countJobsProcessedLower_ = new AtomicInteger(0);
	/**
	 * The upper bound on the number of finished jobs, i.e., those for which
	 * {@link ClassExpressionSaturationListener#notifyFinished(Object)} is
	 * executed.
	 */
	private final AtomicInteger countJobsFinishedUpper_ = new AtomicInteger(0);
	/**
	 * The lower bound on the number of contexts that are saturated (but not
	 * necessarily marked as saturated yet).
	 */
	private final AtomicInteger countContextsSaturatedLower_ = new AtomicInteger(
			0);
	/**
	 * The threshold used to submit new jobs. The job is successfully submitted
	 * if the number of currently non-saturated contexts in the
	 * {@link SaturationState} does not exceed {@link #threshold_}; otherwise
	 * processing of new jobs is suspended, and resumes only when all possible
	 * rules are applied. The threshold has influence on the size of the batches
	 * of the input jobs that are processed simultaneously, which, in turn, has
	 * an effect on throughput and latency of the saturation: in general, the
	 * larger the threshold is, the faster it takes (in theory) to perform the
	 * overall processing of jobs, but it might take longer to process an
	 * individual job because it is possible to detect that the job is processed
	 * only when the whole batch of jobs is processed.
	 */
	private final int threshold_;
	/**
	 * {@code true} if some worker could be blocked because {{@link #threshold_}
	 * is exceeded.
	 */
	private volatile boolean workersWaiting_ = false;
	/**
	 * counter incremented every time a worker starts applying the rules
	 */
	private final AtomicInteger countStartedWorkers_ = new AtomicInteger(0);
	/**
	 * counter incremented every time a worker finishes applying the rules
	 */
	private final AtomicInteger countFinishedWorkers_ = new AtomicInteger(0);
	/**
	 * The statistics about this factory aggregated from statistics for all
	 * workers
	 */
	private final ThisStatistics aggregatedStats_;

	private final ReentrantLock stopWorkersLock_ = new ReentrantLock();

	private final Condition thereAreContextsToProcess_ = stopWorkersLock_
			.newCondition();

	/**
	 * Creates a new {@link ClassExpressionSaturationFactory} using the given
	 * {@link RuleApplicationFactory}for applying the rules, the maximal number
	 * of workers that can apply the rules concurrently, and
	 * {@link ClassExpressionSaturationListener} for reporting finished
	 * saturation jobs.
	 *
	 * saturation state, listener for callback functions, and threshold for the
	 * number of unprocessed contexts.
	 *
	 * @param ruleAppFactory
	 *            specifies how the rules are applied to new
	 *            {@link ClassConclusion}s
	 * @param maxWorkers
	 *            the maximum number of workers that can use this factory
	 * @param listener
	 *            the listener object implementing callback functions
	 */
	public ClassExpressionSaturationFactory(
			RuleApplicationFactory<?, RuleApplicationInput> ruleAppFactory,
			int maxWorkers, ClassExpressionSaturationListener<J> listener) {
		this.threshold_ = 64 + 32 * maxWorkers;
		this.listener_ = listener;
		this.jobsToDo_ = new ConcurrentLinkedQueue<J>();
		this.jobsInProgress_ = new ConcurrentLinkedQueue<J>();
		this.ruleApplicationFactory_ = ruleAppFactory;
		this.saturationState_ = ruleAppFactory.getSaturationState();
		this.aggregatedStats_ = new ThisStatistics();
	}

	/**
	 * Creates a new {@link ClassExpressionSaturationFactory} using the given
	 * {@link RuleApplicationFactory}for applying the rules and the maximal
	 * number of workers that can apply the rules concurrently.
	 *
	 * @param ruleAppFactory
	 * @param maxWorkers
	 *            the maximum number of workers that can use this factory
	 */
	public ClassExpressionSaturationFactory(
			RuleApplicationFactory<?, RuleApplicationInput> ruleAppFactory,
			int maxWorkers) {
		/* we use a dummy listener */
		this(ruleAppFactory, maxWorkers,
				new ClassExpressionSaturationListener<J>() {

					@Override
					public void notifyFinished(J job)
							throws InterruptedException {
						// dummy listener does not do anything
					}
				});
	}

	@Override
	public Engine getEngine() {
		return new Engine();
	}

	/**
	 * Print statistics about the saturation
	 */
	public void printStatistics() {
		ruleApplicationFactory_.getSaturationStatistics().print(LOGGER_);
		if (LOGGER_.isDebugEnabled()) {
			if (aggregatedStats_.jobsSubmittedNo > 0)
				LOGGER_.debug(
						"Saturation Jobs Submitted=Done+Processed: {}={}+{}",
						aggregatedStats_.jobsSubmittedNo,
						aggregatedStats_.jobsAlreadyDoneNo,
						aggregatedStats_.jobsProcessedNo);
			LOGGER_.debug("Locks: " + aggregatedStats_.locks);
		}
	}

	@Override
	public boolean isInterrupted() {
		return ruleApplicationFactory_.isInterrupted();
	}

	@Override
	public void finish() {
		checkStatistics();
	}

	/**
	 * Checks if the statistical values make sense and issues error messages if
	 * not
	 */
	private void checkStatistics() {
		if (aggregatedStats_.jobsSubmittedNo != aggregatedStats_.jobsAlreadyDoneNo
				+ aggregatedStats_.jobsProcessedNo) {
			LOGGER_.error("Some submitted saturation jobs were not processed!");
		}
	}

	public SaturationStatistics getRuleAndConclusionStatistics() {
		return ruleApplicationFactory_.getSaturationStatistics();
	}

	/**
	 * waking up all workers waiting for new saturated contexts
	 */
	private void wakeUpWorkers() {
		if (!workersWaiting_) {
			return;
		}
		stopWorkersLock_.lock();
		try {
			workersWaiting_ = false;
			thereAreContextsToProcess_.signalAll();
		} finally {
			stopWorkersLock_.unlock();
		}
	}

	/**
	 * Updates the counter for processed contexts and jobs
	 *
	 * @param snapshotFinishedWorkers
	 */
	private void updateProcessedCounters(int snapshotFinishedWorkers) {
		if (isInterrupted()) {
			wakeUpWorkers();
			return;
		}
		if (countStartedWorkers_.get() > snapshotFinishedWorkers) {
			/*
			 * We are not the last worker processing the saturation state, so
			 * the current jobs and contexts may not be processed yet.
			 */
			return;
		}
		/*
		 * Otherwise we were the last worker processing the saturation state;
		 * take the values for current jobs and contexts and verify that we are
		 * still the last worker (thus the order is important here).
		 */
		int snapshotCountJobsSubmitted = countJobsSubmittedUpper_.get();
		int snapshotCountContextNonSaturated = saturationState_
				.getContextMarkNonSaturatedCount();
		int snapshotCountStartedWorkers = countStartedWorkers_.get();

		if (snapshotCountStartedWorkers > snapshotFinishedWorkers) {
			/* no longer the last worker */
			return;
		}
		/*
		 * If we arrive here, #snapshotCountJobsSubmitted and
		 * #snapshotCountContextNonSaturated represents at least the number of
		 * jobs processed and saturated contexts. Furthermore, since we took
		 * them in this order, we know that all contexts for the processed jobs
		 * were created, saturated, and counted. Now, we updated the
		 * corresponding counters for the processed contexts and jobs but in the
		 * reversed order to make sure that for every job considered to be
		 * processed all contexts were already considered to be processed.
		 */
		if (updateIfSmaller(countContextsSaturatedLower_,
				snapshotCountContextNonSaturated)) {
			/*
			 * Sleeping workers can now take new inputs.
			 */
			wakeUpWorkers();
		}
		updateIfSmaller(countJobsProcessedLower_, snapshotCountJobsSubmitted);
	}

	/**
	 * Check if the counter for saturated contexts and processed jobs can be
	 * increased and post-process the finished jobs
	 *
	 * @throws InterruptedException
	 */
	private void updateFinishedCounters(ThisStatistics localStatistics)
			throws InterruptedException {
		int snapshotJobsProcessed = countJobsProcessedLower_.get();
		/*
		 * ensure that all contexts for processed jobs are marked as saturated
		 */
		for (;;) {
			int snapshotCountContextsSaturatedLower = countContextsSaturatedLower_
					.get();
			saturationState_
					.setContextsSaturated(snapshotCountContextsSaturatedLower);
			if (saturationState_
					.getContextSetSaturatedCount() < snapshotCountContextsSaturatedLower) {
				/*
				 * this means that some other worker also sets contexts as
				 * saturated, then it will mark the finished jobs instead
				 */
				return;
			}
			/*
			 * ensure that the counter for processed jobs is still up to date
			 */
			int updatedSnapshotJobsProcessed = countJobsProcessedLower_.get();
			if (updatedSnapshotJobsProcessed == snapshotJobsProcessed) {
				break;
			}
			/* else refresh counters */
			snapshotJobsProcessed = updatedSnapshotJobsProcessed;
		}
		/*
		 * ensure that all processed jobs are finished
		 */
		for (;;) {
			int snapshotJobsFinished = countJobsFinishedUpper_.get();
			if (snapshotJobsFinished >= snapshotJobsProcessed) {
				break;
			}
			/*
			 * update the finished context counter at least to the taken
			 * snapshot value and mark the corresponding number of jobs as
			 * processed
			 */
			if (!countJobsFinishedUpper_.compareAndSet(snapshotJobsFinished,
					snapshotJobsFinished + 1)) {
				/* retry */
				continue;
			}
			// else
			J nextJob = jobsInProgress_.poll();
			IndexedContextRoot root = nextJob.getInput();
			Context rootSaturation = saturationState_.getContext(root);
			if (rootSaturation.isInitialized()
					&& !rootSaturation.isSaturated()) {
				LOGGER_.error("{}: context for a finished job not saturated!",
						rootSaturation);
			}
			nextJob.setOutput(rootSaturation);

			LOGGER_.trace("{}: saturation finished", root);

			localStatistics.jobsProcessedNo++;
			listener_.notifyFinished(nextJob);// can be interrupted
		}

	}

	/**
	 * Update the counter to the value provided it is greater. Regardless of the
	 * returned value, it is guaranteed that the value of the counter after
	 * execution will be at least the input value.
	 *
	 * @param counter
	 *            the counter that should be updated
	 * @param value
	 *            the value to which the counter should be updated
	 * @return {@code true} if the counter has been updated
	 */
	private static boolean updateIfSmaller(AtomicInteger counter, int value) {
		for (;;) {
			int snapshotCoutner = counter.get();
			if (snapshotCoutner >= value)
				return false;
			if (counter.compareAndSet(snapshotCoutner, value))
				return true;
		}
	}

	public class Engine implements InputProcessor<J> {

		private final InputProcessor<RuleApplicationInput> ruleApplicationEngine_ = ruleApplicationFactory_
				.getEngine(ContextCreationListener.DUMMY,
						ContextModificationListener.DUMMY);

		private final ThisStatistics stats_ = new ThisStatistics();

		// don't allow creating of engines directly; only through the factory
		private Engine() {
		}

		@Override
		public void submit(J job) {
			jobsToDo_.add(job);
			stats_.jobsSubmittedNo++;
		}

		@Override
		public void process() throws InterruptedException {
			/*
			 * This works as follows. We apply inference rules to the contexts
			 * created so far in batches: when the number of unsaturated
			 * contexts is below a certain threshold, we add a new saturation
			 * job and process the contexts. How do we know when contexts are
			 * completely processed, i.e., there could be nothing more derived
			 * in the context? This is very difficult to know. We apply the
			 * following strategy: we know that all created contexts are
			 * processed when (1) no worker is creating or processing contexts
			 * and (2) after every worker that was interrupted while processing
			 * contexts there was a worker that has started processing contexts.
			 * To check condition (1), we use two counters: first counter is
			 * incremented before a worker starts processing contexts, the
			 * second counter is incremented after a worker finishes processing
			 * contexts. Therefore, at the moment when the values of both
			 * counters coincide, we know that condition (1) is fulfilled. To
			 * check condition (2) we use a variable, where store the snapshot
			 * of the number of started workers at the moment when the last
			 * worker was interrupted, and when condition (1) is fulfilled, we
			 * check if the value of this snapshot is smaller then the number of
			 * started = the number of finished workers. This way we know that
			 * after the last interrupted worker there was a worker that was
			 * finished and not interrupted. To avoid deadlock, it is essential
			 * that whenever conditions (1) and (2) are satisfied, we can update
			 * the number of processed contexts, i.e., the computation was not
			 * interrupted in between processing of contexts and updating these
			 * counters.
			 */
			countStartedWorkers_.incrementAndGet();
			ruleApplicationEngine_.process();
			updateProcessedCounters(countFinishedWorkers_.incrementAndGet());
			updateFinishedCounters(stats_); // can throw InterruptedException

			for (;;) {
				if (isInterrupted()) {
					return;
				}
				int snapshotCountSaturated = countContextsSaturatedLower_.get();
				if (saturationState_.getContextMarkNonSaturatedCount()
						- snapshotCountSaturated > threshold_) {
					stopWorkersLock_.lock();
					try {
						workersWaiting_ = true;
						stats_.locks++;
						/*
						 * it is important to set waiting workers before
						 * checking processed contexts counters because it is
						 * tested in the other order when waking up the workers
						 */
						if (countContextsSaturatedLower_
								.get() > snapshotCountSaturated
								|| isInterrupted()) {
							/*
							 * new contexts were processed meanwhile; all
							 * workers should be notified
							 */
							workersWaiting_ = false;
							thereAreContextsToProcess_.signalAll();
							continue;
						}
						thereAreContextsToProcess_.await();
						continue;
					} finally {
						stopWorkersLock_.unlock();
					}
				}
				J nextJob = jobsToDo_.poll();
				if (nextJob == null)
					return;
				IndexedContextRoot root = nextJob.getInput();
				/*
				 * if the context is already assigned and saturated, this job is
				 * already complete
				 */
				Context rootContext = saturationState_.getContext(root);

				if (rootContext != null && rootContext.isInitialized()
						&& rootContext.isSaturated()) {
					nextJob.setOutput(rootContext);
					stats_.jobsAlreadyDoneNo++;
					// can throw InterruptedException
					listener_.notifyFinished(nextJob);
					continue;
				}

				LOGGER_.trace("{}: saturation started", root);
				/*
				 * submit the job to the rule engine and start processing it
				 */
				countStartedWorkers_.incrementAndGet();
				countJobsSubmittedUpper_.incrementAndGet();
				jobsInProgress_.add(nextJob);
				ruleApplicationEngine_.submit(new RuleApplicationInput(root));
				ruleApplicationEngine_.process();
				updateProcessedCounters(
						countFinishedWorkers_.incrementAndGet());
				updateFinishedCounters(stats_);
			}
		}

		@Override
		public void finish() {
			ruleApplicationEngine_.finish();
			aggregatedStats_.merge(stats_);
		}

	}

	/**
	 * Counters accumulating statistical information about this factory.
	 *
	 * @author "Yevgeny Kazakov"
	 *
	 */
	private static class ThisStatistics {
		/**
		 * the number of submitted jobs
		 */
		int jobsSubmittedNo;
		/**
		 * submitted jobs that were already done when they started to be
		 * processed
		 */
		int jobsAlreadyDoneNo;
		/**
		 * submitted jobs that were finished by this engine
		 */
		int jobsProcessedNo;
		/**
		 * counts how many times workers have been waiting
		 */
		int locks;

		public synchronized void merge(ThisStatistics statistics) {
			this.jobsSubmittedNo += statistics.jobsSubmittedNo;
			this.jobsProcessedNo += statistics.jobsProcessedNo;
			this.jobsAlreadyDoneNo += statistics.jobsAlreadyDoneNo;
			this.locks += statistics.locks;
		}
	}

}


package org.semanticweb.elk.reasoner.saturation.rules.backwardlinks;

/*
 * #%L
 * ELK Reasoner
 * $Id:$
 * $HeadURL:$
 * %%
 * Copyright (C) 2011 - 2014 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%
 */

import java.util.Collection;

import org.semanticweb.elk.reasoner.indexing.model.IndexedComplexPropertyChain;
import org.semanticweb.elk.reasoner.indexing.model.IndexedContextRoot;
import org.semanticweb.elk.reasoner.indexing.model.IndexedObjectSomeValuesFrom;
import org.semanticweb.elk.reasoner.indexing.model.IndexedPropertyChain;
import org.semanticweb.elk.reasoner.saturation.conclusions.model.BackwardLink;
import org.semanticweb.elk.reasoner.saturation.conclusions.model.ForwardLink;
import org.semanticweb.elk.reasoner.saturation.context.Context;
import org.semanticweb.elk.reasoner.saturation.context.ContextPremises;
import org.semanticweb.elk.reasoner.saturation.rules.ClassInferenceProducer;
import org.semanticweb.elk.util.collections.HashSetMultimap;
import org.semanticweb.elk.util.collections.LazySetIntersection;
import org.semanticweb.elk.util.collections.Multimap;
import org.semanticweb.elk.util.collections.chains.Matcher;
import org.semanticweb.elk.util.collections.chains.ReferenceFactory;
import org.semanticweb.elk.util.collections.chains.SimpleTypeBasedMatcher;

/**
 * A {@link LinkableBackwardLinkRule} applied when processing a
 * {@link BackwardLink} producing {@link BackwardLink}s or {@link ForwardLink}s
 * resulted by composing the processed {@link BackwardLink} with the
 * {@link ForwardLink}s contained in the {@link ContextPremises} using property
 * chain axioms.
 *
 * @author "Yevgeny Kazakov"
 *
 */
public class BackwardLinkChainFromBackwardLinkRule
		extends AbstractLinkableBackwardLinkRule {

	// logger for events
	/*
	 * private static final Logger LOGGER_ = LoggerFactory
	 * .getLogger(BackwardLinkChainFromBackwardLinkRule.class);
	 */

	public static final String NAME = "BackwardLink ForwardLink Composition";

	/**
	 * the record that stores all {@link ForwardLink}s produced in the
	 * {@link ContextPremises} in which this rule is saved; it stores every
	 * {@link ForwardLink} by indexing its target by its property
	 */
	private final Multimap<IndexedPropertyChain, IndexedContextRoot> forwardLinksByObjectProperty_;

	private BackwardLinkChainFromBackwardLinkRule(
			LinkableBackwardLinkRule tail) {
		super(tail);
		this.forwardLinksByObjectProperty_ = new HashSetMultimap<IndexedPropertyChain, IndexedContextRoot>(
				3);
	}

	/**
	 * Adds a {@link BackwardLinkChainFromBackwardLinkRule} inferences for the
	 * given {@link ForwardLink} in the given {@link Context}
	 *
	 * @param link
	 * @param context
	 * @return {@code true} if the {@link BackwardLinkChainFromBackwardLinkRule}
	 *         stored in the {@link Context} is modified as a result of this
	 *         operation
	 */
	public static boolean addRuleFor(ForwardLink link, Context context) {
		BackwardLinkChainFromBackwardLinkRule rule = context
				.getBackwardLinkRuleChain().getCreate(MATCHER_, FACTORY_);
		return rule.forwardLinksByObjectProperty_.add(link.getChain(),
				link.getTarget());
	}

	/**
	 * Removes a {@link BackwardLinkChainFromBackwardLinkRule} inferences for
	 * the given {@link ForwardLink} from the given {@link Context}
	 *
	 * @param link
	 * @param context
	 * @return {@code true} if the {@link BackwardLinkChainFromBackwardLinkRule}
	 *         stored in the {@link Context} is modified as a result of this
	 *         operation
	 */
	public static boolean removeRuleFor(ForwardLink link, Context context) {
		BackwardLinkChainFromBackwardLinkRule rule = context
				.getBackwardLinkRuleChain().find(MATCHER_);
		return rule == null ? false : rule.forwardLinksByObjectProperty_
				.remove(link.getChain(), link.getTarget());
	}

	/**
	 * Tests if a {@link BackwardLinkChainFromBackwardLinkRule} inferences for
	 * the given {@link ForwardLink} is present in the given {@link Context}
	 *
	 * @param link
	 * @param context
	 * @return {@code true} if the inference is present
	 */
	public static boolean containsRuleFor(ForwardLink link, Context context) {
		BackwardLinkChainFromBackwardLinkRule rule = context
				.getBackwardLinkRuleChain().find(MATCHER_);
		return rule == null ? false : rule.forwardLinksByObjectProperty_
				.contains(link.getChain(), link.getTarget());
	}

	@Deprecated
	public Multimap<IndexedPropertyChain, IndexedContextRoot> getForwardLinksByObjectProperty() {
		return forwardLinksByObjectProperty_;
	}

	@Override
	public String toString() {
		return NAME;
	}

	private void apply(
			Multimap<IndexedPropertyChain, IndexedComplexPropertyChain> compsByForwardRelations,
			BackwardLink link, ContextPremises premises,
			ClassInferenceProducer producer) {
		/* compose the link with all forward links */
		if (compsByForwardRelations == null)
			return;

		for (IndexedPropertyChain forwardRelation : new LazySetIntersection<IndexedPropertyChain>(
				compsByForwardRelations.keySet(), forwardLinksByObjectProperty_.keySet())) {

			Collection<IndexedComplexPropertyChain> compositions = compsByForwardRelations
					.get(forwardRelation);
			Collection<IndexedContextRoot> forwardTargets = forwardLinksByObjectProperty_
					.get(forwardRelation);

			for (IndexedComplexPropertyChain composition : compositions)
				for (IndexedContextRoot forwardTarget : forwardTargets)
					IndexedObjectSomeValuesFrom.Helper.produceComposedLink(
							producer, link.getTraceRoot(), link.getRelation(),
							premises.getRoot(), forwardRelation, forwardTarget,
							composition);
		}
	}

	@Override
	public void apply(BackwardLink link, ContextPremises premises,
			ClassInferenceProducer producer) {
		apply(link.getRelation().getSaturated()
				.getNonRedundantCompositionsByRightSubProperty(), link,
				premises, producer);
	}

	@Override
	public void applyTracing(BackwardLink link, ContextPremises premises,
			ClassInferenceProducer producer) {
		apply(link.getRelation().getSaturated()
				.getNonRedundantCompositionsByRightSubProperty(), link,
				premises, producer);
		apply(link.getRelation().getSaturated()
				.getRedundantCompositionsByRightSubProperty(), link, premises,
				producer);
	}

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

	@Override
	public void accept(LinkedBackwardLinkRuleVisitor<?> visitor,
			BackwardLink premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		visitor.visit(this, premise, premises, producer);
	}

	static Matcher<LinkableBackwardLinkRule, BackwardLinkChainFromBackwardLinkRule> MATCHER_ = new SimpleTypeBasedMatcher<LinkableBackwardLinkRule, BackwardLinkChainFromBackwardLinkRule>(
			BackwardLinkChainFromBackwardLinkRule.class);

	/**
	 * The factory used for appending a new instance of this rule to a
	 * {@link LinkableBackwardLinkRule} chain
	 */
	static ReferenceFactory<LinkableBackwardLinkRule, BackwardLinkChainFromBackwardLinkRule> FACTORY_ = new ReferenceFactory<LinkableBackwardLinkRule, BackwardLinkChainFromBackwardLinkRule>() {
		@Override
		public BackwardLinkChainFromBackwardLinkRule create(
				LinkableBackwardLinkRule tail) {
			return new BackwardLinkChainFromBackwardLinkRule(tail);
		}
	};

}


package org.semanticweb.elk.reasoner.saturation.rules;

/*
 * #%L
 * ELK Reasoner
 * $Id:$
 * $HeadURL:$
 * %%
 * Copyright (C) 2011 - 2015 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%
 */

import org.semanticweb.elk.reasoner.indexing.model.IndexedClass;
import org.semanticweb.elk.reasoner.indexing.model.IndexedClassEntity;
import org.semanticweb.elk.reasoner.indexing.model.IndexedClassExpression;
import org.semanticweb.elk.reasoner.indexing.model.IndexedObjectComplementOf;
import org.semanticweb.elk.reasoner.indexing.model.IndexedObjectHasSelf;
import org.semanticweb.elk.reasoner.indexing.model.IndexedObjectIntersectionOf;
import org.semanticweb.elk.reasoner.indexing.model.IndexedObjectSomeValuesFrom;
import org.semanticweb.elk.reasoner.saturation.conclusions.model.BackwardLink;
import org.semanticweb.elk.reasoner.saturation.conclusions.model.ClassInconsistency;
import org.semanticweb.elk.reasoner.saturation.conclusions.model.ContextInitialization;
import org.semanticweb.elk.reasoner.saturation.conclusions.model.DisjointSubsumer;
import org.semanticweb.elk.reasoner.saturation.conclusions.model.ForwardLink;
import org.semanticweb.elk.reasoner.saturation.conclusions.model.Propagation;
import org.semanticweb.elk.reasoner.saturation.conclusions.model.SubContextInitialization;
import org.semanticweb.elk.reasoner.saturation.context.ContextPremises;
import org.semanticweb.elk.reasoner.saturation.rules.backwardlinks.BackwardLinkChainFromBackwardLinkRule;
import org.semanticweb.elk.reasoner.saturation.rules.backwardlinks.ContradictionOverBackwardLinkRule;
import org.semanticweb.elk.reasoner.saturation.rules.backwardlinks.SubsumerBackwardLinkRule;
import org.semanticweb.elk.reasoner.saturation.rules.contextinit.OwlThingContextInitRule;
import org.semanticweb.elk.reasoner.saturation.rules.contextinit.RootContextInitializationRule;
import org.semanticweb.elk.reasoner.saturation.rules.contradiction.ContradictionPropagationRule;
import org.semanticweb.elk.reasoner.saturation.rules.disjointsubsumer.ContradictionCompositionRule;
import org.semanticweb.elk.reasoner.saturation.rules.forwardlink.BackwardLinkFromForwardLinkRule;
import org.semanticweb.elk.reasoner.saturation.rules.forwardlink.NonReflexiveBackwardLinkCompositionRule;
import org.semanticweb.elk.reasoner.saturation.rules.forwardlink.ReflexiveBackwardLinkCompositionRule;
import org.semanticweb.elk.reasoner.saturation.rules.propagations.SubsumerPropagationRule;
import org.semanticweb.elk.reasoner.saturation.rules.subcontextinit.PropagationInitializationRule;
import org.semanticweb.elk.reasoner.saturation.rules.subsumers.ComposedFromDecomposedSubsumerRule;
import org.semanticweb.elk.reasoner.saturation.rules.subsumers.ContradictionFromNegationRule;
import org.semanticweb.elk.reasoner.saturation.rules.subsumers.ContradictionFromOwlNothingRule;
import org.semanticweb.elk.reasoner.saturation.rules.subsumers.DisjointSubsumerFromMemberRule;
import org.semanticweb.elk.reasoner.saturation.rules.subsumers.EquivalentClassFirstFromSecondRule;
import org.semanticweb.elk.reasoner.saturation.rules.subsumers.EquivalentClassSecondFromFirstRule;
import org.semanticweb.elk.reasoner.saturation.rules.subsumers.IndexedClassDecompositionRule;
import org.semanticweb.elk.reasoner.saturation.rules.subsumers.IndexedClassFromDefinitionRule;
import org.semanticweb.elk.reasoner.saturation.rules.subsumers.IndexedObjectComplementOfDecomposition;
import org.semanticweb.elk.reasoner.saturation.rules.subsumers.IndexedObjectHasSelfDecomposition;
import org.semanticweb.elk.reasoner.saturation.rules.subsumers.IndexedObjectIntersectionOfDecomposition;
import org.semanticweb.elk.reasoner.saturation.rules.subsumers.IndexedObjectSomeValuesFromDecomposition;
import org.semanticweb.elk.reasoner.saturation.rules.subsumers.ObjectIntersectionFromFirstConjunctRule;
import org.semanticweb.elk.reasoner.saturation.rules.subsumers.ObjectIntersectionFromSecondConjunctRule;
import org.semanticweb.elk.reasoner.saturation.rules.subsumers.ObjectUnionFromDisjunctRule;
import org.semanticweb.elk.reasoner.saturation.rules.subsumers.PropagationFromExistentialFillerRule;
import org.semanticweb.elk.reasoner.saturation.rules.subsumers.SuperClassFromSubClassRule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A {@link RuleVisitor} that always returns {@code null} and prints that the
 * rules are ignored to the logger.
 *
 * @author "Yevgeny Kazakov"
 *
 * @param <O>
 *            the type of output parameter with which this visitor works
 */
public class DummyRuleVisitor<O> implements RuleVisitor<O> {

	// logger for events
	private static final Logger LOGGER_ = LoggerFactory
			.getLogger(DummyRuleVisitor.class);

	/**
	 * The default implementation of all methods
	 *
	 * @param rule
	 * @param premise
	 * @param premises
	 * @param producer
	 * @return
	 */
	protected <P> O defaultVisit(Rule<P> rule, P premise,
			ContextPremises premises, ClassInferenceProducer producer) {
		if (LOGGER_.isTraceEnabled()) {
			LOGGER_.trace("ignore {} by {} in {}", premise, rule, premises);
		}
		return null;
	}

	@Override
	public O visit(BackwardLinkChainFromBackwardLinkRule rule,
			BackwardLink premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(BackwardLinkFromForwardLinkRule rule, ForwardLink premise,
			ContextPremises premises, ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(ComposedFromDecomposedSubsumerRule rule,
			IndexedClassEntity premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(ContradictionCompositionRule rule, DisjointSubsumer premise,
			ContextPremises premises, ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);

	}

	@Override
	public O visit(ContradictionFromNegationRule rule,
			IndexedClassExpression premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(ContradictionFromOwlNothingRule rule,
			IndexedClassExpression premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(ContradictionOverBackwardLinkRule rule, BackwardLink premise,
			ContextPremises premises, ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(ContradictionPropagationRule rule, ClassInconsistency premise,
			ContextPremises premises, ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);

	}

	@Override
	public O visit(DisjointSubsumerFromMemberRule rule,
			IndexedClassExpression premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(EquivalentClassFirstFromSecondRule rule,
			IndexedClassExpression premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(EquivalentClassSecondFromFirstRule rule,
			IndexedClassExpression premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(IndexedClassDecompositionRule rule, IndexedClass premise,
			ContextPremises premises, ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(IndexedClassFromDefinitionRule rule,
			IndexedClassExpression premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(IndexedObjectComplementOfDecomposition rule,
			IndexedObjectComplementOf premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(IndexedObjectHasSelfDecomposition rule,
			IndexedObjectHasSelf premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(IndexedObjectIntersectionOfDecomposition rule,
			IndexedObjectIntersectionOf premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(IndexedObjectSomeValuesFromDecomposition rule,
			IndexedObjectSomeValuesFrom premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(NonReflexiveBackwardLinkCompositionRule rule,
			ForwardLink premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(ObjectIntersectionFromFirstConjunctRule rule,
			IndexedClassExpression premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(ObjectIntersectionFromSecondConjunctRule rule,
			IndexedClassExpression premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(ObjectUnionFromDisjunctRule rule,
			IndexedClassExpression premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(OwlThingContextInitRule rule, ContextInitialization premise,
			ContextPremises premises, ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(PropagationFromExistentialFillerRule rule,
			IndexedClassExpression premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(PropagationInitializationRule rule,
			SubContextInitialization premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(ReflexiveBackwardLinkCompositionRule rule,
			ForwardLink premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);

	}

	@Override
	public O visit(RootContextInitializationRule rule,
			ContextInitialization premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(SubsumerBackwardLinkRule rule, BackwardLink premise,
			ContextPremises premises, ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(SubsumerPropagationRule rule, Propagation premise,
			ContextPremises premises, ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

	@Override
	public O visit(SuperClassFromSubClassRule rule,
			IndexedClassExpression premise, ContextPremises premises,
			ClassInferenceProducer producer) {
		return defaultVisit(rule, premise, premises, producer);
	}

}


/*
 * #%L
 * ELK Reasoner
 * $Id:$
 * $HeadURL:$
 * %%
 * Copyright (C) 2011 - 2016 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.reasoner.saturation.properties;

import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;

import org.liveontologies.puli.Producer;
import org.semanticweb.elk.owl.interfaces.ElkAxiom;
import org.semanticweb.elk.reasoner.indexing.model.IndexedComplexPropertyChain;
import org.semanticweb.elk.reasoner.indexing.model.IndexedObjectProperty;
import org.semanticweb.elk.reasoner.indexing.model.IndexedPropertyChain;
import org.semanticweb.elk.reasoner.saturation.conclusions.model.SubPropertyChain;
import org.semanticweb.elk.reasoner.saturation.properties.inferences.ObjectPropertyInference;
import org.semanticweb.elk.reasoner.saturation.properties.inferences.SubPropertyChainExpandedSubObjectPropertyOf;
import org.semanticweb.elk.reasoner.saturation.properties.inferences.SubPropertyChainInference;
import org.semanticweb.elk.reasoner.saturation.properties.inferences.SubPropertyChainInferenceConclusionVisitor;
import org.semanticweb.elk.reasoner.saturation.properties.inferences.SubPropertyChainTautology;
import org.semanticweb.elk.reasoner.stages.PropertyHierarchyCompositionState;
import org.semanticweb.elk.util.collections.ArrayHashSet;
import org.semanticweb.elk.util.collections.HashSetMultimap;
import org.semanticweb.elk.util.collections.LazySetIntersection;
import org.semanticweb.elk.util.collections.Multimap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A collection of utilities for computing entailed sub-properties of
 * {@link IndexedPropertyChain}
 *
 * @author "Yevgeny Kazakov"
 * @author Peter Skocovsky
 */
class SubPropertyExplorer {

	// logger for this class
	private static final Logger LOGGER_ = LoggerFactory
			.getLogger(SubPropertyExplorer.class);

	/**
	 * The element for which the sub-property (chains) are computed
	 */
	private final IndexedPropertyChain input_;
	/**
	 * the set that will be closed under told sub-properties
	 */
	private final Set<IndexedPropertyChain> subPropertyChains_;
	/**
	 * the subset of {@link #subProperties_} consisting of
	 * {@link IndexedObjectProperty}s
	 */
	private final Set<IndexedObjectProperty> subProperties_;
	/**
	 * the sub-properties for which the told sub-properties may not be yet
	 * expanded
	 */
	private final Queue<IndexedObjectProperty> toDoSubProperties_ = new LinkedList<IndexedObjectProperty>();
	/**
	 * used to record {@link SubPropertyChainInference}
	 */
	private final Producer<? super SubPropertyChainInference> inferenceProducer_;

	private final SubPropertyChainInference.Visitor<Void> inferenceProcessor_ = new SubPropertyChainInferenceConclusionVisitor<Void>(
			new SubPropertyChain.Visitor<Void>() {

				@Override
				public Void visit(SubPropertyChain conclusion) {
					toDoConclusion(conclusion);
					return null;
				}

			});

	SubPropertyExplorer(IndexedPropertyChain input,
			Set<IndexedPropertyChain> subPropertyChains,
			Set<IndexedObjectProperty> subProperties,
			Producer<? super SubPropertyChainInference> inferenceProducer) {
		this.input_ = input;
		this.subPropertyChains_ = subPropertyChains;
		this.subProperties_ = subProperties;
		this.inferenceProducer_ = inferenceProducer;
		toDoInference(new SubPropertyChainTautology(input));
	}

	private void toDoInference(SubPropertyChainInference inference) {
		LOGGER_.trace("{}: new inference", inference);
		inferenceProducer_.produce(inference);
		inference.accept(inferenceProcessor_);

	}

	private void toDoConclusion(SubPropertyChain conclusion) {
		IndexedPropertyChain subChain = conclusion.getSubChain();
		if (subPropertyChains_.add(subChain)) {
			if (subChain instanceof IndexedObjectProperty) {
				IndexedObjectProperty subProperty = (IndexedObjectProperty) subChain;
				subProperties_.add(subProperty);
				toDoSubProperties_.add(subProperty);
			}
		}
	}

	private void process() {
		for (;;) {
			IndexedObjectProperty next = toDoSubProperties_.poll();
			if (next == null)
				break;
			List<IndexedPropertyChain> toldSubChains = next.getToldSubChains();
			List<ElkAxiom> reasons = next.getToldSubChainsReasons();
			for (int i = 0; i < toldSubChains.size(); i++) {
				IndexedPropertyChain sub = toldSubChains.get(i);
				ElkAxiom reason = reasons.get(i);
				toDoInference(new SubPropertyChainExpandedSubObjectPropertyOf(
						sub, next, (IndexedObjectProperty) input_, reason));
			}
		}
	}

	private static void expandUnderSubProperties(IndexedPropertyChain input,
			Set<IndexedPropertyChain> currentSubPropertyChains,
			Set<IndexedObjectProperty> currentSubProperties,
			Producer<? super SubPropertyChainInference> inferenceProducer) {
		new SubPropertyExplorer(input, currentSubPropertyChains,
				currentSubProperties, inferenceProducer).process();
		if (LOGGER_.isTraceEnabled()) {
			LOGGER_.trace("{} sub-property chains: {}, sub-properties: {}",
					input, currentSubPropertyChains, currentSubProperties);
		}
	}

	private static SaturatedPropertyChain computeSubProperties(
			IndexedPropertyChain input,
			Producer<? super SubPropertyChainInference> inferenceProducer,
			final PropertyHierarchyCompositionState.Dispatcher dispatcher) {
		SaturatedPropertyChain saturation = input.getSaturated();
		if (saturation.derivedSubPropertiesComputed)
			return saturation;
		// else
		synchronized (saturation) {
			if (saturation.derivedSubProperyChains == null)
				saturation.derivedSubProperyChains = new ArrayHashSet<IndexedPropertyChain>(
						8);
		}
		synchronized (saturation.derivedSubProperyChains) {
			if (saturation.derivedSubPropertiesComputed)
				return saturation;
			// else
			if (saturation.derivedSubProperties == null)
				saturation.derivedSubProperties = new ArrayHashSet<IndexedObjectProperty>(
						8);
			expandUnderSubProperties(input, saturation.derivedSubProperyChains,
					saturation.derivedSubProperties, inferenceProducer);
			saturation.derivedSubPropertiesComputed = true;
			dispatcher.firePropertyBecameSaturated(input);
		}
		return saturation;
	}

	/**
	 * Computes all sub-{@link IndexedPropertyChain}s of the given
	 * {@link IndexedPropertyChain}, if not computing before, recording all
	 * {@link ObjectPropertyInference}s using the provided {@link Producer}. It
	 * is ensured that all {@link ObjectPropertyInference}s are applied only
	 * once even if the method is called multiple times.
	 *
	 * @param input
	 *            the {@link IndexedPropertyChain} for which to find the sub-
	 *            {@link IndexedPropertyChain}s.
	 * @param inferenceProducer
	 *            the {@link Producer} using which all applied
	 *            {@link ObjectPropertyInference}s are recorded.
	 * @return the sub-{@link IndexedPropertyChain}s of the given
	 *         {@link IndexedPropertyChain}
	 */
	static Set<IndexedPropertyChain> getSubPropertyChains(
			IndexedPropertyChain input,
			Producer<? super SubPropertyChainInference> inferenceProducer,
			final PropertyHierarchyCompositionState.Dispatcher dispatcher) {
		return computeSubProperties(input, inferenceProducer, dispatcher)
				.getSubPropertyChains();
	}

	/**
	 * Computes all sub-{@link IndexedObjectProperty}s of the given
	 * {@link IndexedPropertyChain}, if not computing before, recording all
	 * {@link ObjectPropertyInference}s using the provided {@link Producer}. It
	 * is ensured that all {@link ObjectPropertyInference}s are applied only
	 * once even if the method is called multiple times.
	 *
	 * @param input
	 *            the {@link IndexedPropertyChain} for which to find the sub-
	 *            {@link IndexedObjectProperty}s.
	 * @param inferenceProducer
	 *            the {@link Producer} using which all applied
	 *            {@link ObjectPropertyInference}s are recorded.
	 * @return the sub-{@link IndexedObjectProperty} of the given
	 *         {@link IndexedPropertyChain}
	 */
	static Set<IndexedObjectProperty> getSubProperties(
			IndexedPropertyChain input,
			Producer<? super SubPropertyChainInference> inferenceProducer,
			final PropertyHierarchyCompositionState.Dispatcher dispatcher) {
		return computeSubProperties(input, inferenceProducer, dispatcher)
				.getSubProperties();
	}

	/**
	 * Given an {@link IndexedObjectProperty} Computes a {@link Multimap} from
	 * {@link IndexedObjectProperty}s to {@link IndexedObjectProperty}
	 * consisting of the the assignments T -> S such that both S and
	 * ObjectPropertyChain(S, T) are sub-properties of the given
	 * {@link IndexedObjectProperty}. The provided {@link Producer} is used to
	 * record all {@link ObjectPropertyInference}s that are applied in this
	 * computation of sub-{@link IndexedPropertyChain}s involved. It is ensured
	 * that the computation is performed only once.
	 *
	 * @param input
	 *            a given {@link IndexedObjectProperty}
	 * @param inferenceProducer
	 * @return a multimap consisting of assignments T -> S such that both S and
	 *         ObjectPropertyChain(S, T) are sub-properties of the given
	 *         {@link IndexedObjectProperty}
	 */
	static Multimap<IndexedObjectProperty, IndexedObjectProperty> getLeftSubComposableSubPropertiesByRightProperties(
			IndexedObjectProperty input,
			Producer<? super SubPropertyChainInference> inferenceProducer,
			final PropertyHierarchyCompositionState.Dispatcher dispatcher) {
		SaturatedPropertyChain saturation = input.getSaturated();
		if (saturation.leftSubComposableSubPropertiesByRightPropertiesComputed)
			return saturation.leftSubComposableSubPropertiesByRightProperties;
		// else
		synchronized (saturation) {
			if (saturation.leftSubComposableSubPropertiesByRightProperties == null)
				saturation.leftSubComposableSubPropertiesByRightProperties = new HashSetMultimap<IndexedObjectProperty, IndexedObjectProperty>();
		}
		synchronized (saturation.leftSubComposableSubPropertiesByRightProperties) {
			if (saturation.leftSubComposableSubPropertiesByRightPropertiesComputed)
				return saturation.leftSubComposableSubPropertiesByRightProperties;
			// else compute it
			Set<IndexedObjectProperty> subProperties = getSubProperties(input,
					inferenceProducer, dispatcher);
			for (IndexedPropertyChain subPropertyChain : getSubPropertyChains(
					input, inferenceProducer, dispatcher)) {
				if (subPropertyChain instanceof IndexedComplexPropertyChain) {
					IndexedComplexPropertyChain composition = (IndexedComplexPropertyChain) subPropertyChain;
					Set<IndexedObjectProperty> leftSubProperties = getSubProperties(
							composition.getFirstProperty(), inferenceProducer,
							dispatcher);
					Set<IndexedObjectProperty> commonSubProperties = new LazySetIntersection<IndexedObjectProperty>(
							subProperties, leftSubProperties);
					if (commonSubProperties.isEmpty())
						continue;
					// else
					for (IndexedObjectProperty rightSubProperty : getSubProperties(
							composition.getSuffixChain(), inferenceProducer,
							dispatcher))
						for (IndexedObjectProperty commonLeft : commonSubProperties)
							saturation.leftSubComposableSubPropertiesByRightProperties
									.add(rightSubProperty, commonLeft);
				}
			}
			saturation.leftSubComposableSubPropertiesByRightPropertiesComputed = true;
		}
		return saturation.leftSubComposableSubPropertiesByRightProperties;
	}
}


/*
 * #%L
 * ELK Reasoner
 * $Id:$
 * $HeadURL:$
 * %%
 * Copyright (C) 2011 - 2016 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.reasoner.saturation.properties;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.semanticweb.elk.owl.interfaces.ElkEntity;
import org.semanticweb.elk.owl.interfaces.ElkObjectProperty;
import org.semanticweb.elk.owl.predefined.PredefinedElkObjectPropertyFactory;
import org.semanticweb.elk.reasoner.indexing.model.IndexedObjectProperty;
import org.semanticweb.elk.reasoner.indexing.model.OntologyIndex;
import org.semanticweb.elk.util.collections.ArrayHashMap;
import org.semanticweb.elk.util.collections.ArrayHashSet;
import org.semanticweb.elk.util.collections.Operations;
import org.semanticweb.elk.util.concurrent.computation.InputProcessor;
import org.semanticweb.elk.util.concurrent.computation.InputProcessorFactory;
import org.semanticweb.elk.util.concurrent.computation.InterruptMonitor;
import org.semanticweb.elk.util.concurrent.computation.DelegateInterruptMonitor;

/**
 * Computes object property taxonomy.
 *
 * @author Peter Skocovsky
 */
public class ObjectPropertyTaxonomyComputationFactory
		extends DelegateInterruptMonitor implements
		InputProcessorFactory<IndexedObjectProperty, ObjectPropertyTaxonomyComputationFactory.Engine> {

	/**
	 * Visitor accepting results of the transitive reduction.
	 */
	private final TransitiveReductionOutputVisitor<ElkObjectProperty> outputProcessor_;

	private final IndexedObjectProperty indexedTopProperty_;

	private final IndexedObjectProperty indexedBottomProperty_;

	private final Collection<? extends Collection<? extends ElkObjectProperty>> defaultDirectSubproperties_;

	public ObjectPropertyTaxonomyComputationFactory(
			final InterruptMonitor interrupter,
			final TransitiveReductionOutputVisitor<ElkObjectProperty> outputProcessor,
			final OntologyIndex index,
			final PredefinedElkObjectPropertyFactory predefinedFactory) {
		super(interrupter);
		this.outputProcessor_ = outputProcessor;
		this.indexedTopProperty_ = index.getOwlTopObjectProperty();
		this.indexedBottomProperty_ = index.getOwlBottomObjectProperty();
		this.defaultDirectSubproperties_ = Collections.singleton(Collections
				.singleton(predefinedFactory.getOwlBottomObjectProperty()));
	}

	@Override
	public Engine getEngine() {
		return new Engine();
	}

	@Override
	public void finish() {
		// Empty.
	}

	class Engine implements InputProcessor<IndexedObjectProperty> {

		@Override
		public void submit(final IndexedObjectProperty property) {
			instertIntoTaxonomy(property);
		}

		@Override
		public void process() throws InterruptedException {
			// Everything is done in submit().
		}

		@Override
		public void finish() {
			// Empty.
		}

	}

	/**
	 * Adds the specified object property into the taxonomy if it is not in it
	 * yet and sets its direct sub-properties if not set yet.
	 *
	 * @param property
	 *            The property that should be inserted into taxonomy.
	 */
	private void instertIntoTaxonomy(final IndexedObjectProperty property) {

		/*
		 * @formatter:off
		 *
		 * Transitive reduction and taxonomy computation
		 * 	if sub-properties of a sub-property contain this property,
		 * 		they are equivalent
		 * 	if a property is a strict sub-property of another strict sub-property,
		 * 		it is not direct
		 *
		 * @formatter:on
		 */

		final Map<IndexedObjectProperty, ElkObjectProperty> equivalent = collectEquivalent(
				property);
		if (equivalent == null) {
			// Equivalent to top.
			return;
		}
		final Map<IndexedObjectProperty, Collection<? extends ElkObjectProperty>> subEquivalent = new ArrayHashMap<IndexedObjectProperty, Collection<? extends ElkObjectProperty>>();
		final Set<IndexedObjectProperty> indirect = new ArrayHashSet<IndexedObjectProperty>();
		for (final IndexedObjectProperty subProperty : property.getSaturated()
				.getSubProperties()) {

			if (equivalent.containsKey(subProperty)) {
				// subProperty is not strict
				continue;
			}
			// subProperty is strict

			final Map<IndexedObjectProperty, ElkObjectProperty> subEq = collectEquivalent(
					subProperty);
			// should not be null, because top cannot be a strict sub-property
			subEquivalent.put(subProperty, subEq.values());
			for (final IndexedObjectProperty subSubProperty : subProperty
					.getSaturated().getSubProperties()) {
				if (!subEq.containsKey(subSubProperty)) {
					// strict
					indirect.add(subSubProperty);
				}
			}

		}

		/*
		 * If property is not equivalent to bottom and there are no strict sub
		 * properties, add the bottom as a default sub property.
		 */
		if (subEquivalent.isEmpty() && (indexedBottomProperty_ == null
				|| !equivalent.containsKey(indexedBottomProperty_))) {
			outputProcessor_
					.visit(new TransitiveReductionOutputEquivalentDirectImpl<ElkObjectProperty>(
							equivalent.values(), defaultDirectSubproperties_));
			return;
		}
		// else

		final Collection<Collection<? extends ElkObjectProperty>> direct = Operations
				.map(subEquivalent.entrySet(),
						new Operations.Transformation<Map.Entry<IndexedObjectProperty, Collection<? extends ElkObjectProperty>>, Collection<? extends ElkObjectProperty>>() {

							@Override
							public Collection<? extends ElkObjectProperty> transform(
									final Entry<IndexedObjectProperty, Collection<? extends ElkObjectProperty>> element) {
								if (indirect.contains(element.getKey())) {
									return null;
								} else {
									return element.getValue();
								}
							}

						});

		outputProcessor_
				.visit(new TransitiveReductionOutputEquivalentDirectImpl<ElkObjectProperty>(
						equivalent.values(), direct));

	}

	/**
	 * Collects sub-properties of property that are equivalent to
	 * it. Returns null if property is equivalent to
	 * the top property.
	 *
	 * @param property
	 * @return null if the specified property is equivalent to the
	 *         top property, otherwise mapping from equivalent indexed
	 *         properties to their elk entities.
	 */
	private Map<IndexedObjectProperty, ElkObjectProperty> collectEquivalent(
			final IndexedObjectProperty property) {

		final Set<IndexedObjectProperty> subProperties = property.getSaturated()
				.getSubProperties();
		final Map<IndexedObjectProperty, ElkObjectProperty> equivalent = new ArrayHashMap<IndexedObjectProperty, ElkObjectProperty>();
		for (final IndexedObjectProperty subProperty : subProperties) {
			if (subProperty.equals(indexedTopProperty_)) {
				outputProcessor_
						.visit(new TransitiveReductionOutputExtremeImpl<ElkObjectProperty>(
								property.getElkEntity()));
				return null;
			}
			if (subProperty.getSaturated().getSubProperties().contains(property)
					|| property.equals(indexedBottomProperty_)) {
				equivalent.put(subProperty, subProperty.getElkEntity());
			}
		}
		if (indexedBottomProperty_.getSaturated().getSubProperties()
				.contains(property)) {
			equivalent.put(indexedBottomProperty_,
					indexedBottomProperty_.getElkEntity());
		}

		return equivalent;
	}

	private static class TransitiveReductionOutputEquivalentDirectImpl<E extends ElkEntity>
			implements TransitiveReductionOutputEquivalentDirect<E> {

		final Collection<? extends E> equivalent_;
		final Collection<? extends Collection<? extends E>> direct_;

		public TransitiveReductionOutputEquivalentDirectImpl(
				final Collection<? extends E> equivalent,
				final Collection<? extends Collection<? extends E>> direct) {
			this.equivalent_ = equivalent;
			this.direct_ = direct;
		}

		@Override
		public Collection<? extends E> getEquivalent() {
			return equivalent_;
		}

		@Override
		public Iterable<? extends Collection<? extends E>> getDirectlyRelated() {
			return Collections.unmodifiableCollection(direct_);
		}

		@Override
		public void accept(final TransitiveReductionOutputVisitor<E> visitor) {
			visitor.visit(this);
		}

	}

	private static class TransitiveReductionOutputExtremeImpl<E extends ElkEntity>
			implements TransitiveReductionOutputExtreme<E> {

		final E extremeMember_;

		public TransitiveReductionOutputExtremeImpl(final E extremeMember) {
			this.extremeMember_ = extremeMember;
		}

		@Override
		public E getExtremeMember() {
			return extremeMember_;
		}

		@Override
		public void accept(final TransitiveReductionOutputVisitor<E> visitor) {
			visitor.visit(this);
		}

	}

}