001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2022 microBean™.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
014 * implied.  See the License for the specific language governing
015 * permissions and limitations under the License.
016 */
017package org.microbean.loader.jackson;
018
019import java.io.IOException;
020import java.io.UncheckedIOException;
021
022import java.lang.reflect.Type;
023
024import java.math.BigDecimal;
025
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.Iterator;
030import java.util.List;
031import java.util.Map;
032
033import java.util.function.BiFunction;
034
035import java.util.logging.Level;
036import java.util.logging.Logger;
037
038import com.fasterxml.jackson.core.JsonParser;
039import com.fasterxml.jackson.core.JsonProcessingException;
040import com.fasterxml.jackson.core.JsonToken;
041import com.fasterxml.jackson.core.ObjectCodec;
042import com.fasterxml.jackson.core.TreeNode;
043
044import com.fasterxml.jackson.core.exc.InputCoercionException;
045
046import com.fasterxml.jackson.core.type.TypeReference;
047
048import org.microbean.loader.api.Loader;
049
050import org.microbean.loader.spi.AbstractTreeBasedProvider;
051import org.microbean.loader.spi.Value;
052
053import org.microbean.path.Path;
054import org.microbean.path.Path.Element;
055
056/**
057 * A partial {@link AbstractTreeBasedProvider} implementation backed
058 * by <a href="https://github.com/FasterXML/jackson"
059 * target="_top">Jackson</a>.
060 *
061 * @author <a href="https://about.me/lairdnelson"
062 * target="_parent">Laird Nelson</a>
063 */
064public abstract class JacksonProvider extends AbstractTreeBasedProvider<TreeNode> {
065
066
067  /*
068   * Static fields.
069   */
070
071
072  private static final Logger logger = Logger.getLogger(JacksonProvider.class.getName());
073
074
075  /*
076   * Constructors.
077   */
078
079
080  /**
081   * Creates a new {@link JacksonProvider}.
082   *
083   * @see #JacksonProvider(Type)
084   */
085  protected JacksonProvider() {
086    this(null);
087  }
088
089  /**
090   * Creates a new {@link JacksonProvider}.
091   *
092   * @param lowerBound the {@linkplain #lowerBound() lower type bound}
093   * of this {@link JacksonProvider} implementation; may be {@code
094   * null}
095   */
096  protected JacksonProvider(final Type lowerBound) {
097    super(lowerBound);
098  }
099
100
101  /*
102   * Instance methods.
103   */
104
105
106  @Override // AbstractTreeBasedProvider<TreeNode>
107  public final int size(final TreeNode node) {
108    return node == null ? 0 : node.size();
109  }
110
111  @Override // AbstractTreeBasedProvider<TreeNode>
112  public final Iterator<String> names(final TreeNode node) {
113    return node == null ? Collections.emptyIterator() : node.fieldNames();
114  }
115
116  @Override // AbstractTreeBasedProvider<TreeNode>
117  public final TreeNode get(final TreeNode node, final String name) {
118    return node == null ? null : node.get(name); // ...or path(name)
119  }
120
121  @Override // AbstractTreeBasedProvider<TreeNode>
122  public final TreeNode get(final TreeNode node, final int index) {
123    return node == null ? null : node.get(index);
124  }
125
126  @Override // AbstractTreeBasedProvider<TreeNode>
127  public final boolean absent(final TreeNode node) {
128    return node == null || node.isMissingNode();
129  }
130
131  @Override // AbstractTreeBasedProvider<TreeNode>
132  public final boolean nil(final TreeNode node) {
133    return node == null || node.asToken() == JsonToken.VALUE_NULL;
134  }
135
136  @Override // AbstractTreeBasedProvider<TreeNode>
137  public final boolean map(final TreeNode node) {
138    return node != null && node.isObject();
139  }
140
141  @Override // AbstractTreeBasedProvider<TreeNode>
142  public final boolean list(final TreeNode node) {
143    return node != null && node.isArray();
144  }
145
146  @Override // AbstractTreeBasedProvider<TreeNode>
147  protected TreeNode qualifiers(final TreeNode node) {
148    return get(node, "@qualifiers");
149  }
150
151  /**
152   * Returns an {@link ObjectCodec} suitable for the combination of
153   * the supplied {@link Loader} and {@link Path}, or {@code null}
154   * if there is no such {@link ObjectCodec}.
155   *
156   * <p>This method is called by the {@link #get(Loader, Path)}
157   * method in the normal course of events.</p>
158   *
159   * @param requestor the {@link Loader} seeking a {@link Value};
160   * must not be {@code null}
161   *
162   * @param absolutePath an {@linkplain Path#absolute() absolute
163   * <code>Path</code>} for which the supplied {@link Loader} is
164   * seeking a value; must not be {@code null}
165   *
166   * @return an {@link ObjectCodec} suitable for the combination of
167   * the supplied {@link Loader} and {@link Path}, or {@code null}
168   *
169   * @nullability Implementations of this method may return {@code null}.
170   *
171   * @threadsafety Implementations of this method must be safe for
172   * concurrent use by multiple threads.
173   *
174   * @idempotency Implementations of this method must be idempotent,
175   * but not necessarily deterministic.
176   */
177  protected abstract ObjectCodec objectCodec(final Loader<?> requestor, final Path<? extends Type> absolutePath);
178
179  @Override // AbstractTreeBasedProvider<TreeNode>
180  protected BiFunction<? super TreeNode, ? super Type, ?> reader(final Loader<?> requestor,
181                                                                 final Path<? extends Type> absolutePath) {
182    final ObjectCodec reader = this.objectCodec(requestor, absolutePath);
183    if (reader == null) {
184      return null;
185    }
186    return (treeNode, type) -> {
187      try {
188        if (type instanceof Class<?> c) {
189          return treeNode.traverse(reader).readValueAs(c);
190        } else {
191          return treeNode.traverse(reader).readValueAs(new TypeReference<>() {
192              // This is a slight abuse of the TypeReference class, but
193              // the getType() method is not final and it is public, so
194              // this seems to be at least possible using a public
195              // API. It also avoids type loss we'd otherwise incur (if
196              // we used readValueAs(Class), for example).
197              @Override // TypeReference<T>
198              public final Type getType() {
199                return type;
200              }
201            });
202        }
203      } catch (final IOException ioException) {
204        throw new UncheckedIOException(ioException.getMessage(), ioException);
205      }
206    };
207  }
208
209}