Please wait. This can take some minutes ...
                    
                 
             
         
        
            
                
                    
                    
                        Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. 
                        
                        Project price only 1 $ 
                        
                            You can buy this project and download/modify it how often you want.
                        
                        
                        
                     
                 
             
         
             
    
    
    com.google.gerrit.server.notedb.ChangeNotesParseApprovalUtil Maven / Gradle / Ivy 
    
// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.notedb;
import static com.google.gerrit.server.notedb.ChangeNoteFooters.FOOTER_COPIED_LABEL;
import static com.google.gerrit.server.notedb.ChangeNoteFooters.FOOTER_LABEL;
import com.google.auto.value.AutoValue;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.revwalk.FooterKey;
/**
 * Util to extract {@link com.google.gerrit.entities.PatchSetApproval} from {@link
 * ChangeNoteFooters#FOOTER_LABEL} or {@link ChangeNoteFooters#FOOTER_COPIED_LABEL}.
 */
public class ChangeNotesParseApprovalUtil {
  private static final String LABEL_VOTE_UUID_SEPARATOR = ", ";
  /**
   * {@link com.google.gerrit.entities.PatchSetApproval}, parsed from {@link
   * ChangeNoteFooters#FOOTER_LABEL} or {@link ChangeNoteFooters#FOOTER_COPIED_LABEL}.
   *
   * In comparison to {@link com.google.gerrit.entities.PatchSetApproval}, this entity represent
   * the raw fields, parsed from the NoteDB footer line, without any interpretation of the parsed
   * values. See {@link #parseApproval} and {@link #parseCopiedApproval} for the valid {@link
   * #footerLine} values.
   */
  @AutoValue
  public abstract static class ParsedPatchSetApproval {
    /** The original footer value, that this entity was parsed from. */
    public abstract String footerLine();
    public abstract boolean isRemoval();
    /** Either =VOTE or  for {@link #isRemoval}. */
    public abstract String labelVote();
    public abstract Optional uuid();
    public abstract Optional accountIdent();
    public abstract Optional realAccountIdent();
    public abstract Optional tag();
    public static Builder builder() {
      return new AutoValue_ChangeNotesParseApprovalUtil_ParsedPatchSetApproval.Builder();
    }
    @AutoValue.Builder
    public abstract static class Builder {
      abstract Builder footerLine(String labelLine);
      abstract Builder isRemoval(boolean isRemoval);
      abstract Builder labelVote(String labelVote);
      abstract Builder uuid(Optional uuid);
      abstract Builder accountIdent(Optional accountIdent);
      abstract Builder realAccountIdent(Optional realAccountIdent);
      abstract Builder tag(Optional tag);
      abstract ParsedPatchSetApproval build();
    }
  }
  /**
   * Delegates parsing of {@link ParsedPatchSetApproval} from {@link ChangeNoteFooters#FOOTER_LABEL}
   * line to dedicated methods: {@link #parseAddedApproval} and {@link #parseRemovedApproval}
   * correspondingly.
   */
  public static ParsedPatchSetApproval parseApproval(String footerLine)
      throws ConfigInvalidException {
    try {
      return footerLine.startsWith("-")
          ? parseRemovedApproval(footerLine)
          : parseAddedApproval(footerLine);
    } catch (StringIndexOutOfBoundsException ex) {
      throw parseException(FOOTER_LABEL, footerLine, ex);
    }
  }
  /**
   * Parses added {@link ParsedPatchSetApproval} from {@link ChangeNoteFooters#FOOTER_LABEL} line.
   *
   * Valid added approval footer examples:
   *
   * 
   *   Label: <LABEL>=VOTE
   *    Label: <LABEL>=VOTE <Gerrit Account>
   *    Label: <LABEL>=VOTE, <UUID>
   *    Label: <LABEL>=VOTE, <UUID> <Gerrit Account>
   *   
   *
   * <UUID> is optional, since the approval might have been granted before {@link
   * com.google.gerrit.entities.PatchSetApproval.UUID} was introduced.
   *
   * 
 is only persisted in cases, when the account, that granted the vote does
   * not match the account, that issued {@link ChangeUpdate} (created this NoteDB commit).
   */
  private static ParsedPatchSetApproval parseAddedApproval(String footerLine)
      throws ConfigInvalidException {
    ParsedPatchSetApproval.Builder rawPatchSetApproval =
        ParsedPatchSetApproval.builder().footerLine(footerLine);
    rawPatchSetApproval.isRemoval(false);
    // We need some additional logic to differentiate between labels that have a UUID and those that
    // have a user with a comma. This allows us to separate the following cases (note that the
    // leading `Label: ` has been elided at this point):
    //   Label: =VOTE,  
    //   Label: =VOTE 
    int reviewerStartOffset = 0;
    int scoreStart = footerLine.indexOf('=') + 1;
    StringBuilder labelNameScore = new StringBuilder(footerLine.substring(0, scoreStart));
    for (int i = scoreStart; i < footerLine.length(); i++) {
      char currentChar = footerLine.charAt(i);
      // If we hit ',' before ' ' we have a UUID
      if (currentChar == ',') {
        labelNameScore.append(footerLine, scoreStart, i);
        int uuidStart = i + LABEL_VOTE_UUID_SEPARATOR.length();
        int uuidEnd = footerLine.indexOf(' ', uuidStart);
        String uuid = footerLine.substring(uuidStart, uuidEnd > 0 ? uuidEnd : footerLine.length());
        checkFooter(!Strings.isNullOrEmpty(uuid), FOOTER_LABEL, footerLine);
        rawPatchSetApproval.uuid(Optional.of(uuid));
        reviewerStartOffset = uuidStart + uuid.length();
        break;
      }
      // Otherwise we don't
      if (currentChar == ' ') {
        labelNameScore.append(footerLine, scoreStart, i);
        break;
      }
      // If we hit neither we're defensive assign the whole line
      if (i == footerLine.length() - 1) {
        labelNameScore = new StringBuilder(footerLine);
        break;
      }
    }
    rawPatchSetApproval.labelVote(labelNameScore.toString());
    int reviewerStart = footerLine.indexOf(' ', reviewerStartOffset);
    if (reviewerStart > 0) {
      String ident = footerLine.substring(reviewerStart + 1);
      rawPatchSetApproval.accountIdent(Optional.of(ident));
    }
    return rawPatchSetApproval.build();
  }
  /**
   * Parses removed {@link ParsedPatchSetApproval} from {@link ChangeNoteFooters#FOOTER_LABEL} line.
   *
   * Valid removed approval footer examples:
   *
   * 
   *   -<LABEL>
   *    -<LABEL> <Gerrit Account>
   *   
   *
   * <Gerrit Account> is only persisted in cases, when the account, that granted the vote
   * does not match the account, that issued {@link ChangeUpdate} (created this NoteDB commit).
   */
  private static ParsedPatchSetApproval parseRemovedApproval(String footerLine) {
    ParsedPatchSetApproval.Builder rawPatchSetApproval =
        ParsedPatchSetApproval.builder().footerLine(footerLine);
    rawPatchSetApproval.isRemoval(true);
    int labelStart = 1;
    int reviewerStart = footerLine.indexOf(' ', labelStart);
    rawPatchSetApproval.labelVote(
        reviewerStart != -1
            ? footerLine.substring(labelStart, reviewerStart)
            : footerLine.substring(labelStart));
    if (reviewerStart > 0) {
      String ident = footerLine.substring(reviewerStart + 1);
      rawPatchSetApproval.accountIdent(Optional.of(ident));
    }
    return rawPatchSetApproval.build();
  }
  /**
   * Parses copied {@link ParsedPatchSetApproval} from {@link ChangeNoteFooters#FOOTER_COPIED_LABEL}
   * line.
   *
   * 
Footer example: Copied-Label: =VOTE,  ,
   * :""
   *
   * 
   *   ":<"TAG>"" is optional.
   *     is also optional, if it was not set.
   *    is optional, since the approval might have been granted before {@link
   *       com.google.gerrit.entities.PatchSetApproval.UUID} was introduced.
   *   The label, vote, and the Gerrit account are mandatory (unlike FOOTER_LABEL where Gerrit
   *       Account is also optional since by default it's the committer).
   *     
   *
   * Footer example for removal: Copied-Label: - ,
   *
   * 
   *    is also optional, if it was not set.
   *   
   */
  public static ParsedPatchSetApproval parseCopiedApproval(String labelLine)
      throws ConfigInvalidException {
    try {
      ParsedPatchSetApproval.Builder rawPatchSetApproval =
          ParsedPatchSetApproval.builder().footerLine(labelLine);
      boolean isRemoval = labelLine.startsWith("-");
      rawPatchSetApproval.isRemoval(isRemoval);
      int labelStart = isRemoval ? 1 : 0;
      int tagStart = isRemoval ? -1 : labelLine.indexOf(":\"");
      int uuidStart = parseCopiedApprovalUuidStart(labelLine, tagStart);
      checkFooter(!isRemoval || uuidStart == -1, FOOTER_LABEL, labelLine);
      // Weird tag that contains uuid delimiter. The uuid is actually not present.
      if (tagStart != -1 && uuidStart > tagStart) {
        uuidStart = -1;
      }
      int identitiesStart =
          labelLine.indexOf(
              ' ', uuidStart != -1 ? uuidStart + LABEL_VOTE_UUID_SEPARATOR.length() : 0);
      checkFooter(
          identitiesStart != -1 && identitiesStart < labelLine.length(),
          FOOTER_COPIED_LABEL,
          labelLine);
      String labelVoteStr =
          labelLine.substring(labelStart, uuidStart != -1 ? uuidStart : identitiesStart);
      rawPatchSetApproval.labelVote(labelVoteStr);
      if (uuidStart != -1) {
        String uuid = labelLine.substring(uuidStart + 2, identitiesStart);
        checkFooter(!Strings.isNullOrEmpty(uuid), FOOTER_COPIED_LABEL, labelLine);
        rawPatchSetApproval.uuid(Optional.of(uuid));
      }
      // The first account is the accountId, and second (if applicable) is the realAccountId.
      List identities =
          parseIdentities(
              labelLine.substring(
                  identitiesStart + 1, tagStart == -1 ? labelLine.length() : tagStart));
      rawPatchSetApproval.accountIdent(Optional.of(identities.get(0)));
      if (identities.size() > 1) {
        rawPatchSetApproval.realAccountIdent(Optional.of(identities.get(1)));
      }
      if (tagStart != -1) {
        // tagStart+2 skips ":\"" to parse the actual tag. Tags are in brackets.
        // line.length()-1 skips the last ".
        String tag = labelLine.substring(tagStart + 2, labelLine.length() - 1);
        rawPatchSetApproval.tag(Optional.of(tag));
      }
      return rawPatchSetApproval.build();
    } catch (StringIndexOutOfBoundsException ex) {
      throw parseException(FOOTER_COPIED_LABEL, labelLine, ex);
    }
  }
  // Return the UUID start index or -1 if no UUID is present
  private static int parseCopiedApprovalUuidStart(String line, int tagStart) {
    int separatorIndex = line.indexOf(LABEL_VOTE_UUID_SEPARATOR);
    // The first part of the condition checks whether the footer has the following format:
    //   Copied-Label: =VOTE , :""
    //   Weird tag that contains uuid delimiter. The uuid is actually not present.
    if ((tagStart != -1 && separatorIndex > tagStart)
        ||
        // The second part of the condition allows us to distinguish the following two lines:
        //   Label2=+1, 577fb248e474018276351785930358ec0450e9f7 Gerrit User 1 <1@gerrit>
        //   Label2=+1 User Name (company_name, department) <2@gerrit>
        (line.indexOf(' ') < separatorIndex)) {
      return -1;
    }
    return separatorIndex;
  }
  // Splitting on "," breaks for identities containing commas. The below re-implements splitting on
  // "(?<=>),", but it's 3-5x faster, as performance matters here.
  private static List parseIdentities(String line) {
    List idents = Splitter.on(',').splitToList(line);
    List identitiesList = new ArrayList<>();
    for (int i = 0; i < idents.size(); i++) {
      if (i == 0 || idents.get(i - 1).endsWith(">")) {
        identitiesList.add(idents.get(i));
      } else {
        int lastIndex = identitiesList.size() - 1;
        identitiesList.set(lastIndex, identitiesList.get(lastIndex) + "," + idents.get(i));
      }
    }
    return identitiesList;
  }
  private static void checkFooter(boolean expr, FooterKey footer, String actual)
      throws ConfigInvalidException {
    if (!expr) {
      throw parseException(footer, actual, /* cause= */ null);
    }
  }
  private static ConfigInvalidException parseException(
      FooterKey footer, String actual, Throwable cause) {
    return new ConfigInvalidException(
        String.format("invalid %s: %s", footer.getName(), actual), cause);
  }
}