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