1 /*
   2  * Copyright (c) 2007, 2018 Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.jemmy.support.test;
  24 
  25 import java.util.ArrayList;
  26 import java.util.Collections;
  27 import java.util.LinkedList;
  28 import java.util.List;
  29 import javax.annotation.processing.ProcessingEnvironment;
  30 import javax.lang.model.element.*;
  31 import javax.lang.model.type.DeclaredType;
  32 import javax.lang.model.type.TypeKind;
  33 import javax.lang.model.type.TypeMirror;
  34 import org.jemmy.control.*;
  35 import org.jemmy.dock.DefaultWrapper;
  36 import org.jemmy.dock.*;
  37 import org.jemmy.interfaces.ControlInterface;
  38 import org.jemmy.interfaces.Drag;
  39 import org.jemmy.interfaces.Keyboard;
  40 import org.jemmy.interfaces.Mouse;
  41 
  42 /**
  43  *
  44  * @author shura
  45  */
  46 public class ControlSupport {
  47 
  48     private final DeclaredType wrap;
  49     //this could not be final 'cause an order of wraps is not defined
  50     //and hence inheritance is figured out later 
  51     private DeclaredType superWrap = null;
  52     private final DeclaredType control;
  53     private final List<ControlInterfaceSupport> interfaces = new LinkedList<ControlInterfaceSupport>();
  54     private final List<LookupSupport> objectLookups = new LinkedList<LookupSupport>();
  55     private final List<MappedPropertySupport> properties = new LinkedList<MappedPropertySupport>();
  56     private final List<DirectPropertySupport> propertyMethods = new LinkedList<DirectPropertySupport>();
  57     private final List<ControlInterfaceSupport.ShortcutSupport> shortcuts = new LinkedList<ControlInterfaceSupport.ShortcutSupport>();
  58     private final DeclaredType preferredParent;
  59     private final DefaultParentSupport defaultParent;
  60     private final DefaultWrapperSupport defaultWrapper;
  61     private final DockInfo dockInfo;
  62     private final String dockName;
  63     private final boolean placeholder;
  64     private final DeclaredType ci, w;
  65     private final boolean multipleCriteria;
  66 
  67     private ControlSupport(ProcessingEnvironment env) {
  68         ci = null;
  69         w = null;
  70         placeholder = true;
  71         this.wrap = (DeclaredType) env.getElementUtils().
  72                 getTypeElement(Wrap.class.getName()).asType();
  73         this.control = (DeclaredType) env.getElementUtils().
  74                 getTypeElement(Object.class.getName()).asType();
  75         defaultParent = null;
  76         preferredParent = null;
  77         defaultWrapper = null;
  78         dockInfo = null;
  79         multipleCriteria = false;
  80         dockName = Dock.class.getName();
  81         //TODO while implementing external wrap superclasses learn to pull interfaces from there
  82         interfaces.add(new ControlInterfaceSupport(ControlInterfaceSupport.Kind.ANNOTATION,
  83                 (DeclaredType) env.getElementUtils().
  84                 getTypeElement(Mouse.class.getName()).asType(), null, "mouse",
  85                 null, shortcuts));
  86         interfaces.add(new ControlInterfaceSupport(ControlInterfaceSupport.Kind.ANNOTATION,
  87                 (DeclaredType) env.getElementUtils().
  88                 getTypeElement(Keyboard.class.getName()).asType(), null, "keyboard",
  89                 null, shortcuts));
  90         interfaces.add(new ControlInterfaceSupport(ControlInterfaceSupport.Kind.ANNOTATION,
  91                 (DeclaredType) env.getElementUtils().
  92                 getTypeElement(Drag.class.getName()).asType(), null, "drag",
  93                 null, shortcuts));
  94     }
  95 
  96     ControlSupport(DeclaredType wrap, DeclaredType control, ProcessingEnvironment env) {
  97         ci = (DeclaredType) env.getElementUtils().
  98                 getTypeElement(ControlInterface.class.getName()).asType();
  99         w = (DeclaredType) env.getTypeUtils().erasure(env.getElementUtils().
 100                 getTypeElement(Wrap.class.getName()).asType());
 101         placeholder = false;
 102         this.wrap = wrap;
 103         this.control = control;
 104         TypeMirror pWrap = wrap;
 105         TypeElement typeEl = (TypeElement) ((DeclaredType) pWrap).asElement();
 106         dockInfo = typeEl.getAnnotation(DockInfo.class);
 107         AnnotationMirror ciAM = Proccessor.findAnnotation(typeEl, ControlInterfaces.class);
 108         if (ciAM != null) {
 109             List<TypeMirror> implemented = new ArrayList<TypeMirror>();
 110             findImplementedInterfaces(wrap, implemented, env);
 111             List<ExecutableElement> annotated = new ArrayList<ExecutableElement>();
 112             findAnnotatedInterfaces(wrap, annotated, env);
 113             List<DeclaredType> value = Proccessor.getClassArrayValue(Proccessor.getElementValue(ciAM, "value"));
 114             List<DeclaredType> encapsulates;
 115             AnnotationValue v = Proccessor.getElementValue(ciAM, "encapsulates");
 116             if (v != null) {
 117                 encapsulates = Proccessor.getClassArrayValue(v);
 118             } else {
 119                 encapsulates = Collections.emptyList();
 120             }
 121             List<String> name;
 122             v = Proccessor.getElementValue(ciAM, "name");
 123             if (v != null) {
 124                 name = Proccessor.getStringArrayValue(v);
 125             } else {
 126                 name = Collections.EMPTY_LIST;
 127             }
 128             for (int i = 0; i < value.size(); i++) {
 129                 DeclaredType interfaceType = value.get(i);
 130                 DeclaredType encapsulatedType = (encapsulates.size() > i) ? encapsulates.get(i) : null;
 131                 ControlInterfaceSupport.Kind kind = ControlInterfaceSupport.Kind.METHOD;
 132                 ExecutableElement method = null;
 133                 for (TypeMirror tm : implemented) {
 134                     if (env.getTypeUtils().isSameType(interfaceType, env.getTypeUtils().erasure(tm))) {
 135                         if (encapsulatedType == null) {
 136                             kind = ControlInterfaceSupport.Kind.SELF;
 137                         } else {
 138                             if (((DeclaredType) tm).getTypeArguments().size() == 1
 139                                     && env.getTypeUtils().isSameType(encapsulatedType,
 140                                     ((DeclaredType) tm).getTypeArguments().get(0))) {
 141                                 kind = ControlInterfaceSupport.Kind.SELF;
 142                             }
 143                         }
 144                     }
 145                 }
 146                 for (ExecutableElement mthd : annotated) {
 147                     if (env.getTypeUtils().isSameType(interfaceType, env.getTypeUtils().erasure(mthd.getReturnType()))) {
 148                         if (encapsulatedType == null) {
 149                             kind = ControlInterfaceSupport.Kind.ANNOTATION;
 150                             method = mthd;
 151                         } else {
 152                             AnnotationMirror asAM = Proccessor.findAnnotation(mthd, As.class);
 153                             if (asAM.getElementValues().size() > 0) {
 154                                 DeclaredType vAE = Proccessor.getClassValue(Proccessor.getElementValue(asAM, "value"));
 155                                 if (!env.getTypeUtils().isSameType(vAE, env.getElementUtils().getTypeElement(Void.class.getName()).asType())
 156                                         && env.getTypeUtils().isSameType(vAE, encapsulatedType)) {
 157                                     kind = ControlInterfaceSupport.Kind.ANNOTATION;
 158                                     method = mthd;
 159                                 }
 160                             }
 161                         }
 162                     }
 163                 }
 164                 interfaces.add(new ControlInterfaceSupport(kind, interfaceType,
 165                         encapsulatedType,
 166                         (name.size() > i) ? name.get(i) : ("as" + value.get(i).asElement().getSimpleName()),
 167                         method, shortcuts));
 168             }
 169         }
 170         AnnotationMirror ppAM = Proccessor.findAnnotation(typeEl, PreferredParent.class);
 171         //PreferredParent ppa = typeEl.getAnnotation(PreferredParent.class);
 172         if(ppAM != null) {
 173             System.out.println("found a preffered wrap for " + typeEl.toString());
 174             preferredParent = Proccessor.getClassValue(Proccessor.getElementValue(ppAM, "value"));
 175             //prefferedParent = ppa.value();
 176         } else {
 177             System.out.println("no preffered wrap for " + typeEl.toString());
 178             preferredParent = null;
 179         }
 180         AnnotationMirror mpAM = Proccessor.findAnnotation(typeEl, MethodProperties.class);
 181         AnnotationMirror fpAM = Proccessor.findAnnotation(typeEl, FieldProperties.class);
 182         DefaultParentSupport dParent = null;
 183         DefaultWrapperSupport dWrapper = null;
 184         boolean origWrap = true;
 185         do {
 186             for (Element el : typeEl.getEnclosedElements()) {
 187                 if (el instanceof ExecutableElement) {
 188                     ExecutableElement eel = (ExecutableElement) el;
 189                     if (dParent == null) {
 190                         DefaultParent defaultParentAnn = el.getAnnotation(DefaultParent.class);
 191                         if (defaultParentAnn != null) {
 192                             dParent = new DefaultParentSupport(eel, defaultParentAnn.value());
 193                         }
 194                     }
 195                     if (dWrapper == null) {
 196                         DefaultWrapper defaultWrapperAnn = el.getAnnotation(DefaultWrapper.class);
 197                         if (defaultWrapperAnn != null) {
 198                             dWrapper = new DefaultWrapperSupport(eel, origWrap);
 199                         }
 200                     }
 201                     ObjectLookup olA = el.getAnnotation(ObjectLookup.class);
 202                     if (olA != null) {
 203                         if (eel.getParameters().isEmpty()
 204                                 || !eel.getParameters().get(0).asType().toString().startsWith(Class.class.getName())) { //TODO classname
 205                             throw new IllegalStateException("Expect first parameter to ba a class but found "
 206                                     + (eel.getParameters().isEmpty() ? "none" : eel.getParameters().get(0).asType().toString()));
 207                         }
 208                         LookupSupport newLs = new LookupSupport(typeEl, eel, olA.value());
 209                         copyParameters(eel, 1, newLs.getParams());
 210                         boolean found = false;
 211                         for (LookupSupport ls : objectLookups) {
 212                             if (ls.equalInTypes(newLs)) {
 213                                 found = true;
 214                                 break;
 215                             }
 216                         }
 217                         if (!found) {
 218                             objectLookups.add(newLs);
 219                         }
 220                     }
 221                     if (origWrap) {
 222                         Property pA = el.getAnnotation(Property.class);
 223                         if (pA != null) {
 224                             if (eel.getParameters().size() > 0) {
 225                                 throw new IllegalStateException("Property getter must have no parameters: " + eel.getSimpleName());
 226                             }
 227                             boolean found = false;
 228                             for (DirectPropertySupport dps : propertyMethods) {
 229                                 if (pA.value().equals(dps.getName())) {
 230                                     found = true;
 231                                     break;
 232                                 }
 233                             }
 234                             if (!found) {
 235                                 propertyMethods.add(new DirectPropertySupport(pA.value(), eel, pA.waitable()));
 236                             }
 237                         }
 238                     }
 239                 }
 240             }
 241             typeEl = (TypeElement) ((DeclaredType) typeEl.getSuperclass()).asElement();
 242             origWrap = false;
 243         } while (!typeEl.getQualifiedName().toString().equals(Wrap.class.getName()));
 244         defaultParent = dParent;
 245         defaultWrapper = dWrapper;
 246         if (mpAM != null) {
 247             addProperties(mpAM, true);
 248         }
 249         if (fpAM != null) {
 250             addProperties(fpAM, false);
 251         }
 252         multipleCriteria = (dockInfo != null) ? dockInfo.multipleCriteria() : true;
 253         if (dockInfo != null && dockInfo.name().length() > 0) {
 254             dockName = dockInfo.name();
 255         } else {
 256             String wrapName = ((TypeElement) wrap.asElement()).getQualifiedName().toString();
 257             dockName = wrapName.substring(0, wrapName.lastIndexOf(".")) + "."
 258                     + control.asElement().getSimpleName().toString() + "Dock";
 259         }
 260     }
 261 
 262     public boolean isMultipleCriteria() {
 263         return multipleCriteria;
 264     }
 265 
 266     private void findImplementedInterfaces(DeclaredType wrap, List<TypeMirror> interfaces, ProcessingEnvironment processingEnv) {
 267         if (processingEnv.getTypeUtils().isSameType(w, processingEnv.getTypeUtils().erasure(wrap))) {
 268             return;
 269         }
 270         for (TypeMirror i : ((TypeElement) wrap.asElement()).getInterfaces()) {
 271             if (processingEnv.getTypeUtils().isAssignable(i, ci)) {
 272                 interfaces.add(i);
 273             }
 274         }
 275 //        findImplementedInterfaces((DeclaredType)((TypeElement) wrap.asElement()).getSuperclass(), 
 276 //                interfaces, processingEnv);
 277     }
 278 
 279     private void findAnnotatedInterfaces(DeclaredType wrap, List<ExecutableElement> interfaces, ProcessingEnvironment processingEnv) {
 280         if (processingEnv.getTypeUtils().isSameType(w, processingEnv.getTypeUtils().erasure(wrap))) {
 281             return;
 282         }
 283         for (Element el : ((TypeElement) wrap.asElement()).getEnclosedElements()) {
 284             As as = el.getAnnotation(As.class);
 285             if (as != null) {
 286                 if (el instanceof ExecutableElement) {
 287                     interfaces.add((ExecutableElement) el);
 288                 } else {
 289                     throw new IllegalStateException("@As applied to something else but a method " + el.toString());
 290                 }
 291             }
 292         }
 293 //        findAnnotatedInterfaces((DeclaredType)((TypeElement) wrap.asElement()).getSuperclass(), 
 294 //                interfaces, processingEnv);
 295     }
 296 
 297     public DeclaredType getPreferredParent() {
 298         return preferredParent;
 299     }
 300 
 301     /**
 302      *
 303      * @return
 304      */
 305     public DeclaredType getSuperWrap() {
 306         return superWrap;
 307     }
 308 
 309     /**
 310      *
 311      * @return
 312      */
 313     public boolean isPlaceholder() {
 314         return placeholder;
 315     }
 316 
 317     /**
 318      *
 319      * @return
 320      */
 321     public String getDockName() {
 322         return dockName;
 323     }
 324 
 325     private void addProperties(AnnotationMirror am, boolean isMethod) {
 326         List<String> value = Proccessor.getStringArrayValue(Proccessor.getElementValue(am, "value"));
 327         List<Boolean> waitable;
 328         if(Proccessor.getElementValue(am, "waitable") != null) {
 329             waitable = Proccessor.getBooleanArrayValue(Proccessor.getElementValue(am, "waitable"));
 330         } else {
 331             waitable = Collections.EMPTY_LIST;
 332         }
 333         List<DeclaredType> types;
 334         AnnotationValue v = Proccessor.getElementValue(am, "types");
 335         if (v != null) {
 336             types = Proccessor.getClassArrayValue(v);
 337         } else {
 338             types = Collections.EMPTY_LIST;
 339         }
 340         for (int i = 0; i < value.size(); i++) {
 341             boolean found = false;
 342             for (MappedPropertySupport ps : properties) {
 343                 if (ps.getName().equals(value.get(i))) {
 344                     found = true;
 345                     break;
 346                 }
 347             }
 348             if (!found) {
 349                 TypeMirror type;
 350                 if (types.size() > i) {
 351                     type = types.get(i);
 352                 } else {
 353                     type = findType(control, value.get(i), isMethod);
 354                 }
 355                 properties.add(new MappedPropertySupport(value.get(i), type, isMethod,
 356                         (waitable.size() > i) ? waitable.get(i) : false));
 357             }
 358         }
 359     }
 360 
 361     /**
 362      *
 363      * @return
 364      */
 365     public DeclaredType getControl() {
 366         return control;
 367     }
 368 
 369     /**
 370      *
 371      * @return
 372      */
 373     public List<LookupSupport> getObjectLookups() {
 374         return objectLookups;
 375     }
 376 
 377     /**
 378      *
 379      * @return
 380      */
 381     public DeclaredType getWrap() {
 382         return wrap;
 383     }
 384 
 385     /**
 386      *
 387      * @return
 388      */
 389     public List<ControlInterfaceSupport> getInterfaces() {
 390         return interfaces;
 391     }
 392 
 393     /**
 394      *
 395      * @return
 396      */
 397     public List<MappedPropertySupport> getProperties() {
 398         return properties;
 399     }
 400 
 401     /**
 402      *
 403      * @return
 404      */
 405     public List<DirectPropertySupport> getPropertyMethods() {
 406         return propertyMethods;
 407     }
 408 
 409     /**
 410      *
 411      * @return
 412      */
 413     public DefaultParentSupport getDefaultParent() {
 414         return defaultParent;
 415     }
 416 
 417     /**
 418      *
 419      * @return
 420      */
 421     public DefaultWrapperSupport getDefaultWrapper() {
 422         return defaultWrapper;
 423     }
 424 
 425     /**
 426      *
 427      * @return
 428      */
 429     public DockInfo getDockInfo() {
 430         return dockInfo;
 431     }
 432 
 433     static void copyParameters(ExecutableElement eel, int firstParam, List<SupportParameter> params) {
 434         for (int i = firstParam; i < eel.getParameters().size(); i++) {
 435             VariableElement ve = eel.getParameters().get(i);
 436             params.add(new SupportParameter(ve.getSimpleName().toString(), ve.asType()));
 437         }
 438     }
 439 
 440     private TypeMirror findType(DeclaredType control, String name, boolean isMethod) {
 441         TypeMirror tp = control;
 442         TypeElement te;
 443         while (tp instanceof DeclaredType && tp.getKind() != TypeKind.NULL) {
 444             te = (TypeElement) ((DeclaredType) tp).asElement();
 445             for (Element e : te.getEnclosedElements()) {
 446                 if (e instanceof ExecutableElement) {
 447                     ExecutableElement ee = (ExecutableElement) e;
 448                     if (isMethod && e.getKind() == ElementKind.METHOD
 449                             || !isMethod && e.getKind() == ElementKind.FIELD) {
 450                         if (e.getSimpleName().toString().equals(name)
 451                                 && (!isMethod || ee.getParameters().isEmpty())) {
 452                             return e.asType();
 453                         }
 454                     }
 455                 }
 456             }
 457             tp = te.getSuperclass();
 458         }
 459         return null;
 460     }
 461 
 462     //TODO - do a quicksort, at least
 463     /**
 464      *
 465      * @param controls
 466      * @param env
 467      */
 468     public static void linkSuperClasses(List<ControlSupport> controls, ProcessingEnvironment env) {
 469         ControlSupport root = null;
 470         for (ControlSupport cs : controls) {
 471             DeclaredType superWrap = (DeclaredType) ((TypeElement) cs.getWrap().asElement()).getSuperclass();
 472             boolean foundOne = false;
 473             for (ControlSupport csi : controls) {
 474                 if (((TypeElement) csi.getWrap().asElement()).getQualifiedName().toString().equals(
 475                         ((TypeElement) superWrap.asElement()).getQualifiedName().toString())) {
 476                     //that would mean we have found a super wrap among the compiled ones
 477                     foundOne = true;
 478                     break;
 479                 }
 480             }
 481             if (!foundOne) {
 482                 //the super-wrap is in classpath somewhere
 483                 //for now only org.jemmy.control.Wrap could be external
 484                 //TODO improve DockInfo to allow external parents
 485                 if (((TypeElement) superWrap.asElement()).getQualifiedName().
 486                         toString().equals(Wrap.class.getName())) {
 487                     if (root == null) {
 488                         root = new ControlSupport(env);
 489                     }
 490                 } else {
 491                     throw new IllegalStateException("Unknown parent Wrap type "
 492                             + ((TypeElement) superWrap.asElement()).getQualifiedName().toString());
 493                 }
 494             }
 495             cs.superWrap = superWrap;
 496         }
 497         controls.add(root);
 498     }
 499 }