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

org.carrot2.attrs.Attrs Maven / Gradle / Ivy

There is a newer version: 4.6.0
Show newest version
/*
 * Carrot2 project.
 *
 * Copyright (C) 2002-2020, Dawid Weiss, Stanisław Osiński.
 * All rights reserved.
 *
 * Refer to the full license file "carrot2.LICENSE"
 * in the root folder of the repository checkout or at:
 * https://www.carrot2.org/carrot2.LICENSE
 */
package org.carrot2.attrs;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.carrot2.internal.nanojson.JsonWriter;

/**
 * Static utility methods for converting between {@link AcceptingVisitor} and {@link Map}s.
 *
 * @see #extract(AcceptingVisitor)
 * @see #populate(AcceptingVisitor, Map)
 * @see #toMap(AcceptingVisitor)
 * @see #fromMap(Class, Map, Function)
 */
public final class Attrs {
  private static final String KEY_WRAPPED = "@value";
  static final String KEY_TYPE = "@type";

  private static class Wrapper implements AcceptingVisitor {
    AttrObject value =
        AttrObject.builder(AcceptingVisitor.class).defaultValue(() -> null);

    @Override
    public void accept(AttrVisitor visitor) {
      visitor.visit(KEY_WRAPPED, value);
    }
  }

  /**
   * Convert an instance to a map. The output map will contain the type of the instance and can be
   * recreated with {@link #fromMap(Class, Map)}.
   *
   * 

This method uses default class name mappings. * * @see #fromMap(Class, Map) */ public static Map toMap(AcceptingVisitor composite) { return toMap(composite, AliasMapper.SPI_DEFAULTS::toName); } /** * Convert an instance to a map. The output map will contain the type of the instance and can be * recreated with {@link #fromMap(Class, Map)}. * * @param classToName Class to name mapping provider. * @see #fromMap(Class, Map, Function) */ public static Map toMap( AcceptingVisitor composite, Function classToName) { LinkedHashMap map = new LinkedHashMap<>(); Wrapper wrapped = new Wrapper(); wrapped.value.set(composite); wrapped.accept(new ToMapVisitor(map, classToName)); @SuppressWarnings("unchecked") Map sub = (Map) map.get(KEY_WRAPPED); return sub; } /** * Convert a map to an instance of a class. * *

This method uses default class name mappings. * * @see #toMap(AcceptingVisitor) */ public static E fromMap( Class clazz, Map map) { return fromMap(clazz, map, AliasMapper.SPI_DEFAULTS::fromName); } /** * Convert a map to an instance of a class. * * @param nameToClass Name to new class instance supplier. * @see #toMap(AcceptingVisitor) */ public static E fromMap( Class clazz, Map map, Function nameToClass) { Wrapper wrapper = populate(new Wrapper(), Collections.singletonMap(KEY_WRAPPED, map), nameToClass); return safeCast(wrapper.value.get(), KEY_WRAPPED, clazz); } /** * Extracts just the attributes of an instance (no top-level type information is preserved). * * @param classToName Class to name mapping provider. */ public static Map extract( AcceptingVisitor instance, Function classToName) { Map attrs = toMap(instance, classToName); attrs.remove(KEY_TYPE); return attrs; } /** * Extracts just the attributes of an instance (no top-level type information is preserved). * *

This method uses default class name mappings. */ public static Map extract(AcceptingVisitor instance) { return extract(instance, AliasMapper.SPI_DEFAULTS::toName); } /** * Populates a given instance with the values from the map. * *

This method uses default class name mappings. * * @see #extract(AcceptingVisitor) */ public static E populate(E instance, Map map) { return populate(instance, map, AliasMapper.SPI_DEFAULTS::fromName); } /** * Populates a given instance with the values from the map. * * @param nameToClass Name to new class instance supplier. * @see #extract(AcceptingVisitor, Function) */ public static E populate( E instance, Map map, Function nameToClass) { FromMapVisitor visitor = new FromMapVisitor(map, nameToClass); instance.accept(visitor); visitor.ensureKeysConsumed(); return instance; } /** * Converts an instance (recursively) to JSON. * *

This method uses default class name mappings. */ public static String toJson(AcceptingVisitor composite, ClassNameMapper classNameMapper) { return toJson(composite, classNameMapper::toName); } /** * Converts an instance (recursively) to JSON. * * @param classToName Class to name mapping provider. */ public static String toJson(AcceptingVisitor composite, Function classToName) { Map asMap = toMap(composite, classToName); return JsonWriter.indent(" ").string().object(asMap).done(); } private static class FromMapVisitor implements AttrVisitor { private final Map map; private final Function classToInstance; private FromMapVisitor(Map map, Function classToInstance) { this.map = new LinkedHashMap<>(Objects.requireNonNull(map)); this.classToInstance = classToInstance; } @Override public void visit(String key, AttrInteger attr) { if (map.containsKey(key)) { Number value = safeCast(map.remove(key), key, Number.class); if (value != null) { if (Double.compare(value.doubleValue(), value.longValue()) != 0) { throw new IllegalArgumentException( String.format( Locale.ROOT, "Value at key '%s' should be an integer but encountered floating point value: '%s'", key, toDebugString(value))); } long v = value.longValue(); if ((int) v != v) { throw new IllegalArgumentException( String.format( Locale.ROOT, "Value at key '%s' should be an integer but is out of integer range: '%s'", key, toDebugString(value))); } attr.set((int) v); } else { attr.set(null); } } } @Override public void visit(String key, AttrDouble attr) { if (map.containsKey(key)) { Number value = safeCast(map.remove(key), key, Number.class); attr.set(value == null ? null : value.doubleValue()); } } @Override public void visit(String key, AttrBoolean attr) { if (map.containsKey(key)) { attr.set(safeCast(map.remove(key), key, Boolean.class)); } } @Override public void visit(String key, AttrObject attr) { if (map.containsKey(key)) { @SuppressWarnings("unchecked") Map submap = safeCast(this.map.remove(key), key, Map.class); if (submap == null) { attr.set(null); } else { submap = new LinkedHashMap<>(submap); T value; if (submap.containsKey(KEY_TYPE)) { String valueType = safeCast(submap.remove(KEY_TYPE), KEY_TYPE, String.class); value = safeCast(classToInstance.apply(valueType), key, attr.getInterfaceClass()); } else { value = notNull(key, attr.newDefaultValue()); } FromMapVisitor visitor = new FromMapVisitor(submap, classToInstance); value.accept(visitor); visitor.ensureKeysConsumed(); attr.set(value); } } } @Override public void visit(String key, AttrObjectArray attr) { if (map.containsKey(key)) { Object value = map.remove(key); if (value == null) { attr.set(null); } else { List array; if (value instanceof List) { array = (List) value; } else if (value instanceof Object[]) { array = Arrays.asList((Object[]) value); } else { throw new IllegalArgumentException( String.format( Locale.ROOT, "Value at key '%s' should be a list or an array of objects of type '%s' but is an instance of '%s': '%s'", key, attr.getInterfaceClass().getSimpleName(), value.getClass().getSimpleName(), toDebugString(value))); } List entries = array.stream() .map( entry -> { @SuppressWarnings("unchecked") Map submap = safeCast(entry, key, Map.class); if (submap == null) { return null; } else { submap = new LinkedHashMap<>(submap); T entryValue; if (submap.containsKey(KEY_TYPE)) { String entryValueType = safeCast(submap.remove(KEY_TYPE), KEY_TYPE, String.class); entryValue = safeCast( classToInstance.apply(entryValueType), key, attr.getInterfaceClass()); } else { entryValue = notNull(key, attr.newDefaultEntryValue()); } FromMapVisitor visitor = new FromMapVisitor(submap, classToInstance); entryValue.accept(visitor); visitor.ensureKeysConsumed(); return entryValue; } }) .collect(Collectors.toList()); attr.set(entries); } } } @Override public > void visit(String key, AttrEnum attr) { if (map.containsKey(key)) { Object value = map.remove(key); if (value == null) { attr.set((T) null); } else if (value instanceof String) { try { attr.set(Enum.valueOf(attr.enumClass(), (String) value)); } catch (IllegalArgumentException e) { throw new IllegalArgumentException( String.format( Locale.ROOT, "Value at key '%s' should be an enum constant of class '%s', but no such constant exists: '%s' (available constants: %s)", key, attr.enumClass().getSimpleName(), toDebugString(value), EnumSet.allOf(attr.enumClass()))); } } else { attr.set(safeCast(value, key, attr.enumClass())); } } } @Override public void visit(String key, AttrStringArray attr) { if (map.containsKey(key)) { Object value = map.remove(key); if (value instanceof List) { // Attempt to convert List to String[]. attr.set( ((List) value) .stream().map(ob -> safeCast(ob, key, String.class)).toArray(String[]::new)); } else { attr.set(safeCast(value, key, String[].class)); } } } @Override public void visit(String key, AttrString attr) { if (map.containsKey(key)) { attr.set(safeCast(map.remove(key), key, String.class)); } } private T notNull(String key, T value) { if (value == null) { throw new RuntimeException( String.format( Locale.ROOT, "Default instance supplier returned null for key: '%s'", key)); } return value; } public void ensureKeysConsumed() { if (!map.isEmpty()) { throw new IllegalArgumentException( String.format( Locale.ROOT, "Unrecognized extra attributes with keys: %s", String.join(", ", map.keySet()))); } } } static T safeCast(Object value, String key, Class clazz) { if (value == null) { return null; } else { if (!clazz.isInstance(value)) { throw new IllegalArgumentException( String.format( Locale.ROOT, "Value at key '%s' should be an instance of '%s', but encountered class '%s': '%s'", key, clazz.getSimpleName(), value.getClass().getSimpleName(), toDebugString(value))); } return clazz.cast(value); } } static String toDebugString(Object value) { if (value == null) { return "[null]"; } else if (value instanceof Object[]) { return Arrays.deepToString(((Object[]) value)); } else { return Objects.toString(value); } } private static class ToMapVisitor implements AttrVisitor { private final Map map; private final Function objectToClass; public ToMapVisitor(Map map, Function objectToClass) { this.map = map; this.objectToClass = objectToClass; } @Override public void visit(String key, AttrInteger attr) { ensureNoExistingKey(map, key); map.put(key, attr.get()); } @Override public void visit(String key, AttrDouble attr) { ensureNoExistingKey(map, key); map.put(key, attr.get()); } @Override public void visit(String key, AttrBoolean attr) { ensureNoExistingKey(map, key); map.put(key, attr.get()); } @Override public void visit(String key, AttrObject attr) { ensureNoExistingKey(map, key); AcceptingVisitor currentValue = attr.get(); if (currentValue != null) { Map submap = new LinkedHashMap<>(); if (!attr.isDefaultClass(currentValue)) { submap.put(KEY_TYPE, objectToClass.apply(currentValue)); } currentValue.accept(new ToMapVisitor(submap, objectToClass)); map.put(key, submap); } else { map.put(key, null); } } @Override public void visit(String key, AttrObjectArray attr) { ensureNoExistingKey(map, key); List values = attr.get(); if (values != null) { Object[] array = values.stream() .map( currentValue -> { Map submap = new LinkedHashMap<>(); if (!attr.isDefaultClass(currentValue)) { submap.put(KEY_TYPE, objectToClass.apply(currentValue)); } currentValue.accept(new ToMapVisitor(submap, objectToClass)); return submap; }) .toArray(); map.put(key, array); } else { map.put(key, null); } } @Override public > void visit(String key, AttrEnum attr) { ensureNoExistingKey(map, key); map.put(key, attr.get() != null ? attr.get().name() : null); } @Override public void visit(String key, AttrStringArray attr) { ensureNoExistingKey(map, key); map.put(key, attr.get()); } @Override public void visit(String key, AttrString attr) { ensureNoExistingKey(map, key); map.put(key, attr.get()); } private void ensureNoExistingKey(Map map, String key) { if (map.containsKey(key)) { throw new RuntimeException( String.format( Locale.ROOT, "Could not serialize key '%s' because it already exists in the map with value: %s", key, map.get(key))); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy