]> source.dussan.org Git - sonarqube.git/commitdiff
Decrease complexity when using Protobuf builders
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Fri, 11 Nov 2016 13:44:36 +0000 (14:44 +0100)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Fri, 11 Nov 2016 14:54:49 +0000 (15:54 +0100)
14 files changed:
server/sonar-server/src/main/java/org/sonar/server/batch/ProjectAction.java
server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentDtoToWsComponent.java
server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchAction.java
server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsSupport.java
server/sonar-server/src/main/java/org/sonar/server/permission/ws/GroupsAction.java
server/sonar-server/src/main/java/org/sonar/server/permission/ws/UsersAction.java
server/sonar-server/src/main/java/org/sonar/server/permission/ws/template/PermissionTemplateDtoToPermissionTemplateResponse.java
server/sonar-server/src/main/java/org/sonar/server/permission/ws/template/SearchTemplatesAction.java
server/sonar-server/src/main/java/org/sonar/server/permission/ws/template/TemplateGroupsAction.java
server/sonar-server/src/main/java/org/sonar/server/permission/ws/template/TemplateUsersAction.java
server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchMyProjectsAction.java
sonar-core/src/main/java/org/sonar/core/util/Protobuf.java

index d6917cccc345828478fe21de278e0e385f623aea..028cf04e3a4f0bf41b2727ea35faf4c116c56c9e 100644 (file)
@@ -30,6 +30,7 @@ import org.sonar.scanner.protocol.input.ProjectRepositories;
 import org.sonarqube.ws.WsBatch.WsProjectResponse;
 import org.sonarqube.ws.WsBatch.WsProjectResponse.FileData.Builder;
 
+import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 
@@ -85,7 +86,7 @@ public class ProjectAction implements BatchWsAction {
 
   private static WsProjectResponse buildResponse(ProjectRepositories data) {
     WsProjectResponse.Builder response = WsProjectResponse.newBuilder();
-    setLastAnalysisDate(response, data);
+    setNullable(data.lastAnalysisDate(), response::setLastAnalysisDate, Date::getTime);
     response.setTimestamp(data.timestamp());
     response.getMutableFileDataByModuleAndPath()
       .putAll(buildFileDataByModuleAndPath(data));
@@ -95,13 +96,6 @@ public class ProjectAction implements BatchWsAction {
     return response.build();
   }
 
-  private static void setLastAnalysisDate(WsProjectResponse.Builder response, ProjectRepositories data) {
-    Date lastAnalysisDate = data.lastAnalysisDate();
-    if (lastAnalysisDate != null) {
-      response.setLastAnalysisDate(lastAnalysisDate.getTime());
-    }
-  }
-
   private static Map<String, WsProjectResponse.FileDataByPath> buildFileDataByModuleAndPath(ProjectRepositories data) {
     Map<String, WsProjectResponse.FileDataByPath> fileDataByModuleAndPathResponse = new HashMap<>();
     for (Map.Entry<String, Map<String, FileData>> moduleAndFileDataByPathEntry : data.fileDataByModuleAndPath().entrySet()) {
@@ -150,13 +144,8 @@ public class ProjectAction implements BatchWsAction {
 
   private static WsProjectResponse.FileData toFileDataResponse(FileData fileData) {
     Builder fileDataBuilder = WsProjectResponse.FileData.newBuilder();
-    if (fileData.hash() != null) {
-      fileDataBuilder.setHash(fileData.hash());
-    }
-    if (fileData.revision() != null) {
-      fileDataBuilder.setRevision(fileData.revision());
-    }
-
+    setNullable(fileData.hash(), fileDataBuilder::setHash);
+    setNullable(fileData.revision(), fileDataBuilder::setRevision);
     return fileDataBuilder.build();
   }
 }
index 3fadd22a6c6251143e7de840fc18e24f8190cc15..96cf677e2f2f6ffd6b1c71af84fbf2cb864d32e3 100644 (file)
@@ -24,7 +24,8 @@ import java.util.Map;
 import org.sonar.db.component.ComponentDto;
 import org.sonarqube.ws.WsComponents;
 
-import static com.google.common.base.Strings.isNullOrEmpty;
+import static com.google.common.base.Strings.emptyToNull;
+import static org.sonar.core.util.Protobuf.setNullable;
 
 class ComponentDtoToWsComponent {
   private ComponentDtoToWsComponent() {
@@ -37,16 +38,9 @@ class ComponentDtoToWsComponent {
       .setKey(dto.key())
       .setName(dto.name())
       .setQualifier(dto.qualifier());
-    if (!isNullOrEmpty(dto.path())) {
-      wsComponent.setPath(dto.path());
-    }
-    if (!isNullOrEmpty(dto.description())) {
-      wsComponent.setDescription(dto.description());
-    }
-    if (!isNullOrEmpty(dto.language())) {
-      wsComponent.setLanguage(dto.language());
-    }
-
+    setNullable(emptyToNull(dto.path()), wsComponent::setPath);
+    setNullable(emptyToNull(dto.description()), wsComponent::setDescription);
+    setNullable(emptyToNull(dto.language()), wsComponent::setLanguage);
     return wsComponent;
   }
 
index 22ac602f1eb794ad0956a57cd8dde80da1724653..a561acd6518366b7df2710edba13a5559f9cf5ca 100644 (file)
@@ -42,6 +42,7 @@ import org.sonarqube.ws.WsComponents.SearchWsResponse;
 import org.sonarqube.ws.client.component.SearchWsRequest;
 
 import static com.google.common.collect.FluentIterable.from;
+import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.server.ws.WsParameterBuilder.createQualifiersParameter;
 import static org.sonar.server.ws.WsParameterBuilder.QualifierParameterContext.newQualifierParameterContext;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
@@ -164,10 +165,7 @@ public class SearchAction implements ComponentsWsAction {
         .setKey(dto.key())
         .setName(dto.name())
         .setQualifier(dto.qualifier());
-      if (dto.language() != null) {
-        builder.setLanguage(dto.language());
-      }
-
+      setNullable(dto.language(), builder::setLanguage);
       return builder.build();
     }
   }
index 1e13d6be6fb9a3421c6ca09c1f62d3e1c2c6d092..f1b9749a3aebcba2174bd8131284637ad2916401 100644 (file)
@@ -213,7 +213,7 @@ public class SearchProjectsAction implements ComponentsWsAction {
           .map(bucketToFacetValue)
           .forEach(wsFacet::addValues);
       } else {
-        wsFacet.addAllValues(Collections.<Common.FacetValue>emptyList());
+        wsFacet.addAllValues(Collections.emptyList());
       }
 
       return wsFacet.build();
index 84e5aeb2ded07c3b60ba8509dcb28cd586f8ad33..45367f19916a0d509a34dd24ef88344aad79783c 100644 (file)
@@ -49,7 +49,9 @@ import org.sonar.server.ws.WsResponseCommonFormat;
 import org.sonarqube.ws.Common;
 import org.sonarqube.ws.Issues;
 
+import static com.google.common.base.Strings.emptyToNull;
 import static com.google.common.base.Strings.nullToEmpty;
+import static org.sonar.core.util.Protobuf.setNullable;
 
 public class SearchResponseFormat {
 
@@ -141,10 +143,8 @@ public class SearchResponseFormat {
 
   private void formatIssue(Issues.Issue.Builder issueBuilder, IssueDto dto, SearchResponseData data) {
     issueBuilder.setKey(dto.getKey());
-    Common.RuleType type = Common.RuleType.valueOf(dto.getType());
-    if (type != null) {
-      issueBuilder.setType(type);
-    }
+    setNullable(dto.getType(), issueBuilder::setType, Common.RuleType::valueOf);
+
     ComponentDto component = data.getComponentByUuid(dto.getComponentUuid());
     issueBuilder.setComponent(component.key());
     // Only used for the compatibility with the Java WS Client <= 4.4 used by Eclipse
@@ -159,12 +159,8 @@ public class SearchResponseFormat {
     }
     issueBuilder.setRule(dto.getRuleKey().toString());
     issueBuilder.setSeverity(Common.Severity.valueOf(dto.getSeverity()));
-    if (!Strings.isNullOrEmpty(dto.getAssignee())) {
-      issueBuilder.setAssignee(dto.getAssignee());
-    }
-    if (!Strings.isNullOrEmpty(dto.getResolution())) {
-      issueBuilder.setResolution(dto.getResolution());
-    }
+    setNullable(emptyToNull(dto.getAssignee()), issueBuilder::setAssignee);
+    setNullable(emptyToNull(dto.getResolution()), issueBuilder::setResolution);
     issueBuilder.setStatus(dto.getStatus());
     issueBuilder.setMessage(nullToEmpty(dto.getMessage()));
     issueBuilder.addAllTags(dto.getTags());
@@ -174,40 +170,29 @@ public class SearchResponseFormat {
       issueBuilder.setDebt(effortValue);
       issueBuilder.setEffort(effortValue);
     }
-    Integer line = dto.getLine();
-    if (line != null) {
-      issueBuilder.setLine(line);
-    }
+    setNullable(dto.getLine(), issueBuilder::setLine);
     completeIssueLocations(dto, issueBuilder);
     issueBuilder.setAuthor(nullToEmpty(dto.getAuthorLogin()));
-    Date date = dto.getIssueCreationDate();
-    if (date != null) {
-      issueBuilder.setCreationDate(DateUtils.formatDateTime(date));
-    }
-    date = dto.getIssueUpdateDate();
-    if (date != null) {
-      issueBuilder.setUpdateDate(DateUtils.formatDateTime(date));
-    }
-    date = dto.getIssueCloseDate();
-    if (date != null) {
-      issueBuilder.setCloseDate(DateUtils.formatDateTime(date));
-    }
+    setNullable(dto.getIssueCreationDate(), issueBuilder::setCreationDate, DateUtils::formatDateTime);
+    setNullable(dto.getIssueUpdateDate(), issueBuilder::setUpdateDate, DateUtils::formatDateTime);
+    setNullable(dto.getIssueCloseDate(), issueBuilder::setCloseDate, DateUtils::formatDateTime);
   }
 
   private void completeIssueLocations(IssueDto dto, Issues.Issue.Builder issueBuilder) {
     DbIssues.Locations locations = dto.parseLocations();
-    if (locations != null) {
-      if (locations.hasTextRange()) {
-        DbCommons.TextRange textRange = locations.getTextRange();
-        issueBuilder.setTextRange(convertTextRange(textRange));
-      }
-      for (DbIssues.Flow flow : locations.getFlowList()) {
-        Issues.Flow.Builder targetFlow = Issues.Flow.newBuilder();
-        for (DbIssues.Location flowLocation : flow.getLocationList()) {
-          targetFlow.addLocations(convertLocation(flowLocation));
-        }
-        issueBuilder.addFlows(targetFlow);
+    if (locations == null) {
+      return;
+    }
+    if (locations.hasTextRange()) {
+      DbCommons.TextRange textRange = locations.getTextRange();
+      issueBuilder.setTextRange(convertTextRange(textRange));
+    }
+    for (DbIssues.Flow flow : locations.getFlowList()) {
+      Issues.Flow.Builder targetFlow = Issues.Flow.newBuilder();
+      for (DbIssues.Location flowLocation : flow.getLocationList()) {
+        targetFlow.addLocations(convertLocation(flowLocation));
       }
+      issueBuilder.addFlows(targetFlow);
     }
   }
 
@@ -323,13 +308,10 @@ public class SearchResponseFormat {
         // On a root project, parentProjectId is null but projectId is equal to itself, which make no sense.
         if (!uuid.equals(dto.getRootUuid())) {
           ComponentDto project = data.getComponentByUuid(dto.projectUuid());
-          if (project != null) {
-            builder.setProjectId(project.getId());
-          }
+          setNullable(project, builder::setProjectId, ComponentDto::getId);
+
           ComponentDto subProject = data.getComponentByUuid(dto.getRootUuid());
-          if (subProject != null) {
-            builder.setSubProjectId(subProject.getId());
-          }
+          setNullable(subProject, builder::setSubProjectId, ComponentDto::getId);
         }
         result.add(builder.build());
       }
@@ -376,7 +358,7 @@ public class SearchResponseFormat {
           valueBuilder.build();
         }
       } else {
-        wsFacet.addAllValues(Collections.<Common.FacetValue>emptyList());
+        wsFacet.addAllValues(Collections.emptyList());
       }
       wsFacets.addFacets(wsFacet);
     }
index 75e0f293c5d9a8fb68abb1fb25fac88181a93b35..bdbc004de868c258f29ec280bee077a06521725d 100644 (file)
@@ -26,6 +26,7 @@ import org.sonar.db.organization.OrganizationDto;
 import org.sonarqube.ws.Organizations;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static org.sonar.core.util.Protobuf.setNullable;
 
 /**
  * Factorizes code and constants between Organization WS's actions.
@@ -119,15 +120,9 @@ public class OrganizationsWsSupport {
       .clear()
       .setName(dto.getName())
       .setKey(dto.getKey());
-    if (dto.getDescription() != null) {
-      builder.setDescription(dto.getDescription());
-    }
-    if (dto.getUrl() != null) {
-      builder.setUrl(dto.getUrl());
-    }
-    if (dto.getAvatarUrl() != null) {
-      builder.setAvatar(dto.getAvatarUrl());
-    }
+    setNullable(dto.getDescription(), builder::setDescription);
+    setNullable(dto.getUrl(), builder::setUrl);
+    setNullable(dto.getAvatarUrl(), builder::setAvatar);
     return builder.build();
   }
 }
index 0f1ea915a4340435d322a8934bfd8047028fa475..3bd6492226ca027ace9a0e321a27ee8ff69cb38d 100644 (file)
@@ -44,6 +44,7 @@ import org.sonarqube.ws.WsPermissions.Group;
 import org.sonarqube.ws.WsPermissions.WsGroupsResponse;
 
 import static java.util.Collections.emptyList;
+import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.db.permission.PermissionQuery.DEFAULT_PAGE_SIZE;
 import static org.sonar.db.permission.PermissionQuery.RESULTS_MAX_SIZE;
 import static org.sonar.db.permission.PermissionQuery.SEARCH_QUERY_MIN_LENGTH;
@@ -131,9 +132,7 @@ public class GroupsAction implements PermissionsWsAction {
       if (group.getId() != 0L) {
         wsGroup.setId(String.valueOf(group.getId()));
       }
-      if (group.getDescription() != null) {
-        wsGroup.setDescription(group.getDescription());
-      }
+      setNullable(group.getDescription(), wsGroup::setDescription);
       wsGroup.addAllPermissions(permissionsByGroupId.get(group.getId()));
     });
 
index 1cec93b9602f63ab520877ec512754e74b24c0cb..93a2d717104a823e333f42da369026650b78788d 100644 (file)
@@ -42,6 +42,7 @@ import org.sonarqube.ws.WsPermissions;
 import org.sonarqube.ws.WsPermissions.UsersWsResponse;
 
 import static java.util.Collections.emptyList;
+import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.db.permission.PermissionQuery.DEFAULT_PAGE_SIZE;
 import static org.sonar.db.permission.PermissionQuery.RESULTS_MAX_SIZE;
 import static org.sonar.db.permission.PermissionQuery.SEARCH_QUERY_MIN_LENGTH;
@@ -144,13 +145,8 @@ public class UsersAction implements PermissionsWsAction {
       WsPermissions.User.Builder userResponse = response.addUsersBuilder()
         .setLogin(user.getLogin())
         .addAllPermissions(permissionsByUserId.get(user.getId()));
-
-      if (user.getEmail() != null) {
-        userResponse.setEmail(user.getEmail());
-      }
-      if (user.getName() != null) {
-        userResponse.setName(user.getName());
-      }
+      setNullable(user.getEmail(), userResponse::setEmail);
+      setNullable(user.getName(), userResponse::setName);
     });
 
     response.getPagingBuilder()
index df92a7607c3fe4f9da2cba8335e2491b2570857d..65fdf27b2f8c8c58a13de6848d370c4eba07ff64 100644 (file)
@@ -25,6 +25,8 @@ import org.sonar.api.utils.DateUtils;
 import org.sonar.db.permission.template.PermissionTemplateDto;
 import org.sonarqube.ws.WsPermissions.PermissionTemplate;
 
+import static org.sonar.core.util.Protobuf.setNullable;
+
 public class PermissionTemplateDtoToPermissionTemplateResponse {
 
   private PermissionTemplateDtoToPermissionTemplateResponse() {
@@ -44,12 +46,8 @@ public class PermissionTemplateDtoToPermissionTemplateResponse {
         .setName(permissionTemplate.getName())
         .setCreatedAt(DateUtils.formatDateTime(permissionTemplate.getCreatedAt()))
         .setUpdatedAt(DateUtils.formatDateTime(permissionTemplate.getUpdatedAt()));
-      if (permissionTemplate.getDescription() != null) {
-        permissionTemplateBuilder.setDescription(permissionTemplate.getDescription());
-      }
-      if (permissionTemplate.getKeyPattern() != null) {
-        permissionTemplateBuilder.setProjectKeyPattern(permissionTemplate.getKeyPattern());
-      }
+      setNullable(permissionTemplate.getDescription(), permissionTemplateBuilder::setDescription);
+      setNullable(permissionTemplate.getKeyPattern(), permissionTemplateBuilder::setProjectKeyPattern);
       return permissionTemplateBuilder.build();
     }
   }
index 8c6d6a82ca0e72da7839e98e8223a9e4ec4f9c6f..553a14947622a135a4132244c510da5e9747d114 100644 (file)
@@ -41,6 +41,7 @@ import org.sonarqube.ws.WsPermissions.SearchTemplatesWsResponse.TemplateIdQualif
 import org.sonarqube.ws.client.permission.SearchTemplatesWsRequest;
 
 import static org.sonar.api.utils.DateUtils.formatDateTime;
+import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.server.permission.ws.PermissionsWsParametersBuilder.createOrganizationParameter;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_ORGANIZATION_KEY;
@@ -122,12 +123,8 @@ public class SearchTemplatesAction implements PermissionsWsAction {
         .setName(templateDto.getName())
         .setCreatedAt(formatDateTime(templateDto.getCreatedAt()))
         .setUpdatedAt(formatDateTime(templateDto.getUpdatedAt()));
-      if (templateDto.getKeyPattern() != null) {
-        templateBuilder.setProjectKeyPattern(templateDto.getKeyPattern());
-      }
-      if (templateDto.getDescription() != null) {
-        templateBuilder.setDescription(templateDto.getDescription());
-      }
+      setNullable(templateDto.getKeyPattern(), templateBuilder::setProjectKeyPattern);
+      setNullable(templateDto.getDescription(), templateBuilder::setDescription);
       for (String permission : ProjectPermissions.ALL) {
         templateBuilder.addPermissions(
           permissionResponse
index 77c4c25b228cb1e56f48574e8464ec6fc24972b2..426de4de3c6533e25029a13216ba68d9b73b0d1e 100644 (file)
@@ -44,6 +44,7 @@ import org.sonarqube.ws.WsPermissions;
 import static org.sonar.api.server.ws.WebService.Param.PAGE;
 import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
 import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY;
+import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.db.permission.PermissionQuery.DEFAULT_PAGE_SIZE;
 import static org.sonar.db.permission.PermissionQuery.RESULTS_MAX_SIZE;
 import static org.sonar.db.permission.PermissionQuery.SEARCH_QUERY_MIN_LENGTH;
@@ -128,9 +129,7 @@ public class TemplateGroupsAction implements PermissionsWsAction {
       if (group.getId() != 0L) {
         wsGroup.setId(String.valueOf(group.getId()));
       }
-      if (group.getDescription() != null) {
-        wsGroup.setDescription(group.getDescription());
-      }
+      setNullable(group.getDescription(), wsGroup::setDescription);
       wsGroup.addAllPermissions(permissionsByGroupId.get(group.getId()));
     });
 
index cf0656f42abc355dd85a09d4fe64f6edf4e86ad8..864dd94eae6518b3a033eb7a9e67d535d5c72159 100644 (file)
@@ -45,6 +45,7 @@ import org.sonarqube.ws.WsPermissions.UsersWsResponse;
 import static org.sonar.api.server.ws.WebService.Param.PAGE;
 import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
 import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY;
+import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.db.permission.PermissionQuery.DEFAULT_PAGE_SIZE;
 import static org.sonar.db.permission.PermissionQuery.RESULTS_MAX_SIZE;
 import static org.sonar.db.permission.PermissionQuery.SEARCH_QUERY_MIN_LENGTH;
@@ -130,12 +131,8 @@ public class TemplateUsersAction implements PermissionsWsAction {
       WsPermissions.User.Builder userResponse = responseBuilder.addUsersBuilder()
         .setLogin(user.getLogin())
         .addAllPermissions(permissionsByUserId.get(user.getId()));
-      if (user.getEmail() != null) {
-        userResponse.setEmail(user.getEmail());
-      }
-      if (user.getName() != null) {
-        userResponse.setName(user.getName());
-      }
+      setNullable(user.getEmail(), userResponse::setEmail);
+      setNullable(user.getName(), userResponse::setName);
     });
     responseBuilder.getPagingBuilder()
       .setPageIndex(paging.pageIndex())
index 85eca91fab3a1dd2c0f4f2da575a6747e76578a8..c70443e5596c0391c80163f23e9e3a6d4c0622b9 100644 (file)
@@ -35,8 +35,10 @@ import org.sonarqube.ws.WsProjects.SearchMyProjectsWsResponse.Link;
 import org.sonarqube.ws.WsProjects.SearchMyProjectsWsResponse.Project;
 import org.sonarqube.ws.client.project.SearchMyProjectsRequest;
 
+import static com.google.common.base.Strings.emptyToNull;
 import static com.google.common.base.Strings.isNullOrEmpty;
 import static java.lang.String.format;
+import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.server.ws.WsUtils.checkRequest;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 
@@ -146,9 +148,7 @@ public class SearchMyProjectsAction implements ProjectsWsAction {
       if (qualityGate.isPresent()) {
         project.setQualityGate(qualityGate.get());
       }
-      if (!isNullOrEmpty(dto.description())) {
-        project.setDescription(dto.description());
-      }
+      setNullable(emptyToNull(dto.description()), project::setDescription);
 
       data.projectLinksFor(dto.uuid()).stream()
         .map(ProjectLinkDtoToWs.INSTANCE)
index fe8d68bf8a6f8d3257c757af6ec30555d105696f..484f48fc802c208ee6c41ea2600e3d1c45e1febc 100644 (file)
@@ -29,6 +29,8 @@ import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.function.Function;
+import javax.annotation.Nullable;
 import org.apache.commons.io.IOUtils;
 
 /**
@@ -169,4 +171,41 @@ public class Protobuf {
       IOUtils.closeQuietly(input);
     }
   }
+
+  /**
+   * Call a setter method of {@link com.google.protobuf.GeneratedMessage.Builder} if the parameter
+   * is not null. Do nothing if parameter is null.
+   * <br/>
+   * This utility method is convenient as the setter methods of Protobuf 2 do not accept
+   * {@code null} parameters. It avoid increasing complexity with "if not null" conditions.
+   * <br/>
+   * Example:
+   * <pre>
+   * setNullable(dto.getLine(), issueBuilder::setLine);
+   * </pre>
+   */
+  public static <PARAM> void setNullable(@Nullable PARAM parameter, Function<PARAM, ?> setter) {
+    if (parameter != null) {
+      setter.apply(parameter);
+    }
+  }
+
+  /**
+   * Same as {@link #setNullable(Object, Function)} but the parameter is converted by the function "{@code paramConverter}"
+   * before being applied to setter. If the converter returns {@code null}, then setter method
+   * is not called.
+   * <br/>
+   * Example:
+   * <pre>
+   * setNullable(dto.getIssueCreationDate(), issueBuilder::setCreationDate, DateUtils::formatDateTime);
+   * </pre>
+   * @see #setNullable(Object, Function)
+   */
+  public static <PARAM, PARAM2> void setNullable(@Nullable PARAM param, Function<PARAM2, ?> setter,
+    Function<PARAM, PARAM2> paramConverter) {
+    if (param != null) {
+      PARAM2 output = paramConverter.apply(param);
+      setNullable(output, setter);
+    }
+  }
 }