您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

ComponentLocator.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  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 = 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. w = (Widget) client.getPaintable(pid);
  82. }
  83. if (w == null) {
  84. // Check if the element is part of a widget that is attached
  85. // directly to the root panel
  86. RootPanel rootPanel = RootPanel.get();
  87. int rootWidgetCount = rootPanel.getWidgetCount();
  88. for (int i = 0; i < rootWidgetCount; i++) {
  89. Widget rootWidget = rootPanel.getWidget(i);
  90. if (rootWidget.getElement().isOrHasChild(targetElement)) {
  91. // The target element is contained by this root widget
  92. w = findParentWidget(targetElement, rootWidget);
  93. break;
  94. }
  95. }
  96. }
  97. if (w == null) {
  98. // Containing widget not found
  99. return null;
  100. }
  101. // Determine the path for the target widget
  102. String path = getPathForWidget(w);
  103. if (path == null) {
  104. /*
  105. * No path could be determined for the target widget. Cannot create
  106. * a locator string.
  107. */
  108. return null;
  109. }
  110. if (w.getElement() == targetElement) {
  111. /*
  112. * We are done if the target element is the root of the target
  113. * widget.
  114. */
  115. return path;
  116. } else if (w instanceof SubPartAware) {
  117. /*
  118. * If the widget can provide an identifier for the targetElement we
  119. * let it do that
  120. */
  121. String elementLocator = ((SubPartAware) w)
  122. .getSubPartName(targetElement);
  123. return path + SUBPART_SEPARATOR + elementLocator;
  124. } else {
  125. /*
  126. * If everything else fails we use the DOM path to identify the
  127. * target element
  128. */
  129. return path + getDOMPathForElement(targetElement, w.getElement());
  130. }
  131. }
  132. /**
  133. * Returns the first widget found when going from {@code targetElement}
  134. * upwards in the DOM hierarchy, assuming that {@code ancestorWidget} is a
  135. * parent of {@code targetElement}.
  136. *
  137. * @param targetElement
  138. * @param ancestorWidget
  139. * @return The widget whose root element is a parent of
  140. * {@code targetElement}.
  141. */
  142. private Widget findParentWidget(Element targetElement, Widget ancestorWidget) {
  143. /*
  144. * As we cannot resolve Widgets from the element we start from the
  145. * widget and move downwards to the correct child widget, as long as we
  146. * find one.
  147. */
  148. if (ancestorWidget instanceof HasWidgets) {
  149. for (Widget w : ((HasWidgets) ancestorWidget)) {
  150. if (w.getElement().isOrHasChild(targetElement)) {
  151. return findParentWidget(targetElement, w);
  152. }
  153. }
  154. }
  155. // No children found, this is it
  156. return ancestorWidget;
  157. }
  158. /**
  159. * Locates an element based on a DOM path and a base element.
  160. *
  161. * @param baseElement
  162. * The base element which the path is relative to
  163. * @param path
  164. * String locator (consisting of domChild[x] parts) that
  165. * identifies the element
  166. * @return The element identified by path, relative to baseElement or null
  167. * if the element could not be found.
  168. */
  169. private Element getElementByDOMPath(Element baseElement, String path) {
  170. String parts[] = path.split(PARENTCHILD_SEPARATOR);
  171. Element element = baseElement;
  172. for (String part : parts) {
  173. if (part.startsWith("domChild[")) {
  174. String childIndexString = part.substring("domChild[".length(),
  175. part.length() - 1);
  176. try {
  177. int childIndex = Integer.parseInt(childIndexString);
  178. element = DOM.getChild(element, childIndex);
  179. } catch (Exception e) {
  180. return null;
  181. }
  182. }
  183. }
  184. return element;
  185. }
  186. /**
  187. * Generates a String locator using domChild[x] parts for the element
  188. * relative to the baseElement.
  189. *
  190. * @param element
  191. * The target element
  192. * @param baseElement
  193. * The starting point for the locator. The generated path is
  194. * relative to this element.
  195. * @return A String locator that can be used to locate the target element
  196. * using {@link #getElementByDOMPath(Element, String)} or null if
  197. * the locator String cannot be created.
  198. */
  199. private String getDOMPathForElement(Element element, Element baseElement) {
  200. Element e = element;
  201. String path = "";
  202. while (true) {
  203. Element parent = DOM.getParent(e);
  204. if (parent == null) {
  205. return null;
  206. }
  207. int childIndex = -1;
  208. int childCount = DOM.getChildCount(parent);
  209. for (int i = 0; i < childCount; i++) {
  210. if (e == DOM.getChild(parent, i)) {
  211. childIndex = i;
  212. break;
  213. }
  214. }
  215. if (childIndex == -1) {
  216. return null;
  217. }
  218. path = PARENTCHILD_SEPARATOR + "domChild[" + childIndex + "]"
  219. + path;
  220. if (parent == baseElement) {
  221. break;
  222. }
  223. e = parent;
  224. }
  225. return path;
  226. }
  227. /**
  228. * Locates an element using a String locator (path) which identifies a DOM
  229. * element. The {@link #getPathForElement(Element)} method can be used for
  230. * the inverse operation, i.e. generating a string expression for a DOM
  231. * element.
  232. *
  233. * @since 5.4
  234. * @param path
  235. * The String locater which identifies the target element.
  236. * @return The DOM element identified by {@code path} or null if the element
  237. * could not be located.
  238. */
  239. public Element getElementByPath(String path) {
  240. /*
  241. * Path is of type "targetWidgetPath#componentPart" or
  242. * "targetWidgetPath".
  243. */
  244. String parts[] = path.split(SUBPART_SEPARATOR, 2);
  245. String widgetPath = parts[0];
  246. Widget w = getWidgetFromPath(widgetPath);
  247. if (w == null) {
  248. return null;
  249. }
  250. if (parts.length == 1) {
  251. int pos = widgetPath.indexOf("domChild");
  252. if (pos == -1) {
  253. return w.getElement();
  254. }
  255. // Contains dom reference to a sub element of the widget
  256. String subPath = widgetPath.substring(pos);
  257. return getElementByDOMPath(w.getElement(), subPath);
  258. } else if (parts.length == 2) {
  259. if (w instanceof SubPartAware) {
  260. return ((SubPartAware) w).getSubPartElement(parts[1]);
  261. }
  262. }
  263. return null;
  264. }
  265. /**
  266. * Creates a locator String for the given widget. The path can be used to
  267. * locate the widget using {@link #getWidgetFromPath(String)}.
  268. *
  269. * Returns null if no path can be determined for the widget or if the widget
  270. * is null.
  271. *
  272. * @param w
  273. * The target widget
  274. * @return A String locator for the widget
  275. */
  276. private String getPathForWidget(Widget w) {
  277. if (w == null) {
  278. return null;
  279. }
  280. String pid = client.getPid(w.getElement());
  281. if (isStaticPid(pid)) {
  282. return pid;
  283. }
  284. if (w instanceof VView) {
  285. return "";
  286. } else if (w instanceof VWindow) {
  287. VWindow win = (VWindow) w;
  288. ArrayList<VWindow> subWindowList = client.getView()
  289. .getSubWindowList();
  290. int indexOfSubWindow = subWindowList.indexOf(win);
  291. return PARENTCHILD_SEPARATOR + "VWindow[" + indexOfSubWindow + "]";
  292. } else if (w instanceof RootPanel) {
  293. return ROOT_ID;
  294. }
  295. Widget parent = w.getParent();
  296. String basePath = getPathForWidget(parent);
  297. if (basePath == null) {
  298. return null;
  299. }
  300. String simpleName = Util.getSimpleName(w);
  301. /*
  302. * Check if the parent implements Iterable. At least VPopupView does not
  303. * implement HasWdgets so we cannot check for that.
  304. */
  305. if (!(parent instanceof Iterable<?>)) {
  306. // Parent does not implement Iterable so we cannot find out which
  307. // child this is
  308. return null;
  309. }
  310. Iterator<?> i = ((Iterable<?>) parent).iterator();
  311. int pos = 0;
  312. while (i.hasNext()) {
  313. Object child = i.next();
  314. if (child == w) {
  315. return basePath + PARENTCHILD_SEPARATOR + simpleName + "["
  316. + pos + "]";
  317. }
  318. String simpleName2 = Util.getSimpleName(child);
  319. if (simpleName.equals(simpleName2)) {
  320. pos++;
  321. }
  322. }
  323. return null;
  324. }
  325. /**
  326. * Locates the widget based on a String locator.
  327. *
  328. * @param path
  329. * The String locator that identifies the widget.
  330. * @return The Widget identified by the String locator or null if the widget
  331. * could not be identified.
  332. */
  333. private Widget getWidgetFromPath(String path) {
  334. Widget w = null;
  335. String parts[] = path.split(PARENTCHILD_SEPARATOR);
  336. for (int i = 0; i < parts.length; i++) {
  337. String part = parts[i];
  338. if (part.equals(ROOT_ID)) {
  339. w = RootPanel.get();
  340. } else if (part.equals("")) {
  341. w = client.getView();
  342. } else if (w == null) {
  343. // Must be static pid (PID_S*)
  344. w = (Widget) client.getPaintable(part);
  345. } else if (part.startsWith("domChild[")) {
  346. // The target widget has been found and the rest identifies the
  347. // element
  348. break;
  349. } else if (w instanceof Iterable) {
  350. // W identifies a widget that contains other widgets, as it
  351. // should. Try to locate the child
  352. Iterable<?> parent = (Iterable<?>) w;
  353. // Part is of type "VVerticalLayout[0]", split this into
  354. // VVerticalLayout and 0
  355. String[] split = part.split("\\[", 2);
  356. String widgetClassName = split[0];
  357. String indexString = split[1];
  358. int widgetPosition = Integer.parseInt(indexString.substring(0,
  359. indexString.length() - 1));
  360. // Locate the child
  361. Iterator<? extends Widget> iterator;
  362. /*
  363. * VWindow and VContextMenu workarounds for backwards
  364. * compatibility
  365. */
  366. if (widgetClassName.equals("VWindow")) {
  367. iterator = client.getView().getSubWindowList().iterator();
  368. } else if (widgetClassName.equals("VContextMenu")) {
  369. return client.getContextMenu();
  370. } else {
  371. iterator = (Iterator<? extends Widget>) parent.iterator();
  372. }
  373. boolean ok = false;
  374. // Find the widgetPosition:th child of type "widgetClassName"
  375. while (iterator.hasNext()) {
  376. Widget child = iterator.next();
  377. String simpleName2 = Util.getSimpleName(child);
  378. if (widgetClassName.equals(simpleName2)) {
  379. if (widgetPosition == 0) {
  380. w = child;
  381. ok = true;
  382. break;
  383. }
  384. widgetPosition--;
  385. }
  386. }
  387. if (!ok) {
  388. // Did not find the child
  389. return null;
  390. }
  391. } else {
  392. // W identifies something that is not a "HasWidgets". This
  393. // should not happen as all widget containers should implement
  394. // HasWidgets.
  395. return null;
  396. }
  397. }
  398. return w;
  399. }
  400. /**
  401. * Checks if the given pid is a static pid.
  402. *
  403. * @param pid
  404. * The pid to check
  405. * @return true if the pid is a static pid, false otherwise
  406. */
  407. private boolean isStaticPid(String pid) {
  408. if (pid == null) {
  409. return false;
  410. }
  411. return pid.startsWith("PID_S");
  412. }
  413. }