All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.config.ConfigUtil Maven / Gradle / Ivy

The newest version!
// Copyright (C) 2009 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.config;

import static java.util.Objects.requireNonNull;

import com.google.common.flogger.FluentLogger;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gerrit.common.UsedAt;
import com.google.gerrit.common.UsedAt.Project;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;

public class ConfigUtil {
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();

  @SuppressWarnings("unchecked")
  private static  T[] allValuesOf(T defaultValue) {
    try {
      return (T[]) defaultValue.getClass().getMethod("values").invoke(null);
    } catch (IllegalArgumentException
        | NoSuchMethodException
        | InvocationTargetException
        | IllegalAccessException
        | SecurityException e) {
      throw new IllegalArgumentException("Cannot obtain enumeration values", e);
    }
  }

  /**
   * Parse a Java enumeration from the configuration.
   *
   * @param  type of the enumeration object.
   * @param section section the key is in.
   * @param subsection subsection the key is in, or null if not in a subsection.
   * @param setting name of the setting to read.
   * @param valueString string value from git Config
   * @param all all possible values in the enumeration which should be recognized. This should be
   *     {@code EnumType.values()}.
   * @return the selected enumeration value, or {@code defaultValue}.
   */
  private static > T getEnum(
      final String section,
      final String subsection,
      final String setting,
      String valueString,
      final T[] all) {

    String n = valueString.replace(' ', '_').replace('-', '_');
    for (T e : all) {
      if (e.name().equalsIgnoreCase(n)) {
        return e;
      }
    }

    final StringBuilder r = new StringBuilder();
    r.append("Value \"");
    r.append(valueString);
    r.append("\" not recognized in ");
    r.append(section);
    if (subsection != null) {
      r.append(".");
      r.append(subsection);
    }
    r.append(".");
    r.append(setting);
    r.append("; supported values are: ");
    for (T e : all) {
      r.append(e.name());
      r.append(" ");
    }

    throw new IllegalArgumentException(r.toString().trim());
  }

  /**
   * Parse a Java enumeration list from the configuration.
   *
   * @param  type of the enumeration object.
   * @param config the configuration file to read.
   * @param section section the key is in.
   * @param subsection subsection the key is in, or null if not in a subsection.
   * @param setting name of the setting to read.
   * @param defaultValue default value to return if the setting was not set. Must not be null as the
   *     enumeration values are derived from this.
   * @return the selected enumeration values list, or {@code defaultValue}.
   */
  public static > List getEnumList(
      final Config config,
      final String section,
      final String subsection,
      final String setting,
      final T defaultValue) {
    final T[] all = allValuesOf(defaultValue);
    return getEnumList(config, section, subsection, setting, all, defaultValue);
  }

  /**
   * Parse a Java enumeration list from the configuration.
   *
   * @param  type of the enumeration object.
   * @param config the configuration file to read.
   * @param section section the key is in.
   * @param subsection subsection the key is in, or null if not in a subsection.
   * @param setting name of the setting to read.
   * @param all all possible values in the enumeration which should be recognized. This should be
   *     {@code EnumType.values()}.
   * @param defaultValue default value to return if the setting was not set. This value may be null.
   * @return the selected enumeration values list, or {@code defaultValue}.
   */
  public static > List getEnumList(
      final Config config,
      final String section,
      final String subsection,
      final String setting,
      final T[] all,
      final T defaultValue) {
    final List list = new ArrayList<>();
    final String[] values = config.getStringList(section, subsection, setting);
    if (values.length == 0) {
      list.add(defaultValue);
    } else {
      for (String string : values) {
        if (string != null) {
          try {
            list.add(getEnum(section, subsection, setting, string, all));
          } catch (IllegalArgumentException ex) {
            // It's better to ignore a wrongly configured enum, rather than fail to load Gerrit.
            logger.atWarning().log("%s", ex.getMessage());
          }
        }
      }
    }
    return list;
  }

  /**
   * Parse a numerical time unit, such as "1 minute", from the configuration.
   *
   * @param config the configuration file to read.
   * @param section section the key is in.
   * @param subsection subsection the key is in, or null if not in a subsection.
   * @param setting name of the setting to read.
   * @param defaultValue default value to return if no value was set in the configuration file.
   * @param wantUnit the units of {@code defaultValue} and the return value, as well as the units to
   *     assume if the value does not contain an indication of the units.
   * @return the setting, or {@code defaultValue} if not set, expressed in {@code units}.
   */
  public static long getTimeUnit(
      final Config config,
      final String section,
      final String subsection,
      final String setting,
      final long defaultValue,
      final TimeUnit wantUnit) {
    final String valueString = config.getString(section, subsection, setting);
    if (valueString == null) {
      return defaultValue;
    }

    String s = valueString.trim();
    if (s.length() == 0) {
      return defaultValue;
    }

    if (s.startsWith("-") /* negative */) {
      throw notTimeUnit(section, subsection, setting, valueString);
    }

    try {
      return getTimeUnit(s, defaultValue, wantUnit);
    } catch (IllegalArgumentException notTime) {
      throw notTimeUnit(section, subsection, setting, valueString, notTime);
    }
  }

  /**
   * Parse a numerical time unit, such as "1 minute", from a string.
   *
   * @param valueString the string to parse.
   * @param defaultValue default value to return if no value was set in the configuration file.
   * @param wantUnit the units of {@code defaultValue} and the return value, as well as the units to
   *     assume if the value does not contain an indication of the units.
   * @return the setting, or {@code defaultValue} if not set, expressed in {@code units}.
   */
  public static long getTimeUnit(String valueString, long defaultValue, TimeUnit wantUnit) {
    Matcher m = Pattern.compile("^(0|[1-9][0-9]*)\\s*(.*)$").matcher(valueString);
    if (!m.matches()) {
      return defaultValue;
    }

    String digits = m.group(1);
    String unitName = m.group(2).trim();

    TimeUnit inputUnit;
    int inputMul;

    if ("".equals(unitName)) {
      inputUnit = wantUnit;
      inputMul = 1;

    } else if (match(unitName, "ms", "milliseconds")) {
      inputUnit = TimeUnit.MILLISECONDS;
      inputMul = 1;

    } else if (match(unitName, "s", "sec", "second", "seconds")) {
      inputUnit = TimeUnit.SECONDS;
      inputMul = 1;

    } else if (match(unitName, "m", "min", "minute", "minutes")) {
      inputUnit = TimeUnit.MINUTES;
      inputMul = 1;

    } else if (match(unitName, "h", "hr", "hour", "hours")) {
      inputUnit = TimeUnit.HOURS;
      inputMul = 1;

    } else if (match(unitName, "d", "day", "days")) {
      inputUnit = TimeUnit.DAYS;
      inputMul = 1;

    } else if (match(unitName, "w", "week", "weeks")) {
      inputUnit = TimeUnit.DAYS;
      inputMul = 7;

    } else if (match(unitName, "mon", "month", "months")) {
      inputUnit = TimeUnit.DAYS;
      inputMul = 30;

    } else if (match(unitName, "y", "year", "years")) {
      inputUnit = TimeUnit.DAYS;
      inputMul = 365;

    } else {
      throw notTimeUnit(valueString);
    }

    try {
      return wantUnit.convert(Long.parseLong(digits) * inputMul, inputUnit);
    } catch (NumberFormatException nfe) {
      throw notTimeUnit(valueString, nfe);
    }
  }

  public static String getRequired(Config cfg, String section, String name) {
    final String v = cfg.getString(section, null, name);
    if (v == null || "".equals(v)) {
      throw new IllegalArgumentException("No " + section + "." + name + " configured");
    }
    return v;
  }

  /**
   * Store section by inspecting Java class attributes.
   *
   * 

Optimize the storage by unsetting a variable if it is being set to default value by the * server. * *

Fields marked with final or transient modifiers are skipped. * * @param cfg config in which the values should be stored * @param section section * @param sub subsection * @param s instance of class with config values * @param defaults instance of class with default values */ public static void storeSection(Config cfg, String section, String sub, T s, T defaults) throws ConfigInvalidException { try { for (Field f : s.getClass().getDeclaredFields()) { if (skipField(f)) { continue; } Class t = f.getType(); String n = f.getName(); f.setAccessible(true); Object c = f.get(s); Object d = f.get(defaults); if (!isString(t) && !isCollectionOrMap(t)) { requireNonNull(d, "Default cannot be null for: " + n); } if (c == null || c.equals(d)) { cfg.unset(section, sub, n); } else { if (isString(t)) { cfg.setString(section, sub, n, (String) c); } else if (isInteger(t)) { cfg.setInt(section, sub, n, (Integer) c); } else if (isLong(t)) { cfg.setLong(section, sub, n, (Long) c); } else if (isBoolean(t)) { cfg.setBoolean(section, sub, n, (Boolean) c); } else if (t.isEnum()) { cfg.setEnum(section, sub, n, (Enum) c); } else if (isCollectionOrMap(t)) { // TODO(davido): accept closure passed in from caller continue; } else { throw new ConfigInvalidException("type is unknown: " + t.getName()); } } } } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) { throw new ConfigInvalidException("cannot save values", e); } } /** * Load section by inspecting Java class attributes. * *

Config values are stored optimized: no default values are stored. The loading is performed * eagerly: all values are set. * *

Fields marked with final or transient modifiers are skipped. * * @param cfg config from which the values are loaded * @param section section * @param sub subsection * @param s instance of class in which the values are set * @param defaults instance of class with default values * @param i instance to merge during the load. When present, the boolean fields are not nullified * when their values are false * @return loaded instance */ @CanIgnoreReturnValue public static T loadSection(Config cfg, String section, String sub, T s, T defaults, T i) throws ConfigInvalidException { try { for (Field f : s.getClass().getDeclaredFields()) { if (skipField(f)) { continue; } Class t = f.getType(); String n = f.getName(); f.setAccessible(true); Object d = f.get(defaults); if (!isString(t) && !isCollectionOrMap(t)) { requireNonNull(d, "Default cannot be null for: " + n); } if (isString(t)) { String v = cfg.getString(section, sub, n); if (v == null) { v = (String) d; } f.set(s, v); } else if (isInteger(t)) { f.set(s, cfg.getInt(section, sub, n, (Integer) d)); } else if (isLong(t)) { f.set(s, cfg.getLong(section, sub, n, (Long) d)); } else if (isBoolean(t)) { boolean b = cfg.getBoolean(section, sub, n, (Boolean) d); if (b || i != null) { f.set(s, b); } } else if (t.isEnum()) { f.set(s, cfg.getEnum(section, sub, n, (Enum) d)); } else if (isCollectionOrMap(t)) { // TODO(davido): accept closure passed in from caller continue; } else { throw new ConfigInvalidException("type is unknown: " + t.getName()); } if (i != null) { Object o = f.get(i); if (o != null) { f.set(s, o); } } } } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) { throw new ConfigInvalidException("cannot load values", e); } return s; } /** * Merges config by inspecting Java class attributes, similar to {@link #loadSection}. * *

Config values are stored optimized: no default values are stored. The loading is performed * eagerly: all values are set, except default boolean values. * *

Fields marked with final or transient modifiers are skipped. * * @param cfg config from which the values are loaded * @param s instance of class in which the values are set * @param defaults instance of class with default values * @return loaded instance */ @CanIgnoreReturnValue public static T mergeWithDefaults(T cfg, T s, T defaults) throws ConfigInvalidException { try { for (Field f : s.getClass().getDeclaredFields()) { if (skipField(f)) { continue; } Class t = f.getType(); String n = f.getName(); f.setAccessible(true); Object val = f.get(cfg); if (val == null) { val = f.get(defaults); if (!isString(t) && !isCollectionOrMap(t)) { requireNonNull(val, "Default cannot be null for: " + n); } } if (!isBoolean(t) || (boolean) val) { // To reproduce the same behavior as in the loadSection method above, values are // explicitly set for all types, except the boolean type. For the boolean type, the value // is set only if it is 'true' (so, the false value is omitted in the result object). f.set(s, val); } } } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) { throw new ConfigInvalidException("cannot load values", e); } return s; } /** * Update user config by applying the specified delta * *

As opposed to {@link com.google.gerrit.server.config.ConfigUtil#storeSection}, this method * does not unset a variable that are set to default, because it is expected that the input {@code * original} is the raw user config value (does not include the defaults) * *

To use this method with the proto config (see {@link * CachedPreferences#asUserPreferencesProto()}), the caller can first convert the proto to a java * class usign one of the {@link UserPreferencesConverter} classes. * *

Fields marked with final or transient modifiers are skipped. * * @param original the original current user config * @param updateDelta instance of class with config values that need to be uplied to the original * config */ @UsedAt(Project.GOOGLE) public static void updatePreferences(T original, T updateDelta) throws IOException { try { for (Field f : updateDelta.getClass().getDeclaredFields()) { if (skipField(f)) { continue; } f.setAccessible(true); Object c = f.get(updateDelta); if (c != null) { f.set(original, c); } } } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) { throw new IOException("cannot apply delta the original config", e); } } public static boolean skipField(Field field) { int modifiers = field.getModifiers(); return Modifier.isFinal(modifiers) || Modifier.isTransient(modifiers); } private static boolean isCollectionOrMap(Class t) { return Collection.class.isAssignableFrom(t) || Map.class.isAssignableFrom(t); } private static boolean isString(Class t) { return String.class == t; } private static boolean isBoolean(Class t) { return Boolean.class == t || boolean.class == t; } private static boolean isLong(Class t) { return Long.class == t || long.class == t; } private static boolean isInteger(Class t) { return Integer.class == t || int.class == t; } private static boolean match(String a, String... cases) { for (String b : cases) { if (b != null && b.equalsIgnoreCase(a)) { return true; } } return false; } private static IllegalArgumentException notTimeUnit( String section, String subsection, String setting, String valueString, Throwable why) { return notTimeUnit( section + (subsection != null ? "." + subsection : "") + "." + setting + " = " + valueString, why); } private static IllegalArgumentException notTimeUnit( String section, String subsection, String setting, String valueString) { return notTimeUnit( section + (subsection != null ? "." + subsection : "") + "." + setting + " = " + valueString); } private static IllegalArgumentException notTimeUnit(String val) { return new IllegalArgumentException("Invalid time unit value: " + val); } private static IllegalArgumentException notTimeUnit(String val, Throwable why) { return new IllegalArgumentException("Invalid time unit value: " + val, why); } private ConfigUtil() {} }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy