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}