001package Torello.Browser;
002
003import Torello.JavaDoc.Annotations.LinkJavaSource;
004import Torello.Java.StrIndent;
005import Torello.Java.ReadOnly.ReadOnlyList;
006
007/**
008 * A 'Builder' software design pattern that may be used to construct instances of any of the nested
009 * domain types & classes in CDP.  The goal of this class is to make it easier to construct
010 * instances of CDP types that have <B><I>many, many</I></B> fields.  Although it is perfectly
011 * legal to invoke the constructor of any CDP data type directly, a class with only 2 or 3 fields
012 * won't provide any benefit above and beyond using the constructor of the class.
013 * 
014 * <BR /><BR />
015 * ⚠️ Only classes with "lots and lots" of fields will benefit from using the {@code 'TypeBuilder'}
016 * design pattern!
017 * 
018 * <EMBED CLASS='external-html' DATA-FILE-ID=TB.Example>
019 * 
020 * @param <CDP_TYPE> The CDP data type produced by this builder's {@link #build()} method.
021 * 
022 * @see NestedDescriptor
023 * @see NestedHelper#descriptor()
024 */
025public class TypeBuilder<CDP_TYPE extends BaseType<CDP_TYPE>>
026    extends AbstractBuilder<CDP_TYPE>
027    implements java.io.Serializable, Cloneable
028{
029    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */
030    protected static final long serialVersionUID = 1L;
031
032    /**
033     * Convert 'this' Builder-Instance into an actual Object-Instance 
034     * @return An instance of {@code CDP_TYPE}
035     */
036    @Override
037    @SuppressWarnings("unchecked")
038    public CDP_TYPE build()
039    {
040        return BuildInstance.build
041            ((NestedDescriptor<CDP_TYPE>) this.descriptor, this.assignments, this.assigned);
042    }
043
044    // used by 'clone'
045    private TypeBuilder(final TypeBuilder<CDP_TYPE> other)
046    { super(other); }
047
048    // used by 'builder'
049    private TypeBuilder(final NestedDescriptor<CDP_TYPE> descriptor)
050    { super(descriptor); }
051
052    /**
053     * Creates a new builder, ready to accept field assignments.
054     * 
055     * @param <T> This is the object type, or {@code 'class'} which you would like to have built by
056     * this {@code TypeBuilder}.
057     * 
058     * @param descriptor The singleton descriptor for the CDP data type being built.
059     * Retrieving a descriptor is as easy as invoking {@link NestedHelper#descriptor() descriptor()}
060     * on any CDP data type.
061     * 
062     * @return a new builder instance of this class
063     * 
064     * @see NestedHelper#descriptor()
065     */
066    public static <T extends BaseType<T>> TypeBuilder<T> builder
067        (final NestedDescriptor<T> descriptor)
068    {
069        java.util.Objects.requireNonNull(descriptor, "parameter 'descriptor' is null");
070        return new TypeBuilder<>(descriptor);
071    }
072
073    /**
074     * This creates a new builder, but initializes it with an actual object instance
075     * @param <T>           The {@code 'BaseType'} class.
076     * @param descriptor    descriptor of the CDP data carrier class being constructed
077     * @param bt            instance of the CDP data carrier class, used to populate the builder
078     * @return              a {@code 'TypeBuilder'}, initialized with the values in {@code 'bt'}
079     * 
080     * @see NestedHelper#descriptor()
081     */
082    public static <T extends BaseType<T>> TypeBuilder<T> witherBuilder(
083            final NestedDescriptor<T>   descriptor,
084            final BaseType<T>           bt
085        )
086    {
087        java.util.Objects.requireNonNull(descriptor, "parameter 'descriptor' is null");
088        java.util.Objects.requireNonNull(bt, "parameter 'bt' is null");
089
090        if (! descriptor.baseTypeClass.isInstance(bt)) throw new IllegalArgumentException
091            ("'bt' is not an instance of " + descriptor.baseTypeClass.getCanonicalName());
092
093        final TypeBuilder<T>        tb          = new TypeBuilder<>(descriptor);
094        final ReadOnlyList<Boolean> isPresent   = bt.isPresent();
095
096        for (int i=0; i < descriptor.size; i++) if (isPresent.get(i))
097        {
098            tb.assigned[i] = true;
099
100            try
101            {
102                tb.assignments[i] = descriptor
103                    .baseTypeClass
104                    .getField(descriptor.names.get(i))
105                    .get(bt);
106            }
107
108            catch (NoSuchFieldException | SecurityException | IllegalAccessException e)
109            {
110                throw new TypeBuilderError(
111                    "Could not extract field [" + descriptor.names.get(i) + "] from instance of " +
112                    descriptor.baseTypeClass.getCanonicalName(),
113                    e
114                );
115            }
116        }
117
118        return tb;
119    }
120
121    /** {@inheritDoc} */
122    @Override
123    public TypeBuilder<CDP_TYPE> clone()
124    { return new TypeBuilder<>(this); }
125
126    private static class TypeBuilderError extends Error
127    {
128        protected static final long serialVersionUID = 1L;
129
130        private TypeBuilderError(final String message, final Throwable cause)
131        { super(message, cause); }
132    }
133}