VisualizationTopologyView.java 20.1 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*
 * Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
 *
 * This file is part of PeerfactSim.KOM.
 * 
 * PeerfactSim.KOM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 * 
 * PeerfactSim.KOM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with PeerfactSim.KOM.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package de.tud.kom.p2psim.impl.topology.views;

import java.awt.AWTEvent;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
39
import java.util.Collection;
40
41
42
43
44
45
46
47
48
49
50
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
51
import javax.swing.JMenu;
52
53
54
55
56
57
58
59
60
61
62
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.Timer;

import com.google.common.collect.Maps;

import de.tud.kom.p2psim.api.linklayer.mac.Link;
import de.tud.kom.p2psim.api.linklayer.mac.MacAddress;
import de.tud.kom.p2psim.api.linklayer.mac.MacLayer;
import de.tud.kom.p2psim.api.linklayer.mac.PhyType;
63
import de.tud.kom.p2psim.api.topology.Topology;
64
65
66
67
68
69
70
71
72
import de.tud.kom.p2psim.api.topology.TopologyComponent;
import de.tud.kom.p2psim.api.topology.obstacles.ObstacleModel;
import de.tud.kom.p2psim.api.topology.views.TopologyView;
import de.tud.kom.p2psim.api.topology.waypoints.WaypointModel;
import de.tud.kom.p2psim.impl.topology.PositionVector;
import de.tud.kom.p2psim.impl.topology.views.visualization.ComponentVisManager;
import de.tud.kom.p2psim.impl.topology.views.visualization.ComponentVisManager.VisInfo;
import de.tud.kom.p2psim.impl.topology.views.visualization.ui.PlottingView;
import de.tud.kom.p2psim.impl.topology.views.visualization.ui.SimControlPanel;
73
import de.tud.kom.p2psim.impl.topology.views.visualization.ui.VisualizationComponent;
74
75
76
77
78
79
import de.tud.kom.p2psim.impl.topology.views.visualization.world.NodeVisInteractionListener;
import de.tud.kom.p2psim.impl.topology.views.visualization.world.ObstacleComponentVis;
import de.tud.kom.p2psim.impl.topology.views.visualization.world.StrongWaypointComponentVis;
import de.tud.kom.p2psim.impl.topology.views.visualization.world.TopologyComponentVis;
import de.tud.kom.p2psim.impl.topology.views.visualization.world.WeakWaypointComponentVis;
import de.tud.kom.p2psim.impl.util.NotSupportedException;
80
import de.tudarmstadt.maki.simonstrator.api.Binder;
81
import de.tudarmstadt.maki.simonstrator.api.Host;
82
import de.tudarmstadt.maki.simonstrator.api.Time;
83
import de.tudarmstadt.maki.simonstrator.api.common.graph.INodeID;
84
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location;
85
86
87
88
89
90
91
92
93
94
95
96
97
98

/**
 * A very basic visualization of a Topology (ie. positioning and movement), just
 * to ease further debugging and development of movement and obstacle-related
 * classes.
 * 
 * It is added to the Topology as a TopologyView with no PHY-Type (and therefore
 * it is never used inside a MAC). Lateron an API will be provided that allows
 * other components (analyzers) to display information using this basic
 * Visualization.
 * 
 * @author Bjoern Richerzhagen
 * @version 1.0, 19.03.2012
 */
99
100
public class VisualizationTopologyView extends JFrame
		implements TopologyView, Runnable {
101
102
103
104
105

	protected final int WORLD_X;

	protected final int WORLD_Y;

106
	protected HashMap<INodeID, TopologyComponentVis> compVisPerHost = new HashMap<INodeID, TopologyComponentVis>();
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130

	protected final WorldPanel worldPanel;

	protected SimControlPanel simControls;

	public static final Stroke STROKE_BASIC = new BasicStroke(0.8f);

	public static final Stroke STROKE_THICK = new BasicStroke(2f);

	public static final Font FONT_TINY = new Font("SansSerif", Font.PLAIN, 7);

	public static final Font FONT_MEDIUM = new Font("SansSerif", Font.PLAIN, 9);

	private ComponentVisManager visManager;

	private WeakWaypointComponentVis weakWaypointComponentVis;

	private ObstacleComponentVis obstacleComponentVis;

	private StrongWaypointComponentVis strongWaypointComponentVis;

	/**
	 * 
	 */
131
	public VisualizationTopologyView() {
132
133
134
135
		WORLD_X = (int) Binder.getComponentOrNull(Topology.class)
				.getWorldDimensions().getX();
		WORLD_Y = (int) Binder.getComponentOrNull(Topology.class)
				.getWorldDimensions().getY();
136
137
138
139
140
		worldPanel = new WorldPanel();
		visManager = new ComponentVisManager(worldPanel);

		VisualizationInjector.setVariables(this, visManager, worldPanel,
				WORLD_X, WORLD_Y);
141
142
143
		worldPanel.setPreferredSize(
				new Dimension(VisualizationInjector.getWorldX(),
						VisualizationInjector.getWorldY()));
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
		this.setPreferredSize(new Dimension(800, 600));
		Thread t = new Thread(this);
		t.start();
	}

	@Override
	public void run() {

		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setLayout(new BorderLayout());
		this.setTitle("Topology Visualization");

		/*
		 * World-view
		 */
		JScrollPane mapScrollPanel = new JScrollPane(worldPanel,
160
161
				ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
				ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
162
163
164
165
166
167
		mapScrollPanel.getVerticalScrollBar().setUnitIncrement(50);
		mapScrollPanel.getHorizontalScrollBar().setUnitIncrement(50);
		mapScrollPanel.setBackground(Color.DARK_GRAY);
		getContentPane().add(BorderLayout.CENTER, mapScrollPanel);

		buildSimControls();
168
169
		
		
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188

		getRootPane().setDoubleBuffered(true);

		/*
		 * Finally, lights!
		 */
		this.pack();
		this.setVisible(true);

		// for the movement of the line
		final Timer timer = new Timer(250, null);
		timer.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				worldPanel.invalidate();
				timer.setDelay(250);
			}
		});
		timer.start();
	}
189
190
191
	
	private void buildPlottingArea() {
		
192
193
194
	}

	private void buildSimControls() {
195
		simControls = new SimControlPanel(visManager);
196
		getContentPane().add(BorderLayout.NORTH, simControls);
197
198
		getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
				.put(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0), "pause");
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
		Action pauseAction = new AbstractAction() {
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent e) {
				getSimControls().togglePlayPause();
			}
		};
		getRootPane().getActionMap().put("pause", pauseAction);
	}

	public SimControlPanel getSimControls() {
		return simControls;
	}

	@Override
	public void addedComponent(TopologyComponent comp) {
215
		if (!compVisPerHost.containsKey(comp.getHost().getId())) {
216
			TopologyComponentVis tVis = new TopologyComponentVis(this, comp);
217
			compVisPerHost.put(comp.getHost().getId(), tVis);
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
			// this.visManager.addComponent("Topology", 0, new
			// TopologyComponentVis(this, comp));
			worldPanel.add(new TopologyComponentVis(this, comp));
		}
	}

	@Override
	public void changedWaypointModel(WaypointModel model) {
		if (weakWaypointComponentVis != null) {
			this.visManager.removeComponent(weakWaypointComponentVis);
			this.visManager.removeComponent(strongWaypointComponentVis);
			// worldPanel.remove(waypointComponentVis);
		}

		if (model == null)
			return;
234
235
		weakWaypointComponentVis = new WeakWaypointComponentVis(model);
		strongWaypointComponentVis = new StrongWaypointComponentVis(model);
236
		this.visManager.addComponent("Streets", 0, weakWaypointComponentVis);
237
238
		this.visManager.addComponent("Waypoints", 0,
				strongWaypointComponentVis);
239
240
241
242
243
244
245
246
247
248
249
		// worldPanel.add(waypointComponentVis);
	}

	@Override
	public void changedObstacleModel(ObstacleModel model) {
		if (obstacleComponentVis != null)
			this.visManager.removeComponent(obstacleComponentVis);
		// worldPanel.remove(obstacleComponentVis);

		if (model == null)
			return;
250
		obstacleComponentVis = new ObstacleComponentVis(model);
251
252
253
254
255
256
257
258
259
260
261
		this.visManager.addComponent("Buildings", 0, obstacleComponentVis);
		// worldPanel.add(obstacleComponentVis);
	}

	/*
	 * @Override public void addedObstacle(Obstacle obstacle) { if (obstacle
	 * instanceof PolygonObstacle) { ObstacleComponentVis obsVis = new
	 * ObstacleComponentVis( ((PolygonObstacle) obstacle).getAwtPolygon(),
	 * obstacle); worldPanel.add(obsVis); } }
	 */

262
	long lastMove = 0;
263
264

	@Override
265
	public void onLocationChanged(Host host, Location location) {
266
267
268
269
		if (lastMove != Time.getCurrentTime()) {
			worldPanel.invalidate();
			lastMove = Time.getCurrentTime();
		}
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
	}

	@Override
	public Link getBestNextLink(MacAddress source, MacAddress lastHop,
			MacAddress currentHop, MacAddress destination) {
		throw new UnsupportedOperationException();
	}

	@Override
	public Link getLinkBetween(MacAddress source, MacAddress destination) {
		throw new UnsupportedOperationException();
	}

	@Override
	public MacLayer getMac(MacAddress address) {
		throw new UnsupportedOperationException();
	}

288
289
290
291
	@Override
	public Collection<MacLayer> getAllMacs() {
		throw new UnsupportedOperationException();
	}
292

293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
	@Override
	public List<MacAddress> getNeighbors(MacAddress address) {
		throw new UnsupportedOperationException();
	}

	@Override
	public PhyType getPhyType() {
		return null;
	}

	/**
	 * The world
	 * 
	 * @author Bjoern Richerzhagen
	 * @version 1.0, 19.03.2012
	 */
	protected class WorldPanel extends JLayeredPane {

		private static final long serialVersionUID = -3023020559483652110L;

		public WorldPanel() {
			this.setLayout(null);
		}

		@Override
		public void invalidate() {
			super.invalidate();
			repaint();
		}

		@Override
		protected void paintComponent(Graphics g) {
			Graphics2D g2 = (Graphics2D) g;
			g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);
			paintPlayingFieldBorder(g2);
329
			super.paintComponent(g);
330
331
332
333
334
335
		}

		private void paintPlayingFieldBorder(Graphics2D g2) {
			g2.setColor(Color.DARK_GRAY);
			g2.fillRect(0, 0, getWidth(), getHeight());
			g2.setColor(Color.WHITE);
336
337
			g2.fillRect(0, 0, VisualizationInjector.getWorldX(),
					VisualizationInjector.getWorldY());
338
339
340
			g2.setPaint(Color.MAGENTA);
			g2.setStroke(new BasicStroke(2));
			g2.drawLine(0, 0, VisualizationInjector.scaleValue(100), 0);
341
342
343
344
		}

	}

345
346
	public void notifyInteractionListenersOnClick(long hostid,
			boolean isActive) {
347
348
349
350
351
352
353
		VisualizationInjector.notifyInteractionListenersOnClick(hostid,
				isActive);
	}

	@Deprecated
	public void setPhy(String phy) {
		// legacy
354
355
		System.err.println(
				"The VisualizationTopologyView does no longer require PHY to be configured!");
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
	}

	/**
	 * This component allows the injection of JComponents into the visualization
	 * from anywhere in the simulator. It is marked deprecated due to its hacky
	 * nature. Integrate the visualization into the topology view instead.
	 * 
	 * TODO: Add checks for every component or plotting view if a graphical
	 * environment is present. This will allow calls to the
	 * VisualizationInjector on servers without failing. e.g. supply dummy a
	 * PlottingView or add additional checks to the plotting view for such
	 * cases.
	 * 
	 * @author Fabio Zöllner
	 * @version 1.0, 03.04.2012
	 */
	public static class VisualizationInjector {
		protected static WorldPanel worldPanel;

		public static ComponentVisManager visManager;

		protected static ConcurrentLinkedQueue<VisInfo> components = new ConcurrentLinkedQueue<VisInfo>();

		protected static Map<String, VisInfo> nameToVisInfoMap = Maps
				.newConcurrentMap();

		protected static ConcurrentLinkedQueue<DisplayString> displayStrings = new ConcurrentLinkedQueue<DisplayString>();

		protected static List<MouseClickListener> mouseListeners = new CopyOnWriteArrayList<MouseClickListener>();

		protected static VisualizationTopologyView view;

		protected static ConcurrentLinkedQueue<NodeVisInteractionListener> interactionListeners = new ConcurrentLinkedQueue<NodeVisInteractionListener>();

		protected static long eventMask = AWTEvent.MOUSE_MOTION_EVENT_MASK
				+ AWTEvent.MOUSE_EVENT_MASK;

393
394
395
		private static int WORLD_X = 0;

		private static int WORLD_Y = 0;
396

397
		private static double SCALE = 1;
398
399
400
401
402
403
404
405
406
407

		protected static Map<String, PlottingView> plottingViews = Maps
				.newHashMap();

		public static PlottingView createPlottingView(String name) {
			PlottingView view = new PlottingView(name);
			plottingViews.put(name, view);
			return view;
		}

408
409
		public static PlottingView createPlottingView(String name, int x,
				int y) {
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
			PlottingView view = new PlottingView(name);
			plottingViews.put(name, view);

			view.setBounds(x, y, view.getWidth(), view.getHeight());
			view.setVisible(true);

			return view;
		}

		public static PlottingView getPlottingView(String name) {
			return plottingViews.get(name);
		}

		protected static void notifyInteractionListenersOnClick(long hostid,
				boolean isActive) {
			for (NodeVisInteractionListener listener : VisualizationInjector.interactionListeners) {
				listener.onHostClick(hostid, isActive);
			}
		}

		protected static void setVariables(VisualizationTopologyView view,
				ComponentVisManager visManager, WorldPanel worldPanel,
				int WORLD_X, int WORLD_Y) {
			VisualizationInjector.view = view;
			VisualizationInjector.worldPanel = worldPanel;
			VisualizationInjector.visManager = visManager;
			VisualizationInjector.WORLD_X = WORLD_X;
			VisualizationInjector.WORLD_Y = WORLD_Y;

			for (VisInfo visInfo : components) {
				visManager.addComponent(visInfo);
				// worldPanel.add(comp);
			}

			worldPanel.add(new DrawDisplayStrings(WORLD_X, WORLD_Y));

			setupAWTEventListener();
		}

		public static VisualizationTopologyView getTopologyView() {
			return view;
		}

453
454
455
456
457
458
459
460
461
462
463
464
465
		/**
		 * Returns the {@link TopologyComponentVis} corresponding to the
		 * provided host ID
		 * 
		 * @param nodeID
		 *            id of the host.
		 * @return
		 */
		public static TopologyComponentVis getTopologyComponentVis(
				INodeID nodeID) {
			return view.compVisPerHost.get(nodeID);
		}

466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
		private static void setupAWTEventListener() {
			Toolkit tk = Toolkit.getDefaultToolkit();

			tk.addAWTEventListener(new AWTEventListener() {
				@Override
				public void eventDispatched(AWTEvent e) {
					if (e instanceof MouseEvent) {
						MouseEvent me = (MouseEvent) e;
						if (me.getID() == MouseEvent.MOUSE_CLICKED) {
							// Another dirty hack until we have another graphic
							// drawing system
							if (((JFrame) worldPanel.getTopLevelAncestor())
									.isActive()) {
								int x = me.getXOnScreen();
								int y = me.getYOnScreen();

								if (x > (int) worldPanel.getLocationOnScreen()
										.getX()
										&& y > (int) worldPanel
												.getLocationOnScreen().getY()
										&& x < (int) worldPanel
												.getLocationOnScreen().getX()
												+ worldPanel.getWidth()
										&& y < (int) worldPanel
												.getLocationOnScreen().getX()
												+ worldPanel.getHeight()) {

									x -= (int) worldPanel.getLocationOnScreen()
											.getX();
									y -= (int) worldPanel.getLocationOnScreen()
											.getY();

									for (MouseClickListener l : mouseListeners) {
										l.mouseClicked(x, y);
									}
								}

							}
						}
					}
				}
			}, eventMask);
		}

		public static interface MouseClickListener {
			public void mouseClicked(int x, int y);
		}

		public static JComponent getWorldPanel() {
			return worldPanel;
		}

		public static void addMouseListener(MouseClickListener listener) {
			mouseListeners.add(listener);
		}

		public static void removeMouseListener(MouseClickListener listener) {
			mouseListeners.remove(listener);
		}

		/**
		 * Listener is informed, whenever the user clicks on a node
		 * 
		 * @param listener
		 */
		public static void addInteractionListener(
				NodeVisInteractionListener listener) {
			interactionListeners.add(listener);
		}

		public static void removeInteractionListener(
				NodeVisInteractionListener listener) {
			interactionListeners.remove(listener);
		}

		protected static class DrawDisplayStrings extends JComponent {
			public DrawDisplayStrings(int WORLD_X, int WORLD_Y) {
				super();

				setBounds(0, 0, WORLD_X, WORLD_Y);
				setOpaque(false);
				setVisible(true);
			}

			@Override
			protected void paintComponent(Graphics g) {
				super.paintComponent(g);
				Graphics2D g2 = (Graphics2D) g;

				g2.setColor(Color.BLACK);
				g2.setFont(new Font("SansSerif", Font.PLAIN, 9));

				int height = 10;
				for (DisplayString str : displayStrings) {
					g2.drawString(str.getDisplayString(), 0, height);
					height += 10;
				}
			}
		}

566
567
568
569
570
571
		/**
		 * Returns a SCALED x-length of the world size, only to be used by
		 * visualizations.
		 * 
		 * @return
		 */
572
		public static int getWorldX() {
573
574
			// return WORLD_X;
			return (int) (WORLD_X * getScale());
575
576
		}

577
578
579
580
581
582
		/**
		 * Returns a SCALED y-length of the world size, only to be used by
		 * visualizations.
		 * 
		 * @return
		 */
583
		public static int getWorldY() {
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
			// return WORLD_Y;
			return (int) (WORLD_Y * getScale());
		}

		/**
		 * Still needed by OSM-loader (Fabio), however: should not be used
		 * anymore. The coordinates should be final.
		 * 
		 * @param x
		 * @param y
		 */
		@Deprecated
		public static void setWorldCoordinates(int x, int y) {
			WORLD_X = x;
			WORLD_Y = y;
		}

		public static double getScale() {
			return SCALE;
		}

		public static void setScale(double scale) {
			SCALE = scale;
		}

		public static int scaleValue(double value) {
			return (int) (value * getScale());
611
612
		}

613
614
615
616
617
618
619
620
621
622
623
624
625
626
		/**
		 * Recommended way to add a custom visualization layer.
		 * 
		 * @param comp
		 */
		public static void injectComponent(VisualizationComponent comp) {
			visManager.addComponent(comp);
		}

		/**
		 * @deprecated use injectComponent with a {@link VisualizationComponent}
		 *             instead
		 */
		@Deprecated
627
628
629
630
631
		public static void injectComponent(String name, int priority,
				JComponent component) {
			injectComponent(name, priority, component, true);
		}

632
633
634
635
636
		/**
		 * @deprecated use injectComponent with a {@link VisualizationComponent}
		 *             instead
		 */
		@Deprecated
637
638
		public static void injectComponent(String name, int priority,
				JComponent component, boolean active) {
639
640
641
			injectComponent(name, priority, component, active, true);
		}

642
643
644
645
646
		/**
		 * @deprecated use injectComponent with a {@link VisualizationComponent}
		 *             instead
		 */
		@Deprecated
647
648
		public static void injectComponent(String name, int priority,
				JComponent component, boolean active, boolean showInList) {
649
650
			VisInfo visInfo = new VisInfo(name, priority, component);
			visInfo.setActiveByDefault(active);
651
			visInfo.setShowInList(showInList);
652
653
654
655
656
657
658
659
660

			components.add(visInfo);
			nameToVisInfoMap.put(name, visInfo);

			if (visManager != null) {
				visManager.addComponent(visInfo);
			}
		}

661
662
663
664
665
		/**
		 * @deprecated use removeInjectedComponent with a
		 *             {@link VisualizationComponent} instead
		 */
		@Deprecated
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
		public static void removeInjectedComponent(String name) {
			if (nameToVisInfoMap.containsKey(name)) {
				VisInfo visInfo = nameToVisInfoMap.get(name);
				nameToVisInfoMap.remove(name);
				components.remove(visInfo);
				visManager.removeComponent(name);
			}
		}

		public static void invalidate() {
			worldPanel.invalidate();
		}

		public static void addDisplayString(DisplayString str) {
			displayStrings.add(str);
		}

		public static void removeDisplayString(DisplayString str) {
			displayStrings.remove(str);
		}

		public static interface DisplayString {
			public String getDisplayString();
		}
	}

	@Override
693
	public Location getPosition(MacAddress address) {
694
695
696
697
698
699
700
701
		throw new NotSupportedException();
	}

	@Override
	public double getDistance(MacAddress addressA, MacAddress addressB) {
		throw new NotSupportedException();
	}

702
703
704
705
706
	@Override
	public boolean hasRealLinkLayer() {
		throw new NotSupportedException();
	}

707
}