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

de.sayayi.lib.message.part.parameter.ParameterConfig Maven / Gradle / Ivy

Go to download

Highly configurable message format library supporting message definition through annotations

The newest version!
/*
 * Copyright 2020 Jeroen Gremmen
 *
 * 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
 *
 *   https://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 de.sayayi.lib.message.part.parameter;

import de.sayayi.lib.message.Message;
import de.sayayi.lib.message.MessageSupport.MessageAccessor;
import de.sayayi.lib.message.formatter.ParameterFormatter;
import de.sayayi.lib.message.formatter.ParameterFormatter.ComparatorContext;
import de.sayayi.lib.message.formatter.ParameterFormatter.ConfigKeyComparator;
import de.sayayi.lib.message.formatter.ParameterFormatter.DefaultFormatter;
import de.sayayi.lib.message.pack.PackHelper;
import de.sayayi.lib.message.pack.PackInputStream;
import de.sayayi.lib.message.pack.PackOutputStream;
import de.sayayi.lib.message.part.parameter.key.*;
import de.sayayi.lib.message.part.parameter.key.ConfigKey.MatchResult;
import de.sayayi.lib.message.part.parameter.key.OrderedConfigKeySorter.OrderedConfigKey;
import de.sayayi.lib.message.part.parameter.value.ConfigValue;
import de.sayayi.lib.message.part.parameter.value.ConfigValue.Type;
import de.sayayi.lib.message.part.parameter.value.ConfigValueMessage;
import de.sayayi.lib.message.part.parameter.value.ConfigValueString;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;

import static de.sayayi.lib.message.part.parameter.key.ConfigKey.MatchResult.Defined.MISMATCH;
import static de.sayayi.lib.message.part.parameter.key.ConfigKey.Type.*;
import static java.util.Collections.unmodifiableSet;


/**
 * This class represents the message parameter configuration map.
 *
 * @author Jeroen Gremmen
 * @since 0.4.0
 */
public final class ParameterConfig
{
  /** map containing the parameter configuration values keyed by configuration name. */
  private final @NotNull Map config;

  /** Array containing the sorted mapped message keys (null, empty, bool, number and string). */
  private final @NotNull ConfigKey[] mapKeys;

  /** Array containing the mapped message corresponding to the map key at the same index. */
  private final @NotNull ConfigValue[] mapValues;

  /** Default message. */
  private final ConfigValue defaultValue;

  /** Bitmask for {@link ConfigKey.Type} stating which keys map to a message. */
  private final byte hasKeyType;


  /**
   * Create a message parameter config instance with the given {@code map}.
   *
   * @param map  message parameter config map, not {@code null}
   */
  public ParameterConfig(@NotNull Map map)
  {
    config = new TreeMap<>();

    final List mapKeyList = new ArrayList<>();
    ConfigValue mapNullValue = null;
    ConfigKey key;
    int keyTypeMask = 0;

    for(final Entry entry: map.entrySet())
    {
      ConfigKey.Type keyType;

      if ((key = entry.getKey()) == null)
        mapNullValue = entry.getValue();
      else if ((keyType = key.getType()) != NAME)
      {
        mapKeyList.add(new OrderedConfigKey(mapKeyList.size(), key));
        keyTypeMask |= 1 << keyType.ordinal();
      }
      else
        config.put(((ConfigKeyName)key).getName(), entry.getValue());
    }

    mapKeyList.sort(OrderedConfigKeySorter.INSTANCE);

    final int mapLength = mapKeyList.size();
    mapKeys = new ConfigKey[mapLength];
    mapValues = new ConfigValue[mapLength];

    for(int n = 0; n < mapLength; n++)
    {
      mapKeys[n] = mapKeyList.get(n).getConfigKey();
      mapValues[n] = map.get(mapKeys[n]);
    }

    this.defaultValue = mapNullValue;
    this.hasKeyType = (byte)keyTypeMask;
  }


  /**
   * Tells whether the parameter configuration contains any values.
   *
   * @return  {@code true} if the parameter configuration contains at least 1 value,
   *          {@code false} otherwise
   */
  @Contract(pure = true)
  public boolean isEmpty() {
    return config.isEmpty() && mapValues.length == 0 && defaultValue == null;
  }


  /**
   * Tells whether the parameter configuration map contains a message entry with the given
   * {@code keyType}.
   *
   * @param keyType  entry key type to look for, not {@code null}
   *
   * @return  {@code true} if the map contains a message with the given key type,
   *          {@code false} otherwise
   */
  @Contract(pure = true)
  public boolean hasMessageWithKeyType(@NotNull ConfigKey.Type keyType) {
    return (hasKeyType & (1 << keyType.ordinal())) != 0;
  }


  @Contract(pure = true)
  public ConfigValue getConfigValue(@NotNull String name) {
    return config.get(name);
  }


  @Contract(pure = true)
  public Message.WithSpaces getMessage(@NotNull MessageAccessor messageAccessor,
                                       Object key, @NotNull Locale locale,
                                       @NotNull Set keyTypes,
                                       boolean includeDefault)
  {
    ConfigValue configValue = getMessage_findMappedValue(messageAccessor, locale, key, keyTypes);

    if (configValue == null)
    {
      if (includeDefault && defaultValue != null &&
          Arrays.stream(mapKeys).anyMatch(mk -> mk != null && keyTypes.contains(mk.getType())))
        configValue = defaultValue;
      else
        return null;
    }

    return configValue.getType() == Type.STRING
        ? ((ConfigValueString)configValue).asMessage(messageAccessor.getMessageFactory())
        : (Message.WithSpaces)configValue.asObject();
  }


  @Contract(pure = true)
  private ConfigValue getMessage_findMappedValue(@NotNull MessageAccessor messageAccessor,
                                                 @NotNull Locale locale, Object value,
                                                 @NotNull Set keyTypes)
  {
    ConfigValue bestMatch = null;

    final ConfigKeyComparatorContext comparatorContext = new ConfigKeyComparatorContext(messageAccessor, locale);
    final ParameterFormatter[] formatters =
        messageAccessor.getFormatters(value == null ? Object.class : value.getClass());

    MatchResult bestMatchResult = MISMATCH;

    for(int n = 0, l = mapKeys.length; n < l; n++)
      if (keyTypes.contains((comparatorContext.configKey = mapKeys[n]).getType()))
      {
        final MatchResult matchResult = findBestMatch(comparatorContext, formatters, value);

        if (MatchResult.compare(matchResult, bestMatchResult) > 0)
        {
          bestMatchResult = matchResult;
          bestMatch = mapValues[n];
        }
      }

    return bestMatch;
  }


  @SuppressWarnings({"rawtypes", "unchecked"})
  private static @NotNull MatchResult findBestMatch(@NotNull ComparatorContext context,
                                                    @NotNull ParameterFormatter[] formatters,
                                                    Object value)
  {
    final ConfigKey.Type keyType = context.getKeyType();
    MatchResult bestMatchResult = MISMATCH;

    for(int i = 0, l = formatters.length - 1; i <= l; i++)
    {
      final ParameterFormatter formatter = formatters[i];

      if (formatter instanceof ConfigKeyComparator &&
          !(i > 0 && i == l && formatter instanceof DefaultFormatter))
      {
        final MatchResult matchResult = value != null || keyType == NULL || keyType == EMPTY
            ? keyType.compareValueToKey((ConfigKeyComparator)formatter, value, context)
            : MISMATCH;

        if (MatchResult.compare(matchResult, bestMatchResult) > 0)
          bestMatchResult = matchResult;
      }
    }

    return bestMatchResult;
  }


  /**
   * Returns a set of template names referenced in all message values which are available in
   * the message parameter configuration.
   *
   * @return  unmodifiable set of all referenced template names, never {@code null}
   */
  @Contract(pure = true)
  public @NotNull Set getTemplateNames()
  {
    final Set templateNames = new TreeSet<>();

    for(final ConfigValue configValue: config.values())
      if (configValue instanceof ConfigValueMessage)
        templateNames.addAll(((ConfigValueMessage)configValue).asObject().getTemplateNames());

    for(final ConfigValue configValue: mapValues)
      if (configValue instanceof ConfigValueMessage)
        templateNames.addAll(((ConfigValueMessage)configValue).asObject().getTemplateNames());

    return unmodifiableSet(templateNames);
  }


  @Override
  public boolean equals(Object o)
  {
    if (!(o instanceof ParameterConfig))
      return false;

    final ParameterConfig that = (ParameterConfig)o;

    return
        hasKeyType == that.hasKeyType &&
        config.equals(that.config) &&
        Arrays.equals(mapKeys, that.mapKeys) &&
        Arrays.equals(mapValues, that.mapValues) &&
        Objects.equals(defaultValue, that.defaultValue);
  }


  @Override
  public int hashCode()
  {
    return (((config.hashCode() * 59) + Objects.hashCode(defaultValue)) * 59 +
        Arrays.hashCode(mapKeys)) * 59 + Arrays.hashCode(mapValues);
  }


  @Override
  @Contract(pure = true)
  public String toString()
  {
    final StringJoiner s = new StringJoiner(",", "{", "}");

    // config
    for(final Entry configEntry: config.entrySet())
      s.add(configEntry.getKey() + '=' + configEntry.getValue());

    // map
    for(int n = 0; n < mapKeys.length; n++)
      s.add(mapKeys[n].toString() + ':' + mapValues[n]);

    if (defaultValue != null)
      s.add(":" + defaultValue);

    return s.toString();
  }


  /**
   * @hidden
   */
  @SuppressWarnings("JavadocDeclaration")
  public void pack(@NotNull PackOutputStream packStream) throws IOException
  {
    packStream.writeSmallVar(config.size() + mapKeys.length + (defaultValue == null ? 0 : 1));

    // config
    for(final Entry configEntry: config.entrySet())
    {
      PackHelper.pack(new ConfigKeyName(configEntry.getKey()), packStream);
      PackHelper.pack(configEntry.getValue(), packStream);
    }

    // map
    for(int n = 0, l = mapKeys.length; n < l; n++)
    {
      PackHelper.pack(mapKeys[n], packStream);
      PackHelper.pack(mapValues[n], packStream);
    }

    if (defaultValue != null)
    {
      PackHelper.pack((ConfigKey)null, packStream);
      PackHelper.pack(defaultValue, packStream);
    }
  }


  /**
   * @hidden
   */
  @SuppressWarnings("JavadocDeclaration")
  public static @NotNull ParameterConfig unpack(@NotNull PackHelper unpack, @NotNull PackInputStream packStream)
      throws IOException
  {
    final Map map = new LinkedHashMap<>();

    for(int n = 0, size = packStream.readSmallVar(); n < size; n++)
      map.put(unpack.unpackMapKey(packStream), unpack.unpackMapValue(packStream));

    return new ParameterConfig(map);
  }




  private final class ConfigKeyComparatorContext
      extends AbstractParameterConfigAccessor
      implements ComparatorContext
  {
    private final Locale locale;
    private ConfigKey configKey;


    private ConfigKeyComparatorContext(@NotNull MessageAccessor messageAccessor,
                                       @NotNull Locale locale)
    {
      super(messageAccessor, ParameterConfig.this);

      this.locale = locale;
    }


    @Override
    public @NotNull ConfigKey.CompareType getCompareType() {
      return configKey.getCompareType();
    }


    @Override
    public @NotNull ConfigKey.Type getKeyType() {
      return configKey.getType();
    }


    @Override
    public boolean getBoolKeyValue() {
      return ((ConfigKeyBool)configKey).isBool();
    }


    @Override
    public long getNumberKeyValue() {
      return ((ConfigKeyNumber)configKey).getNumber();
    }


    @Override
    public @NotNull String getStringKeyValue() {
      return ((ConfigKeyString)configKey).getString();
    }


    @Override
    public @NotNull Locale getLocale() {
      return locale;
    }


    @Override
    public @NotNull MatchResult matchForObject(Object value)
    {
      return findBestMatch(this,
          messageAccessor.getFormatters(value == null ? Object.class : value.getClass()), value);
    }


    @Override
    public @NotNull  MatchResult matchForObject(@NotNull T value, @NotNull Class valueType) {
      return findBestMatch(this, messageAccessor.getFormatters(valueType), value);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy