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}