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
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
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
91
92
93
94
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
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
190
191
192
193
194
195
196
197 @SuppressWarnings({"unchecked"})
198 private Class<? extends PageElement> getFieldType(Field field, ElementBy annotation) {
199 Class<?> fieldType = field.getType();
200
201
202 Class<? extends PageElement> annotatedType = annotation.pageElementClass();
203
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 }