From cbfffae957fad3f3c7656f6c641a9a08a67724e4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Patrik=20Lindstr=C3=B6m?= Date: Thu, 28 Nov 2013 21:22:22 +0200 Subject: [PATCH] Add VaadinLocator multielement queries (#13016, #13017, #13018, #13019) Change-Id: If6faa939953023761dccaf256001c6ece018d5e8 --- .../client/ApplicationConfiguration.java | 25 +- .../vaadin/client/ApplicationConnection.java | 9 +- .../componentlocator/ComponentLocator.java | 78 ++- .../LegacyLocatorStrategy.java | 44 ++ .../componentlocator/LocatorStrategy.java | 45 ++ .../VaadinFinderLocatorStrategy.java | 499 +++++++++++++++--- 6 files changed, 628 insertions(+), 72 deletions(-) diff --git a/client/src/com/vaadin/client/ApplicationConfiguration.java b/client/src/com/vaadin/client/ApplicationConfiguration.java index a8ae47385a..1a5b0d836f 100644 --- a/client/src/com/vaadin/client/ApplicationConfiguration.java +++ b/client/src/com/vaadin/client/ApplicationConfiguration.java @@ -512,6 +512,30 @@ public class ApplicationConfiguration implements EntryPoint { } } + /** + * Returns all tags for given class. Tags are used in + * {@link ApplicationConfiguration} to keep track of different classes and + * their hierarchy + * + * @since 7.2 + * @param classname + * name of class which tags we want + * @return Integer array of tags pointing to this classname + */ + public Integer[] getTagsForServerSideClassName(String classname) { + List tags = new ArrayList(); + + for (Map.Entry entry : tagToServerSideClassName + .entrySet()) { + if (classname.equals(entry.getValue())) { + tags.add(entry.getKey()); + } + } + + Integer[] out = new Integer[tags.size()]; + return tags.toArray(out); + } + public Integer getParentTag(int tag) { return componentInheritanceMap.get(tag); } @@ -762,5 +786,4 @@ public class ApplicationConfiguration implements EntryPoint { private static final Logger getLogger() { return Logger.getLogger(ApplicationConfiguration.class.getName()); } - } diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java index 841e1df96d..68fb57b8c0 100644 --- a/client/src/com/vaadin/client/ApplicationConnection.java +++ b/client/src/com/vaadin/client/ApplicationConnection.java @@ -1,4 +1,4 @@ -/* +/* * Copyright 2000-2013 Vaadin Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not @@ -64,7 +64,6 @@ import com.google.gwt.user.client.Window.ClosingHandler; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConfiguration.ErrorMessage; -import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent; import com.vaadin.client.ResourceLoader.ResourceLoadEvent; import com.vaadin.client.ResourceLoader.ResourceLoadListener; import com.vaadin.client.communication.HasJavaScriptConnectorHelper; @@ -577,6 +576,12 @@ public class ApplicationConnection { client.getElementByPathStartingAt = $entry(function(id, element) { return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementByPathStartingAt(Ljava/lang/String;Lcom/google/gwt/user/client/Element;)(id, element); }); + client.getElementsByPath = $entry(function(id) { + return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementsByPath(Ljava/lang/String;)(id); + }); + client.getElementsByPathStartingAt = $entry(function(id, element) { + return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementsByPathStartingAt(Ljava/lang/String;Lcom/google/gwt/user/client/Element;)(id, element); + }); client.getPathForElement = $entry(function(element) { return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getPathForElement(Lcom/google/gwt/user/client/Element;)(element); }); diff --git a/client/src/com/vaadin/client/componentlocator/ComponentLocator.java b/client/src/com/vaadin/client/componentlocator/ComponentLocator.java index 6f6e52c0e1..aa841ce5b0 100644 --- a/client/src/com/vaadin/client/componentlocator/ComponentLocator.java +++ b/client/src/com/vaadin/client/componentlocator/ComponentLocator.java @@ -18,6 +18,8 @@ package com.vaadin.client.componentlocator; import java.util.Arrays; import java.util.List; +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; import com.google.gwt.user.client.Element; import com.vaadin.client.ApplicationConnection; @@ -94,14 +96,74 @@ public class ComponentLocator { */ public Element getElementByPath(String path) { for (LocatorStrategy strategy : locatorStrategies) { - Element element = strategy.getElementByPath(path); - if (null != element) { - return element; + if (strategy.validatePath(path)) { + Element element = strategy.getElementByPath(path); + if (null != element) { + return element; + } } } return null; } + /** + * Locates elements using a String locator (path) which identifies DOM + * elements. + * + * @since 7.2 + * @param path + * The String locator which identifies target elements. + * @return The JavaScriptArray of DOM elements identified by {@code path} or + * empty array if elements could not be located. + */ + public JsArray getElementsByPath(String path) { + JsArray jsElements = JavaScriptObject.createArray().cast(); + for (LocatorStrategy strategy : locatorStrategies) { + if (strategy.validatePath(path)) { + List elements = strategy.getElementsByPath(path); + if (elements.size() > 0) { + for (Element e : elements) { + jsElements.push(e); + } + return jsElements; + } + } + } + return jsElements; + } + + /** + * Locates elements using a String locator (path) which identifies DOM + * elements. The path starts from the specified root element. + * + * @see #getElementByPath(String) + * + * @since 7.2 + * @param path + * The path of elements to be found + * @param root + * The root element where the path is anchored + * @return The JavaScriptArray of DOM elements identified by {@code path} or + * empty array if elements could not be located. + */ + public JsArray getElementsByPathStartingAt(String path, + Element root) { + JsArray jsElements = JavaScriptObject.createArray().cast(); + for (LocatorStrategy strategy : locatorStrategies) { + if (strategy.validatePath(path)) { + List elements = strategy.getElementsByPathStartingAt( + path, root); + if (elements.size() > 0) { + for (Element e : elements) { + jsElements.push(e); + } + return jsElements; + } + } + } + return jsElements; + } + /** * Locates an element using a String locator (path) which identifies a DOM * element. The path starts from the specified root element. @@ -117,9 +179,12 @@ public class ComponentLocator { */ public Element getElementByPathStartingAt(String path, Element root) { for (LocatorStrategy strategy : locatorStrategies) { - Element element = strategy.getElementByPathStartingAt(path, root); - if (null != element) { - return element; + if (strategy.validatePath(path)) { + Element element = strategy.getElementByPathStartingAt(path, + root); + if (null != element) { + return element; + } } } return null; @@ -135,4 +200,5 @@ public class ComponentLocator { public ApplicationConnection getClient() { return client; } + } diff --git a/client/src/com/vaadin/client/componentlocator/LegacyLocatorStrategy.java b/client/src/com/vaadin/client/componentlocator/LegacyLocatorStrategy.java index 5123a57e5d..4a1b100213 100644 --- a/client/src/com/vaadin/client/componentlocator/LegacyLocatorStrategy.java +++ b/client/src/com/vaadin/client/componentlocator/LegacyLocatorStrategy.java @@ -20,6 +20,7 @@ import java.util.Iterator; import java.util.List; import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.HasWidgets; @@ -73,10 +74,18 @@ public class LegacyLocatorStrategy implements LocatorStrategy { private final ApplicationConnection client; + private static final RegExp validSyntax = RegExp + .compile("^((\\w+::)?(PID_S\\w+)?)?(/[a-zA-Z0-9]+\\[\\d+\\])*$"); + public LegacyLocatorStrategy(ApplicationConnection clientConnection) { client = clientConnection; } + @Override + public boolean validatePath(String path) { + return validSyntax.test(path); + } + @Override public String getPathForElement(Element targetElement) { ComponentConnector connector = Util @@ -178,11 +187,17 @@ public class LegacyLocatorStrategy implements LocatorStrategy { } } + /** + * {@inheritDoc} + */ @Override public Element getElementByPath(String path) { return getElementByPathStartingAt(path, null); } + /** + * {@inheritDoc} + */ @Override public Element getElementByPathStartingAt(String path, Element baseElement) { /* @@ -219,6 +234,34 @@ public class LegacyLocatorStrategy implements LocatorStrategy { return null; } + /** + * {@inheritDoc} + */ + @Override + public List getElementsByPath(String path) { + // This type of search is not supported in LegacyLocator + List array = new ArrayList(); + Element e = getElementByPath(path); + if (e != null) { + array.add(e); + } + return array; + } + + /** + * {@inheritDoc} + */ + @Override + public List getElementsByPathStartingAt(String path, Element root) { + // This type of search is not supported in LegacyLocator + List array = new ArrayList(); + Element e = getElementByPathStartingAt(path, root); + if (e != null) { + array.add(e); + } + return array; + } + /** * Finds the first widget in the hierarchy (moving upwards) that implements * SubPartAware. Returns the SubPartAware implementor or null if none is @@ -672,4 +715,5 @@ public class LegacyLocatorStrategy implements LocatorStrategy { return null; } + } diff --git a/client/src/com/vaadin/client/componentlocator/LocatorStrategy.java b/client/src/com/vaadin/client/componentlocator/LocatorStrategy.java index 56ed396609..e892f43d76 100644 --- a/client/src/com/vaadin/client/componentlocator/LocatorStrategy.java +++ b/client/src/com/vaadin/client/componentlocator/LocatorStrategy.java @@ -15,6 +15,8 @@ */ package com.vaadin.client.componentlocator; +import java.util.List; + import com.google.gwt.user.client.Element; /** @@ -28,6 +30,18 @@ import com.google.gwt.user.client.Element; * @author Vaadin Ltd */ public interface LocatorStrategy { + + /** + * Test the given input path for formatting errors. If a given path can not + * be validated, the locator strategy will not be attempted. + * + * @param path + * a locator path expression + * @return true, if the implementing class can process the given path, + * otherwise false + */ + boolean validatePath(String path); + /** * Generates a String locator which uniquely identifies the target element. * The {@link #getElementByPath(String)} method can be used for the inverse @@ -74,4 +88,35 @@ public interface LocatorStrategy { * could not be located. */ Element getElementByPathStartingAt(String path, Element root); + + /** + * Locates all elements that match a String locator (path) which identifies + * DOM elements. + * + * This functionality is limited in {@link LegacyLocatorStrategy}. + * + * @param path + * The String locator which identifies target elements. + * @return List that contains all matched elements. Empty list if none + * found. + */ + List getElementsByPath(String path); + + /** + * Locates all elements that match a String locator (path) which identifies + * DOM elements. The path starts from the specified root element. + * + * This functionality is limited in {@link LegacyLocatorStrategy}. + * + * @see #getElementsByPath(String) + * + * @param path + * The String locator which identifies target elements. + * @param root + * The element that is at the root of the path. + * @return List that contains all matched elements. Empty list if none + * found. + */ + + List getElementsByPathStartingAt(String path, Element root); } diff --git a/client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java b/client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java index 95b2745bf8..6337ca7e8c 100644 --- a/client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java +++ b/client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java @@ -16,18 +16,22 @@ package com.vaadin.client.componentlocator; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import com.google.gwt.core.client.JsArrayString; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.ComponentConnector; +import com.vaadin.client.FastStringSet; +import com.vaadin.client.HasComponentsConnector; import com.vaadin.client.Util; import com.vaadin.client.metadata.NoDataException; import com.vaadin.client.metadata.Property; +import com.vaadin.client.metadata.TypeDataStore; import com.vaadin.client.ui.AbstractConnector; -import com.vaadin.client.ui.AbstractHasComponentsConnector; import com.vaadin.client.ui.SubPartAware; import com.vaadin.client.ui.VNotification; import com.vaadin.shared.AbstractComponentState; @@ -55,6 +59,18 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { private final ApplicationConnection client; + /** + * Internal container/descriptor for search predicates + * + * @author Vaadin Ltd + */ + private static final class Predicate { + private String name = ""; + private String value = ""; + private boolean wildcard = false; + private int index = -1; + } + public VaadinFinderLocatorStrategy(ApplicationConnection clientConnection) { client = clientConnection; } @@ -71,18 +87,145 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { return null; } + private boolean isNotificationExpression(String path) { + String[] starts = { "//", "/" }; + + String[] frags = { "com.vaadin.ui.Notification.class", + "com.vaadin.ui.Notification", "VNotification.class", + "VNotification", "Notification.class", "Notification" }; + + String[] ends = { "/", "[" }; + + for (String s : starts) { + for (String f : frags) { + if (path.equals(s + f)) { + return true; + } + + for (String e : ends) { + if (path.startsWith(s + f + e)) { + return true; + } + } + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public List getElementsByPath(String path) { + + if (isNotificationExpression(path)) { + List elements = new ArrayList(); + + for (VNotification n : findNotificationsByPath(path)) { + elements.add(n.getElement()); + } + + return elements; + } + + List elems = eliminateDuplicates(getElementsByPathStartingAtConnector( + path, client.getUIConnector())); + + return elems; + } + /** * {@inheritDoc} */ @Override public Element getElementByPath(String path) { - if (path.startsWith("//VNotification")) { - return findNotificationByPath(path); + if (isNotificationExpression(path)) { + return findNotificationsByPath(path).get(0).getElement(); } return getElementByPathStartingAtConnector(path, client.getUIConnector()); } + /** + * Generate a list of predicates from a single predicate string + * + * @param str + * a comma separated string of predicates + * @return a List of Predicate objects + */ + private List extractPredicates(String path) { + List predicates = new ArrayList(); + + String str = extractPredicateString(path); + if (null == str || str.length() == 0) { + return predicates; + } + + // Extract input strings + List input = new ArrayList(); + { + int idx = indexOfIgnoringQuotes(str, ',', 0), p = 0; + if (idx == -1) { + input.add(str); + } else { + do { + input.add(str.substring(p, idx)); + p = idx + 1; + idx = indexOfIgnoringQuotes(str, ',', p); + } while (idx > -1); + input.add(str.substring(p)); + } + } + + // Process each predicate into proper predicate descriptor + for (String s : input) { + Predicate p = new Predicate(); + s = s.trim(); + + try { + // If we can parse out the predicate as a pure index argument, + // stop processing here. + p.index = Integer.parseInt(s); + predicates.add(p); + + continue; + } catch (Exception e) { + p.index = -1; + } + + int idx = indexOfIgnoringQuotes(s, '='); + if (idx < 0) { + continue; + } + p.name = s.substring(0, idx); + p.value = s.substring(idx + 1); + + if (p.value.equals("?")) { + p.wildcard = true; + p.value = null; + } else { + // Only unquote predicate value once we're sure it's a proper + // value... + + p.value = unquote(p.value); + } + + predicates.add(p); + } + + // Move any (and all) index predicates to last place in the list. + for (int i = 0, l = predicates.size(); i < l - 1; ++i) { + if (predicates.get(i).index > -1) { + predicates.add(predicates.remove(i)); + --i; + --l; + } + } + + return predicates; + } + /** * Special case for finding notifications as they have no connectors and are * directly attached to {@link RootPanel}. @@ -93,19 +236,29 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { * brackets. * @return the notification element or null if not found. */ - private Element findNotificationByPath(String path) { - ArrayList notifications = new ArrayList(); + private List findNotificationsByPath(String path) { + + List notifications = new ArrayList(); for (Widget w : RootPanel.get()) { if (w instanceof VNotification) { notifications.add((VNotification) w); } } - String indexStr = extractPredicateString(path); - int index = indexStr == null ? 0 : Integer.parseInt(indexStr); - if (index >= 0 && index < notifications.size()) { - return notifications.get(index).getElement(); + + List predicates = extractPredicates(path); + for (Predicate p : predicates) { + + if (p.index > -1) { + VNotification n = notifications.get(p.index); + notifications.clear(); + if (n != null) { + notifications.add(n); + } + } + } - return null; + + return eliminateDuplicates(notifications); } /** @@ -117,6 +270,16 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { Util.findPaintable(client, root)); } + /** + * {@inheritDoc} + */ + @Override + public List getElementsByPathStartingAt(String path, Element root) { + List elements = getElementsByPathStartingAtConnector(path, + Util.findPaintable(client, root)); + return elements; + } + /** * Finds an element by the specified path, starting traversal of the * connector hierarchy from the specified root. @@ -130,8 +293,12 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { private Element getElementByPathStartingAtConnector(String path, ComponentConnector root) { String[] pathComponents = path.split(SUBPART_SEPARATOR); - ComponentConnector connector = findConnectorByPath(pathComponents[0], - root); + ComponentConnector connector; + if (pathComponents[0].length() > 0) { + connector = findConnectorByPath(pathComponents[0], root); + } else { + connector = root; + } if (connector != null) { if (pathComponents.length > 1) { // We have subparts @@ -147,6 +314,47 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { return null; } + /** + * Finds a list of elements by the specified path, starting traversal of the + * connector hierarchy from the specified root. + * + * @param path + * the locator path + * @param root + * the root connector + * @return the list of elements identified by path or empty list if not + * found. + */ + private List getElementsByPathStartingAtConnector(String path, + ComponentConnector root) { + String[] pathComponents = path.split(SUBPART_SEPARATOR); + List connectors; + if (pathComponents[0].length() > 0) { + connectors = findConnectorsByPath(pathComponents[0], + Arrays.asList(root)); + } else { + connectors = Arrays.asList(root); + } + + List output = new ArrayList(); + if (null != connectors && !connectors.isEmpty()) { + if (pathComponents.length > 1) { + // We have subparts + for (ComponentConnector connector : connectors) { + if (connector.getWidget() instanceof SubPartAware) { + output.add(((SubPartAware) connector.getWidget()) + .getSubPartElement(pathComponents[1])); + } + } + } else { + for (ComponentConnector connector : connectors) { + output.add(connector.getWidget().getElement()); + } + } + } + return eliminateDuplicates(output); + } + /** * Recursively finds a connector for the element identified by the provided * path by traversing the connector hierarchy starting from the @@ -156,7 +364,7 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { * The path identifying an element. * @param parent * The connector to start traversing from. - * @return The connector identified by {@code path} or null if it no such + * @return The connector identified by {@code path} or null if no such * connector could be found. */ private ComponentConnector findConnectorByPath(String path, @@ -168,18 +376,55 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { String[] fragments = splitFirstFragmentFromTheRest(path); List potentialMatches = collectPotentialMatches( parent, fragments[0], findRecursively); - ComponentConnector connector = filterPotentialMatches(potentialMatches, - extractPredicateString(fragments[0])); - if (connector != null) { + + List connectors = filterMatches(potentialMatches, + extractPredicates(fragments[0])); + + if (!connectors.isEmpty()) { if (fragments.length > 1) { - return findConnectorByPath(fragments[1], connector); + return findConnectorByPath(fragments[1], connectors.get(0)); } else { - return connector; + return connectors.get(0); } } return null; } + /** + * Recursively finds connectors for the elements identified by the provided + * path by traversing the connector hierarchy starting from {@code parents} + * connectors. + * + * @param path + * The path identifying elements. + * @param parents + * The list of connectors to start traversing from. + * @return The list of connectors identified by {@code path} or empty list + * if no such connectors could be found. + */ + private List findConnectorsByPath(String path, + List parents) { + boolean findRecursively = path.startsWith("//"); + // Strip away the one or two slashes from the beginning of the path + path = path.substring(findRecursively ? 2 : 1); + + String[] fragments = splitFirstFragmentFromTheRest(path); + + List potentialMatches = new ArrayList(); + for (ComponentConnector parent : parents) { + potentialMatches.addAll(collectPotentialMatches(parent, + fragments[0], findRecursively)); + } + + List connectors = filterMatches(potentialMatches, + extractPredicates(fragments[0])); + + if (!connectors.isEmpty() && fragments.length > 1) { + return (findConnectorsByPath(fragments[1], connectors)); + } + return eliminateDuplicates(connectors); + } + /** * Returns the predicate string, i.e. the string between the brackets in a * path fragment. Examples: @@ -189,7 +434,8 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { * * @param pathFragment * The path fragment from which to extract the predicate string. - * @return The predicate string for the path fragment or null if none. + * @return The predicate string for the path fragment or empty string if not + * found. */ private String extractPredicateString(String pathFragment) { int ixOpenBracket = indexOfIgnoringQuotes(pathFragment, '['); @@ -198,60 +444,63 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { ixOpenBracket); return pathFragment.substring(ixOpenBracket + 1, ixCloseBracket); } - return null; + return ""; } /** - * Returns the first ComponentConnector that matches the predicate string - * from a list of potential matches. If {@code predicateString} is null, the - * first element in the {@code potentialMatches} list is returned. + * Go through a list of potentially matching components, modifying that list + * until all elements that remain in that list match the complete list of + * predicates. * * @param potentialMatches - * A list of potential matches to check. - * @param predicateString - * The predicate that should match. Can be an index or a property - * name, value pair or null. - * @return A {@link ComponentConnector} from the {@code potentialMatches} - * list, which matches the {@code predicateString} or null if no - * matches are found. + * a list of component connectors. Will be changed. + * @param predicates + * an immutable list of predicates + * @return filtered list of component connectors. */ - private ComponentConnector filterPotentialMatches( - List potentialMatches, String predicateString) { - - if (potentialMatches.isEmpty()) { - return null; - } - - if (predicateString != null) { + private List filterMatches( + List potentialMatches, + List predicates) { + + for (Predicate p : predicates) { + + if (p.index > -1) { + try { + ComponentConnector v = potentialMatches.get(p.index); + potentialMatches.clear(); + potentialMatches.add(v); + } catch (IndexOutOfBoundsException e) { + potentialMatches.clear(); + } - int split_idx = predicateString.indexOf('='); + continue; + } - if (split_idx != -1) { + for (int i = 0, l = potentialMatches.size(); i < l; ++i) { - String propertyName = predicateString.substring(0, split_idx) - .trim(); - String value = unquote(predicateString.substring(split_idx + 1) - .trim()); + ComponentConnector c = potentialMatches.get(i); + Property property = AbstractConnector.getStateType(c) + .getProperty(p.name); - for (ComponentConnector connector : potentialMatches) { - Property property = AbstractConnector.getStateType( - connector).getProperty(propertyName); - if (valueEqualsPropertyValue(value, property, - connector.getState())) { - return connector; - } + Object propData; + try { + propData = property.getValue(c.getState()); + } catch (NoDataException e) { + propData = null; } - return null; - - } else { - int index = Integer.valueOf(predicateString); - return index < potentialMatches.size() ? potentialMatches - .get(index) : null; + if ((p.wildcard && propData == null) + || (!p.wildcard && !valueEqualsPropertyValue(p.value, + property, c.getState()))) { + potentialMatches.remove(i); + --l; + --i; + } } + } - return potentialMatches.get(0); + return eliminateDuplicates(potentialMatches); } /** @@ -270,7 +519,7 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { AbstractComponentState state) { try { return value.equals(property.getValue(state)); - } catch (NoDataException e) { + } catch (Exception e) { // The property doesn't exist in the state object, so they aren't // equal. return false; @@ -315,8 +564,8 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { ComponentConnector parent, String pathFragment, boolean collectRecursively) { ArrayList potentialMatches = new ArrayList(); - if (parent instanceof AbstractHasComponentsConnector) { - List children = ((AbstractHasComponentsConnector) parent) + if (parent instanceof HasComponentsConnector) { + List children = ((HasComponentsConnector) parent) .getChildComponents(); for (ComponentConnector child : children) { String widgetName = getWidgetName(pathFragment); @@ -329,7 +578,7 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { } } } - return potentialMatches; + return eliminateDuplicates(potentialMatches); } /** @@ -346,7 +595,60 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { */ private boolean connectorMatchesPathFragment(ComponentConnector connector, String widgetName) { - return widgetName.equals(Util.getSimpleName(connector.getWidget())); + Class connectorClass = connector.getClass(); + List ids = new ArrayList(); + + FastStringSet identifiers = TypeDataStore.get().findIdentifiersFor( + connectorClass); + JsArrayString str = identifiers.dump(); + + for (int j = 0; j < str.length(); ++j) { + ids.add(str.get(j)); + } + + Integer[] widgetTags = client.getConfiguration() + .getTagsForServerSideClassName(getFullClassName(widgetName)); + if (widgetTags.length == 0) { + widgetTags = client.getConfiguration() + .getTagsForServerSideClassName( + getFullClassName("com.vaadin.ui." + widgetName)); + } + + for (int i = 0, l = ids.size(); i < l; ++i) { + + // Fuzz the connector name, so that the client can provide (for + // example: /Button, /Button.class, /com.vaadin.ui.Button, + // /com.vaadin.ui.Button.class, etc) + + String name = ids.get(i); + final String simpleName = getSimpleClassName(name); + final String fullName = getFullClassName(name); + + if (widgetTags.length > 0) { + Integer[] foundTags = client.getConfiguration() + .getTagsForServerSideClassName(fullName); + for (int tag : foundTags) { + if (tagsMatch(widgetTags, tag)) { + return true; + } + } + } + + // Fallback if something failed before. + if (widgetName.equals(fullName + ".class") + || widgetName.equals(fullName) + || widgetName.equals(simpleName + ".class") + || widgetName.equals(simpleName) || widgetName.equals(name)) { + return true; + } + } + + // If the server-side class name didn't match, fall back to testing for + // the explicit widget name + String widget = Util.getSimpleName(connector.getWidget()); + return widgetName.equals(widget) + || widgetName.equals(widget + ".class"); + } /** @@ -412,4 +714,75 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { return -1; } + private String getSimpleClassName(String s) { + String[] parts = s.split("\\."); + if (s.endsWith(".class")) { + return parts[parts.length - 2]; + } + return parts.length > 0 ? parts[parts.length - 1] : s; + } + + private String getFullClassName(String s) { + if (s.endsWith(".class")) { + return s.substring(0, s.lastIndexOf(".class")); + } + return s; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.client.componentlocator.LocatorStrategy#validatePath(java. + * lang.String) + */ + @Override + public boolean validatePath(String path) { + // This syntax is so difficult to regexp properly, that we'll just try + // to find something with it regardless of the correctness of the + // syntax... + return true; + } + + /** + * Go through a list, removing all duplicate elements from it. This method + * is used to avoid accumulation of duplicate entries in result lists + * resulting from low-context recursion. + * + * Preserves first entry in list, removes others. Preserves list order. + * + * @return list passed as parameter, after modification + */ + private final List eliminateDuplicates(List list) { + + int l = list.size(); + for (int j = 0; j < l; ++j) { + T ref = list.get(j); + + for (int i = j + 1; i < l; ++i) { + if (list.get(i) == ref) { + list.remove(i); + --i; + --l; + } + } + } + + return list; + } + + private boolean tagsMatch(Integer[] targets, Integer tag) { + for (int i = 0; i < targets.length; ++i) { + if (targets[i].equals(tag)) { + return true; + } + } + + try { + return tagsMatch(targets, + client.getConfiguration().getParentTag(tag)); + } catch (Exception e) { + return false; + } + } } -- 2.39.5