LCOV - code coverage report
Current view: top level - server/notedb - ChangeNotesParseApprovalUtil.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 61 67 91.0 %
Date: 2022-11-19 15:00:39 Functions: 7 8 87.5 %

          Line data    Source code
       1             : // Copyright (C) 2022 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.notedb;
      16             : 
      17             : import static com.google.gerrit.server.notedb.ChangeNoteFooters.FOOTER_COPIED_LABEL;
      18             : import static com.google.gerrit.server.notedb.ChangeNoteFooters.FOOTER_LABEL;
      19             : 
      20             : import com.google.auto.value.AutoValue;
      21             : import com.google.common.base.Splitter;
      22             : import com.google.common.base.Strings;
      23             : import java.util.List;
      24             : import java.util.Optional;
      25             : import org.eclipse.jgit.errors.ConfigInvalidException;
      26             : import org.eclipse.jgit.revwalk.FooterKey;
      27             : 
      28             : /**
      29             :  * Util to extract {@link com.google.gerrit.entities.PatchSetApproval} from {@link
      30             :  * ChangeNoteFooters#FOOTER_LABEL} or {@link ChangeNoteFooters#FOOTER_COPIED_LABEL}.
      31             :  */
      32           0 : public class ChangeNotesParseApprovalUtil {
      33             : 
      34             :   /**
      35             :    * {@link com.google.gerrit.entities.PatchSetApproval}, parsed from {@link
      36             :    * ChangeNoteFooters#FOOTER_LABEL} or {@link ChangeNoteFooters#FOOTER_COPIED_LABEL}.
      37             :    *
      38             :    * <p>In comparison to {@link com.google.gerrit.entities.PatchSetApproval}, this entity represent
      39             :    * the raw fields, parsed from the NoteDB footer line, without any interpretation of the parsed
      40             :    * values. See {@link #parseApproval} and {@link #parseCopiedApproval} for the valid {@link
      41             :    * #footerLine} values.
      42             :    */
      43             :   @AutoValue
      44          68 :   public abstract static class ParsedPatchSetApproval {
      45             : 
      46             :     /** The original footer value, that this entity was parsed from. */
      47             :     public abstract String footerLine();
      48             : 
      49             :     public abstract boolean isRemoval();
      50             : 
      51             :     /** Either <LABEL>=VOTE or <LABEL> for {@link #isRemoval}. */
      52             :     public abstract String labelVote();
      53             : 
      54             :     public abstract Optional<String> uuid();
      55             : 
      56             :     public abstract Optional<String> accountIdent();
      57             : 
      58             :     public abstract Optional<String> realAccountIdent();
      59             : 
      60             :     public abstract Optional<String> tag();
      61             : 
      62             :     public static Builder builder() {
      63          68 :       return new AutoValue_ChangeNotesParseApprovalUtil_ParsedPatchSetApproval.Builder();
      64             :     }
      65             : 
      66             :     @AutoValue.Builder
      67          68 :     public abstract static class Builder {
      68             : 
      69             :       abstract Builder footerLine(String labelLine);
      70             : 
      71             :       abstract Builder isRemoval(boolean isRemoval);
      72             : 
      73             :       abstract Builder labelVote(String labelVote);
      74             : 
      75             :       abstract Builder uuid(Optional<String> uuid);
      76             : 
      77             :       abstract Builder accountIdent(Optional<String> accountIdent);
      78             : 
      79             :       abstract Builder realAccountIdent(Optional<String> realAccountIdent);
      80             : 
      81             :       abstract Builder tag(Optional<String> tag);
      82             : 
      83             :       abstract ParsedPatchSetApproval build();
      84             :     }
      85             :   }
      86             : 
      87             :   /**
      88             :    * Parses {@link ParsedPatchSetApproval} from {@link ChangeNoteFooters#FOOTER_LABEL} line.
      89             :    *
      90             :    * <p>Valid added approval footer examples:
      91             :    *
      92             :    * <ul>
      93             :    *   <li>Label: <LABEL>=VOTE
      94             :    *   <li>Label: <LABEL>=VOTE <Gerrit Account>
      95             :    *   <li>Label: <LABEL>=VOTE, <UUID>
      96             :    *   <li>Label: <LABEL>=VOTE, <UUID> <Gerrit Account>
      97             :    * </ul>
      98             :    *
      99             :    * <p>Valid removed approval footer examples:
     100             :    *
     101             :    * <ul>
     102             :    *   <li>-<LABEL>
     103             :    *   <li>-<LABEL> <Gerrit Account>
     104             :    * </ul>
     105             :    *
     106             :    * <p><UUID> is optional, since the approval might have been granted before {@link
     107             :    * com.google.gerrit.entities.PatchSetApproval.UUID} was introduced.
     108             :    *
     109             :    * <p><Gerrit Account> is only persisted in cases, when the account, that granted the vote does
     110             :    * not match the account, that issued {@link ChangeUpdate} (created this NoteDB commit).
     111             :    */
     112             :   public static ParsedPatchSetApproval parseApproval(String footerLine)
     113             :       throws ConfigInvalidException {
     114             :     try {
     115             :       ParsedPatchSetApproval.Builder rawPatchSetApproval =
     116          68 :           ParsedPatchSetApproval.builder().footerLine(footerLine);
     117             :       String labelVoteStr;
     118          68 :       boolean isRemoval = footerLine.startsWith("-");
     119          68 :       rawPatchSetApproval.isRemoval(isRemoval);
     120          68 :       int uuidStart = isRemoval ? -1 : footerLine.indexOf(", ");
     121          68 :       int reviewerStart = footerLine.indexOf(' ', uuidStart != -1 ? uuidStart + 2 : 0);
     122          68 :       int labelStart = isRemoval ? 1 : 0;
     123          68 :       checkFooter(!isRemoval || uuidStart == -1, FOOTER_LABEL, footerLine);
     124             : 
     125          68 :       if (uuidStart != -1) {
     126          68 :         String uuid =
     127          68 :             footerLine.substring(
     128          68 :                 uuidStart + 2, reviewerStart > 0 ? reviewerStart : footerLine.length());
     129          68 :         checkFooter(!Strings.isNullOrEmpty(uuid), FOOTER_LABEL, footerLine);
     130          68 :         labelVoteStr = footerLine.substring(labelStart, uuidStart);
     131          68 :         rawPatchSetApproval.uuid(Optional.of(uuid));
     132          68 :       } else if (reviewerStart != -1) {
     133           5 :         labelVoteStr = footerLine.substring(labelStart, reviewerStart);
     134             :       } else {
     135           8 :         labelVoteStr = footerLine.substring(labelStart);
     136             :       }
     137          68 :       rawPatchSetApproval.labelVote(labelVoteStr);
     138             : 
     139          68 :       if (reviewerStart > 0) {
     140           7 :         String ident = footerLine.substring(reviewerStart + 1);
     141           7 :         rawPatchSetApproval.accountIdent(Optional.of(ident));
     142             :       }
     143          68 :       return rawPatchSetApproval.build();
     144           0 :     } catch (StringIndexOutOfBoundsException ex) {
     145           0 :       throw parseException(FOOTER_LABEL, footerLine, ex);
     146             :     }
     147             :   }
     148             : 
     149             :   /**
     150             :    * Parses copied {@link ParsedPatchSetApproval} from {@link ChangeNoteFooters#FOOTER_COPIED_LABEL}
     151             :    * line.
     152             :    *
     153             :    * <p>Footer example: Copied-Label: <LABEL>=VOTE, <UUID> <Gerrit Account>,<Gerrit Real Account>
     154             :    * :"<TAG>"
     155             :    *
     156             :    * <ul>
     157             :    *   <li>":<"TAG>"" is optional.
     158             :    *   <li><Gerrit Real Account> is also optional, if it was not set.
     159             :    *   <li><UUID> is optional, since the approval might have been granted before {@link
     160             :    *       com.google.gerrit.entities.PatchSetApproval.UUID} was introduced.
     161             :    *   <li>The label, vote, and the Gerrit account are mandatory (unlike FOOTER_LABEL where Gerrit
     162             :    *       Account is also optional since by default it's the committer).
     163             :    * </ul>
     164             :    *
     165             :    * <p>Footer example for removal: Copied-Label: -<LABEL> <Gerrit Account>,<Gerrit Real Account>
     166             :    *
     167             :    * <ul>
     168             :    *   <li><Gerrit Real Account> is also optional, if it was not set.
     169             :    * </ul>
     170             :    */
     171             :   public static ParsedPatchSetApproval parseCopiedApproval(String labelLine)
     172             :       throws ConfigInvalidException {
     173             :     try {
     174             :       ParsedPatchSetApproval.Builder rawPatchSetApproval =
     175          13 :           ParsedPatchSetApproval.builder().footerLine(labelLine);
     176             : 
     177          13 :       boolean isRemoval = labelLine.startsWith("-");
     178          13 :       rawPatchSetApproval.isRemoval(isRemoval);
     179          13 :       int labelStart = isRemoval ? 1 : 0;
     180          13 :       int uuidStart = isRemoval ? -1 : labelLine.indexOf(", ");
     181          13 :       int tagStart = isRemoval ? -1 : labelLine.indexOf(":\"");
     182             : 
     183          13 :       checkFooter(!isRemoval || uuidStart == -1, FOOTER_LABEL, labelLine);
     184             : 
     185             :       // Weird tag that contains uuid delimiter. The uuid is actually not present.
     186          13 :       if (tagStart != -1 && uuidStart > tagStart) {
     187           0 :         uuidStart = -1;
     188             :       }
     189             : 
     190          13 :       int identitiesStart = labelLine.indexOf(' ', uuidStart != -1 ? uuidStart + 2 : 0);
     191          13 :       checkFooter(
     192          13 :           identitiesStart != -1 && identitiesStart < labelLine.length(),
     193             :           FOOTER_COPIED_LABEL,
     194             :           labelLine);
     195             : 
     196          13 :       String labelVoteStr =
     197          13 :           labelLine.substring(labelStart, uuidStart != -1 ? uuidStart : identitiesStart);
     198          13 :       rawPatchSetApproval.labelVote(labelVoteStr);
     199          13 :       if (uuidStart != -1) {
     200          13 :         String uuid = labelLine.substring(uuidStart + 2, identitiesStart);
     201          13 :         checkFooter(!Strings.isNullOrEmpty(uuid), FOOTER_COPIED_LABEL, labelLine);
     202          13 :         rawPatchSetApproval.uuid(Optional.of(uuid));
     203             :       }
     204             :       // The first account is the accountId, and second (if applicable) is the realAccountId.
     205          13 :       List<String> identities =
     206          13 :           Splitter.on(',')
     207          13 :               .splitToList(
     208          13 :                   labelLine.substring(
     209          13 :                       identitiesStart + 1, tagStart == -1 ? labelLine.length() : tagStart));
     210          13 :       checkFooter(identities.size() >= 1, FOOTER_COPIED_LABEL, labelLine);
     211             : 
     212          13 :       rawPatchSetApproval.accountIdent(Optional.of(identities.get(0)));
     213             : 
     214          13 :       if (identities.size() > 1) {
     215           2 :         rawPatchSetApproval.realAccountIdent(Optional.of(identities.get(1)));
     216             :       }
     217             : 
     218          13 :       if (tagStart != -1) {
     219             :         // tagStart+2 skips ":\"" to parse the actual tag. Tags are in brackets.
     220             :         // line.length()-1 skips the last ".
     221           5 :         String tag = labelLine.substring(tagStart + 2, labelLine.length() - 1);
     222           5 :         rawPatchSetApproval.tag(Optional.of(tag));
     223             :       }
     224          13 :       return rawPatchSetApproval.build();
     225           0 :     } catch (StringIndexOutOfBoundsException ex) {
     226           0 :       throw parseException(FOOTER_COPIED_LABEL, labelLine, ex);
     227             :     }
     228             :   }
     229             : 
     230             :   private static void checkFooter(boolean expr, FooterKey footer, String actual)
     231             :       throws ConfigInvalidException {
     232          68 :     if (!expr) {
     233           1 :       throw parseException(footer, actual, /*cause=*/ null);
     234             :     }
     235          68 :   }
     236             : 
     237             :   private static ConfigInvalidException parseException(
     238             :       FooterKey footer, String actual, Throwable cause) {
     239           1 :     return new ConfigInvalidException(
     240           1 :         String.format("invalid %s: %s", footer.getName(), actual), cause);
     241             :   }
     242             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750