123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781 |
- /*
- * Copyright 2000-2016 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
- package com.vaadin.client.componentlocator;
-
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.LinkedHashSet;
- import java.util.List;
-
- import com.google.gwt.dom.client.Document;
- import com.google.gwt.dom.client.Element;
- import com.google.gwt.user.client.DOM;
- 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.HasComponentsConnector;
- import com.vaadin.client.Util;
- import com.vaadin.client.metadata.Property;
- import com.vaadin.client.metadata.TypeDataStore;
- import com.vaadin.client.ui.AbstractConnector;
- import com.vaadin.client.ui.SubPartAware;
- import com.vaadin.client.ui.VNotification;
- import com.vaadin.client.ui.ui.UIConnector;
-
- /**
- * The VaadinFinder locator strategy implements an XPath-like syntax for
- * locating elements in Vaadin applications. This is used in the new
- * VaadinFinder API in TestBench 4.
- *
- * Examples of the supported syntax:
- * <ul>
- * <li>Find the third text field in the DOM: {@code //VTextField[2]}</li>
- * <li>Find the second button inside the first vertical layout:
- * {@code //VVerticalLayout/VButton[1]}</li>
- * <li>Find the first column on the third row of the "Accounts" table:
- * {@code //VScrollTable[caption="Accounts"]#row[2]/col[0]}</li>
- * </ul>
- *
- * @since 7.2
- * @author Vaadin Ltd
- */
- public class VaadinFinderLocatorStrategy implements LocatorStrategy {
-
- public static final String SUBPART_SEPARATOR = "#";
-
- private final ApplicationConnection client;
-
- /**
- * Internal descriptor for connector/element/widget name combinations
- */
- private static final class ConnectorPath {
- private String name;
- private ComponentConnector connector;
- }
-
- public VaadinFinderLocatorStrategy(ApplicationConnection clientConnection) {
- client = clientConnection;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public String getPathForElement(Element targetElement) {
- Element oldTarget = targetElement;
- Widget targetWidget = Util.findPaintable(client, targetElement)
- .getWidget();
- targetElement = targetWidget.getElement();
-
- // Find SubPart name if needed.
- String subPart = null;
- boolean hasSubParts = targetWidget instanceof SubPartAware;
- if (oldTarget != targetElement) {
- if (hasSubParts) {
- subPart = ((SubPartAware) targetWidget)
- .getSubPartName(DOM.asOld(oldTarget));
- }
-
- if (!hasSubParts || subPart == null) {
- // Couldn't find SubPart name for element.
- return null;
- }
- }
-
- List<ConnectorPath> hierarchy = getConnectorHierarchyForElement(
- targetElement);
- List<String> path = new ArrayList<>();
-
- // Assemble longname path components back-to-forth with useful
- // predicates - first try ID, then caption.
- for (int i = 0; i < hierarchy.size(); ++i) {
- ConnectorPath cp = hierarchy.get(i);
- String pathFragment = cp.name;
- String identifier = getPropertyValue(cp.connector, "id");
-
- if (identifier != null) {
- pathFragment += "[id=\"" + identifier + "\"]";
- } else {
- identifier = getPropertyValue(cp.connector, "caption");
- if (identifier != null) {
- pathFragment += "[caption=\"" + identifier + "\"]";
- }
- }
- path.add(pathFragment);
- }
-
- if (path.isEmpty()) {
- // If we didn't find a single element, return null..
- return null;
- }
-
- return getBestSelector(generateQueries(path), targetElement, subPart);
- }
-
- /**
- * Search different queries for the best one. Use the fact that the lowest
- * possible index is with the last selector. Last selector is the full
- * search path containing the complete Component hierarchy.
- *
- * @param selectors
- * List of selectors
- * @param target
- * Target element
- * @param subPart
- * sub part selector string for actual target
- * @return Best selector string formatted with a post filter
- */
- private String getBestSelector(List<String> selectors, Element target,
- String subPart) {
- // The last selector gives us smallest list index for target element.
- String bestSelector = selectors.get(selectors.size() - 1);
- int min = getElementsByPath(bestSelector).indexOf(target);
- if (selectors.size() > 1
- && min == getElementsByPath(selectors.get(0)).indexOf(target)) {
- // The first selector has same index as last. It's much shorter.
- bestSelector = selectors.get(0);
- } else if (selectors.size() > 2) {
- // See if we get minimum from second last. If not then we already
- // have the best one.. Second last one contains almost full
- // component hierarchy.
- if (getElementsByPath(selectors.get(selectors.size() - 2))
- .indexOf(target) == min) {
- for (int i = 1; i < selectors.size() - 2; ++i) {
- // Loop through the remaining selectors and look for one
- // with the same index
- if (getElementsByPath(selectors.get(i))
- .indexOf(target) == min) {
- bestSelector = selectors.get(i);
- break;
- }
- }
-
- }
- }
- return "(" + bestSelector + (subPart != null ? "#" + subPart : "")
- + ")[" + min + "]";
-
- }
-
- /**
- * Function to generate all possible search paths for given component list.
- * Function strips out all the com.vaadin.ui. prefixes from elements as this
- * functionality makes generating a query later on easier.
- *
- * @param components
- * List of components
- * @return List of Vaadin selectors
- */
- private List<String> generateQueries(List<String> components) {
- // Prepare to loop through all the elements.
- List<String> paths = new ArrayList<>();
- int compIdx = 0;
- String basePath = components.get(compIdx).replace("com.vaadin.ui.", "");
- // Add a basic search for the first element (eg. //Button)
- paths.add((components.size() == 1 ? "/" : "//") + basePath);
- while (++compIdx < components.size()) {
- // Loop through the remaining components
- for (int i = components.size() - 1; i >= compIdx; --i) {
- boolean recursive = false;
- if (i > compIdx) {
- recursive = true;
- }
- paths.add((i == components.size() - 1 ? "/" : "//")
- + components.get(i).replace("com.vaadin.ui.", "")
- + (recursive ? "//" : "/") + basePath);
- }
- // Add the element at index compIdx to the basePath so it is
- // included in all the following searches.
- basePath = components.get(compIdx).replace("com.vaadin.ui.", "")
- + "/" + basePath;
- }
-
- return paths;
- }
-
- /**
- * Helper method to get the string-form value of a named property of a
- * component connector
- *
- * @since 7.2
- * @param c
- * any ComponentConnector instance
- * @param propertyName
- * property name to test for
- * @return a string, if the property is found, or null, if the property does
- * not exist on the object (or some other error is encountered).
- */
- private String getPropertyValue(ComponentConnector c, String propertyName) {
- Property prop = AbstractConnector.getStateType(c)
- .getProperty(propertyName);
- try {
- return prop.getValue(c.getState()).toString();
- } catch (Exception e) {
- return null;
- }
- }
-
- /**
- * Generate a list representing the top-to-bottom connector hierarchy for
- * any given element. ConnectorPath element provides long- and short names,
- * as well as connector and widget root element references.
- *
- * @since 7.2
- * @param elem
- * any Element that is part of a widget hierarchy
- * @return a list of ConnectorPath objects, in descending order towards the
- * common root container.
- */
- private List<ConnectorPath> getConnectorHierarchyForElement(Element elem) {
- Element e = elem;
- ComponentConnector c = Util.findPaintable(client, e);
- List<ConnectorPath> connectorHierarchy = new ArrayList<>();
-
- while (c != null) {
-
- for (String id : getIDsForConnector(c)) {
- ConnectorPath cp = new ConnectorPath();
- cp.name = getFullClassName(id);
- cp.connector = c;
-
- // We want to make an exception for the UI object, since it's
- // our default search context (and can't be found inside itself)
- if (!cp.name.equals("com.vaadin.ui.UI")) {
- connectorHierarchy.add(cp);
- }
- }
-
- e = e.getParentElement();
- if (e != null) {
- c = Util.findPaintable(client, e);
- e = c != null ? c.getWidget().getElement() : null;
- }
-
- }
-
- return connectorHierarchy;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public List<Element> getElementsByPath(String path) {
- List<SelectorPredicate> postFilters = SelectorPredicate
- .extractPostFilterPredicates(path);
- if (!postFilters.isEmpty()) {
- path = path.substring(1, path.lastIndexOf(')'));
- }
-
- List<Element> elements = new ArrayList<>();
- if (LocatorUtil.isNotificationElement(path)) {
-
- for (VNotification n : findNotificationsByPath(path)) {
- elements.add(n.getElement());
- }
-
- } else {
-
- final UIConnector uiConnector = client.getUIConnector();
- elements.addAll(
- eliminateDuplicates(getElementsByPathStartingAtConnector(
- path, uiConnector, Document.get().getBody())));
- }
-
- for (SelectorPredicate p : postFilters) {
- // Post filtering supports only indexes and follows instruction
- // blindly. Index that is outside of our list results into an empty
- // list and multiple indexes are likely to ruin a search completely
- if (p.getIndex() >= 0) {
- if (p.getIndex() >= elements.size()) {
- elements.clear();
- } else {
- Element e = elements.get(p.getIndex());
- elements.clear();
- elements.add(e);
- }
- }
- }
-
- return elements;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Element getElementByPath(String path) {
- List<Element> elements = getElementsByPath(path);
- if (elements.isEmpty()) {
- return null;
- }
- return elements.get(0);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Element getElementByPathStartingAt(String path, Element root) {
- List<Element> elements = getElementsByPathStartingAt(path, root);
- if (elements.isEmpty()) {
- return null;
- }
- return elements.get(0);
-
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public List<Element> getElementsByPathStartingAt(String path,
- Element root) {
- List<SelectorPredicate> postFilters = SelectorPredicate
- .extractPostFilterPredicates(path);
- if (!postFilters.isEmpty()) {
- path = path.substring(1, path.lastIndexOf(')'));
- }
-
- final ComponentConnector searchRoot = Util.findPaintable(client, root);
- List<Element> elements = getElementsByPathStartingAtConnector(path,
- searchRoot, root);
-
- for (SelectorPredicate p : postFilters) {
- // Post filtering supports only indexes and follows instruction
- // blindly. Index that is outside of our list results into an empty
- // list and multiple indexes are likely to ruin a search completely
- if (p.getIndex() >= 0) {
- if (p.getIndex() >= elements.size()) {
- elements.clear();
- } else {
- Element e = elements.get(p.getIndex());
- elements.clear();
- elements.add(e);
- }
- }
- }
-
- return elements;
- }
-
- /**
- * Special case for finding notifications as they have no connectors and are
- * directly attached to {@link RootPanel}.
- *
- * @param path
- * The path of the notification, should be
- * {@code "//VNotification"} optionally followed by an index in
- * brackets.
- * @return the notification element or null if not found.
- */
- private List<VNotification> findNotificationsByPath(String path) {
-
- List<VNotification> notifications = new ArrayList<>();
- for (Widget w : RootPanel.get()) {
- if (w instanceof VNotification) {
- notifications.add((VNotification) w);
- }
- }
-
- List<SelectorPredicate> predicates = SelectorPredicate
- .extractPredicates(path);
- for (SelectorPredicate p : predicates) {
-
- if (p.getIndex() > -1) {
- VNotification n = notifications.get(p.getIndex());
- notifications.clear();
- if (n != null) {
- notifications.add(n);
- }
- }
-
- }
-
- return eliminateDuplicates(notifications);
- }
-
- /**
- * 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, Element actualRoot) {
- String[] pathComponents = path.split(SUBPART_SEPARATOR);
- List<ComponentConnector> connectors;
- if (!pathComponents[0].isEmpty()) {
- connectors = findConnectorsByPath(pathComponents[0],
- Arrays.asList(root));
- } else {
- connectors = Arrays.asList(root);
- }
-
- List<Element> output = new ArrayList<>();
- if (null != connectors && !connectors.isEmpty()) {
- for (ComponentConnector connector : connectors) {
- if (!actualRoot
- .isOrHasChild(connector.getWidget().getElement())) {
- // Filter out widgets that are not children of actual root
- continue;
- }
-
- if (pathComponents.length > 1) {
- // We have SubParts
- if (connector.getWidget() instanceof SubPartAware) {
- output.add(((SubPartAware) connector.getWidget())
- .getSubPartElement(pathComponents[1]));
- }
- } else {
- output.add(connector.getWidget().getElement());
- }
- }
- }
- return eliminateDuplicates(output);
- }
-
- /**
- * 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> connectors = new ArrayList<>();
- for (ComponentConnector parent : parents) {
- connectors.addAll(filterMatches(
- collectPotentialMatches(parent, fragments[0],
- findRecursively),
- SelectorPredicate.extractPredicates(fragments[0])));
- }
-
- if (!connectors.isEmpty() && fragments.length > 1) {
- return (findConnectorsByPath(fragments[1], connectors));
- }
- return eliminateDuplicates(connectors);
- }
-
- /**
- * 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 component connectors. Will be changed.
- * @param predicates
- * an immutable list of predicates
- * @return filtered list of component connectors.
- */
- private List<ComponentConnector> filterMatches(
- List<ComponentConnector> potentialMatches,
- List<SelectorPredicate> predicates) {
-
- for (SelectorPredicate p : predicates) {
-
- if (p.getIndex() > -1) {
- try {
- ComponentConnector v = potentialMatches.get(p.getIndex());
- potentialMatches.clear();
- potentialMatches.add(v);
- } catch (IndexOutOfBoundsException e) {
- potentialMatches.clear();
- }
-
- continue;
- }
-
- for (int i = 0, l = potentialMatches.size(); i < l; ++i) {
-
- String propData = getPropertyValue(potentialMatches.get(i),
- p.getName());
-
- if ((p.isWildcard() && propData == null) || (!p.isWildcard()
- && !p.getValue().equals(propData))) {
- potentialMatches.remove(i);
- --l;
- --i;
- }
- }
-
- }
-
- return eliminateDuplicates(potentialMatches);
- }
-
- /**
- * Collects all connectors that match the widget class name of the path
- * fragment. If the {@code collectRecursively} parameter is true, a
- * depth-first search of the connector hierarchy is performed.
- *
- * Searching depth-first ensure that we can return the matches in correct
- * order for selecting based on index predicates.
- *
- * @param parent
- * The {@link ComponentConnector} to start the search from.
- * @param pathFragment
- * The path fragment identifying which type of widget to search
- * for.
- * @param collectRecursively
- * If true, all matches from all levels below {@code parent} will
- * be collected. If false only direct children will be collected.
- * @return A list of {@link ComponentConnector}s matching the widget type
- * specified in the {@code pathFragment}.
- */
- private List<ComponentConnector> collectPotentialMatches(
- ComponentConnector parent, String pathFragment,
- boolean collectRecursively) {
- List<ComponentConnector> potentialMatches = new ArrayList<>();
- String widgetName = getWidgetName(pathFragment);
- // Special case when searching for UIElement.
- if (LocatorUtil.isUIElement(pathFragment)) {
- if (connectorMatchesPathFragment(parent, widgetName)) {
- potentialMatches.add(parent);
- }
- }
- if (parent instanceof HasComponentsConnector) {
-
- List<ComponentConnector> children = ((HasComponentsConnector) parent)
- .getChildComponents();
- for (ComponentConnector child : children) {
-
- if (connectorMatchesPathFragment(child, widgetName)) {
- potentialMatches.add(child);
- }
- if (collectRecursively) {
- potentialMatches.addAll(collectPotentialMatches(child,
- pathFragment, collectRecursively));
- }
- }
- }
- return eliminateDuplicates(potentialMatches);
- }
-
- private List<String> getIDsForConnector(ComponentConnector connector) {
- Class<?> connectorClass = connector.getClass();
- List<String> ids = new ArrayList<>();
-
- TypeDataStore.get().findIdentifiersFor(connectorClass).addAllTo(ids);
-
- return ids;
- }
-
- /**
- * Determines whether a connector matches a path fragment. This is done by
- * comparing the path fragment to the name of the widget type of the
- * connector.
- *
- * @param connector
- * The connector to compare.
- * @param widgetName
- * The name of the widget class.
- * @return true if the widget type of the connector equals the widget type
- * identified by the path fragment.
- */
- private boolean connectorMatchesPathFragment(ComponentConnector connector,
- String widgetName) {
-
- List<String> ids = getIDsForConnector(connector);
- String exactClass = connector.getConnection().getConfiguration()
- .getServerSideClassNameForTag(connector.getTag());
- if (!ids.contains(exactClass)) {
- ids.add(exactClass);
- }
-
- List<Integer> widgetTags = new ArrayList<>();
- widgetTags.addAll(getTags(widgetName));
-
- if (widgetTags.isEmpty()) {
- widgetTags.addAll(getTags("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.isEmpty()) {
- 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");
-
- }
-
- /**
- * Gets the tags for server side class name. Also includes tags for older
- * components in v7 package.
- *
- * @param widgetName
- * the server side class name for widget
- * @return list of tags
- */
- private List<Integer> getTags(String widgetName) {
- List<Integer> widgetTags = new ArrayList<>();
- Arrays.stream(client.getConfiguration()
- .getTagsForServerSideClassName(getFullClassName(widgetName)))
- .forEach(widgetTags::add);
- if (widgetName.startsWith("com.vaadin.ui")) {
- Arrays.stream(client.getConfiguration()
- .getTagsForServerSideClassName(getFullClassName(widgetName
- .replace("com.vaadin.ui", "com.vaadin.v7.ui"))))
- .forEach(widgetTags::add);
- }
- return widgetTags;
- }
-
- /**
- * Extracts the name of the widget class from a path fragment
- *
- * @param pathFragment
- * the path fragment
- * @return the name of the widget class.
- */
- private String getWidgetName(String pathFragment) {
- String widgetName = pathFragment;
- int ixBracket = pathFragment.indexOf('[');
- if (ixBracket >= 0) {
- widgetName = pathFragment.substring(0, ixBracket);
- }
- return widgetName;
- }
-
- /**
- * Splits off the first path fragment from a path and returns an array of
- * two elements, where the first element is the first path fragment and the
- * second element is the rest of the path (all remaining path fragments
- * untouched).
- *
- * @param path
- * The path to split.
- * @return An array of two elements: The first path fragment and the rest of
- * the path.
- */
- private String[] splitFirstFragmentFromTheRest(String path) {
- int ixOfSlash = LocatorUtil.indexOfIgnoringQuoted(path, '/');
- if (ixOfSlash > 0) {
- return new String[] { path.substring(0, ixOfSlash),
- path.substring(ixOfSlash) };
- }
- return new String[] { path };
- }
-
- 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) {
-
- LinkedHashSet<T> set = new LinkedHashSet<>(list);
- list.clear();
- list.addAll(set);
- return list;
- }
-
- private boolean tagsMatch(List<Integer> targets, Integer tag) {
- for (int i = 0; i < targets.size(); ++i) {
- if (targets.get(i).equals(tag)) {
- return true;
- }
- }
-
- try {
- return tagsMatch(targets,
- client.getConfiguration().getParentTag(tag));
- } catch (Exception e) {
- return false;
- }
- }
- }
|