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

org.microbean.constant.Constables Maven / Gradle / Ivy

The newest version!
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
 *
 * Copyright © 2022–2023 microBean™.
 *
 * 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 org.microbean.constant;

import java.lang.constant.ClassDesc;
import java.lang.constant.Constable;
import java.lang.constant.ConstantDesc;
import java.lang.constant.DirectMethodHandleDesc;
import java.lang.constant.DynamicConstantDesc;
import java.lang.constant.MethodHandleDesc;
import java.lang.constant.MethodTypeDesc;

import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;

import java.util.function.Function;

import static java.lang.constant.ConstantDescs.BSM_INVOKE;
import static java.lang.constant.ConstantDescs.CD_Collection;
import static java.lang.constant.ConstantDescs.CD_List;
import static java.lang.constant.ConstantDescs.CD_Map;
import static java.lang.constant.ConstantDescs.CD_Object;
import static java.lang.constant.ConstantDescs.CD_Set;
import static java.lang.constant.ConstantDescs.NULL;

import static org.microbean.constant.ConstantDescs.CD_Arrays;
import static org.microbean.constant.ConstantDescs.CD_Collections;
import static org.microbean.constant.ConstantDescs.CD_Comparator;
import static org.microbean.constant.ConstantDescs.CD_Entry;
import static org.microbean.constant.ConstantDescs.CD_HashSet;
import static org.microbean.constant.ConstantDescs.CD_Optional;
import static org.microbean.constant.ConstantDescs.CD_SimpleImmutableEntry;
import static org.microbean.constant.ConstantDescs.CD_SortedMap;
import static org.microbean.constant.ConstantDescs.CD_SortedSet;

/**
 * A utility class containing {@code static} methods that can describe various things in {@link Constable} ways.
 *
 * @author Laird Nelson
 *
 * @see #describeConstable(Collection)
 *
 * @see #describeConstable(Map)
 *
 * @see #describeConstable(Entry)
 *
 * @see Constable
 */
public final class Constables {


  private static final ClassDesc CD_BootstrapMethods = ClassDesc.of("org.microbean.invoke.BootstrapMethods");

  private static final ConstantDesc[] EMPTY_CONSTANTDESC_ARRAY = new ConstantDesc[0];


  /*
   * Constructors.
   */


  private Constables() {
    super();
  }


  /*
   * Static methods.
   */


  @SuppressWarnings("unchecked")
  public static final Optional describeConstable(final Object o) {
    return
      o == null ? Optional.of(NULL) :
      o instanceof Constable c ? c.describeConstable() :
      o instanceof ConstantDesc cd ? Optional.of(cd) :
      o instanceof List l ? describeConstable(l) :
      o instanceof Set s ? describeConstable(s) :
      o instanceof Map m ? describeConstable(m) :
      o instanceof Entry e ? describeConstable(e) :
      o instanceof Optional opt ? describeConstable(opt) :
      Optional.empty();
  }

  // Note that this describes the Optional itself, i.e. this is not a convenient shortcut to get to the optional's payload
  public static final Optional describeConstable(final Optional o) {
    return describeConstable(o, Constables::describeConstable);
  }

  public static final  Optional describeConstable(final Optional o,
                                                                             final Function> f) {
    if (o == null) {
      return Optional.of(NULL);
    } else if (o.isEmpty()) {
      return
        Optional.of(callStatic(CD_Optional,
                               "empty",
                               MethodTypeDesc.of(CD_Optional)));
    }
    final Optional payload = f == null ? describeConstable(o.orElseThrow()) : f.apply(o.orElseThrow());
    if (payload.isEmpty()) {
      return Optional.empty();
    }
    return
      Optional.of(callStatic(CD_Optional,
                             "ofNullable",
                             MethodTypeDesc.of(CD_Optional, CD_Object),
                             payload.orElseThrow()));
  }

  public static final Optional describeConstable(final ConstantDesc cd) {
    return
      cd == null ? Optional.of(NULL) :
      cd instanceof Constable c ? c.describeConstable() : // if something implements Constable, always give it a chance to modify its own description
      Optional.of(cd);
  }

  public static final Optional describeConstable(final Constable c) {
    return
      c == null ? Optional.of(NULL) :
      c.describeConstable();
  }

  public static final Optional describeConstable(final Collection elements) {
    return describeConstable(elements, Constables::empty, Constables::describeConstable);
  }

  public static final  Optional describeConstable(final Collection elements,
                                                                             final Function> f) {
    return describeConstable(elements, Constables::empty, f);
  }

  public static final  Optional
    describeConstable(final Collection elements,
                      final Function, ? extends Optional> cf,
                      final Function> f) {
    return
      elements == null ? Optional.of(NULL) :
      elements instanceof List l ? describeConstable0(l, CD_List, cf, f) :
      elements instanceof Set s ? describeConstable0(s, CD_Set, cf, f) :
      Optional.empty();
  }

  public static final Optional describeConstable(final List elements) {
    return describeConstable0(elements, CD_List, Constables::empty, Constables::describeConstable);
  }

  public static final  Optional describeConstable(final List elements,
                                                                             final Function> f) {
    return describeConstable0(elements, CD_List, Constables::empty, f);
  }

  public static final Optional describeConstable(final Set elements) {
    return describeConstable0(elements, CD_Set, Constables::empty, Constables::describeConstable);
  }

  public static final  Optional describeConstable(final Set elements,
                                                                             final Function> f) {
    return describeConstable0(elements, CD_Set, Constables::empty, f);
  }

  public static final  Optional
    describeConstable(final Set elements,
                      final Function, ? extends Optional> cf,
                      final Function> f) {
    return describeConstable0(elements, CD_Set, cf, f);
  }

  public static final Optional describeConstable(final Map map) {
    return describeConstable0(map, Constables::empty, Constables::describeConstable, Constables::describeConstable);
  }

  public static final  Optional describeConstable(final Map map,
                                                                                final Function> kf,
                                                                                final Function> vf) {
    return describeConstable0(map, Constables::empty, kf, vf);
  }

  public static final  Optional
    describeConstable(final Map map,
                      final Function, ? extends Optional> cf,
                      final Function> kf,
                      final Function> vf) {
    return describeConstable0(map, cf, kf, vf);
  }

  public static final Optional describeConstable(final Entry entry) {
    return describeConstable(entry, Constables::describeConstable, Constables::describeConstable);
  }

  public static final  Optional describeConstable(final Entry entry,
                                                                                final Function> kf,
                                                                                final Function> vf) {
    return
      entry == null ? Optional.of(NULL) :
      entry instanceof Constable c ? c.describeConstable() :
      describeConstable(entry.getKey(), entry.getValue(), kf, vf);
  }

  public static final  Optional
    describeConstable(final K k,
                      final V v,
                      final Function> kf,
                      final Function> vf) {
    final Optional key =
      k instanceof Constable c ? c.describeConstable() : kf == null ? describeConstable(k) : kf.apply(k);
    if (key.isPresent()) {
      final Optional value =
        v instanceof Constable c ? c.describeConstable() : vf == null ? describeConstable(v) : vf.apply(v);
      if (value.isPresent()) {
        final ConstantDesc keyDesc = key.orElseThrow();
        final ConstantDesc valueDesc = value.orElseThrow();
        if (keyDesc == NULL || valueDesc == NULL) {
          return
            Optional.of(construct(CD_SimpleImmutableEntry,
                                  new ClassDesc[] { CD_Object, CD_Object },// K and V erasures
                                  keyDesc,
                                  valueDesc));
        }
        // Map#entry(K, V)
        return
          Optional.of(callInterfaceStatic(CD_Map,
                                          "entry",
                                          MethodTypeDesc.of(CD_Entry,
                                                            CD_Object, // K erasure
                                                            CD_Object), // V erasure
                                          keyDesc,
                                          valueDesc));
      }
    }
    return Optional.empty();
  }


  /*
   * Private static methods.
   */


  private static final  Optional
    describeConstable0(final SortedSet set,
                       final Function, ? extends Optional> cf,
                       final Function> f) {
    if (set == null) {
      return Optional.of(NULL);
    } else if (set instanceof Constable c) {
      return c.describeConstable();
    }

    // If the set has a user-supplied Comparator, we need to describe it as a ConstantDesc too.
    final ConstantDesc comparatorDesc = describeComparator(set.comparator(), cf);
    if (comparatorDesc == null) {
      return Optional.empty();
    }

    if (set.isEmpty()) {
      if (comparatorDesc == NULL) {
        return Optional.of(callStatic(CD_Collections, "emptySortedSet", MethodTypeDesc.of(CD_SortedSet)));
      }
      return
        Optional.of(callStatic(CD_BootstrapMethods,
                               "immutableSortedSetOf",
                               MethodTypeDesc.of(CD_SortedSet, CD_Comparator),
                               comparatorDesc));
    }

    final ConstantDesc[] args = elements(set, f);
    if (args.length <= 0) {
      return Optional.empty();
    }

    final ConstantDesc unsortedListDesc = asList(args);

    if (comparatorDesc == NULL) {
      return
        Optional.of(callStatic(CD_BootstrapMethods,
                               "immutableSortedSetOf",
                               MethodTypeDesc.of(CD_SortedSet, CD_Collection),
                               unsortedListDesc));
    }
    return
      Optional.of(callStatic(CD_BootstrapMethods,
                             "immutableSortedSetOf",
                             MethodTypeDesc.of(CD_SortedSet, CD_Collection, CD_Comparator),
                             unsortedListDesc,
                             comparatorDesc));
  }

  private static final  Optional
    describeConstable0(final Collection elements,
                       final ClassDesc listOrSetClassDesc,
                       final Function, ? extends Optional> cf,
                       Function> f) {
    assert CD_List.equals(listOrSetClassDesc) || CD_Set.equals(listOrSetClassDesc) : String.valueOf(listOrSetClassDesc);
    if (elements == null) {
      return Optional.of(NULL);
    } else if (elements instanceof Constable c) {
      return c.describeConstable();
    } else if (elements instanceof SortedSet ss) {
      return describeConstable0(ss, cf, f);
    } else if (elements.isEmpty()) {
      return Optional.of(callInterfaceStatic(listOrSetClassDesc, "of", listOrSetClassDesc));
    }

    if (f == null) {
      f = Constables::describeConstable;
    }
    final int elementsSize = elements.size();
    final ConstantDesc[] args = new ConstantDesc[elementsSize];
    boolean nulls = false;
    int i = 0;
    for (final E element : elements) {
      final Optional arg = element instanceof Constable c ? c.describeConstable() : f.apply(element);
      if (arg == null || arg.isEmpty()) {
        // If there's even one thing that cannot be described, then the whole thing cannot be described.
        return Optional.empty();
      }
      if (element == null) {
        if (!nulls) {
          nulls = true;
        }
      }
      args[i++] = arg.orElseThrow();
    }
    if (nulls) {
      final ConstantDesc cd = asList(args);
      if (CD_List.equals(listOrSetClassDesc)) {
        assert elements instanceof List;
        return
          Optional.of(callStatic(CD_Collections,
                                 "unmodifiableList",
                                 MethodTypeDesc.of(CD_List, CD_List),
                                 cd));
      }
      assert elements instanceof Set;
      return
        Optional.of(callStatic(CD_Collections,
                               "unmodifiableSet",
                               MethodTypeDesc.of(CD_Set, CD_Set),
                               construct(CD_HashSet, new ClassDesc[] { CD_Collection }, cd)));
    }
    final MethodTypeDesc ofMethodTypeDesc;
    if (elementsSize <= 10) {
      // List.of() and Set.of() have explicit polymorphic overrides for parameter counts of up to 10.
      final ClassDesc[] parameterArray = new ClassDesc[elementsSize];
      Arrays.fill(parameterArray, CD_Object); // Object is the erasure of E
      ofMethodTypeDesc = MethodTypeDesc.of(listOrSetClassDesc, parameterArray);
    } else {
      // After 10 parameters, List.of() and Set.of() fall back on varargs.
      ofMethodTypeDesc = MethodTypeDesc.of(listOrSetClassDesc, CD_Object.arrayType());
    }
    return
      Optional.of(callInterfaceStatic(listOrSetClassDesc,
                                      "of",
                                      ofMethodTypeDesc,
                                      args));
  }

  private static final  Optional
    describeConstable0(final SortedMap map,
                       final Function, ? extends Optional> cf,
                       final Function> kf,
                       final Function> vf) {
    if (map == null) {
      return Optional.of(NULL);
    } else if (map instanceof Constable c) {
      return c.describeConstable();
    }

    // If the map has a user-supplied Comparator, we need to describe it as a ConstantDesc too.
    final ConstantDesc comparatorDesc = describeComparator(map.comparator(), cf);
    if (comparatorDesc == null) {
      return Optional.empty();
    }

    if (map.isEmpty()) {
      if (comparatorDesc == NULL) {
        return Optional.of(callStatic(CD_Collections, "emptySortedMap", MethodTypeDesc.of(CD_SortedMap)));
      }
      return
        Optional.of(callStatic(CD_BootstrapMethods,
                               "immutableEmptySortedMap",
                               MethodTypeDesc.of(CD_SortedMap, CD_Comparator),
                               comparatorDesc));
    }

    final ConstantDesc entriesListDesc = asList(entries(map, kf, vf, false));

    if (comparatorDesc == NULL) {
      return
        Optional.of(callStatic(CD_BootstrapMethods,
                               "immutableSortedMapOf",
                               MethodTypeDesc.of(CD_SortedMap, CD_Collection),
                               entriesListDesc));
    }
    return
      Optional.of(callStatic(CD_BootstrapMethods,
                             "immutableSortedMapOf",
                             MethodTypeDesc.of(CD_SortedMap, CD_Collection, CD_Comparator),
                             entriesListDesc,
                             comparatorDesc));
  }

  private static final  Optional
    describeConstable0(final Map map,
                       final Function, ? extends Optional> cf,
                       final Function> kf,
                       final Function> vf) {
    if (map == null) {
      return Optional.of(NULL);
    } else if (map instanceof Constable c) {
      return c.describeConstable();
    } else if (map instanceof SortedMap sm) {
      return describeConstable0(sm, cf, kf, vf);
    } else if (map.isEmpty()) {
      // Map.of()
      return Optional.of(callInterfaceStatic(CD_Map, "of", CD_Map));
    }

    final ConstantDesc[] args = entries(map, kf, vf, true);
    if (args.length <= 0) {
      return Optional.empty();
    }

    // Map.ofEntries(Map.Entry...)
    return Optional.of(callInterfaceStatic(CD_Map, "ofEntries", MethodTypeDesc.of(CD_Map, CD_Entry.arrayType()), args));
  }

  private static final  ConstantDesc[] elements(final Collection source,
                                                   Function> f) {
    if (f == null) {
      f = Constables::describeConstable;
    }
    final ConstantDesc[] args = new ConstantDesc[source.size()];
    int i = 0;
    for (final E element : source) {
      final Optional arg = element instanceof Constable c ? c.describeConstable() : f.apply(element);
      if (arg == null || arg.isEmpty()) {
        // If there's even one thing that cannot be described, then the whole thing cannot be described.
        return EMPTY_CONSTANTDESC_ARRAY;
      }
      args[i++] = arg.orElseThrow();
    }
    return args;
  }

  private static final  ConstantDesc[] entries(final Map map,
                                                     final Function> kf,
                                                     final Function> vf,
                                                     final boolean rejectNulls) {
    if (map.isEmpty()) {
      return EMPTY_CONSTANTDESC_ARRAY;
    }
    final ConstantDesc[] args = new ConstantDesc[map.size()];
    int i = 0;
    for (final Entry entry : map.entrySet()) {
      if (rejectNulls && (entry.getKey() == null || entry.getValue() == null)) {
        return EMPTY_CONSTANTDESC_ARRAY;
      }
      final Optional e = describeConstable(entry, kf, vf);
      if (e.isEmpty()) {
        // If there's even one thing that cannot be described, then the whole thing can't be described.
        return EMPTY_CONSTANTDESC_ARRAY;
      }
      args[i++] = e.orElseThrow();
    }
    return args;
  }

  private static final DynamicConstantDesc construct(final ClassDesc cd, final ClassDesc[] constructorParameterTypes, final ConstantDesc... args) {
    final ConstantDesc[] newArgs = new ConstantDesc[args == null || args.length <= 0 ? 1 : args.length + 1];
    newArgs[0] = MethodHandleDesc.ofConstructor(cd, constructorParameterTypes);
    if (newArgs.length > 1) {
      System.arraycopy(args, 0, newArgs, 1, args.length);
    }
    return DynamicConstantDesc.of(BSM_INVOKE, newArgs);
  }

  private static final DynamicConstantDesc callInterfaceStatic(final ClassDesc cd, final String name, final ClassDesc returnType) {
    return callInterfaceStatic(cd, name, MethodTypeDesc.of(returnType));
  }

  private static final DynamicConstantDesc callInterfaceStatic(final ClassDesc cd, final String name, final MethodTypeDesc sig, final ConstantDesc... args) {
    return call(DirectMethodHandleDesc.Kind.INTERFACE_STATIC, cd, name, sig, args);
  }

  private static final DynamicConstantDesc callStatic(final ClassDesc cd, final String name, final MethodTypeDesc sig, final ConstantDesc... args) {
    return call(DirectMethodHandleDesc.Kind.STATIC, cd, name, sig, args);
  }

  private static final DynamicConstantDesc call(final DirectMethodHandleDesc.Kind kind,
                                                   final ClassDesc cd,
                                                   final String name,
                                                   final MethodTypeDesc sig,
                                                   final ConstantDesc... args) {
    final ConstantDesc[] newArgs = new ConstantDesc[args == null || args.length == 0 ? 1 : args.length + 1];
    newArgs[0] = MethodHandleDesc.ofMethod(kind, cd, name, sig);
    if (newArgs.length > 1) {
      System.arraycopy(args, 0, newArgs, 1, args.length);
    }
    return DynamicConstantDesc.of(BSM_INVOKE, newArgs);
  }

  private static final  Optional empty(final T ignored) {
    return Optional.empty();
  }

  private static final ConstantDesc describeComparator(final Comparator comparator,
                                                       final Function, ? extends Optional> cf) {
    return
      comparator == null ? NULL :
      comparator instanceof Constable c ? c.describeConstable().orElse(null) :
      cf == null ? null :
      cf.apply(comparator).orElse(null);
  }

  private static final DynamicConstantDesc asList(final ConstantDesc[] args) {
    return callStatic(CD_Arrays, "asList", MethodTypeDesc.of(CD_List, CD_Object.arrayType()), args);
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy