001/* 002 * Configurate 003 * Copyright (C) zml and Configurate contributors 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 implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.spongepowered.configurate.objectmapping.meta; 018 019import org.checkerframework.checker.nullness.qual.Nullable; 020import org.spongepowered.configurate.BasicConfigurationNode; 021import org.spongepowered.configurate.ConfigurationNode; 022 023import java.lang.annotation.Annotation; 024import java.lang.reflect.AnnotatedElement; 025 026/** 027 * A function to resolve nodes for a specific field. 028 * 029 * <p>A {@link Factory} is responsible for creating node resolvers for each 030 * field in an object, and provides the context necessary for a resolver to 031 * determine which node to navigate to.</p> 032 * 033 * @since 4.0.0 034 */ 035@FunctionalInterface 036public interface NodeResolver { 037 038 /** 039 * Indicates that a field should be explicitly skipped. 040 * 041 * @since 4.0.0 042 */ 043 NodeResolver SKIP_FIELD = parent -> null; 044 045 /** 046 * Given a parent node, resolve an appropriate child. 047 * 048 * <p>The {@code parent} node is the node that the mapped object is being 049 * deserialized from.</p> 050 * 051 * @param parent parent node 052 * @return child node, or null if the node should not be deserialized. 053 * @since 4.0.0 054 */ 055 @Nullable ConfigurationNode resolve(ConfigurationNode parent); 056 057 /** 058 * Provides fields. 059 * 060 * @since 4.0.0 061 */ 062 @FunctionalInterface 063 interface Factory { 064 065 /** 066 * Create a function that resolves a child node from its parent. 067 * 068 * @param name field name 069 * @param element annotations on the field 070 * @return {@code null} to continue, {@link #SKIP_FIELD} to stop further 071 * processing and exclude this field from serialization, or a 072 * resolver for a node. 073 * @since 4.0.0 074 */ 075 @Nullable NodeResolver make(String name, AnnotatedElement element); 076 } 077 078 /** 079 * Creates resolvers that provide the key of the containing node for values. 080 * 081 * @return key-based resolver 082 * @since 4.0.0 083 */ 084 static NodeResolver.Factory nodeKey() { 085 return (name, element) -> { 086 if (element.isAnnotationPresent(NodeKey.class)) { 087 return node -> BasicConfigurationNode.root(node.options()).raw(node.key()); 088 } 089 return null; 090 }; 091 } 092 093 /** 094 * Creates resolvers that get the node at a key defined by {@link Setting}. 095 * 096 * @return a factory that will extract keys from a provided annotation 097 * @since 4.0.0 098 */ 099 static NodeResolver.Factory keyFromSetting() { 100 return (name, element) -> { 101 if (element.isAnnotationPresent(Setting.class)) { 102 final String key = element.getAnnotation(Setting.class).value(); 103 if (!key.isEmpty()) { 104 return node -> node.node(key); 105 } 106 } 107 return null; 108 }; 109 } 110 111 /** 112 * A resolver that skips any field not annotated with {@code annotation}. 113 * 114 * @param annotation annotation to require 115 * @return a new resolver 116 * @since 4.0.0 117 */ 118 static NodeResolver.Factory onlyWithAnnotation(final Class<? extends Annotation> annotation) { 119 return (name, element) -> { 120 if (!element.isAnnotationPresent(annotation)) { 121 return NodeResolver.SKIP_FIELD; 122 } 123 return null; 124 }; 125 } 126 127 /** 128 * A resolver that will skip any field not annotated with {@link Setting}. 129 * 130 * @return new resolver restricting fields 131 * @since 4.0.0 132 */ 133 static NodeResolver.Factory onlyWithSetting() { 134 return onlyWithAnnotation(Setting.class); 135 } 136 137 /** 138 * A resolver that uses the containing node of a field. 139 * 140 * <p>This can be used to combine multiple Java objects into one 141 * configuration node.</p> 142 * 143 * @return new resolver using containing field value 144 * @since 4.0.0 145 */ 146 static NodeResolver.Factory nodeFromParent() { 147 return (name, element) -> { 148 final @Nullable Setting setting = element.getAnnotation(Setting.class); 149 if (setting != null && setting.nodeFromParent()) { 150 return node -> node; 151 } 152 return null; 153 }; 154 } 155 156}