]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java
Mini desktop graalvm packaging.
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / acr / xml / DomContent.java
1 package org.argeo.cms.acr.xml;
2
3 import java.nio.CharBuffer;
4 import java.util.HashSet;
5 import java.util.Iterator;
6 import java.util.Optional;
7 import java.util.Set;
8 import java.util.concurrent.CompletableFuture;
9
10 import javax.xml.XMLConstants;
11 import javax.xml.namespace.NamespaceContext;
12 import javax.xml.namespace.QName;
13
14 import org.argeo.api.acr.Content;
15 import org.argeo.api.acr.ContentName;
16 import org.argeo.api.acr.spi.ProvidedContent;
17 import org.argeo.api.acr.spi.ProvidedSession;
18 import org.argeo.cms.acr.AbstractContent;
19 import org.argeo.cms.acr.ContentUtils;
20 import org.w3c.dom.Attr;
21 import org.w3c.dom.Document;
22 import org.w3c.dom.Element;
23 import org.w3c.dom.NamedNodeMap;
24 import org.w3c.dom.Node;
25 import org.w3c.dom.NodeList;
26 import org.w3c.dom.Text;
27
28 /** Content persisted as a DOM element. */
29 public class DomContent extends AbstractContent implements ProvidedContent {
30
31 private final ProvidedSession session;
32 private final DomContentProvider provider;
33 private final Element element;
34
35 // private String text = null;
36 private Boolean hasText = null;
37
38 public DomContent(ProvidedSession session, DomContentProvider contentProvider, Element element) {
39 this.session = session;
40 this.provider = contentProvider;
41 this.element = element;
42 }
43
44 public DomContent(DomContent context, Element element) {
45 this(context.getSession(), context.getProvider(), element);
46 }
47
48 @Override
49 public QName getName() {
50 if (element.getParentNode() == null) {// root
51 String mountPath = provider.getMountPath();
52 if (mountPath != null) {
53 Content mountPoint = session.getMountPoint(mountPath);
54 return mountPoint.getName();
55 }
56 }
57 return toQName(this.element);
58 }
59
60 protected QName toQName(Node node) {
61 String prefix = node.getPrefix();
62 if (prefix == null) {
63 String namespaceURI = node.getNamespaceURI();
64 if (namespaceURI == null)
65 namespaceURI = node.getOwnerDocument().lookupNamespaceURI(null);
66 if (namespaceURI == null) {
67 return toQName(node, node.getLocalName());
68 } else {
69 String contextPrefix = provider.getPrefix(namespaceURI);
70 if (contextPrefix == null)
71 throw new IllegalStateException("Namespace " + namespaceURI + " is unbound");
72 return toQName(node, namespaceURI, node.getLocalName(), provider);
73 }
74 } else {
75 String namespaceURI = node.getNamespaceURI();
76 if (namespaceURI == null)
77 namespaceURI = node.getOwnerDocument().lookupNamespaceURI(prefix);
78 if (namespaceURI == null) {
79 namespaceURI = provider.getNamespaceURI(prefix);
80 if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
81 throw new IllegalStateException("Prefix " + prefix + " is unbound");
82 // TODO bind the prefix in the document?
83 }
84 return toQName(node, namespaceURI, node.getLocalName(), provider);
85 }
86 }
87
88 protected QName toQName(Node source, String namespaceURI, String localName, NamespaceContext namespaceContext) {
89 return new ContentName(namespaceURI, localName, namespaceContext);
90 }
91
92 protected QName toQName(Node source, String localName) {
93 return new ContentName(localName);
94 }
95 /*
96 * ATTRIBUTES OPERATIONS
97 */
98
99 @Override
100 public Iterable<QName> keys() {
101 // TODO implement an iterator?
102 Set<QName> result = new HashSet<>();
103 NamedNodeMap attributes = element.getAttributes();
104 for (int i = 0; i < attributes.getLength(); i++) {
105 Attr attr = (Attr) attributes.item(i);
106 QName key = toQName(attr);
107 result.add(key);
108 }
109 return result;
110 }
111
112 @SuppressWarnings("unchecked")
113 @Override
114 public <A> Optional<A> get(QName key, Class<A> clss) {
115 String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
116 : key.getNamespaceURI();
117 if (element.hasAttributeNS(namespaceUriOrNull, key.getLocalPart())) {
118 String value = element.getAttributeNS(namespaceUriOrNull, key.getLocalPart());
119 if (clss.isAssignableFrom(String.class))
120 return Optional.of((A) value);
121 else
122 return Optional.empty();
123 } else
124 return null;
125 }
126
127 @Override
128 public Object put(QName key, Object value) {
129 Object previous = get(key);
130 String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
131 : key.getNamespaceURI();
132 String prefixToUse = registerPrefixIfNeeded(key);
133 element.setAttributeNS(namespaceUriOrNull,
134 namespaceUriOrNull == null ? key.getLocalPart() : prefixToUse + ":" + key.getLocalPart(),
135 value.toString());
136 return previous;
137 }
138
139 protected String registerPrefixIfNeeded(QName name) {
140 String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null
141 : name.getNamespaceURI();
142 String prefixToUse;
143 if (namespaceUriOrNull != null) {
144 String registeredPrefix = provider.getPrefix(namespaceUriOrNull);
145 if (registeredPrefix != null) {
146 prefixToUse = registeredPrefix;
147 } else {
148 provider.registerPrefix(name.getPrefix(), namespaceUriOrNull);
149 prefixToUse = name.getPrefix();
150 }
151 } else {
152 prefixToUse = null;
153 }
154 return prefixToUse;
155 }
156
157 @Override
158 public boolean hasText() {
159 // return element instanceof Text;
160 if (hasText != null)
161 return hasText;
162 NodeList nodeList = element.getChildNodes();
163 if (nodeList.getLength() > 1) {
164 hasText = false;
165 return hasText;
166 }
167 nodes: for (int i = 0; i < nodeList.getLength(); i++) {
168 Node node = nodeList.item(i);
169 if (node instanceof Text) {
170 Text text = (Text) node;
171 if (!text.isElementContentWhitespace()) {
172 hasText = true;
173 break nodes;
174 }
175 }
176 }
177 if (hasText == null)
178 hasText = false;
179 return hasText;
180 // if (text != null)
181 // return true;
182 // text = element.getTextContent();
183 // return text != null;
184 }
185
186 @Override
187 public String getText() {
188 if (hasText())
189 return element.getTextContent();
190 else
191 return null;
192 }
193
194 /*
195 * CONTENT OPERATIONS
196 */
197
198 @Override
199 public Iterator<Content> iterator() {
200 NodeList nodeList = element.getChildNodes();
201 return new ElementIterator(this, session, provider, nodeList);
202 }
203
204 @Override
205 public Content getParent() {
206 Node parentNode = element.getParentNode();
207 if (parentNode == null) {
208 String mountPath = provider.getMountPath();
209 if (mountPath == null)
210 return null;
211 String[] parent = ContentUtils.getParentPath(mountPath);
212 return session.get(parent[0]);
213 }
214 if (parentNode instanceof Document)
215 return null;
216 if (!(parentNode instanceof Element))
217 throw new IllegalStateException("Parent is not an element");
218 return new DomContent(this, (Element) parentNode);
219 }
220
221 @Override
222 public Content add(QName name, QName... classes) {
223 // TODO consider classes
224 Document document = this.element.getOwnerDocument();
225 String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null
226 : name.getNamespaceURI();
227 String prefixToUse = registerPrefixIfNeeded(name);
228 Element child = document.createElementNS(namespaceUriOrNull,
229 namespaceUriOrNull == null ? name.getLocalPart() : prefixToUse + ":" + name.getLocalPart());
230 element.appendChild(child);
231 return new DomContent(this, child);
232 }
233
234 @Override
235 public void remove() {
236 // TODO make it more robust
237 element.getParentNode().removeChild(element);
238
239 }
240
241 @Override
242 protected void removeAttr(QName key) {
243 String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
244 : key.getNamespaceURI();
245 element.removeAttributeNS(namespaceUriOrNull,
246 namespaceUriOrNull == null ? key.getLocalPart() : key.getPrefix() + ":" + key.getLocalPart());
247
248 }
249
250 @SuppressWarnings("unchecked")
251 @Override
252 public <A> A adapt(Class<A> clss) throws IllegalArgumentException {
253 if (CharBuffer.class.isAssignableFrom(clss)) {
254 String textContent = element.getTextContent();
255 CharBuffer buf = CharBuffer.wrap(textContent);
256 return (A) buf;
257 }
258 return super.adapt(clss);
259 }
260
261 @SuppressWarnings("unchecked")
262 public <A> CompletableFuture<A> write(Class<A> clss) {
263 if (String.class.isAssignableFrom(clss)) {
264 CompletableFuture<String> res = new CompletableFuture<>();
265 res.thenAccept((s) -> {
266 session.notifyModification(this);
267 element.setTextContent(s);
268 });
269 return (CompletableFuture<A>) res;
270 }
271 return super.write(clss);
272 }
273
274 /*
275 * MOUNT MANAGEMENT
276 */
277 @Override
278 public ProvidedContent getMountPoint(String relativePath) {
279 // FIXME use qualified names
280 Element childElement = (Element) element.getElementsByTagName(relativePath).item(0);
281 // TODO check that it is a mount
282 return new DomContent(this, childElement);
283 }
284
285 public ProvidedSession getSession() {
286 return session;
287 }
288
289 public DomContentProvider getProvider() {
290 return provider;
291 }
292
293 }