You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ComponentLocator.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  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.Widget;
  10. import com.vaadin.terminal.gwt.client.ui.SubPartAware;
  11. import com.vaadin.terminal.gwt.client.ui.VView;
  12. import com.vaadin.terminal.gwt.client.ui.VWindow;
  13. /**
  14. * ComponentLocator provides methods for uniquely identifying DOM elements using
  15. * string expressions. This class is EXPERIMENTAL and subject to change.
  16. */
  17. public class ComponentLocator {
  18. /**
  19. * Separator used in the string expression between a parent and a child
  20. * widget.
  21. */
  22. private static final String PARENTCHILD_SEPARATOR = "/";
  23. /**
  24. * Separator used in the string expression between a widget and the widget's
  25. * sub part. NOT CURRENTLY IN USE.
  26. */
  27. private static final String SUBPART_SEPARATOR = "#";
  28. private ApplicationConnection client;
  29. public ComponentLocator(ApplicationConnection client) {
  30. this.client = client;
  31. }
  32. /**
  33. * EXPERIMENTAL.
  34. *
  35. * Generates a string expression (path) which uniquely identifies the target
  36. * element. The getElementByPath method can be used for the inverse
  37. * operation, i.e. locating an element based on the string expression:
  38. * getElementByPath(getPathForElement(element)) == element.
  39. *
  40. * @since 5.4
  41. * @param targetElement
  42. * The element to generate a path for.
  43. * @return A string expression uniquely identifying the target element or
  44. * null if a string expression could not be created.
  45. */
  46. public String getPathForElement(Element targetElement) {
  47. String pid = null;
  48. Element e = targetElement;
  49. while (true) {
  50. pid = client.getPid(e);
  51. if (pid != null) {
  52. break;
  53. }
  54. e = DOM.getParent(e);
  55. if (e == null) {
  56. break;
  57. }
  58. }
  59. if (e == null || pid == null) {
  60. // Still test for context menu option
  61. String subPartName = client.getContextMenu().getSubPartName(
  62. targetElement);
  63. if (subPartName != null) {
  64. // VContextMenu, singleton attached directly to rootpanel
  65. return "/VContextMenu[0]" + SUBPART_SEPARATOR + subPartName;
  66. }
  67. return null;
  68. }
  69. Widget w = (Widget) client.getPaintable(pid);
  70. if (w == null) {
  71. return null;
  72. }
  73. // ApplicationConnection.getConsole().log(
  74. // "First parent widget: " + Util.getSimpleName(w));
  75. String path = getPathForWidget(w);
  76. if (path == null) {
  77. // No path could be determined for the widget. Cannot create a
  78. // locator string.
  79. return null;
  80. }
  81. // ApplicationConnection.getConsole().log(
  82. // "getPathFromWidget returned " + path);
  83. if (w.getElement() == targetElement) {
  84. // ApplicationConnection.getConsole().log(
  85. // "Path for " + Util.getSimpleName(w) + ": " + path);
  86. return path;
  87. } else if (w instanceof SubPartAware) {
  88. return path + SUBPART_SEPARATOR
  89. + ((SubPartAware) w).getSubPartName(targetElement);
  90. } else {
  91. path = path + getDOMPathForElement(targetElement, w.getElement());
  92. // ApplicationConnection.getConsole().log(
  93. // "Path with dom addition for " + Util.getSimpleName(w)
  94. // + ": " + path);
  95. return path;
  96. }
  97. }
  98. private Element getElementByDOMPath(Element baseElement, String path) {
  99. String parts[] = path.split(PARENTCHILD_SEPARATOR);
  100. Element element = baseElement;
  101. for (String part : parts) {
  102. if (part.startsWith("domChild[")) {
  103. String childIndexString = part.substring("domChild[".length(),
  104. part.length() - 1);
  105. try {
  106. int childIndex = Integer.parseInt(childIndexString);
  107. element = DOM.getChild(element, childIndex);
  108. } catch (Exception e) {
  109. // ApplicationConnection.getConsole().error(
  110. // "Failed to parse integer in " + childIndexString);
  111. return null;
  112. }
  113. }
  114. }
  115. return element;
  116. }
  117. private String getDOMPathForElement(Element element, Element baseElement) {
  118. Element e = element;
  119. String path = "";
  120. while (true) {
  121. Element parent = DOM.getParent(e);
  122. if (parent == null) {
  123. return "ERROR, baseElement is not a parent to element";
  124. }
  125. int childIndex = -1;
  126. int childCount = DOM.getChildCount(parent);
  127. for (int i = 0; i < childCount; i++) {
  128. if (e == DOM.getChild(parent, i)) {
  129. childIndex = i;
  130. break;
  131. }
  132. }
  133. if (childIndex == -1) {
  134. return "ERROR, baseElement is not a parent to element.";
  135. }
  136. path = PARENTCHILD_SEPARATOR + "domChild[" + childIndex + "]"
  137. + path;
  138. if (parent == baseElement) {
  139. break;
  140. }
  141. e = parent;
  142. }
  143. return path;
  144. }
  145. /**
  146. * EXPERIMENTAL.
  147. *
  148. * Locates an element by using a string expression (path) which uniquely
  149. * identifies the element. The getPathForElement method can be used for the
  150. * inverse operation, i.e. generating a string expression for a target
  151. * element.
  152. *
  153. * @since 5.4
  154. * @param path
  155. * The string expression which uniquely identifies the target
  156. * element.
  157. * @return The DOM element identified by the path or null if the element
  158. * could not be located.
  159. */
  160. public Element getElementByPath(String path) {
  161. // ApplicationConnection.getConsole()
  162. // .log("getElementByPath(" + path + ")");
  163. // Path is of type "PID/componentPart"
  164. String parts[] = path.split(SUBPART_SEPARATOR, 2);
  165. String widgetPath = parts[0];
  166. Widget w = getWidgetFromPath(widgetPath);
  167. if (w == null) {
  168. return null;
  169. }
  170. if (parts.length == 1) {
  171. int pos = widgetPath.indexOf("domChild");
  172. if (pos == -1) {
  173. return w.getElement();
  174. }
  175. // Contains dom reference to a sub element of the widget
  176. String subPath = widgetPath.substring(pos);
  177. return getElementByDOMPath(w.getElement(), subPath);
  178. } else if (parts.length == 2) {
  179. if (w instanceof SubPartAware) {
  180. // ApplicationConnection.getConsole().log(
  181. // "subPartAware: " + parts[1]);
  182. return ((SubPartAware) w).getSubPartElement(parts[1]);
  183. } else {
  184. // ApplicationConnection.getConsole().error(
  185. // "getElementByPath failed because "
  186. // + Util.getSimpleName(w)
  187. // + " is not SubPartAware");
  188. return null;
  189. }
  190. }
  191. return null;
  192. }
  193. /**
  194. * Creates a locator path for the given widget. The path can be used to
  195. * uniquely identify the widget in the application. The path is in a form
  196. * compatible with getWidgetFromPath so that
  197. * getWidgetFromPath(getPathForWidget(widget)).equals(widget).
  198. *
  199. * Returns null if no path can be determined for the widget or if the widget
  200. * is null.
  201. *
  202. * @param w
  203. * @return
  204. */
  205. private String getPathForWidget(Widget w) {
  206. if (w == null) {
  207. return null;
  208. }
  209. String pid = client.getPid(w.getElement());
  210. if (isStaticPid(pid)) {
  211. return pid;
  212. }
  213. if (w instanceof VView) {
  214. return "";
  215. } else if (w instanceof VWindow) {
  216. VWindow win = (VWindow) w;
  217. ArrayList<VWindow> subWindowList = client.getView()
  218. .getSubWindowList();
  219. int indexOfSubWindow = subWindowList.indexOf(win);
  220. return PARENTCHILD_SEPARATOR + "VWindow[" + indexOfSubWindow + "]";
  221. }
  222. Widget parent = w.getParent();
  223. String basePath = getPathForWidget(parent);
  224. if (basePath == null) {
  225. return null;
  226. }
  227. String simpleName = Util.getSimpleName(w);
  228. if (!(parent instanceof Iterable<?>)) {
  229. // Parent does not implement Iterable so we cannot find out which
  230. // child this is
  231. return null;
  232. }
  233. Iterator<Widget> i = ((Iterable<Widget>) parent).iterator();
  234. int pos = 0;
  235. while (i.hasNext()) {
  236. Object child = i.next();
  237. if (child == w) {
  238. return basePath + PARENTCHILD_SEPARATOR + simpleName + "["
  239. + pos + "]";
  240. }
  241. String simpleName2 = Util.getSimpleName(child);
  242. if (simpleName.equals(simpleName2)) {
  243. pos++;
  244. }
  245. }
  246. return null;
  247. }
  248. private Widget getWidgetFromPath(String path) {
  249. Widget w = null;
  250. String parts[] = path.split(PARENTCHILD_SEPARATOR);
  251. // ApplicationConnection.getConsole().log(
  252. // "getWidgetFromPath(" + path + ")");
  253. for (String part : parts) {
  254. // ApplicationConnection.getConsole().log("Part: " + part);
  255. // ApplicationConnection.getConsole().log(
  256. // "Widget: " + Util.getSimpleName(w));
  257. if (part.equals("")) {
  258. w = client.getView();
  259. } else if (w == null) {
  260. w = (Widget) client.getPaintable(part);
  261. } else if (part.startsWith("domChild[")) {
  262. break;
  263. } else if (w instanceof Iterable<?>) {
  264. Iterable<Widget> parent = (Iterable<Widget>) w;
  265. String[] split = part.split("\\[");
  266. Iterator<? extends Widget> i;
  267. String widgetClassName = split[0];
  268. if (widgetClassName.equals("VWindow")) {
  269. i = client.getView().getSubWindowList().iterator();
  270. } else if (widgetClassName.equals("VContextMenu")) {
  271. return client.getContextMenu();
  272. } else {
  273. i = parent.iterator();
  274. }
  275. boolean ok = false;
  276. int pos = Integer.parseInt(split[1].substring(0, split[1]
  277. .length() - 1));
  278. // ApplicationConnection.getConsole().log(
  279. // "Looking for child " + pos);
  280. while (i.hasNext()) {
  281. // ApplicationConnection.getConsole().log("- child found");
  282. Widget child = i.next();
  283. String simpleName2 = Util.getSimpleName(child);
  284. if (widgetClassName.equals(simpleName2)) {
  285. if (pos == 0) {
  286. w = child;
  287. ok = true;
  288. break;
  289. }
  290. pos--;
  291. }
  292. }
  293. if (!ok) {
  294. // Did not find the child
  295. // ApplicationConnection.getConsole().error(
  296. // "getWidgetFromPath(" + path + ") - did not find '"
  297. // + part + "' for "
  298. // + Util.getSimpleName(parent));
  299. return null;
  300. }
  301. } else {
  302. // ApplicationConnection.getConsole().error(
  303. // "getWidgetFromPath(" + path + ") - failed for '" + part
  304. // + "'");
  305. return null;
  306. }
  307. }
  308. return w;
  309. }
  310. private boolean isStaticPid(String pid) {
  311. if (pid == null) {
  312. return false;
  313. }
  314. return pid.startsWith("PID_S");
  315. }
  316. }