import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.parser.DTD;
import javax.swing.text.html.parser.DocumentParser;
import javax.swing.text.html.parser.ParserDelegator;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class JI9079095 {
	static class DtdRef {
		volatile DTD dtd = null;
	}

	static class HtmlCallback extends HTMLEditorKit.ParserCallback {
		private StringBuilder buf = new StringBuilder();

		public String getHtml() {
			return buf.toString();
		}

		@Override public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
			buf.append("<" + t.toString() + ">");
		}

		@Override public void handleText(char[] data, int pos) {
			buf.append(data);
		}

		@Override public void handleEndTag(HTML.Tag t, int pos) {
			buf.append("</" + t.toString() + ">");
		}
	}

	static class Worker extends Thread {
		final CyclicBarrier finished;
		final CyclicBarrier start;
		final DtdRef dtdRef;
		volatile boolean success = true;

		Worker(CyclicBarrier start, CyclicBarrier finished, DtdRef dtdRef) {
			this.start = start;
			this.finished = finished;
			this.dtdRef = dtdRef;
		}
		public void run() {
			try {
				while (true) {
					start.await();
					// code from javax.swing.text.html.parser.ParserDelegator.parse(), except we use our custom DTD
					// instance
					String inputHtml = "<p>outer<span>inner</span></p>";
					HtmlCallback callback = new HtmlCallback();
					try {
						new DocumentParser(dtdRef.dtd).parse(new StringReader(inputHtml), callback, true);
						success = callback.getHtml().equals("<html><head></head><body><p>outer<span>inner</span></p></body></html>");
					} catch (NullPointerException e) {
						success = false;
					}
					finished.await();
				}
			} catch (InterruptedException | BrokenBarrierException e) {
				// clean shutdown
			} catch (IOException e) {
				System.err.println(e.getMessage());
				System.err.println(e.getStackTrace());
			}
		}
	}
	public static void main(String[] args) throws IOException, BrokenBarrierException, InterruptedException {
		String inputHtml = "<p>outer<span>inner</span></p>";
		HtmlCallback callback = new HtmlCallback();
		ParserDelegator parser = new ParserDelegator();
		parser.parse(new StringReader(inputHtml), callback, true);


		final int threadCount = 10;
		final CyclicBarrier start = new CyclicBarrier(threadCount + 1);
		final CyclicBarrier finished = new CyclicBarrier(threadCount + 1);
		final DtdRef dtdRef = new DtdRef();
		List<Worker> workers = new ArrayList<>();
		for (int i = 0; i < threadCount; i++) {
			Worker worker = new Worker(start, finished, dtdRef);
			worker.start();
			workers.add(worker);
		}

		boolean triggeredBug = false;
		int runs = 0;
		while (!triggeredBug) {
			dtdRef.dtd = createDtd();
			start.await();
			finished.await();
			triggeredBug = workers.stream().anyMatch((worker) -> !worker.success);
			++runs;
			System.out.println("Runs: " + runs + ". Triggered bug: " + triggeredBug);
		}
		for (Worker worker : workers) {
			worker.interrupt();
		}
	}

	public static DTD createDtd() throws IOException {
		// logic from javax.swing.text.html.parser.ParserDelegator to initialize the DTD
		String dtdName = "html32";
		DTD instance = DTD.getDTD(dtdName);
		try(InputStream in = ParserDelegator.class.getResourceAsStream(dtdName + ".bdtd")) {
			if (in != null) {
				instance.read(new DataInputStream(new BufferedInputStream(in)));
				instance.putDTDHash(dtdName, instance);
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		return instance;
	}
}