選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

ComponentLocator.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client;
  5. import java.util.ArrayList;
  6. import java.util.Iterator;
  7. import com.google.gwt.user.client.DOM;
  8. import com.google.gwt.user.client.Element;
  9. import com.google.gwt.user.client.ui.HasWidgets;
  10. import com.google.gwt.user.client.ui.RootPanel;
  11. import com.google.gwt.user.client.ui.Widget;
  12. import com.vaadin.terminal.gwt.client.ui.SubPartAware;
  13. import com.vaadin.terminal.gwt.client.ui.VView;
  14. import com.vaadin.terminal.gwt.client.ui.VWindow;
  15. /**
  16. * ComponentLocator provides methods for generating a String locator for a given
  17. * DOM element and for locating a DOM element using a String locator.
  18. */
  19. public class ComponentLocator {
  20. /**
  21. * Separator used in the String locator between a parent and a child widget.
  22. */
  23. private static final String PARENTCHILD_SEPARATOR = "/";
  24. /**
  25. * Separator used in the String locator between the part identifying the
  26. * containing widget and the part identifying the target element within the
  27. * widget.
  28. */
  29. private static final String SUBPART_SEPARATOR = "#";
  30. /**
  31. * String that identifies the root panel when appearing first in the String
  32. * locator.
  33. */
  34. private static final String ROOT_ID = "Root";
  35. /**
  36. * Reference to ApplicationConnection instance.
  37. */
  38. private ApplicationConnection client;
  39. /**
  40. * Construct a ComponentLocator for the given ApplicationConnection.
  41. *
  42. * @param client
  43. * ApplicationConnection instance for the application.
  44. */
  45. public ComponentLocator(ApplicationConnection client) {
  46. this.client = client;
  47. }
  48. /**
  49. * Generates a String locator which uniquely identifies the target element.
  50. * The {@link #getElementByPath(String)} method can be used for the inverse
  51. * operation, i.e. locating an element based on the return value from this
  52. * method.
  53. * <p>
  54. * Note that getElementByPath(getPathForElement(element)) == element is not
  55. * always true as {@link #getPathForElement(Element)} can return a path to
  56. * another element if the widget determines an action on the other element
  57. * will give the same result as the action on the target element.
  58. * </p>
  59. *
  60. * @since 5.4
  61. * @param targetElement
  62. * The element to generate a path for.
  63. * @return A String locator that identifies the target element or null if a
  64. * String locator could not be created.
  65. */
  66. public String getPathForElement(Element targetElement) {
  67. String pid = null;
  68. Element e = targetElement;
  69. while (true) {
  70. pid = PaintableMap.get(client).getPid(e);
  71. if (pid != null) {
  72. break;
  73. }
  74. e = DOM.getParent(e);
  75. if (e == null) {
  76. break;
  77. }
  78. }
  79. Widget w = null;
  80. if (pid != null) {
  81. // If we found a Paintable then we use that as reference. We should
  82. // find the Paintable for all but very special cases (like
  83. // overlays).
  84. w = (Widget) PaintableMap.get(client).getPaintable(pid);
  85. /*
  86. * Still if the Paintable contains a widget that implements
  87. * SubPartAware, we want to use that as a reference
  88. */
  89. Widget targetParent = findParentWidget(targetElement, w);
  90. while (targetParent != w && targetParent != null) {
  91. if (targetParent instanceof SubPartAware) {
  92. /*
  93. * The targetParent widget is a child of the Paintable and
  94. * the first parent (of the targetElement) that implements
  95. * SubPartAware
  96. */
  97. w = targetParent;
  98. break;
  99. }
  100. targetParent = targetParent.getParent();
  101. }
  102. }
  103. if (w == null) {
  104. // Check if the element is part of a widget that is attached
  105. // directly to the root panel
  106. RootPanel rootPanel = RootPanel.get();
  107. int rootWidgetCount = rootPanel.getWidgetCount();
  108. for (int i = 0; i < rootWidgetCount; i++) {
  109. Widget rootWidget = rootPanel.getWidget(i);
  110. if (rootWidget.getElement().isOrHasChild(targetElement)) {
  111. // The target element is contained by this root widget
  112. w = findParentWidget(targetElement, rootWidget);
  113. break;
  114. }
  115. }
  116. if (w != null) {
  117. // We found a widget but we should still see if we find a
  118. // SubPartAware implementor (we cannot find the Paintable as
  119. // there is no link from VOverlay to its paintable/owner).
  120. Widget subPartAwareWidget = findSubPartAwareParentWidget(w);
  121. if (subPartAwareWidget != null) {
  122. w = subPartAwareWidget;
  123. }
  124. }
  125. }
  126. if (w == null) {
  127. // Containing widget not found
  128. return null;
  129. }
  130. // Determine the path for the target widget
  131. String path = getPathForWidget(w);
  132. if (path == null) {
  133. /*
  134. * No path could be determined for the target widget. Cannot create
  135. * a locator string.
  136. */
  137. return null;
  138. }
  139. if (w.getElement() == targetElement) {
  140. /*
  141. * We are done if the target element is the root of the target
  142. * widget.
  143. */
  144. return path;
  145. } else if (w instanceof SubPartAware) {
  146. /*
  147. * If the widget can provide an identifier for the targetElement we
  148. * let it do that
  149. */
  150. String elementLocator = ((SubPartAware) w)
  151. .getSubPartName(targetElement);
  152. if (elementLocator != null) {
  153. return path + SUBPART_SEPARATOR + elementLocator;
  154. }
  155. }
  156. /*
  157. * If everything else fails we use the DOM path to identify the target
  158. * element
  159. */
  160. return path + getDOMPathForElement(targetElement, w.getElement());
  161. }
  162. /**
  163. * Finds the first widget in the hierarchy (moving upwards) that implements
  164. * SubPartAware. Returns the SubPartAware implementor or null if none is
  165. * found.
  166. *
  167. * @param w
  168. * The widget to start from. This is returned if it implements
  169. * SubPartAware.
  170. * @return The first widget (upwards in hierarchy) that implements
  171. * SubPartAware or null
  172. */
  173. private Widget findSubPartAwareParentWidget(Widget w) {
  174. while (w != null) {
  175. if (w instanceof SubPartAware) {
  176. return w;
  177. }
  178. w = w.getParent();
  179. }
  180. return null;
  181. }
  182. /**
  183. * Returns the first widget found when going from {@code targetElement}
  184. * upwards in the DOM hierarchy, assuming that {@code ancestorWidget} is a
  185. * parent of {@code targetElement}.
  186. *
  187. * @param targetElement
  188. * @param ancestorWidget
  189. * @return The widget whose root element is a parent of
  190. * {@code targetElement}.
  191. */
  192. private Widget findParentWidget(Element targetElement, Widget ancestorWidget) {
  193. /*
  194. * As we cannot resolve Widgets from the element we start from the
  195. * widget and move downwards to the correct child widget, as long as we
  196. * find one.
  197. */
  198. if (ancestorWidget instanceof HasWidgets) {
  199. for (Widget w : ((HasWidgets) ancestorWidget)) {
  200. if (w.getElement().isOrHasChild(targetElement)) {
  201. return findParentWidget(targetElement, w);
  202. }
  203. }
  204. }
  205. // No children found, this is it
  206. return ancestorWidget;
  207. }
  208. /**
  209. * Locates an element based on a DOM path and a base element.
  210. *
  211. * @param baseElement
  212. * The base element which the path is relative to
  213. * @param path
  214. * String locator (consisting of domChild[x] parts) that
  215. * identifies the element
  216. * @return The element identified by path, relative to baseElement or null
  217. * if the element could not be found.
  218. */
  219. private Element getElementByDOMPath(Element baseElement, String path) {
  220. String parts[] = path.split(PARENTCHILD_SEPARATOR);
  221. Element element = baseElement;
  222. for (String part : parts) {
  223. if (part.startsWith("domChild[")) {
  224. String childIndexString = part.substring("domChild[".length(),
  225. part.length() - 1);
  226. try {
  227. int childIndex = Integer.parseInt(childIndexString);
  228. element = DOM.getChild(element, childIndex);
  229. } catch (Exception e) {
  230. return null;
  231. }
  232. }
  233. }
  234. return element;
  235. }
  236. /**
  237. * Generates a String locator using domChild[x] parts for the element
  238. * relative to the baseElement.
  239. *
  240. * @param element
  241. * The target element
  242. * @param baseElement
  243. * The starting point for the locator. The generated path is
  244. * relative to this element.
  245. * @return A String locator that can be used to locate the target element
  246. * using {@link #getElementByDOMPath(Element, String)} or null if
  247. * the locator String cannot be created.
  248. */
  249. private String getDOMPathForElement(Element element, Element baseElement) {
  250. Element e = element;
  251. String path = "";
  252. while (true) {
  253. Element parent = DOM.getParent(e);
  254. if (parent == null) {
  255. return null;
  256. }
  257. int childIndex = -1;
  258. int childCount = DOM.getChildCount(parent);
  259. for (int i = 0; i < childCount; i++) {
  260. if (e == DOM.getChild(parent, i)) {
  261. childIndex = i;
  262. break;
  263. }
  264. }
  265. if (childIndex == -1) {
  266. return null;
  267. }
  268. path = PARENTCHILD_SEPARATOR + "domChild[" + childIndex + "]"
  269. + path;
  270. if (parent == baseElement) {
  271. break;
  272. }
  273. e = parent;
  274. }
  275. return path;
  276. }
  277. /**
  278. * Locates an element using a String locator (path) which identifies a DOM
  279. * element. The {@link #getPathForElement(Element)} method can be used for
  280. * the inverse operation, i.e. generating a string expression for a DOM
  281. * element.
  282. *
  283. * @since 5.4
  284. * @param path
  285. * The String locater which identifies the target element.
  286. * @return The DOM element identified by {@code path} or null if the element
  287. * could not be located.
  288. */
  289. public Element getElementByPath(String path) {
  290. /*
  291. * Path is of type "targetWidgetPath#componentPart" or
  292. * "targetWidgetPath".
  293. */
  294. String parts[] = path.split(SUBPART_SEPARATOR, 2);
  295. String widgetPath = parts[0];
  296. Widget w = getWidgetFromPath(widgetPath);
  297. if (w == null || !Util.isAttachedAndDisplayed(w)) {
  298. return null;
  299. }
  300. if (parts.length == 1) {
  301. int pos = widgetPath.indexOf("domChild");
  302. if (pos == -1) {
  303. return w.getElement();
  304. }
  305. // Contains dom reference to a sub element of the widget
  306. String subPath = widgetPath.substring(pos);
  307. return getElementByDOMPath(w.getElement(), subPath);
  308. } else if (parts.length == 2) {
  309. if (w instanceof SubPartAware) {
  310. return ((SubPartAware) w).getSubPartElement(parts[1]);
  311. }
  312. }
  313. return null;
  314. }
  315. /**
  316. * Creates a locator String for the given widget. The path can be used to
  317. * locate the widget using {@link #getWidgetFromPath(String)}.
  318. *
  319. * Returns null if no path can be determined for the widget or if the widget
  320. * is null.
  321. *
  322. * @param w
  323. * The target widget
  324. * @return A String locator for the widget
  325. */
  326. private String getPathForWidget(Widget w) {
  327. if (w == null) {
  328. return null;
  329. }
  330. String pid = PaintableMap.get(client).getPid(w.getElement());
  331. if (isStaticPid(pid)) {
  332. return pid;
  333. }
  334. if (w instanceof VView) {
  335. return "";
  336. } else if (w instanceof VWindow) {
  337. VWindow win = (VWindow) w;
  338. ArrayList<VWindow> subWindowList = client.getView()
  339. .getSubWindowList();
  340. int indexOfSubWindow = subWindowList.indexOf(win);
  341. return PARENTCHILD_SEPARATOR + "VWindow[" + indexOfSubWindow + "]";
  342. } else if (w instanceof RootPanel) {
  343. return ROOT_ID;
  344. }
  345. Widget parent = w.getParent();
  346. String basePath = getPathForWidget(parent);
  347. if (basePath == null) {
  348. return null;
  349. }
  350. String simpleName = Util.getSimpleName(w);
  351. /*
  352. * Check if the parent implements Iterable. At least VPopupView does not
  353. * implement HasWdgets so we cannot check for that.
  354. */
  355. if (!(parent instanceof Iterable<?>)) {
  356. // Parent does not implement Iterable so we cannot find out which
  357. // child this is
  358. return null;
  359. }
  360. Iterator<?> i = ((Iterable<?>) parent).iterator();
  361. int pos = 0;
  362. while (i.hasNext()) {
  363. Object child = i.next();
  364. if (child == w) {
  365. return basePath + PARENTCHILD_SEPARATOR + simpleName + "["
  366. + pos + "]";
  367. }
  368. String simpleName2 = Util.getSimpleName(child);
  369. if (simpleName.equals(simpleName2)) {
  370. pos++;
  371. }
  372. }
  373. return null;
  374. }
  375. /**
  376. * Locates the widget based on a String locator.
  377. *
  378. * @param path
  379. * The String locator that identifies the widget.
  380. * @return The Widget identified by the String locator or null if the widget
  381. * could not be identified.
  382. */
  383. private Widget getWidgetFromPath(String path) {
  384. Widget w = null;
  385. String parts[] = path.split(PARENTCHILD_SEPARATOR);
  386. for (int i = 0; i < parts.length; i++) {
  387. String part = parts[i];
  388. if (part.equals(ROOT_ID)) {
  389. w = RootPanel.get();
  390. } else if (part.equals("")) {
  391. w = client.getView();
  392. } else if (w == null) {
  393. // Must be static pid (PID_S*)
  394. w = (Widget) PaintableMap.get(client).getPaintable(part);
  395. } else if (part.startsWith("domChild[")) {
  396. // The target widget has been found and the rest identifies the
  397. // element
  398. break;
  399. } else if (w instanceof Iterable) {
  400. // W identifies a widget that contains other widgets, as it
  401. // should. Try to locate the child
  402. Iterable<?> parent = (Iterable<?>) w;
  403. // Part is of type "VVerticalLayout[0]", split this into
  404. // VVerticalLayout and 0
  405. String[] split = part.split("\\[", 2);
  406. String widgetClassName = split[0];
  407. String indexString = split[1];
  408. int widgetPosition = Integer.parseInt(indexString.substring(0,
  409. indexString.length() - 1));
  410. // Locate the child
  411. Iterator<? extends Widget> iterator;
  412. /*
  413. * VWindow and VContextMenu workarounds for backwards
  414. * compatibility
  415. */
  416. if (widgetClassName.equals("VWindow")) {
  417. iterator = client.getView().getSubWindowList().iterator();
  418. } else if (widgetClassName.equals("VContextMenu")) {
  419. return client.getContextMenu();
  420. } else {
  421. iterator = (Iterator<? extends Widget>) parent.iterator();
  422. }
  423. boolean ok = false;
  424. // Find the widgetPosition:th child of type "widgetClassName"
  425. while (iterator.hasNext()) {
  426. Widget child = iterator.next();
  427. String simpleName2 = Util.getSimpleName(child);
  428. if (widgetClassName.equals(simpleName2)) {
  429. if (widgetPosition == 0) {
  430. w = child;
  431. ok = true;
  432. break;
  433. }
  434. widgetPosition--;
  435. }
  436. }
  437. if (!ok) {
  438. // Did not find the child
  439. return null;
  440. }
  441. } else {
  442. // W identifies something that is not a "HasWidgets". This
  443. // should not happen as all widget containers should implement
  444. // HasWidgets.
  445. return null;
  446. }
  447. }
  448. return w;
  449. }
  450. /**
  451. * Checks if the given pid is a static pid.
  452. *
  453. * @param pid
  454. * The pid to check
  455. * @return true if the pid is a static pid, false otherwise
  456. */
  457. private boolean isStaticPid(String pid) {
  458. if (pid == null) {
  459. return false;
  460. }
  461. return pid.startsWith("PID_S");
  462. }
  463. }