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