Add a way of constructing OptionsBase subclass instances from maps

Added toMap()/fromMap() to OptionsParser, and moved the implementation of OptionsBase#asMap away from OptionsParserImpl.

RELNOTES: None
PiperOrigin-RevId: 153602479
diff --git a/src/main/java/com/google/devtools/common/options/OptionsBase.java b/src/main/java/com/google/devtools/common/options/OptionsBase.java
index 0ca9e44..48658f7 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsBase.java
+++ b/src/main/java/com/google/devtools/common/options/OptionsBase.java
@@ -16,15 +16,15 @@
 
 import com.google.common.escape.CharEscaperBuilder;
 import com.google.common.escape.Escaper;
-
+import java.lang.reflect.Field;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
 /**
- * Base class for all options classes.  Extend this class, adding public
- * instance fields annotated with @Option.  Then you can create instances
- * either programmatically:
+ * Base class for all options classes. Extend this class, adding public instance fields annotated
+ * with {@link Option}. Then you can create instances either programmatically:
  *
  * <pre>
  *   X x = Options.getDefaults(X.class);
@@ -40,11 +40,10 @@
  *   X x = parser.getOptions(X.class);
  * </pre>
  *
- * <p>Subclasses of OptionsBase <b>must</b> be constructed reflectively,
- * i.e. using not {@code new MyOptions}, but one of the two methods above
- * instead.  (Direct construction creates an empty instance, not containing
- * default values.  This leads to surprising behavior and often
- * NullPointerExceptions, etc.)
+ * <p>Subclasses of {@code OptionsBase} <i>must</i> be constructed reflectively, i.e. using not
+ * {@code new MyOptions()}, but one of the above methods instead. (Direct construction creates an
+ * empty instance, not containing default values. This leads to surprising behavior and often {@code
+ * NullPointerExceptions}, etc.)
  */
 public abstract class OptionsBase {
 
@@ -61,13 +60,24 @@
   }
 
   /**
-   * Returns this options object in the form of a (new) mapping from option
-   * names, including inherited ones, to option values.  If the public fields
-   * are mutated, this will be reflected in subsequent calls to {@code asMap}.
-   * Mutation of this map by the caller does not affect this options object.
+   * Returns a mapping from option names to values, for each option on this object, including
+   * inherited ones. The mapping is a copy, so subsequent mutations to it or to this object are
+   * independent. Entries are sorted alphabetically.
    */
-  public final Map<String, Object> asMap() {
-    return OptionsParserImpl.optionsAsMap(this);
+  public final <O extends OptionsBase> Map<String, Object> asMap() {
+    // Generic O is needed to tell the type system that the toMap() call is safe.
+    // The casts are safe because this <= O <= OptionsBase.
+    @SuppressWarnings("unchecked")
+    O castThis = (O) this;
+    @SuppressWarnings("unchecked")
+    Class<O> castClass = (Class<O>) getClass();
+
+    Map<String, Object> map = new LinkedHashMap<>();
+    for (Map.Entry<Field, Object> entry : OptionsParser.toMap(castClass, castThis).entrySet()) {
+      String name = entry.getKey().getAnnotation(Option.class).name();
+      map.put(name, entry.getValue());
+    }
+    return map;
   }
 
   @Override