123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637 |
- /*
- * Copyright 2000-2013 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.Iterator;
- import java.util.List;
-
- import com.google.gwt.core.client.JavaScriptObject;
- import com.google.gwt.user.client.DOM;
- import com.google.gwt.user.client.Element;
- import com.google.gwt.user.client.ui.HasWidgets;
- import com.google.gwt.user.client.ui.RootPanel;
- import com.google.gwt.user.client.ui.Widget;
- import com.vaadin.client.ComponentConnector;
- import com.vaadin.client.ConnectorMap;
- import com.vaadin.client.ServerConnector;
- import com.vaadin.client.Util;
- import com.vaadin.client.VCaption;
- import com.vaadin.client.ui.SubPartAware;
- import com.vaadin.client.ui.VCssLayout;
- import com.vaadin.client.ui.VGridLayout;
- import com.vaadin.client.ui.VOverlay;
- import com.vaadin.client.ui.VTabsheetPanel;
- import com.vaadin.client.ui.VUI;
- import com.vaadin.client.ui.VWindow;
- import com.vaadin.client.ui.orderedlayout.Slot;
- import com.vaadin.client.ui.orderedlayout.VAbstractOrderedLayout;
- import com.vaadin.client.ui.window.WindowConnector;
- import com.vaadin.shared.AbstractComponentState;
- import com.vaadin.shared.Connector;
- import com.vaadin.shared.communication.SharedState;
-
- /**
- * The LegacyLocatorStrategy class handles the legacy locator syntax that was
- * introduced in version 5.4 of the framework. The legacy locator strategy is
- * always used if no other strategy claims responsibility for a locator string.
- *
- * @since 7.2
- * @author Vaadin Ltd
- */
- public class LegacyLocatorStrategy implements LocatorStrategy {
- private final ComponentLocator componentLocator;
- /**
- * Separator used in the String locator between a parent and a child widget.
- */
- static final String PARENTCHILD_SEPARATOR = "/";
- /**
- * Separator used in the String locator between the part identifying the
- * containing widget and the part identifying the target element within the
- * widget.
- */
- static final String SUBPART_SEPARATOR = "#";
- /**
- * String that identifies the root panel when appearing first in the String
- * locator.
- */
- static final String ROOT_ID = "Root";
-
- public LegacyLocatorStrategy(ComponentLocator componentLocator) {
- this.componentLocator = componentLocator;
- }
-
- @Override
- public String getPathForElement(Element targetElement) {
- ComponentConnector connector = Util.findPaintable(
- componentLocator.getClient(), targetElement);
-
- Widget w = null;
- if (connector != null) {
- // If we found a Paintable then we use that as reference. We should
- // find the Paintable for all but very special cases (like
- // overlays).
- w = connector.getWidget();
-
- /*
- * Still if the Paintable contains a widget that implements
- * SubPartAware, we want to use that as a reference
- */
- Widget targetParent = findParentWidget(targetElement, w);
- while (targetParent != w && targetParent != null) {
- if (targetParent instanceof SubPartAware) {
- /*
- * The targetParent widget is a child of the Paintable and
- * the first parent (of the targetElement) that implements
- * SubPartAware
- */
- w = targetParent;
- break;
- }
- targetParent = targetParent.getParent();
- }
- }
- if (w == null) {
- // Check if the element is part of a widget that is attached
- // directly to the root panel
- RootPanel rootPanel = RootPanel.get();
- int rootWidgetCount = rootPanel.getWidgetCount();
- for (int i = 0; i < rootWidgetCount; i++) {
- Widget rootWidget = rootPanel.getWidget(i);
- if (rootWidget.getElement().isOrHasChild(targetElement)) {
- // The target element is contained by this root widget
- w = findParentWidget(targetElement, rootWidget);
- break;
- }
- }
- if (w != null) {
- // We found a widget but we should still see if we find a
- // SubPartAware implementor (we cannot find the Paintable as
- // there is no link from VOverlay to its paintable/owner).
- Widget subPartAwareWidget = findSubPartAwareParentWidget(w);
- if (subPartAwareWidget != null) {
- w = subPartAwareWidget;
- }
- }
- }
-
- if (w == null) {
- // Containing widget not found
- return null;
- }
-
- // Determine the path for the target widget
- String path = getPathForWidget(w);
- if (path == null) {
- /*
- * No path could be determined for the target widget. Cannot create
- * a locator string.
- */
- return null;
- }
-
- // The parent check is a work around for Firefox 15 which fails to
- // compare elements properly (#9534)
- if (w.getElement() == targetElement) {
- /*
- * We are done if the target element is the root of the target
- * widget.
- */
- return path;
- } else if (w instanceof SubPartAware) {
- /*
- * If the widget can provide an identifier for the targetElement we
- * let it do that
- */
- String elementLocator = ((SubPartAware) w)
- .getSubPartName(targetElement);
- if (elementLocator != null) {
- return path + LegacyLocatorStrategy.SUBPART_SEPARATOR
- + elementLocator;
- }
- }
- /*
- * If everything else fails we use the DOM path to identify the target
- * element
- */
- String domPath = getDOMPathForElement(targetElement, w.getElement());
- if (domPath == null) {
- return path;
- } else {
- return path + domPath;
- }
- }
-
- @Override
- public Element getElementByPath(String path) {
- /*
- * Path is of type "targetWidgetPath#componentPart" or
- * "targetWidgetPath".
- */
- String parts[] = path.split(LegacyLocatorStrategy.SUBPART_SEPARATOR, 2);
- String widgetPath = parts[0];
- Widget w = getWidgetFromPath(widgetPath);
- if (w == null || !Util.isAttachedAndDisplayed(w)) {
- return null;
- }
-
- if (parts.length == 1) {
- int pos = widgetPath.indexOf("domChild");
- if (pos == -1) {
- return w.getElement();
- }
-
- // Contains dom reference to a sub element of the widget
- String subPath = widgetPath.substring(pos);
- return getElementByDOMPath(w.getElement(), subPath);
- } else if (parts.length == 2) {
- if (w instanceof SubPartAware) {
- return ((SubPartAware) w).getSubPartElement(parts[1]);
- }
- }
- return null;
- }
-
- /**
- * Finds the first widget in the hierarchy (moving upwards) that implements
- * SubPartAware. Returns the SubPartAware implementor or null if none is
- * found.
- *
- * @param w
- * The widget to start from. This is returned if it implements
- * SubPartAware.
- * @return The first widget (upwards in hierarchy) that implements
- * SubPartAware or null
- */
- Widget findSubPartAwareParentWidget(Widget w) {
-
- while (w != null) {
- if (w instanceof SubPartAware) {
- return w;
- }
- w = w.getParent();
- }
- return null;
- }
-
- /**
- * Returns the first widget found when going from {@code targetElement}
- * upwards in the DOM hierarchy, assuming that {@code ancestorWidget} is a
- * parent of {@code targetElement}.
- *
- * @param targetElement
- * @param ancestorWidget
- * @return The widget whose root element is a parent of
- * {@code targetElement}.
- */
- private Widget findParentWidget(Element targetElement, Widget ancestorWidget) {
- /*
- * As we cannot resolve Widgets from the element we start from the
- * widget and move downwards to the correct child widget, as long as we
- * find one.
- */
- if (ancestorWidget instanceof HasWidgets) {
- for (Widget w : ((HasWidgets) ancestorWidget)) {
- if (w.getElement().isOrHasChild(targetElement)) {
- return findParentWidget(targetElement, w);
- }
- }
- }
-
- // No children found, this is it
- return ancestorWidget;
- }
-
- /**
- * Locates an element based on a DOM path and a base element.
- *
- * @param baseElement
- * The base element which the path is relative to
- * @param path
- * String locator (consisting of domChild[x] parts) that
- * identifies the element
- * @return The element identified by path, relative to baseElement or null
- * if the element could not be found.
- */
- private Element getElementByDOMPath(Element baseElement, String path) {
- String parts[] = path.split(PARENTCHILD_SEPARATOR);
- Element element = baseElement;
-
- for (String part : parts) {
- if (part.startsWith("domChild[")) {
- String childIndexString = part.substring("domChild[".length(),
- part.length() - 1);
-
- if (Util.findWidget(baseElement, null) instanceof VAbstractOrderedLayout) {
- if (element.hasChildNodes()) {
- Element e = element.getFirstChildElement().cast();
- String cn = e.getClassName();
- if (cn != null
- && (cn.equals("v-expand") || cn
- .contains("v-has-caption"))) {
- element = e;
- }
- }
- }
-
- try {
- int childIndex = Integer.parseInt(childIndexString);
- element = DOM.getChild(element, childIndex);
- } catch (Exception e) {
- return null;
- }
-
- if (element == null) {
- return null;
- }
-
- }
- }
-
- return element;
- }
-
- /**
- * Generates a String locator using domChild[x] parts for the element
- * relative to the baseElement.
- *
- * @param element
- * The target element
- * @param baseElement
- * The starting point for the locator. The generated path is
- * relative to this element.
- * @return A String locator that can be used to locate the target element
- * using
- * {@link #getElementByDOMPath(com.google.gwt.user.client.Element, String)}
- * or null if the locator String cannot be created.
- */
- private String getDOMPathForElement(Element element, Element baseElement) {
- Element e = element;
- String path = "";
- while (true) {
- int childIndex = -1;
- Element siblingIterator = e;
- while (siblingIterator != null) {
- childIndex++;
- siblingIterator = siblingIterator.getPreviousSiblingElement()
- .cast();
- }
-
- path = PARENTCHILD_SEPARATOR + "domChild[" + childIndex + "]"
- + path;
-
- JavaScriptObject parent = e.getParentElement();
- if (parent == null) {
- return null;
- }
- // The parent check is a work around for Firefox 15 which fails to
- // compare elements properly (#9534)
- if (parent == baseElement) {
- break;
- }
-
- e = parent.cast();
- }
-
- return path;
- }
-
- /**
- * Creates a locator String for the given widget. The path can be used to
- * locate the widget using {@link #getWidgetFromPath(String)}.
- * <p/>
- * Returns null if no path can be determined for the widget or if the widget
- * is null.
- *
- * @param w
- * The target widget
- * @return A String locator for the widget
- */
- private String getPathForWidget(Widget w) {
- if (w == null) {
- return null;
- }
- String elementId = w.getElement().getId();
- if (elementId != null && !elementId.isEmpty()
- && !elementId.startsWith("gwt-uid-")) {
- // Use PID_S+id if the user has set an id but do not use it for auto
- // generated id:s as these might not be consistent
- return "PID_S" + elementId;
- } else if (w instanceof VUI) {
- return "";
- } else if (w instanceof VWindow) {
- Connector windowConnector = ConnectorMap.get(
- componentLocator.getClient()).getConnector(w);
- List<WindowConnector> subWindowList = componentLocator.getClient()
- .getUIConnector().getSubWindows();
- int indexOfSubWindow = subWindowList.indexOf(windowConnector);
- return PARENTCHILD_SEPARATOR + "VWindow[" + indexOfSubWindow + "]";
- } else if (w instanceof RootPanel) {
- return ROOT_ID;
- }
-
- Widget parent = w.getParent();
-
- String basePath = getPathForWidget(parent);
- if (basePath == null) {
- return null;
- }
- String simpleName = Util.getSimpleName(w);
-
- /*
- * Check if the parent implements Iterable. At least VPopupView does not
- * implement HasWdgets so we cannot check for that.
- */
- if (!(parent instanceof Iterable<?>)) {
- // Parent does not implement Iterable so we cannot find out which
- // child this is
- return null;
- }
-
- Iterator<?> i = ((Iterable<?>) parent).iterator();
- int pos = 0;
- while (i.hasNext()) {
- Object child = i.next();
- if (child == w) {
- return basePath + PARENTCHILD_SEPARATOR + simpleName + "["
- + pos + "]";
- }
- String simpleName2 = Util.getSimpleName(child);
- if (simpleName.equals(simpleName2)) {
- pos++;
- }
- }
-
- return null;
- }
-
- /**
- * Locates the widget based on a String locator.
- *
- * @param path
- * The String locator that identifies the widget.
- * @return The Widget identified by the String locator or null if the widget
- * could not be identified.
- */
- private Widget getWidgetFromPath(String path) {
- Widget w = null;
- String parts[] = path.split(PARENTCHILD_SEPARATOR);
-
- for (int i = 0; i < parts.length; i++) {
- String part = parts[i];
-
- if (part.equals(ROOT_ID)) {
- w = RootPanel.get();
- } else if (part.equals("")) {
- w = componentLocator.getClient().getUIConnector().getWidget();
- } else if (w == null) {
- String id = part;
- // Must be old static pid (PID_S*)
- ServerConnector connector = ConnectorMap.get(
- componentLocator.getClient()).getConnector(id);
- if (connector == null) {
- // Lookup by component id
- // TODO Optimize this
- connector = findConnectorById(componentLocator.getClient()
- .getUIConnector(), id.substring(5));
- }
-
- if (connector instanceof ComponentConnector) {
- w = ((ComponentConnector) connector).getWidget();
- } else {
- // Not found
- return null;
- }
- } else if (part.startsWith("domChild[")) {
- // The target widget has been found and the rest identifies the
- // element
- break;
- } else if (w instanceof Iterable) {
- // W identifies a widget that contains other widgets, as it
- // should. Try to locate the child
- Iterable<?> parent = (Iterable<?>) w;
-
- // Part is of type "VVerticalLayout[0]", split this into
- // VVerticalLayout and 0
- String[] split = part.split("\\[", 2);
- String widgetClassName = split[0];
- String indexString = split[1].substring(0,
- split[1].length() - 1);
- int widgetPosition = Integer.parseInt(indexString);
-
- // AbsolutePanel in GridLayout has been removed -> skip it
- if (w instanceof VGridLayout
- && "AbsolutePanel".equals(widgetClassName)) {
- continue;
- }
-
- // FlowPane in CSSLayout has been removed -> skip it
- if (w instanceof VCssLayout
- && "VCssLayout$FlowPane".equals(widgetClassName)) {
- continue;
- }
-
- // ChildComponentContainer and VOrderedLayout$Slot have been
- // replaced with Slot
- if (w instanceof VAbstractOrderedLayout
- && ("ChildComponentContainer".equals(widgetClassName) || "VOrderedLayout$Slot"
- .equals(widgetClassName))) {
- widgetClassName = "Slot";
- }
-
- if (w instanceof VTabsheetPanel && widgetPosition != 0) {
- // TabSheetPanel now only contains 1 connector => the index
- // is always 0 which indicates the widget in the active tab
- widgetPosition = 0;
- }
- if (w instanceof VOverlay
- && "VCalendarPanel".equals(widgetClassName)) {
- // Vaadin 7.1 adds a wrapper for datefield popups
- parent = (Iterable<?>) ((Iterable) parent).iterator()
- .next();
- }
- /*
- * The new grid and ordered layotus do not contain
- * ChildComponentContainer widgets. This is instead simulated by
- * constructing a path step that would find the desired widget
- * from the layout and injecting it as the next search step
- * (which would originally have found the widget inside the
- * ChildComponentContainer)
- */
- if ((w instanceof VGridLayout)
- && "ChildComponentContainer".equals(widgetClassName)
- && i + 1 < parts.length) {
-
- HasWidgets layout = (HasWidgets) w;
-
- String nextPart = parts[i + 1];
- String[] nextSplit = nextPart.split("\\[", 2);
- String nextWidgetClassName = nextSplit[0];
-
- // Find the n:th child and count the number of children with
- // the same type before it
- int nextIndex = 0;
- for (Widget child : layout) {
- boolean matchingType = nextWidgetClassName.equals(Util
- .getSimpleName(child));
- if (matchingType && widgetPosition == 0) {
- // This is the n:th child that we looked for
- break;
- } else if (widgetPosition < 0) {
- // Error if we're past the desired position without
- // a match
- return null;
- } else if (matchingType) {
- // If this was another child of the expected type,
- // increase the count for the next step
- nextIndex++;
- }
-
- // Don't count captions
- if (!(child instanceof VCaption)) {
- widgetPosition--;
- }
- }
-
- // Advance to the next step, this time checking for the
- // actual child widget
- parts[i + 1] = nextWidgetClassName + '[' + nextIndex + ']';
- continue;
- }
-
- // Locate the child
- Iterator<? extends Widget> iterator;
-
- /*
- * VWindow and VContextMenu workarounds for backwards
- * compatibility
- */
- if (widgetClassName.equals("VWindow")) {
- List<WindowConnector> windows = componentLocator
- .getClient().getUIConnector().getSubWindows();
- List<VWindow> windowWidgets = new ArrayList<VWindow>(
- windows.size());
- for (WindowConnector wc : windows) {
- windowWidgets.add(wc.getWidget());
- }
- iterator = windowWidgets.iterator();
- } else if (widgetClassName.equals("VContextMenu")) {
- return componentLocator.getClient().getContextMenu();
- } else {
- iterator = (Iterator<? extends Widget>) parent.iterator();
- }
-
- boolean ok = false;
-
- // Find the widgetPosition:th child of type "widgetClassName"
- while (iterator.hasNext()) {
-
- Widget child = iterator.next();
- String simpleName2 = Util.getSimpleName(child);
-
- if (!widgetClassName.equals(simpleName2)
- && child instanceof Slot) {
- /*
- * Support legacy tests without any selector for the
- * Slot widget (i.e. /VVerticalLayout[0]/VButton[0]) by
- * directly checking the stuff inside the slot
- */
- child = ((Slot) child).getWidget();
- simpleName2 = Util.getSimpleName(child);
- }
-
- if (widgetClassName.equals(simpleName2)) {
- if (widgetPosition == 0) {
- w = child;
- ok = true;
- break;
- }
- widgetPosition--;
-
- }
- }
-
- if (!ok) {
- // Did not find the child
- return null;
- }
- } else {
- // W identifies something that is not a "HasWidgets". This
- // should not happen as all widget containers should implement
- // HasWidgets.
- return null;
- }
- }
-
- return w;
- }
-
- private ServerConnector findConnectorById(ServerConnector root, String id) {
- SharedState state = root.getState();
- if (state instanceof AbstractComponentState
- && id.equals(((AbstractComponentState) state).id)) {
- return root;
- }
- for (ServerConnector child : root.getChildren()) {
- ServerConnector found = findConnectorById(child, id);
- if (found != null) {
- return found;
- }
- }
-
- return null;
- }
- }
|