Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

VaadinFinderLocatorStrategy.java 27KB

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