Merge changes from topic 'fix-group-reference' into stable-2.14

* changes:
  Support plugin group reference with inheritance
  Fix PluginConfig.setGroupReference method
  Align group reference from plugin with core group reference
  Add method to convert a group reference into a configuration value
  Add method to extract group name from a configured value
  Add method to check if configured value is a group reference
  Extract group reference prefix into a constant
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index 8ce7d7e..85fb5e6 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -255,9 +255,9 @@
 
 patchSet:: link:json.html#patchSet[patchSet attribute]
 
-reviewer:: link:json.html#account[account attribute]
+reviewer:: reviewer that was removed as link:json.html#account[account attribute]
 
-author:: link:json.html#account[account attribute]
+remover:: user that removed the reviewer as link:json.html#account[account attribute]
 
 approvals:: All link:json.html#approval[approval attributes] removed.
 
@@ -281,6 +281,24 @@
 eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
 created.
 
+=== Vote Deleted
+
+Sent when a vote was removed from a change.
+
+type:: "vote-deleted"
+
+change:: link:json.html#change[change attribute]
+
+patchSet:: link:json.html#patchSet[patchSet attribute]
+
+reviewer:: user whose vote was removed as link:json.html#account[account attribute]
+
+remover:: user who removed the vote as link:json.html#account[account attribute]
+
+approvals:: all votes as link:json.html#approval[approval attributes]
+
+comment:: Review comment cover message.
+
 == SEE ALSO
 
 * link:json.html[JSON Data Formats]
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/VoteDeletedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/VoteDeletedListener.java
index cb7a014..2e2a8f6 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/VoteDeletedListener.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/VoteDeletedListener.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.extensions.events;
 
 import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.ApprovalInfo;
 import java.util.Map;
 
@@ -26,9 +27,9 @@
 
     Map<String, ApprovalInfo> getApprovals();
 
-    Map<String, ApprovalInfo> getRemoved();
-
     String getMessage();
+
+    AccountInfo getReviewer();
   }
 
   void onVoteDeleted(Event event);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
index 963e7b4..e02dee9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
@@ -116,7 +116,7 @@
     try (BatchUpdate bu =
         batchUpdateFactory.create(
             db.get(), change.getProject(), r.getControl().getUser(), TimeUtil.nowTs())) {
-      bu.addOp(change.getId(), new Op(r.getReviewerUser().getAccountId(), rsrc.getLabel(), input));
+      bu.addOp(change.getId(), new Op(r.getReviewerUser().getAccount(), rsrc.getLabel(), input));
       bu.execute();
     }
 
@@ -124,7 +124,7 @@
   }
 
   private class Op implements BatchUpdateOp {
-    private final Account.Id accountId;
+    private final Account account;
     private final String label;
     private final DeleteVoteInput input;
     private ChangeMessage changeMessage;
@@ -133,8 +133,8 @@
     private Map<String, Short> newApprovals = new HashMap<>();
     private Map<String, Short> oldApprovals = new HashMap<>();
 
-    private Op(Account.Id accountId, String label, DeleteVoteInput input) {
-      this.accountId = accountId;
+    private Op(Account account, String label, DeleteVoteInput input) {
+      this.account = account;
       this.label = label;
       this.input = input;
     }
@@ -150,7 +150,8 @@
       boolean found = false;
       LabelTypes labelTypes = ctx.getControl().getLabelTypes();
 
-      for (PatchSetApproval a : approvalsUtil.byPatchSetUser(ctx.getDb(), ctl, psId, accountId)) {
+      for (PatchSetApproval a :
+          approvalsUtil.byPatchSetUser(ctx.getDb(), ctl, psId, account.getId())) {
         if (labelTypes.byLabel(a.getLabelId()) == null) {
           continue; // Ignore undefined labels.
         } else if (!a.getLabel().equals(label)) {
@@ -172,13 +173,13 @@
         throw new ResourceNotFoundException();
       }
 
-      ctx.getUpdate(psId).removeApprovalFor(accountId, label);
+      ctx.getUpdate(psId).removeApprovalFor(account.getId(), label);
       ctx.getDb().patchSetApprovals().upsert(Collections.singleton(deletedApproval(ctx)));
 
       StringBuilder msg = new StringBuilder();
       msg.append("Removed ");
       LabelVote.appendTo(msg, label, checkNotNull(oldApprovals.get(label)));
-      msg.append(" by ").append(userFactory.create(accountId).getNameEmail()).append("\n");
+      msg.append(" by ").append(userFactory.create(account.getId()).getNameEmail()).append("\n");
       changeMessage =
           ChangeMessagesUtil.newMessage(ctx, msg.toString(), ChangeMessagesUtil.TAG_DELETE_VOTE);
       cmUtil.addChangeMessage(ctx.getDb(), ctx.getUpdate(psId), changeMessage);
@@ -191,7 +192,7 @@
       // set the real user; this preserves the calling user as the NoteDb
       // committer.
       return new PatchSetApproval(
-          new PatchSetApproval.Key(ps.getId(), accountId, new LabelId(label)),
+          new PatchSetApproval.Key(ps.getId(), account.getId(), new LabelId(label)),
           (short) 0,
           ctx.getWhen());
     }
@@ -219,6 +220,7 @@
       voteDeleted.fire(
           change,
           ps,
+          account,
           newApprovals,
           oldApprovals,
           input.notify,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerDeletedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerDeletedEvent.java
index f206cac..02f9d43 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerDeletedEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerDeletedEvent.java
@@ -22,6 +22,7 @@
 public class ReviewerDeletedEvent extends PatchSetEvent {
   public static final String TYPE = "reviewer-deleted";
   public Supplier<AccountAttribute> reviewer;
+  public Supplier<AccountAttribute> remover;
   public Supplier<ApprovalAttribute[]> approvals;
   public String comment;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/StreamEventsApiListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/StreamEventsApiListener.java
index 15c1ae9..b76cbef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/StreamEventsApiListener.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/StreamEventsApiListener.java
@@ -37,6 +37,7 @@
 import com.google.gerrit.extensions.events.ReviewerDeletedListener;
 import com.google.gerrit.extensions.events.RevisionCreatedListener;
 import com.google.gerrit.extensions.events.TopicEditedListener;
+import com.google.gerrit.extensions.events.VoteDeletedListener;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.reviewdb.client.Account;
@@ -84,7 +85,8 @@
         ReviewerAddedListener,
         ReviewerDeletedListener,
         RevisionCreatedListener,
-        TopicEditedListener {
+        TopicEditedListener,
+        VoteDeletedListener {
   private static final Logger log = LoggerFactory.getLogger(StreamEventsApiListener.class);
 
   public static class Module extends AbstractModule {
@@ -104,6 +106,7 @@
       DynamicSet.bind(binder(), ReviewerDeletedListener.class).to(StreamEventsApiListener.class);
       DynamicSet.bind(binder(), RevisionCreatedListener.class).to(StreamEventsApiListener.class);
       DynamicSet.bind(binder(), TopicEditedListener.class).to(StreamEventsApiListener.class);
+      DynamicSet.bind(binder(), VoteDeletedListener.class).to(StreamEventsApiListener.class);
     }
   }
 
@@ -306,6 +309,7 @@
       event.change = changeAttributeSupplier(change);
       event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes));
       event.reviewer = accountAttributeSupplier(ev.getReviewer());
+      event.remover = accountAttributeSupplier(ev.getWho());
       event.comment = ev.getComment();
       event.approvals =
           approvalsAttributeSupplier(change, ev.getNewApprovals(), ev.getOldApprovals());
@@ -473,4 +477,24 @@
       log.error("Failed to dispatch event", e);
     }
   }
+
+  @Override
+  public void onVoteDeleted(VoteDeletedListener.Event ev) {
+    try {
+      ChangeNotes notes = getNotes(ev.getChange());
+      Change change = notes.getChange();
+      VoteDeletedEvent event = new VoteDeletedEvent(change);
+
+      event.change = changeAttributeSupplier(change);
+      event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes));
+      event.comment = ev.getMessage();
+      event.reviewer = accountAttributeSupplier(ev.getReviewer());
+      event.remover = accountAttributeSupplier(ev.getWho());
+      event.approvals = approvalsAttributeSupplier(change, ev.getApprovals(), ev.getOldApprovals());
+
+      dispatcher.get().postEvent(change, event);
+    } catch (OrmException e) {
+      log.error("Failed to dispatch event", e);
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/VoteDeletedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/VoteDeletedEvent.java
new file mode 100644
index 0000000..87c4c05
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/VoteDeletedEvent.java
@@ -0,0 +1,32 @@
+// Copyright (C) 2017 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.events;
+
+import com.google.common.base.Supplier;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.data.AccountAttribute;
+import com.google.gerrit.server.data.ApprovalAttribute;
+
+public class VoteDeletedEvent extends PatchSetEvent {
+  static final String TYPE = "vote-deleted";
+  public Supplier<AccountAttribute> reviewer;
+  public Supplier<AccountAttribute> remover;
+  public Supplier<ApprovalAttribute[]> approvals;
+  public String comment;
+
+  public VoteDeletedEvent(Change change) {
+    super(TYPE, change);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/VoteDeleted.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/VoteDeleted.java
index acb332d..71a603c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/VoteDeleted.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/VoteDeleted.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.extensions.events;
 
-import com.google.common.collect.Maps;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.ApprovalInfo;
@@ -50,6 +49,7 @@
   public void fire(
       Change change,
       PatchSet ps,
+      Account reviewer,
       Map<String, Short> approvals,
       Map<String, Short> oldApprovals,
       NotifyHandling notify,
@@ -64,6 +64,7 @@
           new Event(
               util.changeInfo(change),
               util.revisionInfo(change.getProject(), ps),
+              util.accountInfo(reviewer),
               util.approvals(remover, approvals, when),
               util.approvals(remover, oldApprovals, when),
               notify,
@@ -83,7 +84,7 @@
   }
 
   private static class Event extends AbstractRevisionEvent implements VoteDeletedListener.Event {
-
+    private final AccountInfo reviewer;
     private final Map<String, ApprovalInfo> approvals;
     private final Map<String, ApprovalInfo> oldApprovals;
     private final String message;
@@ -91,6 +92,7 @@
     Event(
         ChangeInfo change,
         RevisionInfo revision,
+        AccountInfo reviewer,
         Map<String, ApprovalInfo> approvals,
         Map<String, ApprovalInfo> oldApprovals,
         NotifyHandling notify,
@@ -98,6 +100,7 @@
         AccountInfo remover,
         Timestamp when) {
       super(change, revision, remover, when, notify);
+      this.reviewer = reviewer;
       this.approvals = approvals;
       this.oldApprovals = oldApprovals;
       this.message = message;
@@ -114,13 +117,13 @@
     }
 
     @Override
-    public Map<String, ApprovalInfo> getRemoved() {
-      return Maps.difference(oldApprovals, approvals).entriesOnlyOnLeft();
+    public String getMessage() {
+      return message;
     }
 
     @Override
-    public String getMessage() {
-      return message;
+    public AccountInfo getReviewer() {
+      return reviewer;
     }
   }
 }