001/*
002 * $Id: FluentNode.InvocationHandler.html 3972 2019-03-30 22:20:34Z ball $
003 *
004 * Copyright 2019 Allen D. Ball.  All rights reserved.
005 */
006package ball.xml;
007
008import ball.lang.reflect.FacadeProxyInvocationHandler;
009import java.util.ArrayList;
010import java.util.Arrays;
011import java.util.Collections;
012import java.util.HashMap;
013import java.util.LinkedHashSet;
014import java.util.List;
015import java.util.Map;
016import java.util.Objects;
017import java.util.Optional;
018import java.util.stream.Collectors;
019import java.util.stream.Stream;
020import java.util.stream.StreamSupport;
021import lombok.NoArgsConstructor;
022import lombok.ToString;
023import org.w3c.dom.Attr;
024import org.w3c.dom.CDATASection;
025import org.w3c.dom.Comment;
026import org.w3c.dom.Document;
027import org.w3c.dom.DocumentFragment;
028import org.w3c.dom.DocumentType;
029import org.w3c.dom.Element;
030import org.w3c.dom.Entity;
031import org.w3c.dom.EntityReference;
032import org.w3c.dom.Node;
033import org.w3c.dom.Notation;
034import org.w3c.dom.ProcessingInstruction;
035import org.w3c.dom.Text;
036
037import static lombok.AccessLevel.PROTECTED;
038
039/**
040 * Fluent {@link Node} interface Note: This interface is an implementation
041 * detail of {@link FluentDocument.Builder} and should not be implemented or
042 * extended directly.
043 *
044 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
045 * @version $Revision: 3972 $
046 */
047public interface FluentNode extends Node {
048
049    /**
050     * {@link Map} {@link Node#getNodeType()} to type ({@link Class})
051     *
052     * {@include #NODE_TYPE_MAP}
053     */
054    public static final Map<Short,Class<? extends Node>> NODE_TYPE_MAP =
055        Stream.of(new Object[][] {
056            { ATTRIBUTE_NODE, Attr.class },
057            { CDATA_SECTION_NODE, CDATASection.class },
058            { COMMENT_NODE, Comment.class },
059            { DOCUMENT_FRAGMENT_NODE, DocumentFragment.class },
060            { DOCUMENT_NODE, Document.class },
061            { DOCUMENT_TYPE_NODE, DocumentType.class },
062            { ELEMENT_NODE, Element.class },
063            { ENTITY_NODE, Entity.class },
064            { ENTITY_REFERENCE_NODE, EntityReference.class },
065            { NOTATION_NODE, Notation.class },
066            { PROCESSING_INSTRUCTION_NODE, ProcessingInstruction.class },
067            { TEXT_NODE, Text.class }
068        }).collect(Collectors.toMap(t -> (Short) t[0],
069                                    t -> ((Class<?>) t[1])
070                                             .asSubclass(Node.class)));
071
072    /**
073     * Method to convert an {@link Iterable} of {@link Node}s to an array.
074     *
075     * @param   iterable        The {@link Iterable} of {@link Node}s.
076     *
077     * @return  The array of {@link Node}s.
078     */
079    public static Node[] toArray(Iterable<Node> iterable) {
080        return(StreamSupport
081               .stream(Optional
082                       .ofNullable(iterable)
083                       .orElse(Collections.emptyList())
084                       .spliterator(),
085                       false)
086               .toArray(Node[]::new));
087    }
088
089    /**
090     * See {@link Node#getOwnerDocument()}.
091     *
092     * @return  The owner {@link FluentDocument}.
093     */
094    default FluentDocument owner() {
095        return (FluentDocument) getOwnerDocument();
096    }
097
098    /**
099     * See {@link #getNodeName()}.
100     *
101     * @return  {@link #getNodeName()}
102     */
103    default String name() { return getNodeName(); }
104
105    /**
106     * See {@link #getNodeValue()}.
107     *
108     * @return  {@link #getNodeValue()}
109     */
110    default String value() { return getNodeValue(); }
111
112    /**
113     * See {@link #setNodeValue(String)}.
114     *
115     * @param   value           The {@link Node} value.
116     *
117     * @return  {@link.this}
118     */
119    default FluentNode value(String value) {
120        setNodeValue(value);
121
122        return this;
123    }
124
125    /**
126     * See {@link #getTextContent()}.
127     *
128     * @return  {@link #getTextContent()}
129     */
130    default String content() { return getTextContent(); }
131
132    /**
133     * See {@link #setTextContent(String)}.
134     *
135     * @param   content         The {@link Node} content.
136     *
137     * @return  {@link.this}
138     */
139    default FluentNode content(String content) {
140        setTextContent(content);
141
142        return this;
143    }
144
145    /**
146     * Method to add {@link Node}s to {@link.this} {@link FluentNode}.
147     *
148     * @param   stream          The {@link Stream} of {@link Node}s to
149     *                          add.
150     *
151     * @return  {@link.this}
152     */
153    default FluentNode add(Stream<Node> stream) {
154        return add(stream.toArray(Node[]::new));
155    }
156
157    /**
158     * Method to add {@link Node}s to {@link.this} {@link FluentNode}.
159     *
160     * @param   iterable        The {@link Iterable} of {@link Node}s to
161     *                          add.
162     *
163     * @return  {@link.this}
164     */
165    default FluentNode add(Iterable<Node> iterable) {
166        return add(toArray(iterable));
167    }
168
169    /**
170     * Method to add {@link Node}s to {@link.this} {@link FluentNode}.
171     *
172     * @param   nodes           The {@link Node}s to add.
173     *
174     * @return  {@link.this}
175     */
176    default FluentNode add(Node... nodes) {
177        for (Node node : nodes) {
178            switch (node.getNodeType()) {
179            case ATTRIBUTE_NODE:
180                getAttributes().setNamedItem(node);
181                break;
182
183            default:
184                appendChild(node);
185                break;
186            }
187        }
188
189        return this;
190    }
191
192    /**
193     * Create an {@link DocumentFragment} {@link Node}.
194     *
195     * @param   stream          The {@link Stream} of {@link Node}s to
196     *                          append to the newly created
197     *                          {@link DocumentFragment}.
198     *
199     * @return  The newly created {@link DocumentFragment}.
200     */
201    default FluentNode fragment(Stream<Node> stream) {
202        return fragment(stream.toArray(Node[]::new));
203    }
204
205    /**
206     * Create an {@link DocumentFragment} {@link Node}.
207     *
208     * @param   iterable        The {@link Iterable} of {@link Node}s to
209     *                          append to the newly created
210     *                          {@link DocumentFragment}.
211     *
212     * @return  The newly created {@link DocumentFragment}.
213     */
214    default FluentNode fragment(Iterable<Node> iterable) {
215        return fragment(toArray(iterable));
216    }
217
218    /**
219     * Create an {@link DocumentFragment} {@link Node}.
220     *
221     * @param   nodes           The {@link Node}s to append to the newly
222     *                          created {@link DocumentFragment}.
223     *
224     * @return  The newly created {@link DocumentFragment}.
225     */
226    default FluentNode fragment(Node... nodes) {
227        return ((FluentNode) owner().createDocumentFragment()).add(nodes);
228    }
229
230    /**
231     * Create an {@link Element} {@link Node}.
232     *
233     * @param   name            The {@link Element} name.
234     * @param   stream          The {@link Stream} of {@link Node}s to
235     *                          append to the newly created {@link Element}.
236     *
237     * @return  The newly created {@link Element}.
238     */
239    default FluentNode element(String name, Stream<Node> stream) {
240        return element(name, stream.toArray(Node[]::new));
241    }
242
243    /**
244     * Create an {@link Element} {@link Node}.
245     *
246     * @param   name            The {@link Element} name.
247     * @param   iterable        The {@link Iterable} of {@link Node}s to
248     *                          append to the newly created {@link Element}.
249     *
250     * @return  The newly created {@link Element}.
251     */
252    default FluentNode element(String name, Iterable<Node> iterable) {
253        return element(name, toArray(iterable));
254    }
255
256    /**
257     * Create an {@link Element} {@link Node}.
258     *
259     * @param   name            The {@link Element} name.
260     * @param   nodes           The {@link Node}s to append to the newly
261     *                          created {@link Element}.
262     *
263     * @return  The newly created {@link Element}.
264     */
265    default FluentNode element(String name, Node... nodes) {
266        return ((FluentNode) owner().createElement(name)).add(nodes);
267    }
268
269    /**
270     * Create an {@link Element} {@link Node}.
271     *
272     * @param   ns              The {@link Element} namespace.
273     * @param   qn              The {@link Element} qualified name.
274     * @param   stream          The {@link Stream} of {@link Node}s to
275     *                          append to the newly created {@link Element}.
276     *
277     * @return  The newly created {@link Element}.
278     */
279    default FluentNode elementNS(String ns, String qn, Stream<Node> stream) {
280        return elementNS(ns, qn, stream.toArray(Node[]::new));
281    }
282
283    /**
284     * Create an {@link Element} {@link Node}.
285     *
286     * @param   ns              The {@link Element} namespace.
287     * @param   qn              The {@link Element} qualified name.
288     * @param   iterable        The {@link Iterable} of {@link Node}s to
289     *                          append to the newly created {@link Element}.
290     *
291     * @return  The newly created {@link Element}.
292     */
293    default FluentNode elementNS(String ns, String qn,
294                                 Iterable<Node> iterable) {
295        return elementNS(ns, qn, toArray(iterable));
296    }
297
298    /**
299     * Create an {@link Element} {@link Node}.
300     *
301     * @param   ns              The {@link Element} namespace.
302     * @param   qn              The {@link Element} qualified name.
303     * @param   nodes           The {@link Node}s to append to the newly
304     *                          created {@link Element}.
305     *
306     * @return  The newly created {@link Element}.
307     */
308    default FluentNode elementNS(String ns, String qn, Node... nodes) {
309        return ((FluentNode) owner().createElementNS(ns, qn)).add(nodes);
310    }
311
312    /**
313     * Create an {@link Attr} {@link Node}.
314     *
315     * @param   name            The {@link Attr} name.
316     *
317     * @return  The newly created {@link Attr}.
318     */
319    default FluentNode attr(String name) {
320        return (FluentNode) owner().createAttribute(name);
321    }
322
323    /**
324     * Create an {@link Attr} {@link Node}.
325     *
326     * @param   name            The {@link Attr} name.
327     * @param   value           The {@link Attr} value.
328     *
329     * @return  The newly created {@link Attr}.
330     */
331    default FluentNode attr(String name, String value) {
332        FluentNode node = attr(name);
333
334        ((Attr) node).setValue(value);
335
336        return node;
337    }
338
339    /**
340     * Create an {@link Attr} {@link Node}.
341     *
342     * @param   ns              The {@link Attr} namespace.
343     * @param   qn              The {@link Attr} qualified name.
344     *
345     * @return  The newly created {@link Attr}.
346     */
347    default FluentNode attrNS(String ns, String qn) {
348        return (FluentNode) owner().createAttributeNS(ns, qn);
349    }
350
351    /**
352     * Create an {@link Attr} {@link Node}.
353     *
354     * @param   ns              The {@link Attr} namespace.
355     * @param   qn              The {@link Attr} qualified name.
356     * @param   value           The {@link Attr} value.
357     *
358     * @return  The newly created {@link Attr}.
359     */
360    default FluentNode attrNS(String ns, String qn, String value) {
361        FluentNode node = attrNS(ns, qn);
362
363        ((Attr) node).setValue(value);
364
365        return node;
366    }
367
368    /**
369     * Create a {@link Text} {@link Node}.
370     *
371     * @param   content         The {@link Text} content.
372     *
373     * @return  The newly created {@link Text}.
374     */
375    default FluentNode text(String content) {
376        return (FluentNode) owner().createTextNode(content);
377    }
378
379    /**
380     * Create a {@link CDATASection} {@link Node}.
381     *
382     * @param   data            The {@link CDATASection} data.
383     *
384     * @return  The newly created {@link CDATASection}.
385     */
386    default FluentNode cdata(String data) {
387        return (FluentNode) owner().createCDATASection(data);
388    }
389
390    /**
391     * Create a {@link Comment} {@link Node}.
392     *
393     * @param   data            The {@link Comment} data.
394     *
395     * @return  The newly created {@link Comment}.
396     */
397    default FluentNode comment(String data) {
398        return (FluentNode) owner().createComment(data);
399    }
400
401    /**
402     * {@link FluentNode} {@link java.lang.reflect.InvocationHandler}
403     */
404    @NoArgsConstructor(access = PROTECTED)
405    @ToString
406    public class InvocationHandler extends FacadeProxyInvocationHandler {
407        private final HashMap<List<Class<?>>,Class<?>> map =
408            new HashMap<>();
409
410        /**
411         * Implementation provides {@link java.lang.reflect.Proxy} class if
412         * {@link Object} implements {@link Node} with the corresponding
413         * {@link FluentNode} sub-interface(s).
414         *
415         * {@inheritDoc}
416         */
417        @Override
418        protected Class<?> getProxyClassFor(Object object) {
419            Class<?> type = null;
420
421            if (object instanceof Node && (! (object instanceof FluentNode))) {
422                Node node = (Node) object;
423                List<Class<?>> key =
424                    Arrays.asList(NODE_TYPE_MAP
425                                  .getOrDefault(node.getNodeType(),
426                                                Node.class),
427                                  node.getClass());
428
429                type = map.computeIfAbsent(key, k -> compute(k));
430            }
431
432            return type;
433        }
434
435        private Class<?> compute(List<Class<?>> key) {
436            LinkedHashSet<Class<?>> implemented =
437                key.stream()
438                .flatMap(t -> getImplementedInterfacesOf(t).stream())
439                .filter(t -> Node.class.isAssignableFrom(t))
440                .filter(t -> Node.class.getPackage().equals(t.getPackage()))
441                .collect(Collectors.toCollection(LinkedHashSet::new));
442            LinkedHashSet<Class<?>> interfaces =
443                implemented.stream()
444                .map(t -> fluent(t))
445                .filter(Objects::nonNull)
446                .collect(Collectors.toCollection(LinkedHashSet::new));
447
448            interfaces.addAll(implemented);
449
450            new ArrayList<>(interfaces)
451                .stream()
452                .forEach(t -> interfaces.removeAll(Arrays.asList(t.getInterfaces())));
453
454            return getProxyClass(interfaces.toArray(new Class<?>[] { }));
455        }
456
457        private Class<?> fluent(Class<?> type) {
458            Class<?> fluent = null;
459
460            if (Node.class.isAssignableFrom(type)
461                && Node.class.getPackage().equals(type.getPackage())) {
462                try {
463                    String name =
464                        String.format("%s.Fluent%s",
465                                      FluentNode.class.getPackage().getName(),
466                                      type.getSimpleName());
467
468                    fluent = Class.forName(name).asSubclass(FluentNode.class);
469                } catch (Exception exception) {
470                }
471            }
472
473            return fluent;
474        }
475    }
476}