public void generateMethods(
      JavacNode typeNode,
      JavacNode source,
      List<String> excludes,
      List<String> includes,
      Boolean callSuper,
      boolean whineIfExists,
      FieldAccess fieldAccess) {
    boolean notAClass = true;
    if (typeNode.get() instanceof JCClassDecl) {
      long flags = ((JCClassDecl) typeNode.get()).mods.flags;
      notAClass = (flags & (Flags.INTERFACE | Flags.ANNOTATION | Flags.ENUM)) != 0;
    }

    if (notAClass) {
      source.addError("@EqualsAndHashCode is only supported on a class.");
      return;
    }

    boolean isDirectDescendantOfObject = true;
    boolean implicitCallSuper = callSuper == null;
    if (callSuper == null) {
      try {
        callSuper =
            ((Boolean) EqualsAndHashCode.class.getMethod("callSuper").getDefaultValue())
                .booleanValue();
      } catch (Exception ignore) {
        throw new InternalError(
            "Lombok bug - this cannot happen - can't find callSuper field in EqualsAndHashCode annotation.");
      }
    }

    JCTree extending = Javac.getExtendsClause((JCClassDecl) typeNode.get());
    if (extending != null) {
      String p = extending.toString();
      isDirectDescendantOfObject = p.equals("Object") || p.equals("java.lang.Object");
    }

    if (isDirectDescendantOfObject && callSuper) {
      source.addError(
          "Generating equals/hashCode with a supercall to java.lang.Object is pointless.");
      return;
    }

    if (!isDirectDescendantOfObject && !callSuper && implicitCallSuper) {
      source.addWarning(
          "Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type.");
    }

    ListBuffer<JavacNode> nodesForEquality = new ListBuffer<JavacNode>();
    if (includes != null) {
      for (JavacNode child : typeNode.down()) {
        if (child.getKind() != Kind.FIELD) continue;
        JCVariableDecl fieldDecl = (JCVariableDecl) child.get();
        if (includes.contains(fieldDecl.name.toString())) nodesForEquality.append(child);
      }
    } else {
      for (JavacNode child : typeNode.down()) {
        if (child.getKind() != Kind.FIELD) continue;
        JCVariableDecl fieldDecl = (JCVariableDecl) child.get();
        // Skip static fields.
        if ((fieldDecl.mods.flags & Flags.STATIC) != 0) continue;
        // Skip transient fields.
        if ((fieldDecl.mods.flags & Flags.TRANSIENT) != 0) continue;
        // Skip excluded fields.
        if (excludes != null && excludes.contains(fieldDecl.name.toString())) continue;
        // Skip fields that start with $
        if (fieldDecl.name.toString().startsWith("$")) continue;
        nodesForEquality.append(child);
      }
    }

    boolean isFinal = (((JCClassDecl) typeNode.get()).mods.flags & Flags.FINAL) != 0;
    boolean needsCanEqual = !isFinal || !isDirectDescendantOfObject;
    MemberExistsResult equalsExists = methodExists("equals", typeNode, 1);
    MemberExistsResult hashCodeExists = methodExists("hashCode", typeNode, 0);
    MemberExistsResult canEqualExists = methodExists("canEqual", typeNode, 1);
    switch (Collections.max(Arrays.asList(equalsExists, hashCodeExists, canEqualExists))) {
      case EXISTS_BY_LOMBOK:
        return;
      case EXISTS_BY_USER:
        if (whineIfExists) {
          String msg =
              String.format(
                  "Not generating equals%s: A method with one of those names already exists. (Either all or none of these methods will be generated).",
                  needsCanEqual ? ", hashCode and canEquals" : " and hashCode");
          source.addWarning(msg);
        } else if (equalsExists == MemberExistsResult.NOT_EXISTS
            || hashCodeExists == MemberExistsResult.NOT_EXISTS) {
          // This means equals OR hashCode exists and not both (or neither, but canEqual is there).
          // Even though we should suppress the message about not generating these, this is such a
          // weird and surprising situation we should ALWAYS generate a warning.
          // The user code couldn't possibly (barring really weird subclassing shenanigans) be in a
          // shippable state anyway; the implementations of these 3 methods are
          // all inter-related and should be written by the same entity.
          String msg =
              String.format(
                  "Not generating %s: One of equals, hashCode, and canEqual exists. "
                      + "You should either write all of these or none of these (in the latter case, lombok generates them).",
                  equalsExists == MemberExistsResult.NOT_EXISTS
                          && hashCodeExists == MemberExistsResult.NOT_EXISTS
                      ? "equals and hashCode"
                      : equalsExists == MemberExistsResult.NOT_EXISTS ? "equals" : "hashCode");
          source.addWarning(msg);
        }
        return;
      case NOT_EXISTS:
      default:
        // fallthrough
    }

    JCMethodDecl equalsMethod =
        createEquals(
            typeNode,
            nodesForEquality.toList(),
            callSuper,
            fieldAccess,
            needsCanEqual,
            source.get());
    injectMethod(typeNode, equalsMethod);

    if (needsCanEqual) {
      JCMethodDecl canEqualMethod = createCanEqual(typeNode, source.get());
      injectMethod(typeNode, canEqualMethod);
    }

    JCMethodDecl hashCodeMethod =
        createHashCode(typeNode, nodesForEquality.toList(), callSuper, fieldAccess, source.get());
    injectMethod(typeNode, hashCodeMethod);
  }
Exemple #2
0
  @Override
  public void handle(
      AnnotationValues<Builder> annotation, JCAnnotation ast, JavacNode annotationNode) {
    handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.BUILDER_FLAG_USAGE, "@Builder");

    Builder builderInstance = annotation.getInstance();
    String builderMethodName = builderInstance.builderMethodName();
    String buildMethodName = builderInstance.buildMethodName();
    String builderClassName = builderInstance.builderClassName();

    if (builderMethodName == null) builderMethodName = "builder";
    if (buildMethodName == null) buildMethodName = "build";
    if (builderClassName == null) builderClassName = "";

    if (!checkName("builderMethodName", builderMethodName, annotationNode)) return;
    if (!checkName("buildMethodName", buildMethodName, annotationNode)) return;
    if (!builderClassName.isEmpty()) {
      if (!checkName("builderClassName", builderClassName, annotationNode)) return;
    }

    deleteAnnotationIfNeccessary(annotationNode, Builder.class);
    deleteImportFromCompilationUnit(annotationNode, "lombok.experimental.Builder");

    JavacNode parent = annotationNode.up();

    java.util.List<JCExpression> typesOfParameters = new ArrayList<JCExpression>();
    java.util.List<Name> namesOfParameters = new ArrayList<Name>();
    JCExpression returnType;
    List<JCTypeParameter> typeParams = List.nil();
    List<JCExpression> thrownExceptions = List.nil();
    Name nameOfStaticBuilderMethod;
    JavacNode tdParent;

    JCMethodDecl fillParametersFrom =
        parent.get() instanceof JCMethodDecl ? ((JCMethodDecl) parent.get()) : null;

    if (parent.get() instanceof JCClassDecl) {
      tdParent = parent;
      JCClassDecl td = (JCClassDecl) tdParent.get();
      ListBuffer<JavacNode> allFields = new ListBuffer<JavacNode>();
      @SuppressWarnings("deprecation")
      boolean valuePresent =
          (hasAnnotation(lombok.Value.class, parent)
              || hasAnnotation(lombok.experimental.Value.class, parent));
      for (JavacNode fieldNode : HandleConstructor.findAllFields(tdParent)) {
        JCVariableDecl fd = (JCVariableDecl) fieldNode.get();
        // final fields with an initializer cannot be written to, so they can't be 'builderized'.
        // Unfortunately presence of @Value makes
        // non-final fields final, but @Value's handler hasn't done this yet, so we have to do this
        // math ourselves.
        // Value will only skip making a field final if it has an explicit @NonFinal annotation, so
        // we check for that.
        if (fd.init != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue;
        namesOfParameters.add(removePrefixFromField(fieldNode));
        typesOfParameters.add(fd.vartype);
        allFields.append(fieldNode);
      }

      new HandleConstructor()
          .generateConstructor(
              tdParent,
              AccessLevel.PACKAGE,
              List.<JCAnnotation>nil(),
              allFields.toList(),
              null,
              SkipIfConstructorExists.I_AM_BUILDER,
              null,
              annotationNode);

      returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams);
      typeParams = td.typarams;
      thrownExceptions = List.nil();
      nameOfStaticBuilderMethod = null;
      if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder";
    } else if (fillParametersFrom != null
        && fillParametersFrom.getName().toString().equals("<init>")) {
      if (!fillParametersFrom.typarams.isEmpty()) {
        annotationNode.addError(
            "@Builder is not supported on constructors with constructor type parameters.");
        return;
      }
      tdParent = parent.up();
      JCClassDecl td = (JCClassDecl) tdParent.get();
      returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams);
      typeParams = td.typarams;
      thrownExceptions = fillParametersFrom.thrown;
      nameOfStaticBuilderMethod = null;
      if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder";
    } else if (fillParametersFrom != null) {
      tdParent = parent.up();
      JCClassDecl td = (JCClassDecl) tdParent.get();
      if ((fillParametersFrom.mods.flags & Flags.STATIC) == 0) {
        annotationNode.addError(
            "@Builder is only supported on types, constructors, and static methods.");
        return;
      }
      returnType = fillParametersFrom.restype;
      typeParams = fillParametersFrom.typarams;
      thrownExceptions = fillParametersFrom.thrown;
      nameOfStaticBuilderMethod = fillParametersFrom.name;
      if (builderClassName.isEmpty()) {
        if (returnType instanceof JCTypeApply) {
          returnType = ((JCTypeApply) returnType).clazz;
        }
        if (returnType instanceof JCFieldAccess) {
          builderClassName = ((JCFieldAccess) returnType).name.toString() + "Builder";
        } else if (returnType instanceof JCIdent) {
          Name n = ((JCIdent) returnType).name;

          for (JCTypeParameter tp : typeParams) {
            if (tp.name.equals(n)) {
              annotationNode.addError(
                  "@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type.");
              return;
            }
          }
          builderClassName = n.toString() + "Builder";
        } else if (returnType instanceof JCPrimitiveTypeTree) {
          builderClassName = returnType.toString() + "Builder";
          if (Character.isLowerCase(builderClassName.charAt(0))) {
            builderClassName =
                Character.toTitleCase(builderClassName.charAt(0)) + builderClassName.substring(1);
          }

        } else {
          // This shouldn't happen.
          System.err.println(
              "Lombok bug ID#20140614-1651: javac HandleBuilder: return type to name conversion failed: "
                  + returnType.getClass());
          builderClassName = td.name.toString() + "Builder";
        }
      }
    } else {
      annotationNode.addError(
          "@Builder is only supported on types, constructors, and static methods.");
      return;
    }

    if (fillParametersFrom != null) {
      for (JCVariableDecl param : fillParametersFrom.params) {
        namesOfParameters.add(param.name);
        typesOfParameters.add(param.vartype);
      }
    }

    JavacNode builderType = findInnerClass(tdParent, builderClassName);
    if (builderType == null) {
      builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast);
    } else {
      sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode);
    }
    java.util.List<JavacNode> fieldNodes =
        addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast);
    java.util.List<JCMethodDecl> newMethods = new ArrayList<JCMethodDecl>();
    for (JavacNode fieldNode : fieldNodes) {
      JCMethodDecl newMethod =
          makeSetterMethodForBuilder(
              builderType,
              fieldNode,
              annotationNode,
              builderInstance.fluent(),
              builderInstance.chain());
      if (newMethod != null) newMethods.add(newMethod);
    }

    if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) {
      JCMethodDecl cd =
          HandleConstructor.createConstructor(
              AccessLevel.PACKAGE,
              List.<JCAnnotation>nil(),
              builderType,
              List.<JavacNode>nil(),
              null,
              annotationNode);
      if (cd != null) injectMethod(builderType, cd);
    }

    for (JCMethodDecl newMethod : newMethods) injectMethod(builderType, newMethod);

    if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) {
      JCMethodDecl md =
          generateBuildMethod(
              buildMethodName,
              nameOfStaticBuilderMethod,
              returnType,
              namesOfParameters,
              builderType,
              thrownExceptions);
      if (md != null) injectMethod(builderType, md);
    }

    if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) {
      JCMethodDecl md =
          HandleToString.createToString(
              builderType, fieldNodes, true, false, FieldAccess.ALWAYS_FIELD, ast);
      if (md != null) injectMethod(builderType, md);
    }

    if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) {
      JCMethodDecl md =
          generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams);
      if (md != null) injectMethod(tdParent, md);
    }
  }