public Optional generate()

in mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/dao/DaoSelectMethodGenerator.java [94:213]


  public Optional<MethodSpec> generate() {

    // Validate the return type:
    DaoReturnType returnType =
        parseAndValidateReturnType(getSupportedReturnTypes(), Select.class.getSimpleName());
    if (returnType == null) {
      return Optional.empty();
    }

    TypeElement entityElement = returnType.getEntityElement();
    EntityDefinition entityDefinition = context.getEntityFactory().getDefinition(entityElement);

    // Validate the parameters:
    // - if there is a custom clause, they are free-form (they'll be used as bind variables)
    // - otherwise, we accept the primary key components or a subset thereof (possibly empty to
    //   select all rows), followed by free-form parameters bound to the secondary clauses (such as
    //   LIMIT).
    // In either case, a Function<BoundStatementBuilder, BoundStatementBuilder> can be added in last
    // position.
    List<? extends VariableElement> parameters = methodElement.getParameters();

    VariableElement boundStatementFunction = findBoundStatementFunction(methodElement);
    if (boundStatementFunction != null) {
      parameters = parameters.subList(0, parameters.size() - 1);
    }

    final List<? extends VariableElement> primaryKeyParameters;
    final List<? extends VariableElement> freeFormParameters;
    Select selectAnnotation = methodElement.getAnnotation(Select.class);
    assert selectAnnotation != null; // otherwise we wouldn't have gotten into this class
    String customClause = selectAnnotation.customWhereClause();
    if (parameters.isEmpty()) {
      primaryKeyParameters = freeFormParameters = Collections.emptyList();
    } else if (customClause.isEmpty()) {
      // If we have a partial primary key *and* free-form parameters, things get ambiguous because
      // we don't know where the primary key ends. By convention, we require the first free-form
      // parameter to be annotated with @CqlName in those cases.
      // So the boundary is either when we have enough parameters for a full primary key, or when we
      // encounter the first annotated parameter.
      int firstNamedParameter = parameters.size();
      for (int i = 0; i < parameters.size(); i++) {
        if (parameters.get(i).getAnnotation(CqlName.class) != null) {
          firstNamedParameter = i;
          break;
        }
      }
      int primaryKeyEnd = Math.min(firstNamedParameter, entityDefinition.getPrimaryKey().size());
      if (primaryKeyEnd >= parameters.size()) {
        primaryKeyParameters = parameters;
        freeFormParameters = Collections.emptyList();
      } else {
        primaryKeyParameters = parameters.subList(0, primaryKeyEnd);
        freeFormParameters = parameters.subList(primaryKeyEnd, parameters.size());
      }
    } else {
      primaryKeyParameters = Collections.emptyList();
      freeFormParameters = parameters;
    }

    // If we have parameters for some primary key components, validate that the types match:
    if (!primaryKeyParameters.isEmpty()
        && !EntityUtils.areParametersValid(
            entityElement,
            entityDefinition,
            primaryKeyParameters,
            Select.class,
            context,
            methodElement,
            processedType,
            "don't use a custom clause")) {
      return Optional.empty();
    }

    // Generate the method:
    String helperFieldName = enclosingClass.addEntityHelperField(ClassName.get(entityElement));
    String statementName =
        enclosingClass.addPreparedStatement(
            methodElement,
            (methodBuilder, requestName) ->
                generateSelectRequest(
                    methodBuilder, requestName, helperFieldName, primaryKeyParameters.size()));

    CodeBlock.Builder createStatementBlock = CodeBlock.builder();

    createStatementBlock.addStatement(
        "$T boundStatementBuilder = $L.boundStatementBuilder()",
        BoundStatementBuilder.class,
        statementName);
    populateBuilderWithStatementAttributes(createStatementBlock, methodElement);
    populateBuilderWithFunction(createStatementBlock, boundStatementFunction);

    if (!primaryKeyParameters.isEmpty()) {
      List<CodeBlock> primaryKeyNames =
          entityDefinition.getPrimaryKey().stream()
              .map(PropertyDefinition::getCqlName)
              .collect(Collectors.toList())
              .subList(0, primaryKeyParameters.size());
      GeneratedCodePatterns.bindParameters(
          primaryKeyParameters,
          primaryKeyNames,
          createStatementBlock,
          enclosingClass,
          context,
          false);
    }

    if (!freeFormParameters.isEmpty()) {
      if (validateCqlNamesPresent(freeFormParameters)) {
        GeneratedCodePatterns.bindParameters(
            freeFormParameters, createStatementBlock, enclosingClass, context, false);
      } else {
        return Optional.empty();
      }
    }

    createStatementBlock.addStatement(
        "$T boundStatement = boundStatementBuilder.build()", BoundStatement.class);

    return crudMethod(createStatementBlock, returnType, helperFieldName);
  }