1   package com.atlassian.selenium.keyboard;
2   
3   import com.atlassian.selenium.AbstractSeleniumDriver;
4   import com.atlassian.selenium.SeleniumClient;
5   import com.atlassian.selenium.SeleniumKeyHandler;
6   import com.atlassian.webtest.ui.keys.CharacterKey;
7   import com.atlassian.webtest.ui.keys.CharacterKeySequence;
8   import com.atlassian.webtest.ui.keys.Key;
9   import com.atlassian.webtest.ui.keys.KeyEventAware;
10  import com.atlassian.webtest.ui.keys.KeyEventType;
11  import com.atlassian.webtest.ui.keys.KeySequence;
12  import com.atlassian.webtest.ui.keys.KeySequenceBuilder;
13  import com.atlassian.webtest.ui.keys.Sequences;
14  import com.atlassian.webtest.ui.keys.SpecialKey;
15  import com.atlassian.webtest.ui.keys.SpecialKeys;
16  import com.atlassian.webtest.ui.keys.SpecialKeysSequence;
17  import com.atlassian.webtest.ui.keys.TypeMode;
18  import com.atlassian.webtest.ui.keys.TypeModeAware;
19  import com.google.common.collect.Maps;
20  
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import static com.google.common.base.Preconditions.checkNotNull;
29  
30  /**
31   * Utility to type into Selenium components.
32   *
33   * @author Dariusz Kordonski
34   */
35  public final class SeleniumTypeWriter extends AbstractSeleniumDriver
36  {
37      private final String target;
38      private final TypeMode defaultMode;
39      private final Map<TypeMode, Typer> typers = Maps.newHashMap();
40  
41      public SeleniumTypeWriter(SeleniumClient client, String target, TypeMode defaultMode)
42      {
43          super(client);
44          this.target = checkNotNull(target, "target");
45          validateMode(checkNotNull(defaultMode, "defaultMode"));
46          this.defaultMode = defaultMode;
47          initTypers();
48      }
49  
50      private void validateMode(TypeMode defaultMode)
51      {
52          if (defaultMode == TypeMode.DEFAULT)
53          {
54              throw new IllegalArgumentException("DEFAULT TypeMode not allowed");
55          }
56      }
57  
58      private void initTypers()
59      {
60          typers.put(TypeMode.INSERT, new InsertingTyper());
61          typers.put(TypeMode.INSERT_WITH_EVENT, new LastCharEventTyper());
62          typers.put(TypeMode.TYPE, new FullEventTyper());
63      }
64  
65      public SeleniumTypeWriter type(KeySequence sequence)
66      {
67          typers.get(resolveMode(sequence)).type(sequence);
68          return this;
69      }
70  
71      public SeleniumTypeWriter clear()
72      {
73          client.type(target, "");
74          return this;
75      }
76  
77      private TypeMode resolveMode(KeySequence sequence)
78      {
79          if (isTypeModeAware(sequence))
80          {
81              if (asTypeModeAware(sequence).typeMode() == TypeMode.DEFAULT)
82              {
83                  return defaultMode;
84              }
85              return asTypeModeAware(sequence).typeMode();
86          }
87          return defaultMode;
88      }
89  
90      private boolean isTypeModeAware(KeySequence sequence)
91      {
92          return sequence instanceof TypeModeAware;
93      }
94  
95      private TypeModeAware asTypeModeAware(KeySequence seq)
96      {
97          return (TypeModeAware) seq;
98      }
99  
100     private void setUpModifiers(KeySequence sequence)
101     {
102         for (SeleniumModifierKey key : seleniumModifiersFor(sequence))
103         {
104             key.keyDown(client);
105         }
106     }
107 
108     private void tearDownModifiers(KeySequence sequence)
109     {
110         for (SeleniumModifierKey key : seleniumReversedModifiersFor(sequence))
111         {
112             key.keyUp(client);
113         }
114     }
115 
116     private Collection<SeleniumModifierKey> seleniumModifiersFor(KeySequence sequence)
117     {
118         return SeleniumModifierKey.forKeys(sequence.withPressed());
119     }
120 
121     private Collection<SeleniumModifierKey> seleniumReversedModifiersFor(KeySequence sequence)
122     {
123         List<SeleniumModifierKey> answer = new ArrayList<SeleniumModifierKey>(seleniumModifiersFor(sequence));
124         Collections.reverse(answer);
125         return answer;
126     }
127 
128     private void insert(CharacterKeySequence sequence)
129     {
130         setUpModifiers(sequence);
131         try
132         {
133             bareInsert(sequence);
134         }
135         finally
136         {
137             tearDownModifiers(sequence);
138         }
139     }
140 
141     private void bareInsert(CharacterKeySequence sequence)
142     {
143         client.type(target, existingValue() + sequence.string());
144     }
145 
146     private void fullEventType(CharacterKeySequence sequence)
147     {
148         setUpModifiers(sequence);
149         try
150         {
151             bareFullEventType(sequence);
152         }
153         finally
154         {
155             tearDownModifiers(sequence);
156         }
157     }
158 
159     private void bareFullEventType(CharacterKeySequence sequence)
160     {
161         new SeleniumKeyHandler(client, target, extractKeyEvents(sequence), false).typeWithFullKeyEvents(sequence.string());
162     }
163 
164     private void lastCharType(CharacterKeySequence sequence)
165     {
166         if (sequence.string().length() == 0)
167         {
168             bareInsert(sequence);
169             return;
170         }
171         setUpModifiers(sequence);
172         try
173         {
174             bareLastCharType(sequence);
175         }
176         finally
177         {
178             tearDownModifiers(sequence);
179         }
180     }
181 
182     private void bareLastCharType(CharacterKeySequence sequence)
183     {
184         final String toType = sequence.string();
185         bareInsert(withoutLastChar(toType));
186         client.simulateKeyPressForCharacter(target, lastChar(toType).string().charAt(0), extractKeyEvents(sequence));
187     }
188 
189     private CharacterKeySequence withoutLastChar(String toType)
190     {
191         return asCharacterSequence(Sequences.chars(toType.substring(0, toType.length()-1)));
192     }
193 
194     private CharacterKeySequence lastChar(String toType)
195     {
196         return asCharacterSequence(Sequences.chars(toType.substring(toType.length()-1)));
197     }
198 
199     private void typeSpecialKeys(KeySequence sequence)
200     {
201         if (sequence instanceof SpecialKeys)
202         {
203             typeSpecialKey((SpecialKey) sequence, extractKeyEvents(sequence));
204         }
205         else if (sequence instanceof SpecialKeysSequence)
206         {
207             final SpecialKeysSequence specialKeysSequence = asSpecialKeysSequence(sequence);
208             Collection<KeyEventType> events = specialKeysSequence.keyEvents();
209             for (SpecialKeys specialKey : specialKeysSequence.specialKeys())
210             {
211                 typeSpecialKey(specialKey, events);
212             }
213         }
214         else
215         {
216             throw new AssertionError("Should not invoke .typeSpecialKeys for: " + sequence);
217         }
218     }
219 
220     private void typeSpecialKey(SpecialKey key, Collection<KeyEventType> events)
221     {
222         client.simulateKeyPressForSpecialKey(target, SeleniumSpecialKeys.forKey(key).keyCode(), events);
223     }
224 
225     private void insertMixedSequence(KeySequence sequence)
226     {
227         setUpModifiers(sequence);
228         for (KeySequence subSequence : divide(sequence))
229         {
230             if (subSequence instanceof CharacterKeySequence)
231             {
232                 bareInsert(asCharacterSequence(subSequence));
233             }
234             else if (isSpecialKeys(subSequence))
235             {
236                 typeSpecialKeys(subSequence);
237             }
238             else
239             {
240                 throw new AssertionError("WTF is that? " + sequence);
241             }
242         }
243         tearDownModifiers(sequence);
244     }
245 
246     private void lastCharTypeMixedSequence(KeySequence sequence)
247     {
248         setUpModifiers(sequence);
249         List<KeySequence> subSequences = divide(sequence);
250         for (KeySequence subSequence : subSequences.subList(0, subSequences.size()-1))
251         {
252             if (subSequence instanceof CharacterKeySequence)
253             {
254                 bareInsert(asCharacterSequence(subSequence));
255             }
256             else if (isSpecialKeys(subSequence))
257             {
258                 typeSpecialKeys(subSequence);
259             }
260             else
261             {
262                 throw new AssertionError("WTF is that? " + sequence);
263             }
264         }
265         KeySequence last = subSequences.get(subSequences.size()-1);
266         if (last instanceof CharacterKeySequence)
267         {
268             bareLastCharType(asCharacterSequence(last));
269         }
270         else if (isSpecialKeys(last))
271         {
272             typeSpecialKeys(last);
273         }
274         else
275         {
276             throw new AssertionError("WTF is that? " + sequence);
277         }
278         tearDownModifiers(sequence);
279     }
280 
281     private void fullEventTypeMixedSequence(KeySequence sequence)
282     {
283         setUpModifiers(sequence);
284         for (KeySequence subSequence : divide(sequence))
285         {
286             if (subSequence instanceof CharacterKeySequence)
287             {
288                 bareFullEventType(asCharacterSequence(subSequence));
289             }
290             else if (isSpecialKeys(subSequence))
291             {
292                 typeSpecialKeys(subSequence);
293             }
294             else
295             {
296                 throw new AssertionError("WTF is that? " + subSequence);
297             }
298         }
299         tearDownModifiers(sequence);
300     }
301 
302     private String existingValue()
303     {
304         final String existingValue = client.getValue(target);
305         return existingValue != null ? existingValue : "";
306     }
307 
308     private List<KeySequence> divide(KeySequence sequence)
309     {
310         List<KeySequence> answer = new ArrayList<KeySequence>();
311         KeySequenceBuilder charBuilder = newBuilderFrom(sequence);
312         KeySequenceBuilder keysBuilder = newBuilderFrom(sequence);
313         for (Key key : sequence.keys())
314         {
315             if (key instanceof CharacterKey)
316             {
317                 keysBuilder = addCollected(answer, keysBuilder);
318                 charBuilder.append(((CharacterKey)key).string());
319             }
320             else if (key instanceof SpecialKey)
321             {
322                 charBuilder = addCollected(answer, charBuilder);
323                 keysBuilder.append(key);
324             }
325             else
326             {
327                 throw new IllegalArgumentException("Not suported: " + key);
328             }
329         }
330         addCollected(answer, charBuilder);
331         addCollected(answer, keysBuilder);
332         return answer;
333     }
334 
335     private CharacterKeySequence asCharacterSequence(KeySequence seq)
336     {
337         return (CharacterKeySequence) seq;
338     }
339 
340     private SpecialKeys asSppecialKeys(KeySequence seq)
341     {
342         return (SpecialKeys) seq;
343     }
344 
345     private KeySequenceBuilder addCollected(List<KeySequence> answer, KeySequenceBuilder charBuilder)
346     {
347         if (charBuilder.size() > 0)
348         {
349             answer.add(charBuilder.build());
350             return new KeySequenceBuilder().keyEvents(charBuilder.keyEvents());
351         }
352         return charBuilder;
353     }
354 
355     private KeySequenceBuilder newBuilderFrom(KeySequence sequence)
356     {
357         return new KeySequenceBuilder().keyEvents(extractKeyEvents(sequence));
358     }
359 
360     private Set<KeyEventType> extractKeyEvents(KeySequence sequence)
361     {
362         if (sequence instanceof KeyEventAware)
363         {
364             return ((KeyEventAware)sequence).keyEvents();
365         }
366         return KeyEventType.ALL;
367     }
368 
369     private boolean isSpecialKeys(KeySequence sequence)
370     {
371         return sequence instanceof SpecialKeys || sequence instanceof SpecialKeysSequence;
372     }
373 
374     private SpecialKeysSequence asSpecialKeysSequence(final KeySequence sequence)
375     {
376         return (SpecialKeysSequence) sequence;
377     }
378 
379     private interface Typer
380     {
381         void type(KeySequence sequence);
382     }
383 
384     private class InsertingTyper implements Typer
385     {
386         public void type(KeySequence sequence)
387         {
388             if (sequence instanceof CharacterKeySequence)
389             {
390                 insert((CharacterKeySequence)sequence);
391             }
392             else if (isSpecialKeys(sequence))
393             {
394                 typeSpecialKeys(sequence);
395             }
396             else
397             {
398                 insertMixedSequence(sequence);
399             }
400         }
401     }
402 
403     private class LastCharEventTyper implements Typer
404     {
405         public void type(KeySequence sequence)
406         {
407             if (sequence instanceof CharacterKeySequence)
408             {
409                 lastCharType((CharacterKeySequence)sequence);
410             }
411             else if (isSpecialKeys(sequence))
412             {
413                 typeSpecialKeys(sequence);
414             }
415             else
416             {
417                 lastCharTypeMixedSequence(sequence);
418             }
419         }
420     }
421 
422     private class FullEventTyper implements Typer
423     {
424         public void type(KeySequence sequence)
425         {
426             if (sequence instanceof CharacterKeySequence)
427             {
428                 fullEventType((CharacterKeySequence)sequence);
429             }
430             else if (isSpecialKeys(sequence))
431             {
432                 typeSpecialKeys(sequence);
433             }
434             else
435             {
436                 fullEventTypeMixedSequence(sequence);
437             }
438         }
439     }
440 
441 
442 }