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.

VaadinFinderLocatorStrategy.java 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  1. /*
  2. * Copyright 2000-2018 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client.componentlocator;
  17. import java.util.ArrayList;
  18. import java.util.Arrays;
  19. import java.util.LinkedHashSet;
  20. import java.util.List;
  21. import com.google.gwt.dom.client.Document;
  22. import com.google.gwt.dom.client.Element;
  23. import com.google.gwt.user.client.DOM;
  24. import com.google.gwt.user.client.ui.RootPanel;
  25. import com.google.gwt.user.client.ui.Widget;
  26. import com.vaadin.client.ApplicationConnection;
  27. import com.vaadin.client.ComponentConnector;
  28. import com.vaadin.client.HasComponentsConnector;
  29. import com.vaadin.client.Util;
  30. import com.vaadin.client.metadata.Property;
  31. import com.vaadin.client.metadata.TypeDataStore;
  32. import com.vaadin.client.ui.AbstractConnector;
  33. import com.vaadin.client.ui.SubPartAware;
  34. import com.vaadin.client.ui.VNotification;
  35. import com.vaadin.client.ui.ui.UIConnector;
  36. /**
  37. * The VaadinFinder locator strategy implements an XPath-like syntax for
  38. * locating elements in Vaadin applications. This is used in the new
  39. * VaadinFinder API in TestBench 4.
  40. *
  41. * Examples of the supported syntax:
  42. * <ul>
  43. * <li>Find the third text field in the DOM: {@code //VTextField[2]}</li>
  44. * <li>Find the second button inside the first vertical layout:
  45. * {@code //VVerticalLayout/VButton[1]}</li>
  46. * <li>Find the first column on the third row of the "Accounts" table:
  47. * {@code //VScrollTable[caption="Accounts"]#row[2]/col[0]}</li>
  48. * </ul>
  49. *
  50. * @since 7.2
  51. * @author Vaadin Ltd
  52. */
  53. public class VaadinFinderLocatorStrategy implements LocatorStrategy {
  54. public static final String SUBPART_SEPARATOR = "#";
  55. private final ApplicationConnection client;
  56. /**
  57. * Internal descriptor for connector/element/widget name combinations
  58. */
  59. private static final class ConnectorPath {
  60. private String name;
  61. private ComponentConnector connector;
  62. }
  63. public VaadinFinderLocatorStrategy(ApplicationConnection clientConnection) {
  64. client = clientConnection;
  65. }
  66. /**
  67. * {@inheritDoc}
  68. */
  69. @Override
  70. public String getPathForElement(Element targetElement) {
  71. Element oldTarget = targetElement;
  72. Widget targetWidget = Util.findPaintable(client, targetElement)
  73. .getWidget();
  74. targetElement = targetWidget.getElement();
  75. // Find SubPart name if needed.
  76. String subPart = null;
  77. boolean hasSubParts = targetWidget instanceof SubPartAware;
  78. if (oldTarget != targetElement) {
  79. if (hasSubParts) {
  80. subPart = ((SubPartAware) targetWidget)
  81. .getSubPartName(DOM.asOld(oldTarget));
  82. }
  83. if (!hasSubParts || subPart == null) {
  84. // Couldn't find SubPart name for element.
  85. return null;
  86. }
  87. }
  88. List<ConnectorPath> hierarchy = getConnectorHierarchyForElement(
  89. targetElement);
  90. List<String> path = new ArrayList<>();
  91. // Assemble longname path components back-to-forth with useful
  92. // predicates - first try ID, then caption.
  93. for (int i = 0; i < hierarchy.size(); ++i) {
  94. ConnectorPath cp = hierarchy.get(i);
  95. String pathFragment = cp.name;
  96. String identifier = getPropertyValue(cp.connector, "id");
  97. if (identifier != null) {
  98. pathFragment += "[id=\"" + identifier + "\"]";
  99. } else {
  100. identifier = getPropertyValue(cp.connector, "caption");
  101. if (identifier != null) {
  102. pathFragment += "[caption=\"" + identifier + "\"]";
  103. }
  104. }
  105. path.add(pathFragment);
  106. }
  107. if (path.isEmpty()) {
  108. // If we didn't find a single element, return null..
  109. return null;
  110. }
  111. return getBestSelector(generateQueries(path), targetElement, subPart);
  112. }
  113. /**
  114. * Search different queries for the best one. Use the fact that the lowest
  115. * possible index is with the last selector. Last selector is the full
  116. * search path containing the complete Component hierarchy.
  117. *
  118. * @param selectors
  119. * List of selectors
  120. * @param target
  121. * Target element
  122. * @param subPart
  123. * sub part selector string for actual target
  124. * @return Best selector string formatted with a post filter
  125. */
  126. private String getBestSelector(List<String> selectors, Element target,
  127. String subPart) {
  128. // The last selector gives us smallest list index for target element.
  129. String bestSelector = selectors.get(selectors.size() - 1);
  130. int min = getElementsByPath(bestSelector).indexOf(target);
  131. if (selectors.size() > 1
  132. && min == getElementsByPath(selectors.get(0)).indexOf(target)) {
  133. // The first selector has same index as last. It's much shorter.
  134. bestSelector = selectors.get(0);
  135. } else if (selectors.size() > 2) {
  136. // See if we get minimum from second last. If not then we already
  137. // have the best one.. Second last one contains almost full
  138. // component hierarchy.
  139. if (getElementsByPath(selectors.get(selectors.size() - 2))
  140. .indexOf(target) == min) {
  141. for (int i = 1; i < selectors.size() - 2; ++i) {
  142. // Loop through the remaining selectors and look for one
  143. // with the same index
  144. if (getElementsByPath(selectors.get(i))
  145. .indexOf(target) == min) {
  146. bestSelector = selectors.get(i);
  147. break;
  148. }
  149. }
  150. }
  151. }
  152. return "(" + bestSelector + (subPart != null ? "#" + subPart : "")
  153. + ")[" + min + "]";
  154. }
  155. /**
  156. * Function to generate all possible search paths for given component list.
  157. * Function strips out all the com.vaadin.ui. prefixes from elements as this
  158. * functionality makes generating a query later on easier.
  159. *
  160. * @param components
  161. * List of components
  162. * @return List of Vaadin selectors
  163. */
  164. private List<String> generateQueries(List<String> components) {
  165. // Prepare to loop through all the elements.
  166. List<String> paths = new ArrayList<>();
  167. int compIdx = 0;
  168. String basePath = components.get(compIdx).replace("com.vaadin.ui.", "");
  169. // Add a basic search for the first element (e.g. //Button)
  170. paths.add((components.size() == 1 ? "/" : "//") + basePath);
  171. while (++compIdx < components.size()) {
  172. // Loop through the remaining components
  173. for (int i = components.size() - 1; i >= compIdx; --i) {
  174. boolean recursive = false;
  175. if (i > compIdx) {
  176. recursive = true;
  177. }
  178. paths.add((i == components.size() - 1 ? "/" : "//")
  179. + components.get(i).replace("com.vaadin.ui.", "")
  180. + (recursive ? "//" : "/") + basePath);
  181. }
  182. // Add the element at index compIdx to the basePath so it is
  183. // included in all the following searches.
  184. basePath = components.get(compIdx).replace("com.vaadin.ui.", "")
  185. + "/" + basePath;
  186. }
  187. return paths;
  188. }
  189. /**
  190. * Helper method to get the string-form value of a named property of a
  191. * component connector
  192. *
  193. * @since 7.2
  194. * @param c
  195. * any ComponentConnector instance
  196. * @param propertyName
  197. * property name to test for
  198. * @return a string, if the property is found, or null, if the property does
  199. * not exist on the object (or some other error is encountered).
  200. */
  201. private String getPropertyValue(ComponentConnector c, String propertyName) {
  202. Property prop = AbstractConnector.getStateType(c)
  203. .getProperty(propertyName);
  204. try {
  205. return prop.getValue(c.getState()).toString();
  206. } catch (Exception e) {
  207. return null;
  208. }
  209. }
  210. /**
  211. * Generate a list representing the top-to-bottom connector hierarchy for
  212. * any given element. ConnectorPath element provides long- and short names,
  213. * as well as connector and widget root element references.
  214. *
  215. * @since 7.2
  216. * @param elem
  217. * any Element that is part of a widget hierarchy
  218. * @return a list of ConnectorPath objects, in descending order towards the
  219. * common root container.
  220. */
  221. private List<ConnectorPath> getConnectorHierarchyForElement(Element elem) {
  222. Element e = elem;
  223. ComponentConnector c = Util.findPaintable(client, e);
  224. List<ConnectorPath> connectorHierarchy = new ArrayList<>();
  225. while (c != null) {
  226. for (String id : getIDsForConnector(c)) {
  227. ConnectorPath cp = new ConnectorPath();
  228. cp.name = getFullClassName(id);
  229. cp.connector = c;
  230. // We want to make an exception for the UI object, since it's
  231. // our default search context (and can't be found inside itself)
  232. if (!cp.name.equals("com.vaadin.ui.UI")) {
  233. connectorHierarchy.add(cp);
  234. }
  235. }
  236. e = e.getParentElement();
  237. if (e != null) {
  238. c = Util.findPaintable(client, e);
  239. e = c != null ? c.getWidget().getElement() : null;
  240. }
  241. }
  242. return connectorHierarchy;
  243. }
  244. /**
  245. * {@inheritDoc}
  246. */
  247. @Override
  248. public List<Element> getElementsByPath(String path) {
  249. List<SelectorPredicate> postFilters = SelectorPredicate
  250. .extractPostFilterPredicates(path);
  251. if (!postFilters.isEmpty()) {
  252. path = path.substring(1, path.lastIndexOf(')'));
  253. }
  254. List<Element> elements = new ArrayList<>();
  255. if (LocatorUtil.isNotificationElement(path)) {
  256. for (VNotification n : findNotificationsByPath(path)) {
  257. elements.add(n.getElement());
  258. }
  259. } else {
  260. final UIConnector uiConnector = client.getUIConnector();
  261. elements.addAll(
  262. eliminateDuplicates(getElementsByPathStartingAtConnector(
  263. path, uiConnector, Document.get().getBody())));
  264. }
  265. for (SelectorPredicate p : postFilters) {
  266. // Post filtering supports only indexes and follows instruction
  267. // blindly. Index that is outside of our list results into an empty
  268. // list and multiple indexes are likely to ruin a search completely
  269. if (p.getIndex() >= 0) {
  270. if (p.getIndex() >= elements.size()) {
  271. elements.clear();
  272. } else {
  273. Element e = elements.get(p.getIndex());
  274. elements.clear();
  275. elements.add(e);
  276. }
  277. }
  278. }
  279. return elements;
  280. }
  281. /**
  282. * {@inheritDoc}
  283. */
  284. @Override
  285. public Element getElementByPath(String path) {
  286. List<Element> elements = getElementsByPath(path);
  287. if (elements.isEmpty()) {
  288. return null;
  289. }
  290. return elements.get(0);
  291. }
  292. /**
  293. * {@inheritDoc}
  294. */
  295. @Override
  296. public Element getElementByPathStartingAt(String path, Element root) {
  297. List<Element> elements = getElementsByPathStartingAt(path, root);
  298. if (elements.isEmpty()) {
  299. return null;
  300. }
  301. return elements.get(0);
  302. }
  303. /**
  304. * {@inheritDoc}
  305. */
  306. @Override
  307. public List<Element> getElementsByPathStartingAt(String path,
  308. Element root) {
  309. List<SelectorPredicate> postFilters = SelectorPredicate
  310. .extractPostFilterPredicates(path);
  311. if (!postFilters.isEmpty()) {
  312. path = path.substring(1, path.lastIndexOf(')'));
  313. }
  314. final ComponentConnector searchRoot = Util.findPaintable(client, root);
  315. List<Element> elements = getElementsByPathStartingAtConnector(path,
  316. searchRoot, root);
  317. for (SelectorPredicate p : postFilters) {
  318. // Post filtering supports only indexes and follows instruction
  319. // blindly. Index that is outside of our list results into an empty
  320. // list and multiple indexes are likely to ruin a search completely
  321. if (p.getIndex() >= 0) {
  322. if (p.getIndex() >= elements.size()) {
  323. elements.clear();
  324. } else {
  325. Element e = elements.get(p.getIndex());
  326. elements.clear();
  327. elements.add(e);
  328. }
  329. }
  330. }
  331. return elements;
  332. }
  333. /**
  334. * Special case for finding notifications as they have no connectors and are
  335. * directly attached to {@link RootPanel}.
  336. *
  337. * @param path
  338. * The path of the notification, should be
  339. * {@code "//VNotification"} optionally followed by an index in
  340. * brackets.
  341. * @return the notification element or null if not found.
  342. */
  343. private List<VNotification> findNotificationsByPath(String path) {
  344. List<VNotification> notifications = new ArrayList<>();
  345. for (Widget w : RootPanel.get()) {
  346. if (w instanceof VNotification) {
  347. notifications.add((VNotification) w);
  348. }
  349. }
  350. List<SelectorPredicate> predicates = SelectorPredicate
  351. .extractPredicates(path);
  352. for (SelectorPredicate p : predicates) {
  353. if (p.getIndex() > -1) {
  354. VNotification n = notifications.get(p.getIndex());
  355. notifications.clear();
  356. if (n != null) {
  357. notifications.add(n);
  358. }
  359. }
  360. }
  361. return eliminateDuplicates(notifications);
  362. }
  363. /**
  364. * Finds a list of elements by the specified path, starting traversal of the
  365. * connector hierarchy from the specified root.
  366. *
  367. * @param path
  368. * the locator path
  369. * @param root
  370. * the root connector
  371. * @return the list of elements identified by path or empty list if not
  372. * found.
  373. */
  374. private List<Element> getElementsByPathStartingAtConnector(String path,
  375. ComponentConnector root, Element actualRoot) {
  376. String[] pathComponents = path.split(SUBPART_SEPARATOR);
  377. List<ComponentConnector> connectors;
  378. if (!pathComponents[0].isEmpty()) {
  379. connectors = findConnectorsByPath(pathComponents[0],
  380. Arrays.asList(root));
  381. } else {
  382. connectors = Arrays.asList(root);
  383. }
  384. List<Element> output = new ArrayList<>();
  385. if (null != connectors && !connectors.isEmpty()) {
  386. for (ComponentConnector connector : connectors) {
  387. if (!actualRoot
  388. .isOrHasChild(connector.getWidget().getElement())) {
  389. // Filter out widgets that are not children of actual root
  390. continue;
  391. }
  392. if (pathComponents.length > 1) {
  393. // We have SubParts
  394. if (connector.getWidget() instanceof SubPartAware) {
  395. output.add(((SubPartAware) connector.getWidget())
  396. .getSubPartElement(pathComponents[1]));
  397. }
  398. } else {
  399. output.add(connector.getWidget().getElement());
  400. }
  401. }
  402. }
  403. return eliminateDuplicates(output);
  404. }
  405. /**
  406. * Recursively finds connectors for the elements identified by the provided
  407. * path by traversing the connector hierarchy starting from {@code parents}
  408. * connectors.
  409. *
  410. * @param path
  411. * The path identifying elements.
  412. * @param parents
  413. * The list of connectors to start traversing from.
  414. * @return The list of connectors identified by {@code path} or empty list
  415. * if no such connectors could be found.
  416. */
  417. private List<ComponentConnector> findConnectorsByPath(String path,
  418. List<ComponentConnector> parents) {
  419. boolean findRecursively = path.startsWith("//");
  420. // Strip away the one or two slashes from the beginning of the path
  421. path = path.substring(findRecursively ? 2 : 1);
  422. String[] fragments = splitFirstFragmentFromTheRest(path);
  423. List<ComponentConnector> connectors = new ArrayList<>();
  424. for (ComponentConnector parent : parents) {
  425. connectors.addAll(filterMatches(
  426. collectPotentialMatches(parent, fragments[0],
  427. findRecursively),
  428. SelectorPredicate.extractPredicates(fragments[0])));
  429. }
  430. if (!connectors.isEmpty() && fragments.length > 1) {
  431. return (findConnectorsByPath(fragments[1], connectors));
  432. }
  433. return eliminateDuplicates(connectors);
  434. }
  435. /**
  436. * Go through a list of potentially matching components, modifying that list
  437. * until all elements that remain in that list match the complete list of
  438. * predicates.
  439. *
  440. * @param potentialMatches
  441. * a list of component connectors. Will be changed.
  442. * @param predicates
  443. * an immutable list of predicates
  444. * @return filtered list of component connectors.
  445. */
  446. private List<ComponentConnector> filterMatches(
  447. List<ComponentConnector> potentialMatches,
  448. List<SelectorPredicate> predicates) {
  449. for (SelectorPredicate p : predicates) {
  450. if (p.getIndex() > -1) {
  451. try {
  452. ComponentConnector v = potentialMatches.get(p.getIndex());
  453. potentialMatches.clear();
  454. potentialMatches.add(v);
  455. } catch (IndexOutOfBoundsException e) {
  456. potentialMatches.clear();
  457. }
  458. continue;
  459. }
  460. for (int i = 0, l = potentialMatches.size(); i < l; ++i) {
  461. String propData = getPropertyValue(potentialMatches.get(i),
  462. p.getName());
  463. if ((p.isWildcard() && propData == null) || (!p.isWildcard()
  464. && !p.getValue().equals(propData))) {
  465. potentialMatches.remove(i);
  466. --l;
  467. --i;
  468. }
  469. }
  470. }
  471. return eliminateDuplicates(potentialMatches);
  472. }
  473. /**
  474. * Collects all connectors that match the widget class name of the path
  475. * fragment. If the {@code collectRecursively} parameter is true, a
  476. * depth-first search of the connector hierarchy is performed.
  477. *
  478. * Searching depth-first ensure that we can return the matches in correct
  479. * order for selecting based on index predicates.
  480. *
  481. * @param parent
  482. * The {@link ComponentConnector} to start the search from.
  483. * @param pathFragment
  484. * The path fragment identifying which type of widget to search
  485. * for.
  486. * @param collectRecursively
  487. * If true, all matches from all levels below {@code parent} will
  488. * be collected. If false only direct children will be collected.
  489. * @return A list of {@link ComponentConnector}s matching the widget type
  490. * specified in the {@code pathFragment}.
  491. */
  492. private List<ComponentConnector> collectPotentialMatches(
  493. ComponentConnector parent, String pathFragment,
  494. boolean collectRecursively) {
  495. List<ComponentConnector> potentialMatches = new ArrayList<>();
  496. String widgetName = getWidgetName(pathFragment);
  497. // Special case when searching for UIElement.
  498. if (LocatorUtil.isUIElement(pathFragment)) {
  499. if (connectorMatchesPathFragment(parent, widgetName)) {
  500. potentialMatches.add(parent);
  501. }
  502. }
  503. if (parent instanceof HasComponentsConnector) {
  504. List<ComponentConnector> children = ((HasComponentsConnector) parent)
  505. .getChildComponents();
  506. for (ComponentConnector child : children) {
  507. if (connectorMatchesPathFragment(child, widgetName)) {
  508. potentialMatches.add(child);
  509. }
  510. if (collectRecursively) {
  511. potentialMatches.addAll(collectPotentialMatches(child,
  512. pathFragment, collectRecursively));
  513. }
  514. }
  515. }
  516. return eliminateDuplicates(potentialMatches);
  517. }
  518. private List<String> getIDsForConnector(ComponentConnector connector) {
  519. Class<?> connectorClass = connector.getClass();
  520. List<String> ids = new ArrayList<>();
  521. TypeDataStore.get().findIdentifiersFor(connectorClass).addAllTo(ids);
  522. return ids;
  523. }
  524. /**
  525. * Determines whether a connector matches a path fragment. This is done by
  526. * comparing the path fragment to the name of the widget type of the
  527. * connector.
  528. *
  529. * @param connector
  530. * The connector to compare.
  531. * @param widgetName
  532. * The name of the widget class.
  533. * @return true if the widget type of the connector equals the widget type
  534. * identified by the path fragment.
  535. */
  536. private boolean connectorMatchesPathFragment(ComponentConnector connector,
  537. String widgetName) {
  538. List<String> ids = getIDsForConnector(connector);
  539. String exactClass = connector.getConnection().getConfiguration()
  540. .getServerSideClassNameForTag(connector.getTag());
  541. if (!ids.contains(exactClass)) {
  542. ids.add(exactClass);
  543. }
  544. List<Integer> widgetTags = new ArrayList<>();
  545. widgetTags.addAll(getTags(widgetName));
  546. if (widgetTags.isEmpty()) {
  547. widgetTags.addAll(getTags("com.vaadin.ui." + widgetName));
  548. }
  549. for (int i = 0, l = ids.size(); i < l; ++i) {
  550. // Fuzz the connector name, so that the client can provide (for
  551. // example: /Button, /Button.class, /com.vaadin.ui.Button,
  552. // /com.vaadin.ui.Button.class, etc)
  553. String name = ids.get(i);
  554. final String simpleName = getSimpleClassName(name);
  555. final String fullName = getFullClassName(name);
  556. if (!widgetTags.isEmpty()) {
  557. Integer[] foundTags = client.getConfiguration()
  558. .getTagsForServerSideClassName(fullName);
  559. for (int tag : foundTags) {
  560. if (tagsMatch(widgetTags, tag)) {
  561. return true;
  562. }
  563. }
  564. }
  565. // Fallback if something failed before.
  566. if (widgetName.equals(fullName + ".class")
  567. || widgetName.equals(fullName)
  568. || widgetName.equals(simpleName + ".class")
  569. || widgetName.equals(simpleName)
  570. || widgetName.equals(name)) {
  571. return true;
  572. }
  573. }
  574. // If the server-side class name didn't match, fall back to testing for
  575. // the explicit widget name
  576. String widget = Util.getSimpleName(connector.getWidget());
  577. return widgetName.equals(widget)
  578. || widgetName.equals(widget + ".class");
  579. }
  580. /**
  581. * Gets the tags for server side class name. Also includes tags for older
  582. * components in v7 package.
  583. *
  584. * @param widgetName
  585. * the server side class name for widget
  586. * @return list of tags
  587. */
  588. private List<Integer> getTags(String widgetName) {
  589. List<Integer> widgetTags = new ArrayList<>();
  590. Arrays.stream(client.getConfiguration()
  591. .getTagsForServerSideClassName(getFullClassName(widgetName)))
  592. .forEach(widgetTags::add);
  593. if (widgetName.startsWith("com.vaadin.ui")) {
  594. Arrays.stream(client.getConfiguration()
  595. .getTagsForServerSideClassName(getFullClassName(widgetName
  596. .replace("com.vaadin.ui", "com.vaadin.v7.ui"))))
  597. .forEach(widgetTags::add);
  598. }
  599. return widgetTags;
  600. }
  601. /**
  602. * Extracts the name of the widget class from a path fragment
  603. *
  604. * @param pathFragment
  605. * the path fragment
  606. * @return the name of the widget class.
  607. */
  608. private String getWidgetName(String pathFragment) {
  609. String widgetName = pathFragment;
  610. int ixBracket = pathFragment.indexOf('[');
  611. if (ixBracket >= 0) {
  612. widgetName = pathFragment.substring(0, ixBracket);
  613. }
  614. return widgetName;
  615. }
  616. /**
  617. * Splits off the first path fragment from a path and returns an array of
  618. * two elements, where the first element is the first path fragment and the
  619. * second element is the rest of the path (all remaining path fragments
  620. * untouched).
  621. *
  622. * @param path
  623. * The path to split.
  624. * @return An array of two elements: The first path fragment and the rest of
  625. * the path.
  626. */
  627. private String[] splitFirstFragmentFromTheRest(String path) {
  628. int ixOfSlash = LocatorUtil.indexOfIgnoringQuoted(path, '/');
  629. if (ixOfSlash > 0) {
  630. return new String[] { path.substring(0, ixOfSlash),
  631. path.substring(ixOfSlash) };
  632. }
  633. return new String[] { path };
  634. }
  635. private String getSimpleClassName(String s) {
  636. String[] parts = s.split("\\.");
  637. if (s.endsWith(".class")) {
  638. return parts[parts.length - 2];
  639. }
  640. return parts.length > 0 ? parts[parts.length - 1] : s;
  641. }
  642. private String getFullClassName(String s) {
  643. if (s.endsWith(".class")) {
  644. return s.substring(0, s.lastIndexOf(".class"));
  645. }
  646. return s;
  647. }
  648. /*
  649. * (non-Javadoc)
  650. *
  651. * @see
  652. * com.vaadin.client.componentlocator.LocatorStrategy#validatePath(java.
  653. * lang.String)
  654. */
  655. @Override
  656. public boolean validatePath(String path) {
  657. // This syntax is so difficult to regexp properly, that we'll just try
  658. // to find something with it regardless of the correctness of the
  659. // syntax...
  660. return true;
  661. }
  662. /**
  663. * Go through a list, removing all duplicate elements from it. This method
  664. * is used to avoid accumulation of duplicate entries in result lists
  665. * resulting from low-context recursion.
  666. *
  667. * Preserves first entry in list, removes others. Preserves list order.
  668. *
  669. * @return list passed as parameter, after modification
  670. */
  671. private final <T> List<T> eliminateDuplicates(List<T> list) {
  672. LinkedHashSet<T> set = new LinkedHashSet<>(list);
  673. list.clear();
  674. list.addAll(set);
  675. return list;
  676. }
  677. private boolean tagsMatch(List<Integer> targets, Integer tag) {
  678. for (int i = 0; i < targets.size(); ++i) {
  679. if (targets.get(i).equals(tag)) {
  680. return true;
  681. }
  682. }
  683. try {
  684. return tagsMatch(targets,
  685. client.getConfiguration().getParentTag(tag));
  686. } catch (Exception e) {
  687. return false;
  688. }
  689. }
  690. }