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}