package com.revolsys.parallel.process;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import com.revolsys.collection.map.ThreadSharedProperties;
import com.revolsys.logging.Logs;
import com.revolsys.logging.log4j.ThreadLocalAppenderRunnable;
import com.revolsys.parallel.ThreadUtil;
import com.revolsys.parallel.channel.Channel;
import com.revolsys.spring.TargetBeanProcess;
public class ProcessNetwork {
public static void startAndWait(final Process... processes) {
final ProcessNetwork processNetwork = new ProcessNetwork(processes);
processNetwork.startAndWait();
}
public static void startAndWait(final Runnable... processes) {
final ProcessNetwork processNetwork = new ProcessNetwork(processes);
processNetwork.startAndWait();
}
private boolean autoStart;
private int count = 0;
private String name = "processNetwork";
private ProcessNetwork parent;
private final Map<Process, Thread> processes = new HashMap<>();
private boolean running = false;
private boolean stopping = false;
private final Object sync = new Object();
private ThreadGroup threadGroup;
public ProcessNetwork() {
}
public ProcessNetwork(final Collection<? extends Runnable> processes) {
for (final Runnable runnable : processes) {
if (runnable instanceof Process) {
final Process process = (Process)runnable;
addProcess(process);
} else {
addProcess(runnable);
}
}
}
public ProcessNetwork(final Runnable... processes) {
this(Arrays.asList(processes));
}
public void addProcess(final Process process) {
synchronized (this.sync) {
if (this.stopping) {
return;
} else {
if (!this.stopping) {
if (this.processes != null && !this.processes.containsKey(process)) {
this.processes.put(process, null);
process.initialize();
}
}
}
}
if (this.parent == null) {
if (this.running && !this.stopping) {
start(process);
}
} else {
this.parent.addProcess(process);
}
}
public void addProcess(final Runnable runnable) {
if (runnable != null) {
final RunnableProcess process = new RunnableProcess(runnable);
addProcess(process);
}
}
public void addProcess(final String processName, final Runnable runnable) {
if (runnable != null) {
final RunnableProcess process = new RunnableProcess(processName, runnable);
addProcess(process);
}
}
private void finishRunning() {
synchronized (this.sync) {
this.running = false;
this.processes.clear();
}
}
public String getName() {
return this.name;
}
public ProcessNetwork getParent() {
return this.parent;
}
public Collection<Process> getProcesses() {
if (this.processes == null) {
return Collections.emptySet();
} else {
return this.processes.keySet();
}
}
protected Map<Process, Thread> getProcessMap() {
return this.processes;
}
protected Object getSync() {
return this.sync;
}
public ThreadGroup getThreadGroup() {
return this.threadGroup;
}
@PostConstruct
public void init() {
if (this.parent == null) {
this.threadGroup = new ThreadGroup(this.name);
ThreadSharedProperties.initialiseThreadGroup(this.threadGroup);
}
}
public boolean isAutoStart() {
return this.autoStart;
}
void removeProcess(final Process process) {
synchronized (this.sync) {
if (this.processes != null) {
this.processes.remove(process);
this.count--;
}
if (this.parent == null) {
if (process.getProcessNetwork() == this) {
process.setProcessNetwork(null);
}
if (this.count == 0) {
finishRunning();
this.sync.notifyAll();
}
} else {
this.parent.removeProcess(process);
}
}
}
public void setAutoStart(final boolean autoStart) {
this.autoStart = autoStart;
}
public void setName(final String name) {
this.name = name;
}
public void setParent(final ProcessNetwork parent) {
this.parent = parent;
}
public void setProcesses(final Collection<Process> processes) {
for (final Process process : processes) {
addProcess(process);
}
}
public void start() {
if (this.parent == null) {
synchronized (this.sync) {
this.running = true;
if (this.processes != null) {
for (final Process process : new ArrayList<>(this.processes.keySet())) {
process.setProcessNetwork(this);
start(process);
}
}
}
}
}
private synchronized void start(final Process process) {
if (this.parent == null) {
if (this.processes != null) {
Thread thread = this.processes.get(process);
if (thread == null) {
final Process runProcess;
if (process instanceof TargetBeanProcess) {
final TargetBeanProcess targetBeanProcess = (TargetBeanProcess)process;
runProcess = targetBeanProcess.getProcess();
this.processes.remove(process);
} else {
runProcess = process;
}
final Runnable runnable = new ProcessRunnable(this, runProcess);
final String name = runProcess.toString();
final Runnable appenderRunnable = new ThreadLocalAppenderRunnable(runnable);
thread = new Thread(this.threadGroup, appenderRunnable, name);
this.processes.put(runProcess, thread);
if (!thread.isAlive()) {
thread.start();
this.count++;
}
}
}
}
}
public void startAndWait() {
synchronized (this.sync) {
start();
waitTillFinished();
}
}
@SuppressWarnings("deprecation")
@PreDestroy
public void stop() {
final List<Thread> threads;
synchronized (this.sync) {
this.stopping = true;
this.sync.notifyAll();
threads = new ArrayList<>(this.processes.values());
}
boolean interrupted = false;
try {
final long maxWait = System.currentTimeMillis() + 10000;
while (!threads.isEmpty() && System.currentTimeMillis() < maxWait) {
for (final Iterator<Thread> threadIter = threads.iterator(); threadIter.hasNext();) {
final Thread thread = threadIter.next();
if (thread == null || !thread.isAlive() || Thread.currentThread() == thread) {
threadIter.remove();
} else {
try {
thread.interrupt();
} catch (final Exception e) {
if (e instanceof InterruptedException) {
interrupted = true;
}
}
if (!thread.isAlive()) {
threadIter.remove();
}
}
}
}
for (final Thread thread : threads) {
if (thread.isAlive()) {
try {
thread.stop();
} catch (final Exception e) {
if (e instanceof InterruptedException) {
interrupted = true;
}
}
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
} finally {
finishRunning();
}
}
@Override
public String toString() {
return this.name;
}
public void waitTillFinished() {
if (this.parent == null) {
synchronized (this.sync) {
try {
while (!this.stopping && this.count > 0) {
ThreadUtil.pause(this.sync);
}
} finally {
finishRunning();
}
}
} else {
this.parent.waitTillFinished();
}
}
public static void processTasks(final int processCount, final Channel<Runnable> tasks) {
final ProcessNetwork processNetwork = new ProcessNetwork();
for (int i = 0; i < processCount; i++) {
processNetwork.addProcess(() -> {
while (true) {
final Runnable task = tasks.read(1000);
if (task == null) {
return;
} else {
try {
task.run();
} catch (final Throwable e) {
Logs.error(ProcessNetwork.class, "Error procesing task", e);
}
}
}
});
}
processNetwork.startAndWait();
}
}