diff options
author | Patrik Lindström <patrik@vaadin.com> | 2013-11-28 21:22:22 +0200 |
---|---|---|
committer | Teemu Suo-Anttila <teemusa@vaadin.com> | 2013-12-11 15:33:14 +0200 |
commit | cbfffae957fad3f3c7656f6c641a9a08a67724e4 (patch) | |
tree | 52a461cc6f9dbb5377c62dd6cb060ce72e3fc968 | |
parent | 45cebafddc76a0a27eec1769ec775e1d2435f47f (diff) | |
download | vaadin-framework-cbfffae957fad3f3c7656f6c641a9a08a67724e4.tar.gz vaadin-framework-cbfffae957fad3f3c7656f6c641a9a08a67724e4.zip |
Add VaadinLocator multielement queries (#13016, #13017, #13018, #13019)
Change-Id: If6faa939953023761dccaf256001c6ece018d5e8
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<Integer> tags = new ArrayList<Integer>(); + + for (Map.Entry<Integer, String> 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,15 +96,75 @@ 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<Element> getElementsByPath(String path) { + JsArray<Element> jsElements = JavaScriptObject.createArray().cast(); + for (LocatorStrategy strategy : locatorStrategies) { + if (strategy.validatePath(path)) { + List<Element> 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<Element> getElementsByPathStartingAt(String path, + Element root) { + JsArray<Element> jsElements = JavaScriptObject.createArray().cast(); + for (LocatorStrategy strategy : locatorStrategies) { + if (strategy.validatePath(path)) { + List<Element> 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,11 +74,19 @@ 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 .findPaintable(client, targetElement); @@ -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) { /* @@ -220,6 +235,34 @@ public class LegacyLocatorStrategy implements LocatorStrategy { } /** + * {@inheritDoc} + */ + @Override + public List<Element> getElementsByPath(String path) { + // This type of search is not supported in LegacyLocator + List<Element> array = new ArrayList<Element>(); + Element e = getElementByPath(path); + if (e != null) { + array.add(e); + } + return array; + } + + /** + * {@inheritDoc} + */ + @Override + public List<Element> getElementsByPathStartingAt(String path, Element root) { + // This type of search is not supported in LegacyLocator + List<Element> array = new ArrayList<Element>(); + 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 * found. @@ -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<Element> 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<Element> 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,19 +87,146 @@ 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<Element> getElementsByPath(String path) { + + if (isNotificationExpression(path)) { + List<Element> elements = new ArrayList<Element>(); + + for (VNotification n : findNotificationsByPath(path)) { + elements.add(n.getElement()); + } + + return elements; + } + + List<Element> 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<Predicate> extractPredicates(String path) { + List<Predicate> predicates = new ArrayList<Predicate>(); + + String str = extractPredicateString(path); + if (null == str || str.length() == 0) { + return predicates; + } + + // Extract input strings + List<String> input = new ArrayList<String>(); + { + 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<VNotification> notifications = new ArrayList<VNotification>(); + private List<VNotification> findNotificationsByPath(String path) { + + List<VNotification> notifications = new ArrayList<VNotification>(); 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<Predicate> 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); } /** @@ -118,6 +271,16 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { } /** + * {@inheritDoc} + */ + @Override + public List<Element> getElementsByPathStartingAt(String path, Element root) { + List<Element> 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 @@ -148,6 +315,47 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { } /** + * 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<Element> getElementsByPathStartingAtConnector(String path, + ComponentConnector root) { + String[] pathComponents = path.split(SUBPART_SEPARATOR); + List<ComponentConnector> connectors; + if (pathComponents[0].length() > 0) { + connectors = findConnectorsByPath(pathComponents[0], + Arrays.asList(root)); + } else { + connectors = Arrays.asList(root); + } + + List<Element> output = new ArrayList<Element>(); + 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 * {@code parent} connector. @@ -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,19 +376,56 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { String[] fragments = splitFirstFragmentFromTheRest(path); List<ComponentConnector> potentialMatches = collectPotentialMatches( parent, fragments[0], findRecursively); - ComponentConnector connector = filterPotentialMatches(potentialMatches, - extractPredicateString(fragments[0])); - if (connector != null) { + + List<ComponentConnector> 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<ComponentConnector> findConnectorsByPath(String path, + List<ComponentConnector> 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<ComponentConnector> potentialMatches = new ArrayList<ComponentConnector>(); + for (ComponentConnector parent : parents) { + potentialMatches.addAll(collectPotentialMatches(parent, + fragments[0], findRecursively)); + } + + List<ComponentConnector> 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: <code> * VTextField[0] => 0 @@ -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<ComponentConnector> potentialMatches, String predicateString) { - - if (potentialMatches.isEmpty()) { - return null; - } - - if (predicateString != null) { + private List<ComponentConnector> filterMatches( + List<ComponentConnector> potentialMatches, + List<Predicate> 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<ComponentConnector> potentialMatches = new ArrayList<ComponentConnector>(); - if (parent instanceof AbstractHasComponentsConnector) { - List<ComponentConnector> children = ((AbstractHasComponentsConnector) parent) + if (parent instanceof HasComponentsConnector) { + List<ComponentConnector> 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<String> ids = new ArrayList<String>(); + + 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 <T> List<T> eliminateDuplicates(List<T> 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; + } + } } |