1 /*******************************************************************************
2  * Copyright (c) 2008, 2017 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 
15 package org.eclipse.ua.tests.help.other;
16 
17 import static org.junit.Assert.assertEquals;
18 import static org.junit.Assert.fail;
19 
20 import org.eclipse.help.ITopic;
21 import org.eclipse.help.internal.toc.Toc;
22 import org.eclipse.ua.tests.help.util.DocumentCreator;
23 import org.junit.Test;
24 import org.w3c.dom.Document;
25 import org.w3c.dom.Element;
26 
27 public class ConcurrentTocAccess {
28 
29 	private boolean checkAttributes = true;
30 
31 	// Set enableTimeout to false for debugging
32 	private boolean enableTimeout = true;
33 
34 	private class TocGenerator {
35 		private int[] dimensions;
36 		private StringBuilder result;
37 
generateToc(int dimensions[])38 		public String generateToc(int dimensions[]) {
39 			this.dimensions = dimensions;
40 			result = new StringBuilder();
41 			result.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
42 			result.append("<?NLS TYPE=\"org.eclipse.help.toc\"?>\n");
43 			result.append("<toc label=\"Test Toc\" >\n");
44 			generateTopics(0);
45 			result.append("</toc>");
46 			return result.toString();
47 		}
48 
generateTopics(int depth)49 		private void generateTopics(int depth) {
50 			if (depth >= dimensions.length) {
51 				return;
52 			}
53 			int numChildren = dimensions[depth];
54 			for (int i = 0; i < numChildren; i++) {
55 				result.append("<topic label=\"topicLabel"  + i + "\" href=\"page" + i + ".html\">\n");
56 				generateTopics(depth + 1);
57 				result.append("</topic>\n");
58 			}
59 		}
60 	}
61 
62 	/*
63 	 * Class which visits every topic in a TOC
64 	 */
65 	private class TocVisitor extends Thread {
66 		private final Toc toc;
67 		private int leafCount = -2;
68 		public Exception exception;
69 
TocVisitor(Toc toc)70 		TocVisitor(Toc toc) {
71 			this.toc = toc;
72 		}
73 
74 		@Override
run()75 		public void run() {
76 			try {
77 				int result = traverseToc(toc);
78 				setLeafCount(result);
79 			} catch (Exception e) {
80 				setLeafCount(-1);
81 				this.exception = e;
82 			}
83 		}
84 
setLeafCount(int leafCount)85 		synchronized public void setLeafCount(int leafCount) {
86 			this.leafCount = leafCount;
87 		}
88 
getLeafCount()89 		synchronized public int getLeafCount() {
90 			return leafCount;
91 		}
92 	}
93 
94 	private class BadHrefException extends RuntimeException {
95 		private static final long serialVersionUID = 410319402417607912L;
96 	}
97 	private class BadLabelException extends RuntimeException {
98 		private static final long serialVersionUID = -4581518572807575035L;
99 	}
100 
accessInParallel(int[] dimensions, int numberOfThreads)101 	private void accessInParallel(int[] dimensions, int numberOfThreads) throws Exception {
102 		Toc toc = createToc(dimensions);
103 		int expectedLeafCount = computeNumberOfLeafTopics(dimensions);
104 		TocVisitor[] visitors = new TocVisitor[numberOfThreads];
105 		for (int i = 0; i < numberOfThreads; i++) {
106 			visitors[i] = new TocVisitor(toc);
107 		}
108 		for (int i = 0; i < numberOfThreads; i++) {
109 			visitors[i].start();
110 		}
111 		// Now wait for the threads to complete
112 		boolean complete = false;
113 		int iterations = 0;
114 		do {
115 			complete = true;
116 			iterations++;
117 			if (enableTimeout && iterations > 100) {
118 				fail("Test did not complete within 10 seconds");
119 			}
120 			for (int i = 0; i < numberOfThreads; i++) {
121 				if (visitors[i].isAlive()) {
122 					complete = false;
123 					try {
124 						Thread.sleep(100);
125 					} catch (InterruptedException e) {
126 						fail("Interrupted Exception");
127 					}
128 				}
129 			}
130 		} while (!complete);
131 		for (int i = 0; i < numberOfThreads; i++) {
132 			if (visitors[i].exception != null) {
133 				throw visitors[i].exception;
134 			}
135 			assertEquals(expectedLeafCount, visitors[i].getLeafCount());
136 		}
137 	}
138 
139 	// Visit every child of a TOC and count the number of leaf topics
traverseToc(Toc toc)140 	private int traverseToc(Toc toc) {
141 		int leafNodes = 0;
142 		ITopic[] children = toc.getTopics();
143 		for (int i = 0; i < children.length; i++) {
144 			leafNodes += traverseTopic(children[i], i);
145 		}
146 		return leafNodes;
147 	}
148 
computeNumberOfLeafTopics(int[] dimensions)149 	private int computeNumberOfLeafTopics(int[] dimensions) {
150 		int expectedLeaves = 1;
151 		for (int dimension : dimensions) {
152 			expectedLeaves = expectedLeaves * dimension;
153 		}
154 		return expectedLeaves;
155 	}
156 
traverseTopic(ITopic topic, int index)157 	private int traverseTopic(ITopic topic, int index) {
158 		if (checkAttributes) {
159 			String expectedLabel = "topicLabel" + index;
160 			String expectedHref = "page" + index + ".html";
161 			String label = topic.getLabel();
162 			String href = topic.getHref();
163 			if (!label.equals(expectedLabel)) {
164 				throw new BadLabelException();
165 			}
166 			if (!href.equals(expectedHref)) {
167 				throw new BadHrefException();
168 			}
169 		}
170 		ITopic[] children = topic.getSubtopics();
171 		if (children.length == 0) {
172 			return 1;
173 		}
174 		int leafNodes = 0;
175 		for (int i = 0; i < children.length; i++) {
176 			leafNodes += traverseTopic(children[i], i);
177 		}
178 		return leafNodes;
179 	}
180 
createToc(int[] dimensions)181 	private Toc createToc(int[] dimensions) {
182 		String tocSource = new TocGenerator().generateToc(dimensions);
183 		Toc toc;
184 		Document doc;
185 		try {
186 			doc = DocumentCreator.createDocument(tocSource);
187 		} catch (Exception e) {
188 			fail("Exception creating TOC");
189 			doc = null;
190 		}
191 		Element tocElement = (Element) doc.getElementsByTagName("toc").item(0);
192 		toc = new Toc(tocElement);
193 		return toc;
194 	}
195 
196 	@Test
testFlatTocSize5()197 	public void testFlatTocSize5() throws Exception {
198 		int[] dimensions = {5};
199 		accessInParallel(dimensions, 2);
200 	}
201 
202 	@Test
testFlatTocSize1000()203 	public void testFlatTocSize1000() throws Exception {
204 		int[] dimensions = {1000};
205 		accessInParallel(dimensions, 2);
206 	}
207 
208 	@Test
testFlatTocSize10000()209 	public void testFlatTocSize10000() throws Exception {
210 		int[] dimensions = {10000};
211 		accessInParallel(dimensions, 2);
212 	}
213 
214 	@Test
testTwoLevelToc()215 	public void testTwoLevelToc() throws Exception {
216 		int[] dimensions = {50, 50};
217 		accessInParallel(dimensions, 2);
218 	}
219 
220 	@Test
testDeepToc()221 	public void testDeepToc() throws Exception {
222 		int[] dimensions = {2,2,2,2,2,2,2,2,2,2,2};
223 		accessInParallel(dimensions, 2);
224 	}
225 
226 	@Test
testFlatTocManyThreads()227 	public void testFlatTocManyThreads() throws Exception {
228 		int[] dimensions = {100};
229 		accessInParallel(dimensions, 100);
230 	}
231 
232 }
233