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