| // Copyright (C) 2008 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.entities; |
| |
| import com.google.auto.value.AutoValue; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.flogger.FluentLogger; |
| import com.google.gerrit.common.Nullable; |
| import java.sql.Timestamp; |
| import java.util.HashSet; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * A message attached to a {@link Change}. This message is persisted in data storage, that is why it |
| * must have template form that does not contain Gerrit user identifiable information. Hence, it |
| * requires processing to convert it to user-facing form. |
| * |
| * <p>These messages are normally auto-generated by gerrit operations, but might also incorporate |
| * user input. |
| */ |
| public final class ChangeMessage { |
| |
| private static final FluentLogger logger = FluentLogger.forEnclosingClass(); |
| |
| /** Template to identify an account in {@link ChangeMessage#message}. */ |
| public static final String ACCOUNT_TEMPLATE = "<GERRIT_ACCOUNT_%d>"; |
| |
| public static final String ACCOUNT_TEMPLATE_REGEX = "<GERRIT_ACCOUNT_([0-9]+)>"; |
| |
| public static final Pattern ACCOUNT_TEMPLATE_PATTERN = Pattern.compile(ACCOUNT_TEMPLATE_REGEX); |
| |
| public static Key key(Change.Id changeId, String uuid) { |
| return new AutoValue_ChangeMessage_Key(changeId, uuid); |
| } |
| |
| @AutoValue |
| public abstract static class Key { |
| public abstract Change.Id changeId(); |
| |
| public abstract String uuid(); |
| } |
| |
| protected Key key; |
| |
| /** Who wrote this comment; null if it was written by the Gerrit system. */ |
| @Nullable protected Account.Id author; |
| |
| /** When this comment was drafted. */ |
| protected Timestamp writtenOn; |
| |
| /** |
| * The text left by the user or Gerrit system in template form, that is free of Gerrit User |
| * Identifiable Information and can be persisted in data storage. |
| */ |
| @Nullable protected String message; |
| |
| /** {@link Account.Id}s that are used in {@link #message} template. */ |
| protected ImmutableSet<Account.Id> accountsInMessage; |
| |
| /** Which patchset (if any) was this message generated from? */ |
| @Nullable protected PatchSet.Id patchset; |
| |
| /** Tag associated with change message */ |
| @Nullable protected String tag; |
| |
| /** Real user that added this message on behalf of the user recorded in {@link #author}. */ |
| @Nullable protected Account.Id realAuthor; |
| |
| protected ChangeMessage() {} |
| |
| public static ChangeMessage create( |
| final ChangeMessage.Key k, @Nullable Account.Id a, Timestamp wo, @Nullable PatchSet.Id psid) { |
| return create(k, a, wo, psid, /*messageTemplate=*/ null, /*realAuthor=*/ null, /*tag=*/ null); |
| } |
| |
| public static ChangeMessage create( |
| final ChangeMessage.Key k, |
| @Nullable Account.Id a, |
| Timestamp wo, |
| @Nullable PatchSet.Id psid, |
| @Nullable String messageTemplate, |
| @Nullable Account.Id realAuthor, |
| @Nullable String tag) { |
| ChangeMessage message = new ChangeMessage(); |
| message.key = k; |
| message.author = a; |
| message.writtenOn = wo; |
| message.patchset = psid; |
| message.message = messageTemplate; |
| message.accountsInMessage = |
| messageTemplate == null ? ImmutableSet.of() : parseTemplates(messageTemplate); |
| // Use null for same real author, as before the column was added. |
| message.realAuthor = Objects.equals(a, realAuthor) ? null : realAuthor; |
| message.tag = tag; |
| return message; |
| } |
| |
| /* Returns account ids that are used in {@code messageTemplate}. */ |
| public static ImmutableSet<Account.Id> parseTemplates(String messageTemplate) { |
| Matcher matcher = ACCOUNT_TEMPLATE_PATTERN.matcher(messageTemplate); |
| Set<Account.Id> accountsInTemplate = new HashSet<>(); |
| while (matcher.find()) { |
| String accountId = matcher.group(1); |
| Optional<Account.Id> parsedAccountId = Account.Id.tryParse(accountId); |
| if (parsedAccountId.isPresent()) { |
| accountsInTemplate.add(parsedAccountId.get()); |
| } else { |
| logger.atFine().log("Failed to parse accountId from template %s", matcher.group()); |
| } |
| } |
| return ImmutableSet.copyOf(accountsInTemplate); |
| } |
| |
| public ChangeMessage.Key getKey() { |
| return key; |
| } |
| |
| /** If null, the message was written 'by the Gerrit system'. */ |
| public Account.Id getAuthor() { |
| return author; |
| } |
| |
| public Account.Id getRealAuthor() { |
| return realAuthor != null ? realAuthor : getAuthor(); |
| } |
| |
| public Timestamp getWrittenOn() { |
| return writtenOn; |
| } |
| |
| /** Message template, as persisted in data storage. */ |
| public String getMessage() { |
| return message; |
| } |
| |
| /** Account ids, used in {@link #message} template. */ |
| public ImmutableSet<Account.Id> getAccountsInMessage() { |
| return accountsInMessage == null ? ImmutableSet.of() : accountsInMessage; |
| } |
| |
| public String getTag() { |
| return tag; |
| } |
| |
| public PatchSet.Id getPatchSetId() { |
| return patchset; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (!(o instanceof ChangeMessage)) { |
| return false; |
| } |
| ChangeMessage m = (ChangeMessage) o; |
| return Objects.equals(key, m.key) |
| && Objects.equals(author, m.author) |
| && Objects.equals(writtenOn, m.writtenOn) |
| && Objects.equals(message, m.message) |
| && Objects.equals(accountsInMessage, m.accountsInMessage) |
| && Objects.equals(patchset, m.patchset) |
| && Objects.equals(tag, m.tag) |
| && Objects.equals(realAuthor, m.realAuthor); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash( |
| key, author, writtenOn, message, accountsInMessage, patchset, tag, realAuthor); |
| } |
| |
| @Override |
| public String toString() { |
| return "ChangeMessage{" |
| + "key=" |
| + key |
| + ", author=" |
| + author |
| + ", realAuthor=" |
| + realAuthor |
| + ", writtenOn=" |
| + writtenOn |
| + ", patchset=" |
| + patchset |
| + ", tag=" |
| + tag |
| + ", message=[" |
| + message |
| + "], accountsInMessage=" |
| + accountsInMessage |
| + "}"; |
| } |
| } |