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