Fix to apply task properties to names-factory fields

Before this change, task properties were not being applied on
names-factory fields. This change makes it possible to apply task
properties on names-factory fields.

Change-Id: I5c087af775c212f21369b9d01fc7f0eb3d4cbfa2
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/Properties.java b/src/main/java/com/googlesource/gerrit/plugins/task/Properties.java
index fe98ce0..7c8ceb8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/Properties.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/Properties.java
@@ -14,11 +14,14 @@
 
 package com.googlesource.gerrit.plugins.task;
 
+import com.google.common.collect.Sets;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gwtorm.server.OrmException;
+import com.googlesource.gerrit.plugins.task.TaskConfig.NamesFactory;
 import com.googlesource.gerrit.plugins.task.TaskConfig.Task;
 import java.lang.reflect.Field;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -33,7 +36,7 @@
   // "${_name}" -> group(1) = "_name"
   protected static final Pattern PATTERN = Pattern.compile("\\$\\{([^}]+)\\}");
 
-  protected Task definition;
+  protected Object definition;
   protected Map<String, String> expanded = new HashMap<>();
   protected Map<String, String> unexpanded;
   protected boolean expandingNonPropertyFields;
@@ -65,21 +68,29 @@
     }
 
     this.definition = definition;
-    expandNonPropertyFields();
+    expandNonPropertyFields(Collections.emptySet());
   }
 
-  protected void expandNonPropertyFields() {
+  public Properties(NamesFactory namesFactory, Map<String, String> properties) throws OrmException {
+    expanded.putAll(properties);
+    definition = namesFactory;
+    expandNonPropertyFields(Sets.newHashSet(TaskConfig.KEY_TYPE));
+  }
+
+  protected void expandNonPropertyFields(Set<String> excludedFields) {
     expandingNonPropertyFields = true;
-    for (Field field : Task.class.getFields()) {
+    for (Field field : definition.getClass().getFields()) {
       try {
-        field.setAccessible(true);
-        Object o = field.get(definition);
-        if (o instanceof String) {
-          field.set(definition, expandLiteral((String) o));
-        } else if (o instanceof List) {
-          @SuppressWarnings("unchecked")
-          List<String> forceCheck = List.class.cast(o);
-          expandInPlace(forceCheck);
+        if (!excludedFields.contains(field.getName())) {
+          field.setAccessible(true);
+          Object o = field.get(definition);
+          if (o instanceof String) {
+            field.set(definition, expandLiteral((String) o));
+          } else if (o instanceof List) {
+            @SuppressWarnings("unchecked")
+            List<String> forceCheck = List.class.cast(o);
+            expandInPlace(forceCheck);
+          }
         }
       } catch (IllegalAccessException e) {
         throw new RuntimeException(e);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java b/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java
index 0c0847a..b7fde16 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java
@@ -209,13 +209,14 @@
       return defs;
     }
 
-    protected List<Task> getTasksFactoryDefinitions() {
+    protected List<Task> getTasksFactoryDefinitions() throws OrmException {
       List<Task> taskList = new ArrayList<>();
       for (String taskFactoryName : definition.subTasksFactories) {
         TasksFactory tasksFactory = definition.config.getTasksFactory(taskFactoryName);
         if (tasksFactory != null) {
           NamesFactory namesFactory = definition.config.getNamesFactory(tasksFactory.namesFactory);
           if (namesFactory != null && namesFactory.type != null) {
+            new Properties(namesFactory, definition.properties);
             switch (NamesFactoryType.getNamesFactoryType(namesFactory.type)) {
               case STATIC:
                 getStaticTypeTasksDefinitions(tasksFactory, namesFactory, taskList);
diff --git a/src/main/resources/Documentation/task.md b/src/main/resources/Documentation/task.md
index 1928186..8c1309e 100644
--- a/src/main/resources/Documentation/task.md
+++ b/src/main/resources/Documentation/task.md
@@ -405,7 +405,7 @@
 Properties
 ----------
 The task plugin supplies the following properties which may be used anywhere in
-a task or tasks-factory definition.
+a task, tasks-factory, or names-factory definition.
 
 ```
     ${_name}            represents the name of the current task
@@ -422,6 +422,7 @@
     fail-hint = {$_name} needs to be fixed
     fail-hint = {$_change_number} with {$_change_status} needs to be fixed
     fail-hint = {$_change_id} on {$_change_project} and {$_change_branch} needs to be fixed
+    changes = parentof:${_change_number} project:${_change_project} branch:${_change_branch}
 ```
 
 Custom properties may be defined on a task using the following syntax:
diff --git a/src/main/resources/Documentation/task_states.md b/src/main/resources/Documentation/task_states.md
index 714d446..020a836 100644
--- a/src/main/resources/Documentation/task_states.md
+++ b/src/main/resources/Documentation/task_states.md
@@ -224,7 +224,7 @@
 
 [names-factory "NamesFactory Properties"]
   type = change
-  changes = change:_change1_number
+  changes = change:_change1_number OR change:${_change_number} project:${_change_project} branch:${_change_branch}
 
 [task "Subtask Preload"]
   preload-task = Subtask READY
@@ -291,6 +291,7 @@
   name = my a task
   name = my b task
   name = my c task
+  name = my d task Change Number(${_change_number}) Change Id(${_change_id}) Change Project(${_change_project}) Change Branch(${_change_branch}) Change Status(${_change_status}) Change Topic(${_change_topic})
   type = static
 
 [names-factory "names-factory static (empty name list)"]
@@ -815,6 +816,11 @@
                   },
                   {
                      "hasPass" : true,
+                     "name" : "my d task Change Number(_change3_number) Change Id(_change3_id) Change Project(_change3_project) Change Branch(_change3_branch) Change Status(_change3_status) Change Topic(_change3_topic)",
+                     "status" : "FAIL"
+                  },
+                  {
+                     "hasPass" : true,
                      "name" : "_change1_number",
                      "status" : "FAIL"
                   },
@@ -865,6 +871,12 @@
                         },
                         {
                            "hasPass" : true,
+                           "hint" : "Name(_change3_number) Change Number(_change3_number) Change Id(_change3_id) Change Project(_change3_project) Change Branch(_change3_branch) Change Status(_change3_status) Change Topic(_change3_topic)",
+                           "name" : "_change3_number",
+                           "status" : "FAIL"
+                        },
+                        {
+                           "hasPass" : true,
                            "hint" : "Name(_change1_number) Change Number(_change3_number) Change Id(_change3_id) Change Project(_change3_project) Change Branch(_change3_branch) Change Status(_change3_status) Change Topic(_change3_topic)",
                            "name" : "_change1_number",
                            "status" : "FAIL"
diff --git a/test/all b/test/all
index a0a691b..437965f 100644
--- a/test/all
+++ b/test/all
@@ -426,6 +426,12 @@
                   {
                      "applicable" : true,
                      "hasPass" : true,
+                     "name" : "my d task Change Number(_change3_number) Change Id(_change3_id) Change Project(_change3_project) Change Branch(_change3_branch) Change Status(_change3_status) Change Topic(_change3_topic)",
+                     "status" : "FAIL"
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : true,
                      "name" : "_change1_number",
                      "status" : "FAIL"
                   },
@@ -489,6 +495,13 @@
                         {
                            "applicable" : true,
                            "hasPass" : true,
+                           "hint" : "Name(_change3_number) Change Number(_change3_number) Change Id(_change3_id) Change Project(_change3_project) Change Branch(_change3_branch) Change Status(_change3_status) Change Topic(_change3_topic)",
+                           "name" : "_change3_number",
+                           "status" : "FAIL"
+                        },
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
                            "hint" : "Name(_change1_number) Change Number(_change3_number) Change Id(_change3_id) Change Project(_change3_project) Change Branch(_change3_branch) Change Status(_change3_status) Change Topic(_change3_topic)",
                            "name" : "_change1_number",
                            "status" : "FAIL"