001/*
002 * ============================================================================
003 *  Copyright © 2002-2024 by Thomas Thrien.
004 *  All Rights Reserved.
005 * ============================================================================
006 *  Licensed to the public under the agreements of the GNU Lesser General Public
007 *  License, version 3.0 (the "License"). You may obtain a copy of the License at
008 *
009 *       http://www.gnu.org/licenses/lgpl.html
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, WITHOUT
013 *  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
014 *  License for the specific language governing permissions and limitations
015 *  under the License.
016 */
017
018package org.tquadrat.foundation.config.ap;
019
020import static java.lang.String.format;
021import static org.apiguardian.api.API.Status.MAINTAINED;
022import static org.tquadrat.foundation.config.ap.CollectionKind.NO_COLLECTION;
023import static org.tquadrat.foundation.util.StringUtils.capitalize;
024
025import javax.lang.model.element.Name;
026import java.util.List;
027import java.util.Optional;
028import java.util.OptionalInt;
029import java.util.Set;
030
031import org.apiguardian.api.API;
032import org.tquadrat.foundation.annotation.ClassVersion;
033import org.tquadrat.foundation.config.SpecialPropertyType;
034import org.tquadrat.foundation.config.ap.impl.CodeBuilder;
035import org.tquadrat.foundation.exception.UnsupportedEnumError;
036import org.tquadrat.foundation.javacomposer.CodeBlock;
037import org.tquadrat.foundation.javacomposer.FieldSpec;
038import org.tquadrat.foundation.javacomposer.MethodSpec;
039import org.tquadrat.foundation.javacomposer.ParameterizedTypeName;
040import org.tquadrat.foundation.javacomposer.TypeName;
041
042/**
043 *  The specification for a property of a configuration bean.
044 *
045 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
046 *  @version $Id: PropertySpec.java 1105 2024-02-28 12:58:46Z tquadrat $
047 *  @since 0.1.0
048 *
049 *  @UMLGraph.link
050 */
051@SuppressWarnings( "ClassWithTooManyMethods" )
052@ClassVersion( sourceVersion = "$Id: PropertySpec.java 1105 2024-02-28 12:58:46Z tquadrat $" )
053@API( status = MAINTAINED, since = "0.1.0" )
054public interface PropertySpec
055{
056        /*---------------*\
057    ====** Inner Classes **====================================================
058        \*---------------*/
059    /**
060     *  The flags for a property.
061     *
062     *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
063     *  @version $Id: PropertySpec.java 1105 2024-02-28 12:58:46Z tquadrat $
064     *  @since 0.1.0
065     *
066     *  @UMLGraph.link
067     */
068    @ClassVersion( sourceVersion = "$Id: PropertySpec.java 1105 2024-02-28 12:58:46Z tquadrat $" )
069    @API( status = MAINTAINED, since = "0.1.0" )
070    public static enum PropertyFlag
071    {
072            /*------------------*\
073        ====** Enum Declaration **=============================================
074            \*------------------*/
075        /**
076         * Indicates that this property can be initialised from and persisted
077         * to an
078         * {@link org.tquadrat.foundation.inifile.INIFile}.
079         * This also means that the methods
080         * {@link #getINIGroup()}
081         * and
082         * {@link #getINIKey()}
083         * may not return
084         * {@linkplain Optional#empty() empty}.
085         */
086        ALLOWS_INIFILE,
087
088        /**
089         * Indicates that this property can be initialised from and persisted
090         * to a preference. This also means that the methods
091         * {@link #getPrefsAccessorClass()}
092         * and
093         * {@link #getPrefsKey()}
094         * may not return
095         * {@linkplain Optional#empty() empty}.
096         */
097        ALLOWS_PREFERENCES,
098
099        /**
100         *  <p>{@summary Indicates that the element type of a collection is an
101         *  enum type.} Obviously this is only relevant if the property is a
102         *  collection.</p>
103         *
104         *  @see PropertySpec#getCollectionKind()
105         *  @see CollectionKind#SET
106         *  @see CollectionKind#LIST
107         */
108        ELEMENTTYPE_IS_ENUM,
109
110        /**
111         * Indicates that this property is initialised from the value of an
112         * environment variable. The method
113         * {@link #getEnvironmentVariableName()}
114         * may not return
115         * {@linkplain Optional#empty() empty}.
116         *
117         * @see System#getenv()
118         */
119        ENVIRONMENT_VARIABLE,
120
121        /**
122         *  <p>{@summary Indicates that this property will not be part of the
123         *  map, in case the configuration bean implements
124         *  {@link java.util.Map}.}</p>
125         *  <p>There is no related annotation for this flag, it is used mainly
126         *  for
127         *  {@linkplain org.tquadrat.foundation.config.SpecialProperty special properties}.</p>
128         */
129        EXEMPT_FROM_MAP,
130
131        /**
132         *  Indicates that this property will not appear in the return value of
133         *  {@link #toString()}.
134         */
135        @SuppressWarnings( "SpellCheckingInspection" )
136        EXEMPT_FROM_TOSTRING,
137
138        /**
139         *  <p>{@summary Indicates that the getter method for this property is
140         *  default.} This means that the method will not be implemented, and
141         *  that there is no field for the property.</p>
142         *  <p>This flag implies
143         *  {@link #EXEMPT_FROM_MAP}.</p>
144         */
145        GETTER_IS_DEFAULT,
146
147        /**
148         *  <p>{@summary Indicates that the implementation of
149         *  {@link java.util.Map}
150         *  will refer to the getter for this property, instead of the
151         *  field.}</p>
152         *  <p>There is no related annotation for this flag, it is used mainly
153         *  for
154         *  {@linkplain org.tquadrat.foundation.config.SpecialProperty special properties}.</p>
155         */
156        GETTER_ON_MAP,
157
158        /**
159         * Indicates that the getter method returns an instance of
160         * {@link Optional}
161         * holding the configuration value.
162         */
163        GETTER_RETURNS_OPTIONAL,
164
165        /**
166         *  Indicates that a value for this property is required on the
167         *  command line.
168         */
169        PROPERTY_CLI_MANDATORY,
170
171        /**
172         *  Indicates that it is possible to provide multiple values for this
173         *  property on the command line.
174         */
175        PROPERTY_CLI_MULTIVALUED,
176
177        /**
178         *  Indicates that the property can be initialised with a command line
179         *  argument.
180         *
181         *  @see    #getCLIArgumentIndex()
182         *  @see    #getCLIValueHandlerClass()
183         *  @see    #getCLIFormat()
184         *  @see    #getCLIMetaVar()
185         */
186        PROPERTY_IS_ARGUMENT,
187
188        /**
189         *  Indicates that the property value can be modified. Basically, it
190         *  means that there is a setter and/or an 'add' method for that
191         *  property.
192         */
193        PROPERTY_IS_MUTABLE,
194
195        /**
196         *  Indicates that the property can be initialised with a command line
197         *  option.
198         *
199         *  @see    #getCLIOptionNames()
200         *  @see    #getCLIValueHandlerClass()
201         *  @see    #getCLIFormat()
202         *  @see    #getCLIMetaVar()
203         */
204        PROPERTY_IS_OPTION,
205
206        /**
207         * Indicates that the property is a 'special' property. This also
208         * means that
209         * {@link PropertySpec#getSpecialPropertyType()}
210         * will not return an
211         * {@linkplain Optional#empty() empty}
212         * value.
213         */
214        PROPERTY_IS_SPECIAL,
215
216        /**
217         *  Indicates that the access to the property requires synchronisation.
218         */
219        PROPERTY_REQUIRES_SYNCHRONIZATION,
220
221        /**
222         * Indicates that the setter method for this property should check for
223         * an empty argument.
224         */
225        SETTER_CHECK_EMPTY,
226
227        /**
228         * Indicates that the setter method for this property should check for
229         * a null argument.
230         */
231        SETTER_CHECK_NULL,
232
233        /**
234         * Indicates that the setter method for this property is default; this
235         * means that the method will not be implemented, and that there is no
236         * field for the property.
237         */
238        SETTER_IS_DEFAULT,
239
240        /**
241         * Indicates that this property is initialised from the value of a
242         * system preference. The methods
243         * {@link #getSystemPrefsPath()},
244         * {@link #getPrefsKey()}
245         * and
246         * {@link #getPrefsAccessorClass()}
247         * may not return
248         * {@linkplain Optional#empty() empty}.
249         */
250        SYSTEM_PREFERENCE,
251
252        /**
253         * Indicates that this property is initialised from the value of a
254         * system property. The method
255         * {@link #getSystemPropertyName()}
256         * may not return
257         * {@linkplain Optional#empty() empty}.
258         *
259         * @see System#getProperties()
260         */
261        SYSTEM_PROPERTY
262    }
263    //  enum PropertyFlag
264
265        /*-----------*\
266    ====** Constants **========================================================
267        \*-----------*/
268
269        /*---------*\
270    ====** Methods **==========================================================
271        \*---------*/
272    /**
273     *  Creates the specification of the 'add' method for this property.
274     *
275     *  @param  codeBuilder The factory for the code generation.
276     *  @return An instance of
277     *      {@link Optional}
278     *      that holds the method specification.
279     */
280    public Optional<MethodSpec> createAddMethod( final CodeBuilder codeBuilder );
281
282    /**
283     *  Creates a code block that is a fragment for the constructor of the new
284     *  configuration bean and that initialises this property.
285     *
286     *  @param  codeBuilder The factory for the code generation.
287     *  @return An instance of
288     *      {@link Optional}
289     *      that holds the code block.
290     */
291    public Optional<CodeBlock> createConstructorFragment( final CodeBuilder codeBuilder );
292
293    /**
294     *  Creates the field specification for this property.
295     *
296     *  @param  codeBuilder The factory for the code generation.
297     *  @return An instance of
298     *      {@link Optional}
299     *      that holds the field specification.
300     */
301    public Optional<FieldSpec> createField( final CodeBuilder codeBuilder );
302
303    /**
304     *  Creates the specification of the getter for this property.
305     *
306     *  @param  codeBuilder The factory for the code generation.
307     *  @return An instance of
308     *      {@link Optional}
309     *      that holds the method specification.
310     */
311    public Optional<MethodSpec> createGetter( final CodeBuilder codeBuilder );
312
313    /**
314     *  Creates the specification of the setter for this property.
315     *
316     *  @param  codeBuilder The factory for the code generation.
317     *  @return An instance of
318     *      {@link Optional}
319     *      that holds the method specification.
320     */
321    public Optional<MethodSpec> createSetter( final CodeBuilder codeBuilder );
322
323    /**
324     *  Returns the name of the 'add' method's argument.
325     *
326     *  @return The argument name; is probable {@code null} when
327     *      {@link #getAddMethodName()}
328     *      returns
329     *      {@link Optional#empty()}.
330     */
331    public Name getAddMethodArgumentName();
332
333    /**
334     *  Returns the name of the 'add' method for this property.
335     *
336     *  @return An instance of
337     *      {@link Optional}
338     *      that holds the name of the add method.
339     */
340    public Optional<Name> getAddMethodName();
341
342    /**
343     *  <p>{@summary Returns the index for an argument on the command
344     *  line.}</p>
345     *  <p>The return value will be
346     *  {@linkplain OptionalInt#empty() empty}
347     *  if the property is not a CLI argument.</p>
348     *
349     *  @return An instance of
350     *      {@link OptionalInt}
351     *      that holds the index.
352     *
353     *  @see org.tquadrat.foundation.config.Argument#index()
354     */
355    public OptionalInt getCLIArgumentIndex();
356
357    /**
358     *  Returns the special CLI format.
359     *
360     *  @return An instance of
361     *      {@link Optional}
362     *      that holds the format.
363     *
364     *  @see org.tquadrat.foundation.config.Argument#format()
365     *  @see org.tquadrat.foundation.config.Option#format()
366     */
367    public Optional<String> getCLIFormat();
368
369    /**
370     *  Returns the name of the CLI meta variable for this property.
371     *
372     *  @return An instance of
373     *      {@link Optional}
374     *      that holds the name of the meta variable.
375     *
376     *  @see org.tquadrat.foundation.config.Argument#metaVar()
377     *  @see org.tquadrat.foundation.config.Option#metaVar()
378     */
379    public Optional<String> getCLIMetaVar();
380
381    /**
382     *  <p>{@summary Returns the CLI option names.} The mandatory first name in
383     *  the list is the primary name, the optional others are the aliases.</p>
384     *  <p>The return value will be
385     *  {@linkplain Optional#empty() empty}
386     *  if the property is not a CLI option.</p>
387     *
388     *  @return An instance of
389     *      {@link Optional}
390     *      that holds the option names.
391     *
392     *  @see org.tquadrat.foundation.config.Option#name()
393     *  @see org.tquadrat.foundation.config.Option#aliases()
394     */
395    @SuppressWarnings( "OptionalContainsCollection" )
396    public Optional<List<String>> getCLIOptionNames();
397
398    /**
399     *  Returns the CLI usage text for this property. This text will not be
400     *  localised.
401     *
402     *  @return An instance of
403     *      {@link Optional}
404     *      that holds the usage text.
405     *
406     *  @see org.tquadrat.foundation.config.Argument#usage()
407     *  @see org.tquadrat.foundation.config.Option#usage()
408     */
409    public Optional<String> getCLIUsage();
410
411    /**
412     *  Returns the CLI usage key for this property. This key is used to
413     *  retrieve a localised usage text.
414     *
415     *  @return An instance of
416     *      {@link Optional}
417     *      that holds the usage key.
418     *
419     *  @see org.tquadrat.foundation.config.Argument#usageKey()
420     *  @see org.tquadrat.foundation.config.Option#usageKey()
421     */
422    public Optional<String> getCLIUsageKey();
423
424    /**
425     *  Returns the CLI value handler class for this property.
426     *
427     *  @return An instance of
428     *      {@link Optional}
429     *      that holds the
430     *      {@link TypeName}
431     *      for the handler class.
432     *
433     *  @see org.tquadrat.foundation.config.Argument#handler()
434     *  @see org.tquadrat.foundation.config.Option#handler()
435     */
436    public Optional<TypeName> getCLIValueHandlerClass();
437
438    /**
439     *  Returns the kind of collection for this property.
440     *
441     *  @return The collection kind.
442     */
443    public CollectionKind getCollectionKind();
444
445    /**
446     *  If the property is a collection (either
447     *  {@link Set}
448     *  or
449     *  {@link List},
450     *  this method returns the element type of that collection.
451     *
452     *  @return An instance of
453     *      {@link Optional}
454     *      that holds the element type.
455     */
456    public default Optional<TypeName> getElementType()
457    {
458        final var elementType = switch( getCollectionKind() )
459            {
460                case LIST, SET ->
461                {
462                    if( getPropertyType() instanceof final ParameterizedTypeName propertyType )
463                    {
464                        final var typeArguments = propertyType.typeArguments();
465                        yield typeArguments.getFirst();
466                    }
467                    yield null;
468                }
469
470                case MAP, NO_COLLECTION -> null;
471
472                default -> throw new UnsupportedEnumError( getCollectionKind() );
473            };
474
475        final var retValue = Optional.ofNullable( elementType );
476
477        //---* Done *----------------------------------------------------------
478        return retValue;
479    }   //  getElementType()
480
481    /**
482     *  <p>{@summary Returns the default value for an environment variable or a
483     *  system property.} This is used to initialise this property when it has
484     *  the annotation
485     *  {@link org.tquadrat.foundation.config.EnvironmentVariable &#64;EnvironmentVariable}
486     *  or
487     *  {@link org.tquadrat.foundation.config.SystemProperty &#64;EnvironmentVariable},
488     *  but no value is provided.</p>
489     *  <p>A default value is mandatory when the annotated property has a
490     *  primitive type.</p>
491     *
492     *  @return An instance of
493     *      {@link Optional}
494     *      that holds the default value.
495     */
496    public Optional<String> getEnvironmentDefaultValue();
497
498    /**
499     *  Returns the name of the environment variable that is used to initialise
500     *  this property.
501     *
502     *  @return An instance of
503     *      {@link Optional}
504     *      that holds the environment variable name.
505     *
506     * @see org.tquadrat.foundation.config.EnvironmentVariable
507     */
508    public Optional<String> getEnvironmentVariableName();
509
510    /**
511     *  Returns the name of the field for the property.
512     *
513     *  @return The field name.
514     */
515    public default String getFieldName() { return format( "m_%s", capitalize( getPropertyName() ) ); }
516
517    /**
518     *  Returns a builder for the getter for this property.
519     *
520     *  @return An instance of
521     *      {@link Optional}
522     *      that holds the builder.
523     */
524    public Optional<MethodSpec.Builder> getGetterBuilder();
525
526    /**
527     *  Returns the name of the getter method name. If there is no name for the
528     *  method, it will not be generated.
529     *
530     *  @return An instance of
531     *      {@link Optional}
532     *      that holds the name of the getter method.
533     */
534    public Optional<Name> getGetterMethodName();
535
536    /**
537     *  Returns the return type of the getter. This is not necessarily the same
538     *  as the
539     *  {@linkplain #getPropertyType() property type}.
540     *
541     *  @return The getter's return type.
542     */
543    public TypeName getGetterReturnType();
544
545    /**
546     *  Returns the comment for this property in the {@code INI} file.
547     *
548     *  @return An instance of
549     *      {@link Optional}
550     *      that holds the comment.
551     */
552    public Optional<String> getINIComment();
553
554    /**
555     *  Returns the group for this property in the {@code INI} file.
556     *
557     *  @return An instance of
558     *      {@link Optional}
559     *      that holds the group name.
560     */
561    public Optional<String> getINIGroup();
562
563    /**
564     *  Returns the key for this property in the {@code INI} file.
565     *
566     *  @return An instance of
567     *      {@link Optional}
568     *      that holds the key.
569     */
570    public Optional<String> getINIKey();
571
572    /**
573     *  <p>{@summary Returns the {@code Preferences} accessor class.}</p>
574     *  <p>This is used when this property is linked to a preference, but also
575     *  to initialise it from a SYSTEM preference.</p>
576     *
577     *  @see org.tquadrat.foundation.config.PreferencesBeanSpec
578     *  @see org.tquadrat.foundation.config.Preference#accessor()
579     *  @see org.tquadrat.foundation.config.SystemPreference#accessor()
580     *
581     *  @return An instance of
582     *      {@link Optional}
583     *      that holds the
584     *      {@link TypeName}
585     *      for the accessor class.
586     */
587    public Optional<TypeName> getPrefsAccessorClass();
588
589    /**
590     *  <p>{@summary Returns the {@code Preferences} key for this
591     *  property.}</p>
592     *  <p>This is used when this property is linked to a preference, but also
593     *  to initialise it from a SYSTEM preference. In first case, the name is
594     *  defaulted to the property name, while it is mandatory otherwise.</p>
595     *
596     *  @see org.tquadrat.foundation.config.PreferencesBeanSpec
597     *  @see org.tquadrat.foundation.config.Preference#key()
598     *  @see org.tquadrat.foundation.config.SystemPreference#key()
599     *
600     *  @return An instance of
601     *      {@link Optional}
602     *      that holds the preferences key for this property.
603     */
604    public Optional<String> getPrefsKey();
605
606    /**
607     *  Returns the name of the configuration property.
608     *
609     *  @return The name.
610     */
611    public String getPropertyName();
612
613    /**
614     *  Returns the property type.
615     *
616     *  @return The property type.
617     */
618    public TypeName getPropertyType();
619
620    /**
621     *  Returns the name of the setter's argument.
622     *
623     *  @return The argument name; is probably {@code null} when
624     *      {@link #getSetterMethodName()}
625     *      returns
626     *      {@link Optional#empty()}.
627     */
628    public Name getSetterArgumentName();
629
630    /**
631     *  Returns a builder for the setter for this property.
632     *
633     *  @return An instance of
634     *      {@link Optional}
635     *      that holds the builder.
636     */
637    public Optional<MethodSpec.Builder> getSetterBuilder();
638
639    /**
640     *  Returns the name of the setter method name. If there is no name for the
641     *  method, it will not be generated.
642     *
643     *  @return An instance of
644     *      {@link Optional}
645     *      that holds the name of the setter method.
646     */
647    public Optional<Name> getSetterMethodName();
648
649    /**
650     *  Return the 'speciality' type for this property.
651     *
652     *  @return An instance of
653     *      {@link Optional}
654     *      that holds the speciality type.
655     */
656    public Optional<SpecialPropertyType> getSpecialPropertyType();
657
658    /**
659     *  Returns the class that implements the String converter for the type of
660     *  this property.
661     *
662     *  @return An instance of
663     *      {@link Optional}
664     *      that holds the implementation class for
665     *      {@link org.tquadrat.foundation.lang.StringConverter}.
666     */
667    public Optional<TypeName> getStringConverterClass();
668
669    /**
670     *  Returns the path to the SYSTEM {@code Preferences} node that holds the
671     *  data for the initialisation of this property.
672     *
673     *  @return An instance of
674     *      {@link Optional}
675     *      that holds the path.
676     *
677     *  @see org.tquadrat.foundation.config.SystemPreference#path()
678     */
679    public Optional<String> getSystemPrefsPath();
680
681    /**
682     *  Returns the name of the system property that is used to initialise this
683     *  property.
684     *
685     *  @return An instance of
686     *      {@link Optional}
687     *      that holds the system property name.
688     *
689     * @see org.tquadrat.foundation.config.SystemProperty
690     */
691    public Optional<String> getSystemPropertyName();
692
693    /**
694     *  Checks whether the given flag is set for this property.
695     *
696     *  @param  flag    The flag to test for.
697     *  @return {@code true} if the flag is set, {@code false} otherwise.
698     */
699    public boolean hasFlag( final PropertyFlag flag );
700
701    /**
702     *  Checks whether this property is a collection of some kind.
703     *
704     *  @return {@code true} if this property is a collection, {@code false}
705     *      otherwise.
706     */
707    public default boolean isCollection() { return getCollectionKind() != NO_COLLECTION; }
708
709    /**
710     *  Returns the flag that indicates whether the property is an
711     *  {@link Enum enum}
712     *  type.
713     *
714     *  @return {@code true} if the property type is an {@code enum},
715     *      {@code false} otherwise.
716     */
717    public boolean isEnum();
718
719    /**
720     *  {@summary Checks whether this property is exposed to the CLI.} This
721     *  means that it has either the flag
722     *  {@link PropertyFlag#PROPERTY_IS_ARGUMENT}
723     *  or the
724     *  {@link PropertyFlag#PROPERTY_IS_OPTION}
725     *  set to it.
726     *
727     *  @return {@code true} if the property is exposed to the CLI,
728     *      {@code false} otherwise.
729     */
730    public default boolean isOnCLI() { return hasFlag( PropertyFlag.PROPERTY_IS_ARGUMENT ) || hasFlag( PropertyFlag.PROPERTY_IS_OPTION ); }
731
732    /**
733     *  <p>{@summary 'Merges' the attributes from a
734     *  {@linkplain SpecialPropertyType special property}
735     *  with the attributes retrieved from the configuration bean
736     *  specification and returns a new instance of {@code PropertySpec}.} The
737     *  original instance remains unchanged.</p>
738     *  <p>If the property is not a special property (the flag
739     *  {@link PropertyFlag#PROPERTY_IS_SPECIAL}
740     *  is not set), this instance will be returned.</p>
741     *
742     *  @return The effective property specification.
743     */
744    public PropertySpec merge();
745}
746//  interface PropertySpec
747
748/*
749 *  End of File
750 */