1 /*******************************************************************************
2  * Copyright (c) 2009, 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.e4.ui.tests.application;
16 
17 import static org.junit.Assert.assertFalse;
18 import static org.junit.Assert.assertTrue;
19 import static org.junit.Assert.fail;
20 
21 import java.util.ArrayList;
22 import java.util.List;
23 import org.eclipse.e4.core.contexts.EclipseContextFactory;
24 import org.eclipse.e4.core.contexts.IEclipseContext;
25 import org.eclipse.e4.core.services.events.IEventBroker;
26 import org.eclipse.e4.ui.internal.workbench.UIEventPublisher;
27 import org.eclipse.e4.ui.model.application.MApplication;
28 import org.eclipse.e4.ui.model.application.MApplicationElement;
29 import org.eclipse.e4.ui.model.application.MApplicationFactory;
30 import org.eclipse.e4.ui.model.application.ui.basic.MBasicFactory;
31 import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
32 import org.eclipse.e4.ui.model.application.ui.menu.MMenu;
33 import org.eclipse.e4.ui.model.application.ui.menu.MMenuFactory;
34 import org.eclipse.e4.ui.tests.model.test.MTestFactory;
35 import org.eclipse.e4.ui.tests.model.test.MTestHarness;
36 import org.eclipse.e4.ui.workbench.UIEvents.ApplicationElement;
37 import org.eclipse.e4.ui.workbench.UIEvents.Command;
38 import org.eclipse.e4.ui.workbench.UIEvents.Context;
39 import org.eclipse.e4.ui.workbench.UIEvents.Contribution;
40 import org.eclipse.e4.ui.workbench.UIEvents.Dirtyable;
41 import org.eclipse.e4.ui.workbench.UIEvents.ElementContainer;
42 import org.eclipse.e4.ui.workbench.UIEvents.EventTags;
43 import org.eclipse.e4.ui.workbench.UIEvents.Input;
44 import org.eclipse.e4.ui.workbench.UIEvents.Parameter;
45 import org.eclipse.e4.ui.workbench.UIEvents.UIElement;
46 import org.eclipse.e4.ui.workbench.UIEvents.UILabel;
47 import org.eclipse.e4.ui.workbench.UIEvents.Window;
48 import org.eclipse.emf.common.notify.Notifier;
49 import org.junit.Test;
50 import org.osgi.service.event.EventHandler;
51 
52 public class UIEventsTest extends HeadlessApplicationElementTest {
53 
54 	class EventTester {
55 		String testerName;
56 		IEventBroker eventBroker;
57 		String topic;
58 		String[] attIds;
59 		boolean[] hasFired;
60 
61 		EventHandler attListener = event -> {
62 			assertTrue(event.getTopic().equals(topic),
63 					"Incorrect Topic: " + event.getTopic()); //$NON-NLS-1$
64 
65 			String attId = (String) event.getProperty(EventTags.ATTNAME);
66 			int attIndex = getAttIndex(attId);
67 			assertTrue(attIndex >= 0, "Unknown Attribite: " + attId); //$NON-NLS-1$
68 			hasFired[attIndex] = true;
69 		};
70 
EventTester(String name, String topic, String[] attIds, IEventBroker eventBroker)71 		public EventTester(String name, String topic, String[] attIds,
72 				IEventBroker eventBroker) {
73 			this.testerName = name;
74 			this.topic = topic;
75 			this.attIds = attIds;
76 			this.eventBroker = eventBroker;
77 
78 			hasFired = new boolean[attIds.length];
79 			reset();
80 
81 			eventBroker.subscribe(this.topic, attListener);
82 		}
83 
84 		/**
85 		 * @param b
86 		 * @param string
87 		 */
assertTrue(boolean b, String string)88 		protected void assertTrue(boolean b, String string) {
89 		}
90 
91 		/**
92 		 * @param attId
93 		 * @return
94 		 */
getAttIndex(String attId)95 		protected int getAttIndex(String attId) {
96 			for (int i = 0; i < attIds.length; i++) {
97 				if (attIds[i].equals(attId))
98 					return i;
99 			}
100 			return -1;
101 		}
102 
dispose()103 		public void dispose() {
104 			eventBroker.unsubscribe(attListener);
105 		}
106 
reset()107 		public void reset() {
108 			for (int i = 0; i < hasFired.length; i++)
109 				hasFired[i] = false;
110 		}
111 
getAttIds(boolean fired)112 		public String[] getAttIds(boolean fired) {
113 			List<String> atts = new ArrayList<>();
114 			for (int i = 0; i < hasFired.length; i++) {
115 				if (hasFired[i] == fired)
116 					atts.add(attIds[i]);
117 			}
118 
119 			return atts.toArray(new String[atts.size()]);
120 		}
121 	}
122 
123 	public class AppElementTester extends EventTester {
AppElementTester(IEventBroker eventBroker)124 		AppElementTester(IEventBroker eventBroker) {
125 			super("AppElement", ApplicationElement.TOPIC_ALL, new String[] {
126 					ApplicationElement.ELEMENTID, ApplicationElement.TAGS,
127 					ApplicationElement.PERSISTEDSTATE }, eventBroker);
128 		}
129 	}
130 
131 	public class CommandTester extends EventTester {
CommandTester(IEventBroker eventBroker)132 		CommandTester(IEventBroker eventBroker) {
133 			super("Command", Command.TOPIC_ALL,
134 					new String[] { Command.COMMANDNAME }, eventBroker);
135 		}
136 	}
137 
138 	public class ContextTester extends EventTester {
ContextTester(IEventBroker eventBroker)139 		ContextTester(IEventBroker eventBroker) {
140 			super("Context", Context.TOPIC_ALL, new String[] { Context.CONTEXT,
141 					Context.VARIABLES }, eventBroker);
142 		}
143 	}
144 
145 	public class ContributionTester extends EventTester {
ContributionTester(IEventBroker eventBroker)146 		ContributionTester(IEventBroker eventBroker) {
147 			super("Contribution", Contribution.TOPIC_ALL, new String[] {
148 					Contribution.CONTRIBUTIONURI, Contribution.OBJECT },
149 					eventBroker);
150 		}
151 	}
152 
153 	public class ElementContainerTester extends EventTester {
ElementContainerTester(IEventBroker eventBroker)154 		ElementContainerTester(IEventBroker eventBroker) {
155 			super("ElementContainer", ElementContainer.TOPIC_ALL,
156 					new String[] { ElementContainer.CHILDREN,
157 							ElementContainer.SELECTEDELEMENT }, eventBroker);
158 		}
159 	}
160 
161 	public class DirtyableTester extends EventTester {
DirtyableTester(IEventBroker eventBroker)162 		DirtyableTester(IEventBroker eventBroker) {
163 			super("Dirtyable", Dirtyable.TOPIC_ALL,
164 					new String[] { Dirtyable.DIRTY }, eventBroker);
165 		}
166 	}
167 
168 	public class InputTester extends EventTester {
InputTester(IEventBroker eventBroker)169 		InputTester(IEventBroker eventBroker) {
170 			super("Input", Input.TOPIC_ALL, new String[] { Input.INPUTURI },
171 					eventBroker);
172 		}
173 	}
174 
175 	public class ParameterTester extends EventTester {
ParameterTester(IEventBroker eventBroker)176 		ParameterTester(IEventBroker eventBroker) {
177 			super("Parameter", Parameter.TOPIC_ALL, new String[] {
178 					Parameter.NAME, Parameter.VALUE }, eventBroker);
179 		}
180 	}
181 
182 	public class UIElementTester extends EventTester {
UIElementTester(IEventBroker eventBroker)183 		UIElementTester(IEventBroker eventBroker) {
184 			super("UIElement", UIElement.TOPIC_ALL, new String[] {
185 					UIElement.RENDERER, UIElement.TOBERENDERED,
186 					UIElement.PARENT, UIElement.ONTOP, UIElement.VISIBLE,
187 					UIElement.CONTAINERDATA, UIElement.WIDGET }, eventBroker);
188 		}
189 	}
190 
191 	public class UIItemTester extends EventTester {
UIItemTester(IEventBroker eventBroker)192 		UIItemTester(IEventBroker eventBroker) {
193 			super("UIItem", UILabel.TOPIC_ALL, new String[] { UILabel.LABEL,
194 					UILabel.ICONURI, UILabel.TOOLTIP }, eventBroker);
195 		}
196 	}
197 
198 	public class WindowTester extends EventTester {
WindowTester(IEventBroker eventBroker)199 		WindowTester(IEventBroker eventBroker) {
200 			super("Window", Window.TOPIC_ALL, new String[] { Window.MAINMENU,
201 					Window.X, Window.Y, Window.WIDTH, Window.HEIGHT },
202 					eventBroker);
203 		}
204 	}
205 
206 	@Override
createApplicationElement( IEclipseContext appContext)207 	protected MApplicationElement createApplicationElement(
208 			IEclipseContext appContext) throws Exception {
209 		MApplication application = MApplicationFactory.INSTANCE
210 				.createApplication();
211 		application.getChildren().add(MBasicFactory.INSTANCE.createWindow());
212 		return application;
213 	}
214 
215 	@Test
testAllTopics()216 	public void testAllTopics() {
217 		IEventBroker eventBroker = rule.getApplicationContext().get(IEventBroker.class);
218 
219 		// Create a tester for each topic
220 		AppElementTester appTester = new AppElementTester(eventBroker);
221 		CommandTester commandTester = new CommandTester(eventBroker);
222 		ContextTester contextTester = new ContextTester(eventBroker);
223 		ContributionTester contributionTester = new ContributionTester(
224 				eventBroker);
225 		ElementContainerTester elementContainerTester = new ElementContainerTester(
226 				eventBroker);
227 		DirtyableTester dirtyableTester = new DirtyableTester(eventBroker);
228 		InputTester inputTester = new InputTester(eventBroker);
229 		ParameterTester parameterTester = new ParameterTester(eventBroker);
230 		UIElementTester uiElementTester = new UIElementTester(eventBroker);
231 		UIItemTester uiItemTester = new UIItemTester(eventBroker);
232 		WindowTester windowTester = new WindowTester(eventBroker);
233 
234 		// Create an array to check for 'cross talk' (i.e. events being fired
235 		// on incorrect topics
236 		EventTester[] allTesters = { appTester, commandTester, contextTester,
237 				contributionTester, elementContainerTester, inputTester,
238 				parameterTester, uiElementTester, uiItemTester, windowTester };
239 
240 		// Create the test harness and hook up the event publisher
241 		MTestHarness allData = MTestFactory.eINSTANCE.createTestHarness();
242 		final UIEventPublisher ep = new UIEventPublisher(rule.getApplicationContext());
243 		((Notifier) allData).eAdapters().add(ep);
244 		rule.getApplicationContext().set(UIEventPublisher.class, ep);
245 
246 		// AppElement
247 		reset(allTesters);
248 		String newId = "Some New Id";
249 		allData.setElementId(newId);
250 		allData.getTags().add("Testing");
251 		// allData.setTags("new Style");
252 		allData.getPersistedState().put("testing", "Some state");
253 		checkForFailures(allTesters, appTester);
254 
255 		// Test that no-ops don't throw events
256 		appTester.reset();
257 		allData.setElementId(newId);
258 		assertTrue("event thrown on No-Op",
259 				appTester.getAttIds(true).length == 0);
260 
261 		// Command
262 		reset(allTesters);
263 		IEclipseContext newContext = EclipseContextFactory.create();
264 		allData.setContext(newContext);
265 		allData.getVariables().add("foo");
266 		checkForFailures(allTesters, contextTester);
267 
268 		// Context
269 		reset(allTesters);
270 		allData.setContext(EclipseContextFactory.create());
271 		allData.getVariables().add("A var");
272 		checkForFailures(allTesters, contextTester);
273 
274 		// Contribution
275 		reset(allTesters);
276 		allData.setContributionURI("Some URI");
277 		allData.setObject("Some onbject");
278 		checkForFailures(allTesters, contributionTester);
279 
280 		// ElementContainer
281 		reset(allTesters);
282 		MMenu menu = MMenuFactory.INSTANCE.createMenu();
283 		allData.getChildren().add(menu);
284 		allData.setSelectedElement(menu);
285 		checkForFailures(allTesters, elementContainerTester);
286 
287 		// Input
288 		reset(allTesters);
289 		allData.setInputURI("New Input Uri");
290 		checkForFailures(allTesters, inputTester);
291 
292 		// Dirtyable
293 		reset(allTesters);
294 		allData.setDirty(!allData.isDirty());
295 		checkForFailures(allTesters, dirtyableTester);
296 
297 		// Parameter
298 		reset(allTesters);
299 		allData.setName("New Tag");
300 		allData.setValue("New Value");
301 		checkForFailures(allTesters, parameterTester);
302 
303 		// UIElement
304 		reset(allTesters);
305 		MTestHarness newParent = MTestFactory.eINSTANCE.createTestHarness();
306 		allData.setRenderer("New Renderer");
307 		allData.setParent(newParent);
308 		allData.setToBeRendered(!allData.isToBeRendered());
309 		allData.setVisible(!allData.isVisible());
310 		allData.setOnTop(!allData.isOnTop());
311 		allData.setWidget("New Widget");
312 		allData.setContainerData("new Data");
313 		checkForFailures(allTesters, uiElementTester);
314 
315 		// UIItem
316 		reset(allTesters);
317 		allData.setLabel("New Name");
318 		allData.setIconURI("New Icon URI");
319 		allData.setTooltip("New Tooltip");
320 		checkForFailures(allTesters, uiItemTester);
321 
322 		// Window tests
323 		reset(allTesters);
324 		MWindow window = ((MApplication) applicationElement).getChildren().get(
325 				0);
326 		window.setX(1234);
327 		window.setY(1234);
328 		window.setWidth(1234);
329 		window.setHeight(1234);
330 
331 		MMenu newMainMenu = MMenuFactory.INSTANCE.createMenu();
332 		window.setMainMenu(newMainMenu);
333 		checkForFailures(allTesters, windowTester);
334 	}
335 
336 	// Verify bug 374534
337 	@Test
testBrokerCleanup()338 	public void testBrokerCleanup() {
339 		final String testTopic = "test/374534";
340 		IEventBroker appEB = rule.getApplicationContext().get(IEventBroker.class);
341 
342 		IEclipseContext childContext = rule.getApplicationContext().createChild();
343 		IEventBroker childEB = childContext.get(IEventBroker.class);
344 		assertFalse("child context has same IEventBroker", appEB == childEB);
345 
346 		final boolean seen[] = { false };
347 		childEB.subscribe(testTopic, event -> seen[0] = true);
348 
349 		// ensure the EBs are wired up
350 		assertFalse(seen[0]);
351 		appEB.send(testTopic, null);
352 		assertTrue(seen[0]);
353 
354 		seen[0] = false;
355 		childContext.dispose();
356 		appEB.send(testTopic, null);
357 		assertFalse(seen[0]);
358 	}
359 
360 	/**
361 	 * @param allTesters
362 	 * @param tester
363 	 */
checkForFailures(EventTester[] allTesters, EventTester tester)364 	private void checkForFailures(EventTester[] allTesters, EventTester tester) {
365 		ensureAllSet(tester);
366 		ensureNoCrossTalk(allTesters, tester);
367 	}
368 
369 	/**
370 	 * Ensures that no events were picked up from topics other than the one we
371 	 * expect to see changes in.
372 	 *
373 	 * @param tester
374 	 */
ensureNoCrossTalk(EventTester[] allTesters, EventTester skipMe)375 	private void ensureNoCrossTalk(EventTester[] allTesters, EventTester skipMe) {
376 		List<EventTester> badTesters = new ArrayList<>();
377 		for (EventTester t : allTesters) {
378 			if (t.equals(skipMe))
379 				continue;
380 
381 			if (t.getAttIds(true).length > 0)
382 				badTesters.add(t);
383 		}
384 
385 		if (badTesters.size() > 0) {
386 			String msg = "Events were fired in the wrong topic(s): "
387 					+ badTesters;
388 			fail(msg);
389 		}
390 	}
391 
392 	/**
393 	 * @param tester
394 	 */
ensureAllSet(EventTester tester)395 	private void ensureAllSet(EventTester tester) {
396 		String[] unfiredIds = tester.getAttIds(false);
397 		if (unfiredIds.length > 0) {
398 			StringBuilder msg = new StringBuilder("No event fired:").append(unfiredIds);
399 			for (String unfiredId : unfiredIds) {
400 				msg.append(' ').append(unfiredId);
401 			}
402 			fail(msg.toString());
403 		}
404 	}
405 
406 	/**
407 	 * @param allTesters
408 	 */
reset(EventTester[] allTesters)409 	private void reset(EventTester[] allTesters) {
410 		for (EventTester t : allTesters) {
411 			t.reset();
412 		}
413 	}
414 }
415