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

de.sayayi.lib.message.formatter.GenericFormatterService Maven / Gradle / Ivy

Go to download

Highly configurable message format library supporting message definition through annotations

The newest version!
/*
 * Copyright 2019 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.formatter;

import de.sayayi.lib.message.formatter.ParameterFormatter.DefaultFormatter;
import de.sayayi.lib.message.formatter.named.StringFormatter;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.MustBeInvokedByOverriders;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;

import static java.util.Arrays.copyOf;
import static java.util.Objects.requireNonNull;


/**
 * Generic formatter service implementation.
 *
 * @author Jeroen Gremmen
 * @since 0.1.0 (renamed in 0.4.1)
 */
@SuppressWarnings("UnstableApiUsage")
public class GenericFormatterService implements FormatterService.WithRegistry
{
  /** default cache size for type to parameter formatters cache */
  public static final int DEFAULT_FORMATTER_CACHE_SIZE = 256;

  private static final @NotNull Map,Class> WRAPPER_CLASS_MAP = new HashMap<>();

  private final @NotNull Map namedFormatters = new ConcurrentHashMap<>();
  private final @NotNull Map,List> typeFormatters = new ConcurrentHashMap<>();
  private final @NotNull FormatterCache formatterCache;


  static
  {
    // primitives
    WRAPPER_CLASS_MAP.put(boolean.class, Boolean.class);
    WRAPPER_CLASS_MAP.put(char.class, Character.class);
    WRAPPER_CLASS_MAP.put(byte.class, Byte.class);
    WRAPPER_CLASS_MAP.put(double.class, Double.class);
    WRAPPER_CLASS_MAP.put(float.class, Float.class);
    WRAPPER_CLASS_MAP.put(int.class, Integer.class);
    WRAPPER_CLASS_MAP.put(long.class, Long.class);
    WRAPPER_CLASS_MAP.put(short.class, Short.class);

    // primitive arrays
    WRAPPER_CLASS_MAP.put(boolean[].class, Boolean[].class);
    WRAPPER_CLASS_MAP.put(char[].class, Character[].class);
    WRAPPER_CLASS_MAP.put(byte[].class, Byte[].class);
    WRAPPER_CLASS_MAP.put(double[].class, Double[].class);
    WRAPPER_CLASS_MAP.put(float[].class, Float[].class);
    WRAPPER_CLASS_MAP.put(int[].class, Integer[].class);
    WRAPPER_CLASS_MAP.put(long[].class, Long[].class);
    WRAPPER_CLASS_MAP.put(short[].class, Short[].class);
  }


  /**
   * Create a generic formatter service with a {@link #DEFAULT_FORMATTER_CACHE_SIZE default} size.
   * 

* The formatter service provides a default formatter which translates every object into a string using the * {@link Object#toString()} method. */ public GenericFormatterService() { this(DEFAULT_FORMATTER_CACHE_SIZE); } /** * Create a generic formatter service with the given {@code formatterCacheSize} size. *

* The formatter service provides a default formatter which translates every object into a string using the * {@link Object#toString()} method. */ public GenericFormatterService(int formatterCacheSize) { formatterCache = new FormatterCache(formatterCacheSize); addFormatterForType(new FormattableType(Object.class), new StringFormatter()); } @Override @MustBeInvokedByOverriders public void addFormatterForType(@NotNull FormattableType formattableType, @NotNull ParameterFormatter formatter) { if (formattableType.getType() == Object.class && !(formatter instanceof DefaultFormatter)) throw new IllegalArgumentException("formatter associated with Object must implement DefaultFormatter interface"); typeFormatters .computeIfAbsent( requireNonNull(formattableType, "formattableType must not be null").getType(), type -> new ArrayList<>(4)) .add(new PrioritizedFormatter(formattableType.getOrder(), formatter)); formatterCache.clear(); } @Override @MustBeInvokedByOverriders public void addFormatter(@NotNull ParameterFormatter formatter) { requireNonNull(formatter, "formatter must not be null"); if (formatter instanceof NamedParameterFormatter) { final NamedParameterFormatter namedParameterFormatter = (NamedParameterFormatter)formatter; final String format = namedParameterFormatter.getName(); //noinspection ConstantValue if (format == null || format.isEmpty()) throw new IllegalArgumentException("formatter name must not be empty"); namedFormatters.put(format, namedParameterFormatter); } for(FormattableType formattableType: formatter.getFormattableTypes()) addFormatterForType(formattableType, formatter); } @Override public @NotNull ParameterFormatter[] getFormatters(String format, @NotNull Class type) { requireNonNull(type, "type must not be null"); if (format != null) { final NamedParameterFormatter namedFormatter = namedFormatters.get(format); if (namedFormatter != null && namedFormatter.canFormat(type)) return new ParameterFormatter[] { namedFormatter }; } final ParameterFormatter[] formatters = formatterCache.lookup(type, t -> streamTypes(t) .map(typeFormatters::get) .filter(Objects::nonNull) .flatMap(Collection::stream) .sorted() .map(pf -> pf.formatter) .distinct() .toArray(ParameterFormatter[]::new)); return copyOf(formatters, formatters.length, ParameterFormatter[].class); } @Contract(pure = true) private @NotNull Stream> streamTypes(@NotNull Class type) { final Set> collectedTypes = new HashSet<>(); if (!typeFormatters.containsKey(type)) { final boolean isArray = type.isArray(); // if no formatter for this primitive (array) type exists, continue collecting using its wrapper type if (type.isPrimitive() || (isArray && type.getComponentType().isPrimitive())) type = WRAPPER_CLASS_MAP.get(type); // object arrays != Object[] (eg. String[]) will imply Object[] as well if (isArray && type.getComponentType() != Object.class) collectedTypes.add(Object[].class); // default array formatter } for(; type != null; type = type.getSuperclass()) { collectedTypes.add(type); addInterfaceTypes(type, collectedTypes); } collectedTypes.add(Object.class); return collectedTypes.stream(); } @Contract(mutates = "param2") private static void addInterfaceTypes(@NotNull Class type, @NotNull Set> collectedTypes) { for(final Class interfaceType: type.getInterfaces()) if (!collectedTypes.contains(interfaceType)) { collectedTypes.add(interfaceType); addInterfaceTypes(interfaceType, collectedTypes); } } private static final class PrioritizedFormatter implements Comparable { private final int order; private final @NotNull ParameterFormatter formatter; private PrioritizedFormatter(int order, @NotNull ParameterFormatter formatter) { this.order = order; this.formatter = formatter; } @Override public int compareTo(@NotNull PrioritizedFormatter o) { return Integer.compare(order, o.order); } @Override public boolean equals(Object o) { if (!(o instanceof PrioritizedFormatter)) return false; final PrioritizedFormatter that = (PrioritizedFormatter)o; return order == that.order && formatter.equals(that.formatter); } @Override public int hashCode() { return formatter.hashCode() * 31 + order; } @Override public String toString() { return "PrioritizedFormatter(order=" + order + ",formatter=" + formatter + ')'; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy