001package com.mrivanplays.annotationconfig.core;
002
003import java.lang.annotation.Annotation;
004import java.lang.reflect.Field;
005import java.util.HashMap;
006import java.util.Map;
007import java.util.function.Supplier;
008
009/** Represents annotation registry for custom annotations. */
010public final class CustomAnnotationRegistry {
011
012  /**
013   * Represents annotation resolver, responsible for........ wait for it....... resolving custom
014   * annotations!!!
015   *
016   * @param <T> type of the annotation resolved
017   */
018  public interface AnnotationResolver<T extends Annotation> {
019
020    /**
021     * Called when a brand new config is being generated, should write the custom annotation's
022     * values to the writer with the desired syntax of the config type. There are syntax exceptions
023     * for config types the project maintains itself, and they are the following:
024     *
025     * <ul>
026     *   <li>TOML: When writing something, it needs to be in the format <code>key=value</code>, or
027     *       be a map.
028     * </ul>
029     *
030     * @param writer writer
031     * @param annotation annotation written
032     * @param context writer context
033     */
034    void write(AnnotationWriter writer, T annotation, AnnotationResolverContext context);
035
036    /**
037     * Called when it is being read from the config, should return a {@link Supplier} of the
038     * annotation's {@link FieldTypeResolver}.
039     *
040     * @return field type resolver supplier
041     */
042    Supplier<FieldTypeResolver> typeResolver();
043  }
044
045  /** Represents a {@link AnnotationResolver} context. */
046  public static final class AnnotationResolverContext {
047
048    private final Class<?> configType;
049    private final Field field;
050    private final Object annotatedConfig;
051    private final Object defaultsToValue;
052    private final String keyName;
053    private final boolean partOfConfigObject;
054
055    public AnnotationResolverContext(
056        Class<?> configType,
057        Field field,
058        Object annotatedConfig,
059        Object defaultsToValue,
060        String keyName,
061        boolean partOfConfigObject) {
062      this.configType = configType;
063      this.field = field;
064      this.annotatedConfig = annotatedConfig;
065      this.defaultsToValue = defaultsToValue;
066      this.keyName = keyName;
067      this.partOfConfigObject = partOfConfigObject;
068    }
069
070    /**
071     * Returns the config type, which triggered the write method.
072     *
073     * @return config type
074     */
075    public Class<?> getConfigType() {
076      return configType;
077    }
078
079    /**
080     * Returns the field, holder of the written annotation.
081     *
082     * @return field
083     */
084    public Field getField() {
085      return field;
086    }
087
088    /**
089     * Returns the annotated config, holder of the field.
090     *
091     * @return annotated config
092     */
093    public Object getAnnotatedConfig() {
094      return annotatedConfig;
095    }
096
097    /**
098     * Returns the defaults value.
099     *
100     * @return defaults
101     */
102    public Object getDefaultsToValue() {
103      return defaultsToValue;
104    }
105
106    /**
107     * Returns the preferred name of the field for configuration use.
108     *
109     * @return key
110     */
111    public String getKeyName() {
112      return keyName;
113    }
114
115    /**
116     * Returns whether or not the field is a part of config object, and the {@link
117     * #getAnnotatedConfig()} is a config object.
118     *
119     * @return config object or not
120     */
121    public boolean isPartOfConfigObject() {
122      return partOfConfigObject;
123    }
124  }
125
126  /**
127   * Represents a wrapped writer.
128   *
129   * <p>The reason behind why we don't use java's writer is because different file formats have
130   * different ways of writing things, and so we want to make advantage of that.
131   */
132  public static final class AnnotationWriter {
133
134    private Map<WriteFunction, Object> toWrite = new HashMap<>();
135
136    /** @deprecated internal use only */
137    @Deprecated
138    public Map<WriteFunction, Object> toWrite() {
139      return toWrite;
140    }
141
142    /**
143     * Writes a string.
144     *
145     * @param s string
146     */
147    public void write(String s) {
148      toWrite.put(WriteFunction.WRITE, s);
149    }
150
151    /**
152     * Writes a character array. This will probably be converted to string upon writing.
153     *
154     * @param chars character array
155     */
156    public void write(char[] chars) {
157      toWrite.put(WriteFunction.WRITE, chars);
158    }
159
160    /**
161     * Writes a object.
162     *
163     * @param obj object
164     */
165    public void write(Object obj) {
166      toWrite.put(WriteFunction.WRITE, obj);
167    }
168
169    /**
170     * Writes a single character.
171     *
172     * @param c character
173     */
174    public void write(char c) {
175      toWrite.put(WriteFunction.WRITE, c);
176    }
177
178    /**
179     * Appends the typed character.
180     *
181     * @param c character
182     */
183    public void append(char c) {
184      toWrite.put(WriteFunction.APPEND, c);
185    }
186
187    public enum WriteFunction {
188      APPEND,
189      WRITE
190    }
191  }
192
193  private Map<AnnotationType, AnnotationResolver<? extends Annotation>> registry = new HashMap<>();
194
195  /**
196   * Registers a new annotation type.
197   *
198   * @param rawAnnoType raw annotation type
199   * @param annoWriter annotation resolver
200   * @param <T> type of annotation registered
201   */
202  public <T extends Annotation> void register(
203      Class<T> rawAnnoType, AnnotationResolver<T> annoWriter) {
204    registry.put(new AnnotationType(rawAnnoType), annoWriter);
205  }
206
207  /** @deprecated internal use only */
208  @Deprecated
209  public Map<AnnotationType, AnnotationResolver<? extends Annotation>> registry() {
210    return registry;
211  }
212}