AptAssignabilityUtils.java

/*
 * Copyright © 2017, Salesforce.com, Inc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <organization> nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.salesforce.apt.graph.types.impl;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

import com.salesforce.apt.graph.model.InstanceModel;
import com.salesforce.apt.graph.naming.NamingTools;
import com.salesforce.apt.graph.types.AssignabilityUtils;

/**
 * Compares types stored in the DefinitionGraph with the best available utils, Types and Elements in the case of apt.
 */
public class AptAssignabilityUtils implements AssignabilityUtils {

  private Types typeUtils;
  private Elements elementUtils;
  
  public AptAssignabilityUtils(Types types, Elements elements) {
    typeUtils = types;
    elementUtils = elements;
  }
  
  /**
   * Given two instances models, the subject and target, it is determined
   * if the subject can be assigned to the dependency on the subject, from the
   * target.
   *
   * @param subject instance to be injected in to the target
   * @param target instance to have the subject injected in to it.
   * @return whether the target can be safely injected in to the correct parameter of the target.
   */
  public boolean isAssignableFrom(InstanceModel subject, InstanceModel target) {
    ExecutableElement factoryOrConstructor = lookUpElement(subject);
    TypeMirror subjectElementType = null;
    if ("<init>".equals(factoryOrConstructor.getSimpleName().toString())) {
      subjectElementType = factoryOrConstructor.getEnclosingElement().asType();
    } else {
      subjectElementType = factoryOrConstructor.getReturnType();
    }
    int count = 0;
    while (!target.getDependencies().get(count).getIdentity().equals(subject.getIdentity())) {
      count ++;
    }
    TypeMirror targetElementType = lookUpElement(target).getParameters().get(count).asType();
    return typeUtils.isAssignable(subjectElementType, targetElementType);
  }
  
  /**
   * Find's the executable element that will have the necessary type
   * information extracted from it.
   * 
   * @param target the instance model that is our target
   * @return the element in question
   */
  public ExecutableElement lookUpElement(final InstanceModel target) {
    //Prior I had used the cached instances of the source element, since idea reuses compiler instances, 
    //and hence AptProcessor instances that proves dangerous as the Type references for the same class/type
    //are not portable across different compilation rounds.
    //
    // Leaving this as a warning to my future self.
    //
    //if (target.getSourceElement().isPresent()) {
    //  return (ExecutableElement) target.getSourceElement().get();
    //} else {
    TypeElement type = elementUtils.getTypeElement(target.getOwningDefinition().replace('$', '.'));
    final NamingTools names = new NamingTools();
    return type.getEnclosedElements().stream().filter(e -> 
        names.elementToName(e).equals(target.getElementLocation()) && ExecutableElement.class.isAssignableFrom(e.getClass()))
          .findFirst()
          .map(e -> (ExecutableElement) e)
          .get();
  }  
  
  /* If the above doesn't work, this handles all but ?, &, | in types.
   * 
  public boolean isAssignableFrom(String subject, String target) {
    ParseType subjectType = ParseType.parse(subject);
    ParseType targetType = ParseType.parse(target);
    return typeUtils.isAssignable(from(subjectType),
        from(targetType));
  }
  
  public TypeMirror from(ParseType parsed) {
    return typeUtils.getDeclaredType(elementUtils.getTypeElement(parsed.getType()),
        parsed.getParameters().stream().map(p -> from(p)).toArray(i -> new TypeMirror[i]));
  }
  */
}