code: // Licensed to the Software Freedom Conservancy (SFC) under one
  // or more contributor license agreements.  See the NOTICE file
  // distributed with this work for additional information
  // regarding copyright ownership.  The SFC licenses this file
  // to you 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 org.openqa.selenium.grid.config;
  
  import com.beust.jcommander.Parameter;
  import com.google.common.collect.ImmutableList;
  import com.google.common.collect.ImmutableMap;
  import com.google.common.collect.ImmutableSortedSet;
  import com.google.common.primitives.Primitives;
  import java.lang.reflect.Field;
  import java.util.ArrayDeque;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Deque;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  import java.util.Optional;
  import java.util.Set;
  import org.openqa.selenium.internal.Require;
  
  /**
   * A form of {@link Config} that is generated by looking at fields in the constructor arg that are
   * annotated with {@link ConfigValue}. The class hierarchy is walked from closest to Object to the
   * constructor argument's type, null values are ignored, and the order in which fields are read is
   * not stable (meaning duplicate config values may give different values each time).
   *
   * <p>The main use of this class is to allow an object configured using (for example) jcommander to
   * be used directly within the app, without requiring intermediate support classes to transform
   * flags to config values.
   */
  public class AnnotatedConfig implements Config {
  
    private final Map<String, Map<String, List<String>>> config;
  
    public AnnotatedConfig(Object obj) {
      this(obj, Collections.emptySet(), false);
    }
  
    public AnnotatedConfig(Object obj, Set<String> cliArgs, boolean includeCliArgs) {
      Map<String, Map<String, List<String>>> values = new HashMap<>();
  
      Deque<Field> allConfigValues = findConfigFields(obj.getClass());
  
      for (Field field : allConfigValues) {
        if (Map.class.isAssignableFrom(field.getType())) {
          throw new ConfigException("Map fields may not be used for configuration: " + field);
        }
  
        field.setAccessible(true);
        Object value;
        try {
          value = field.get(obj);
        } catch (IllegalAccessException e) {
          throw new ConfigException("Unable to read field: " + field);
        }
  
        ConfigValue annotation = field.getAnnotation(ConfigValue.class);
        Parameter cliAnnotation = field.getAnnotation(Parameter.class);
        boolean containsCliArg =
            cliAnnotation != null && Arrays.stream(cliAnnotation.names()).anyMatch(cliArgs::contains);
        if (cliArgs.size() > 0 && !containsCliArg && includeCliArgs) {
          // Only getting config values for args entered by the user.
          continue;
        }
        if (cliArgs.size() > 0 && containsCliArg && !includeCliArgs) {
          // Excluding config values for args entered by the user.
          continue;
        }
        Map<String, List<String>> section =
            values.computeIfAbsent(annotation.section(), str -> new HashMap<>());
        List<String> all = section.computeIfAbsent(annotation.name(), str -> new LinkedList<>());
  
        if (value instanceof Collection) {
          for (Object o : ((Collection<?>) value)) {
            String singleValue = getSingleValue(o);
            if (singleValue != null) {
              all.add(singleValue);
            }
          }
        } else {
          String singleValue = getSingleValue(value);
          if (singleValue != null) {
            all.add(singleValue);
          }
        }
      }
  
      // Now make the config immutable.
      this.config = values;
    }
  
    private String getSingleValue(Object value) {
      if (value == null) {
        return null;
      }
  
      if (value instanceof Map) {
        throw new ConfigException("Map fields may not be used for configuration: " + value);
      }
  
      if (value instanceof Collection) {
        throw new ConfigException("Collection fields may not be used for configuration: " + value);
      }
  
      if (Boolean.FALSE.equals(value) && !Primitives.isWrapperType(value.getClass())) {
        return null;
      }
  
      if (value instanceof Number && ((Number) value).floatValue() == 0f) {
        return null;
      }
  
      return String.valueOf(value);
    }
  
    private Deque<Field> findConfigFields(Class<?> clazz) {
      Deque<Field> toSet = new ArrayDeque<>();
      Set<Class<?>> toVisit = new HashSet<>();
      toVisit.add(clazz);
  
      Set<Class<?>> seen = new HashSet<>();
  
      while (!toVisit.isEmpty()) {
        clazz = toVisit.iterator().next();
        toVisit.remove(clazz);
        seen.add(clazz);
  
        Arrays.stream(clazz.getDeclaredFields())
            .filter(field -> field.getAnnotation(ConfigValue.class) != null)
            .forEach(toSet::addLast);
  
        Class<?> toAdd = clazz.getSuperclass();
        if (toAdd != null && !Object.class.equals(toAdd) && !seen.contains(toAdd)) {
          toVisit.add(toAdd);
        }
        Arrays.stream(clazz.getInterfaces())
            .filter(face -> !seen.contains(face))
            .forEach(toVisit::add);
      }
  
      return toSet;
    }
  
    @Override
    public Optional<List<String>> getAll(String section, String option) {
      Require.nonNull("Section name", section);
      Require.nonNull("Option name", option);
  
      Map<String, List<String>> sec = config.get(section);
      if (sec == null || sec.isEmpty()) {
        return Optional.empty();
      }
  
      List<String> values = sec.get(option);
      if (values == null || values.isEmpty()) {
        return Optional.empty();
      }
  
      return Optional.of(ImmutableList.copyOf(values));
    }
  
    @Override
    public Set<String> getSectionNames() {
      return ImmutableSortedSet.copyOf(config.keySet());
    }
  
    @Override
    public Set<String> getOptions(String section) {
      Require.nonNull("Section name to get options for", section);
      return ImmutableSortedSet.copyOf(config.getOrDefault(section, ImmutableMap.of()).keySet());
    }
  }
  