View Javadoc

1   package com.atlassian.pageobjects.elements;
2   
3   import com.atlassian.pageobjects.binder.PostInjectionProcessor;
4   import com.atlassian.pageobjects.util.InjectUtils;
5   import com.google.common.base.Function;
6   import com.google.common.collect.Lists;
7   import com.google.common.collect.Maps;
8   import org.apache.commons.lang.StringUtils;
9   import org.openqa.selenium.By;
10  
11  import javax.inject.Inject;
12  import java.lang.reflect.Field;
13  import java.util.Iterator;
14  import java.util.List;
15  import java.util.Map;
16  
17  import static com.atlassian.pageobjects.util.InjectUtils.forEachFieldWithAnnotation;
18  import static com.google.common.base.Preconditions.checkArgument;
19  import static com.google.common.collect.Lists.transform;
20  
21  /**
22   * Find fields marked with @ElementBy annotation and set them to instances of WebDriverElement
23   */
24  public class ElementByPostInjectionProcessor implements PostInjectionProcessor
25  {
26      @Inject
27      PageElementFinder finder;
28  
29      public <T> T process(T pageObject)
30      {
31          injectElements(pageObject);
32          return pageObject;
33      }
34  
35      private void injectElements(final Object instance)
36      {
37          final Map<String,Object> populatedFields = Maps.newHashMap();
38          final List<Field> childFields = Lists.newArrayList();
39          forEachFieldWithAnnotation(instance, ElementBy.class, new InjectUtils.FieldVisitor<ElementBy>()
40          {
41              public void visit(Field field, ElementBy annotation)
42              {
43                  // Create the element to inject
44                  if (StringUtils.isNotEmpty(annotation.within()))
45                  {
46                      childFields.add(field);
47                  }
48                  else
49                  {
50                      final Object result = createAndInject(field, annotation, instance, getFinder(instance));
51                      populatedFields.put(field.getName(), result);
52                  }
53              }
54          });
55          int previousChildSize;
56          do
57          {
58              previousChildSize = childFields.size();
59              for (Iterator<Field> childFieldIterator = childFields.iterator(); childFieldIterator.hasNext();)
60              {
61                  final Field childField = childFieldIterator.next();
62                  final ElementBy elementBy = childField.getAnnotation(ElementBy.class);
63                  final Object parent = populatedFields.get(elementBy.within());
64                  if (parent != null && parent instanceof PageElementFinder)
65                  {
66                      final Object result = createAndInject(childField, elementBy, instance, (PageElementFinder)parent);
67                      populatedFields.put(childField.getName(), result);
68                      childFieldIterator.remove();
69                  }
70                  else if (parent != null)
71                  {
72                      throw new IllegalStateException("@ElementBy for field " + childField.getName() + " defines parent '"
73                       + elementBy.within() + "' whose value is of type '" + parent.getClass().getName() + "'. Parent "
74                       + "fields must be strictly instances of '" + PageElementFinder.class.getName() + "' (usually "
75                      + " that would be '" + PageElement.class.getName() + "')");
76                  }
77  
78              }
79          }
80          while (!childFields.isEmpty() && childFields.size() < previousChildSize);
81          if (!childFields.isEmpty())
82          {
83              throw new IllegalStateException("Could not find parents for fields "
84                      + transform(childFields, FieldToName.INSTANCE) + " annotated with @ElementBy in <" + instance
85                      + ">. Please verify the problematic fields in the page object class");
86          }
87      }
88  
89      /**
90       * If the object we're injecting into implements {@link com.atlassian.pageobjects.elements.PageElementFinder},
91       * we let it do the job! Otherwise we use global finder.
92       *
93       * @param instance instance we're injecting into
94       * @return page element finder to use
95       */
96      private PageElementFinder getFinder(Object instance)
97      {
98          if (instance instanceof PageElementFinder)
99          {
100             return (PageElementFinder) instance;
101         }
102         else
103         {
104             return finder;
105         }
106     }
107 
108     private Object createAndInject(Field field, ElementBy annotation, Object instance, PageElementFinder elementFinder)
109     {
110         Object value;
111         if (isIterable(field))
112         {
113             value = createIterable(field, annotation, elementFinder);
114         } else
115         {
116             value = createPageElement(field, annotation, elementFinder);
117         }
118 
119         // Assign the value
120         try
121         {
122             field.setAccessible(true);
123             field.set(instance, value);
124         } catch (IllegalAccessException e)
125         {
126             throw new RuntimeException(e);
127         }
128         return value;
129     }
130 
131     private Iterable<? extends PageElement> createIterable(Field field, ElementBy elementBy, PageElementFinder elementFinder)
132     {
133         By by = getSelector(elementBy);
134         Class<? extends PageElement> fieldType = getFieldType(field, elementBy);
135         return new PageElementIterableImpl(elementFinder, fieldType, by, elementBy.timeoutType());
136     }
137 
138     private PageElement createPageElement(Field field, ElementBy elementBy, PageElementFinder elementFinder)
139     {
140         By by = getSelector(elementBy);
141         Class<? extends PageElement> fieldType = getFieldType(field, elementBy);
142         return elementFinder.find(by, fieldType, elementBy.timeoutType());
143     }
144 
145     private By getSelector(ElementBy elementBy)
146     {
147         By by;
148 
149         if (elementBy.className().length() > 0)
150         {
151             by = By.className(elementBy.className());
152         }
153         else if (elementBy.id().length() > 0)
154         {
155             by = By.id(elementBy.id());
156         }
157         else if (elementBy.linkText().length() > 0)
158         {
159             by = By.linkText(elementBy.linkText());
160         }
161         else if (elementBy.partialLinkText().length() > 0)
162         {
163             by = By.partialLinkText(elementBy.partialLinkText());
164         }
165         else if(elementBy.cssSelector().length() >0)
166         {
167             by = By.cssSelector(elementBy.cssSelector());
168         }
169         else if(elementBy.name().length() > 0)
170         {
171             by = By.name(elementBy.name());
172         }
173         else if(elementBy.xpath().length() > 0)
174         {
175             by = By.xpath(elementBy.xpath());
176         }
177         else if(elementBy.tagName().length() > 0)
178         {
179             by = By.tagName(elementBy.tagName());
180         }
181         else
182         {
183             throw new IllegalArgumentException("No selector found");
184         }
185         return by;
186     }
187 
188     /**
189      * Returns the type of requested PageElement: it is the type of the field
190      * overridden by the attribute 'pageElementClass' in the annotation.
191      * 
192      * @param field the field to inject
193      * @param annotation the ElementBy annotation on the field 
194      * @return the requested PageElement
195      * @throws IllegalArgumentException if the field or the annotation don't extend PageElement
196      */
197     @SuppressWarnings({"unchecked"})
198     private Class<? extends PageElement> getFieldType(Field field, ElementBy annotation) {
199         Class<?> fieldType = field.getType();
200 
201         // Check whether the annotation overrides this type
202         Class<? extends PageElement> annotatedType = annotation.pageElementClass();
203         // Checks whether annotatedType is more specific than PageElement
204         if (Iterable.class.isAssignableFrom(fieldType))
205         {
206             return annotatedType;
207         }
208         else if (annotatedType != PageElement.class)
209         {
210             checkArgument(fieldType.isAssignableFrom(annotatedType), "Field type " + annotatedType.getName()
211                     + " does not implement " + fieldType.getName());
212             return annotatedType;
213         }
214 
215         checkArgument(PageElement.class.isAssignableFrom(fieldType), "Field type " + fieldType.getName()
216                 + " does not implement " + PageElement.class.getName());
217         return (Class<? extends PageElement>) fieldType;
218     }
219     
220     
221     private boolean isIterable(Field field) {
222         return Iterable.class.isAssignableFrom(field.getType());
223     }
224 
225     private static enum FieldToName implements Function<Field,String>
226     {
227         INSTANCE;
228 
229         public String apply(Field from)
230         {
231             return from.getName();
232         }
233     }
234 
235 
236 }