Skip to content

Commit f717ddb

Browse files
committed
fix NPE with xmlElement.newClientElement()
refactoring
1 parent d9ca8b1 commit f717ddb

File tree

6 files changed

+128
-71
lines changed

6 files changed

+128
-71
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* XML Chai Library
3+
* Copyright (c) 2021-2023 Jason D. Rivard
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 2.1 of the License, or (at your option) any later version.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18+
*/
19+
20+
package org.jrivard.xmlchai;
21+
22+
import javax.xml.xpath.XPath;
23+
import javax.xml.xpath.XPathFactory;
24+
import java.util.Collections;
25+
import java.util.HashMap;
26+
import java.util.HashSet;
27+
import java.util.Map;
28+
import java.util.Set;
29+
30+
/**
31+
* Internal helper to inject variables into xpath expressions.
32+
*/
33+
class XPathVariableInjector
34+
{
35+
/**
36+
* A result set of used variables.
37+
*/
38+
private final Set<String> unusedKeys = new HashSet<>();
39+
40+
/**
41+
* XPath object to be used by the base class.
42+
*/
43+
private final XPath xpath;
44+
45+
XPathVariableInjector( final Map<String, String> suppliedParams )
46+
{
47+
xpath = XPathFactory.newInstance().newXPath();
48+
49+
final Map<String, String> copiedParams = new HashMap<>( suppliedParams == null ? Collections.emptyMap() : suppliedParams );
50+
51+
unusedKeys.addAll( copiedParams.keySet() );
52+
53+
addInjectorToXPath( copiedParams );
54+
}
55+
56+
private void addInjectorToXPath( final Map<String, String> params )
57+
{
58+
xpath.setXPathVariableResolver( variableName ->
59+
{
60+
final String key = variableName.getLocalPart();
61+
final String value = params.get( key );
62+
if ( value != null )
63+
{
64+
unusedKeys.remove( key );
65+
}
66+
return value;
67+
} );
68+
}
69+
70+
public XPath getXPath()
71+
{
72+
return xpath;
73+
}
74+
75+
public void throwIfParamsUnused()
76+
{
77+
if ( !unusedKeys.isEmpty() )
78+
{
79+
final String key = unusedKeys.iterator().next();
80+
throw new IllegalArgumentException( "xpath expression did not utilize variable $"
81+
+ key
82+
+ " for which a parameter value was included" );
83+
}
84+
}
85+
}

src/main/java/org/jrivard/xmlchai/XmlDocument.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,5 +128,4 @@ List<XmlElement> evaluateXpathToElements(
128128
* @return The access mode of this document.
129129
*/
130130
AccessMode getAccessMode();
131-
132131
}

src/main/java/org/jrivard/xmlchai/XmlDocumentW3c.java

Lines changed: 2 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,15 @@
2323
import org.w3c.dom.Document;
2424
import org.w3c.dom.NodeList;
2525

26-
import javax.xml.xpath.XPath;
2726
import javax.xml.xpath.XPathConstants;
2827
import javax.xml.xpath.XPathExpression;
2928
import javax.xml.xpath.XPathExpressionException;
3029
import java.util.Collections;
3130
import java.util.HashMap;
32-
import java.util.HashSet;
3331
import java.util.List;
3432
import java.util.Map;
3533
import java.util.Objects;
3634
import java.util.Optional;
37-
import java.util.Set;
3835
import java.util.concurrent.locks.Lock;
3936
import java.util.concurrent.locks.ReentrantLock;
4037

@@ -81,12 +78,6 @@ Document getW3cDocument()
8178
return document;
8279
}
8380

84-
85-
XmlFactoryW3c getFactory()
86-
{
87-
return factory;
88-
}
89-
9081
public AccessMode getAccessMode()
9182
{
9283
return accessMode;
@@ -98,7 +89,7 @@ public XmlElement getRootElement()
9889
lock.lock();
9990
try
10091
{
101-
return new XmlElementW3c( document.getDocumentElement(), this );
92+
return new XmlElementW3c( document.getDocumentElement(), factory, this );
10293
}
10394
finally
10495
{
@@ -167,7 +158,7 @@ public List<XmlElement> evaluateXpathToElements(
167158

168159
xPathVariableInjector.throwIfParamsUnused();
169160

170-
return XmlFactoryW3c.nodeListToElementList( nodeList, this );
161+
return XmlFactoryW3c.nodeListToElementList( factory, nodeList, this );
171162
}
172163
catch ( final XPathExpressionException e )
173164
{
@@ -195,51 +186,4 @@ public XmlDocument copy()
195186
}
196187
}
197188

198-
/**
199-
* Internal helper to inject variables into xpath expressions.
200-
*/
201-
private static class XPathVariableInjector
202-
{
203-
/** A result set of used variables. */
204-
private final Set<String> unusedKeys = new HashSet<>();
205-
206-
/** XPath object to be used by the base class. */
207-
private final XPath xpath;
208-
209-
XPathVariableInjector( final Map<String, String> suppliedParams )
210-
{
211-
xpath = javax.xml.xpath.XPathFactory.newInstance().newXPath();
212-
213-
final Map<String, String> copiedParams = new HashMap<>( suppliedParams == null ? Collections.emptyMap() : suppliedParams );
214-
215-
unusedKeys.addAll( copiedParams.keySet() );
216-
217-
xpath.setXPathVariableResolver( variableName ->
218-
{
219-
final String key = variableName.getLocalPart();
220-
final String value = copiedParams.get( key );
221-
if ( value != null )
222-
{
223-
unusedKeys.remove( key );
224-
}
225-
return value;
226-
} );
227-
}
228-
229-
public XPath getXPath()
230-
{
231-
return xpath;
232-
}
233-
234-
public void throwIfParamsUnused()
235-
{
236-
if ( !unusedKeys.isEmpty() )
237-
{
238-
final String key = unusedKeys.iterator().next();
239-
throw new IllegalArgumentException( "xpath expression did not utilize variable $"
240-
+ key
241-
+ " for which a parameter value was included" );
242-
}
243-
}
244-
}
245189
}

src/main/java/org/jrivard/xmlchai/XmlElement.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,4 +199,10 @@ public interface XmlElement
199199
* @return A new instance of the current element.
200200
*/
201201
XmlElement copy();
202+
203+
/**
204+
* Get the access mode of this document.
205+
* @return The access mode of this document.
206+
*/
207+
AccessMode getAccessMode();
202208
}

src/main/java/org/jrivard/xmlchai/XmlElementW3c.java

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,27 @@ class XmlElementW3c implements XmlElement
4242
*/
4343
private final org.w3c.dom.Element element;
4444

45+
/**
46+
* The factory that made me.
47+
*/
48+
private final XmlFactory factory;
49+
4550
/**
4651
* The parent document of this element. If null, this element is detached from its parent.
4752
*/
4853
private XmlDocumentW3c xmlDocument;
4954

55+
56+
5057
/**
5158
* A local lock instance used only when mutations are done on detached elements.
5259
*/
5360
private Lock localLock;
5461

5562
@SuppressFBWarnings( "FCCD_FIND_CLASS_CIRCULAR_DEPENDENCY" )
56-
XmlElementW3c( final org.w3c.dom.Element element, final XmlDocumentW3c xmlDocument )
63+
XmlElementW3c( final org.w3c.dom.Element element, final XmlFactory factory, final XmlDocumentW3c xmlDocument )
5764
{
65+
this.factory = Objects.requireNonNull( factory );
5866
this.element = Objects.requireNonNull( element );
5967
this.xmlDocument = xmlDocument;
6068
}
@@ -74,9 +82,20 @@ private Lock getLock()
7482
return localLock;
7583
}
7684

85+
@Override
86+
public AccessMode getAccessMode()
87+
{
88+
if ( xmlDocument == null )
89+
{
90+
return AccessMode.MUTABLE;
91+
}
92+
93+
return xmlDocument.getAccessMode();
94+
}
95+
7796
private void modificationCheck()
7897
{
79-
if ( xmlDocument != null && xmlDocument.getAccessMode() == AccessMode.IMMUTABLE )
98+
if ( getAccessMode() == AccessMode.IMMUTABLE )
8099
{
81100
throw new UnsupportedOperationException( "parent XmlDocument has modify mode set to immutable" );
82101
}
@@ -165,7 +184,7 @@ public List<XmlElement> getChildren()
165184
try
166185
{
167186
final NodeList nodeList = element.getChildNodes();
168-
return XmlFactoryW3c.nodeListToElementList( nodeList, xmlDocument );
187+
return XmlFactoryW3c.nodeListToElementList( factory, nodeList, xmlDocument );
169188
}
170189
finally
171190
{
@@ -183,7 +202,7 @@ public List<XmlElement> getChildren( final String elementName )
183202
try
184203
{
185204
final NodeList nodeList = element.getElementsByTagName( elementName );
186-
return XmlFactoryW3c.nodeListToElementList( nodeList, xmlDocument );
205+
return XmlFactoryW3c.nodeListToElementList( factory, nodeList, xmlDocument );
187206
}
188207
finally
189208
{
@@ -275,7 +294,7 @@ public void removeChildren()
275294
try
276295
{
277296
final NodeList nodeList = element.getChildNodes();
278-
for ( final XmlElement child : XmlFactoryW3c.nodeListToElementList( nodeList, xmlDocument ) )
297+
for ( final XmlElement child : XmlFactoryW3c.nodeListToElementList( factory, nodeList, xmlDocument ) )
279298
{
280299
element.removeChild( ( ( XmlElementW3c ) child ).element );
281300
( ( XmlElementW3c ) child ).xmlDocument = null;
@@ -297,7 +316,7 @@ public void removeChildren( final String elementName )
297316
try
298317
{
299318
final NodeList nodeList = element.getElementsByTagName( elementName );
300-
for ( final XmlElement child : XmlFactoryW3c.nodeListToElementList( nodeList, xmlDocument ) )
319+
for ( final XmlElement child : XmlFactoryW3c.nodeListToElementList( factory, nodeList, xmlDocument ) )
301320
{
302321
element.removeChild( ( ( XmlElementW3c ) child ).element );
303322
( ( XmlElementW3c ) child ).xmlDocument = null;
@@ -355,7 +374,7 @@ public XmlElement newChildElement( final String elementName )
355374

356375
modificationCheck();
357376

358-
final XmlElement newNode = xmlDocument.getFactory().newElement( elementName );
377+
final XmlElement newNode = factory.newElement( elementName );
359378
attachElement( newNode );
360379
return newNode;
361380
}
@@ -495,7 +514,7 @@ public XmlElement copy()
495514
{
496515
final Node newNode = this.element.cloneNode( true );
497516
this.element.getOwnerDocument().adoptNode( newNode );
498-
return new XmlElementW3c( ( org.w3c.dom.Element ) newNode, null );
517+
return new XmlElementW3c( ( org.w3c.dom.Element ) newNode, factory, null );
499518
}
500519
finally
501520
{
@@ -515,7 +534,7 @@ public Optional<XmlElement> parent()
515534
{
516535
return Optional.empty();
517536
}
518-
return Optional.of( new XmlElementW3c( ( org.w3c.dom.Element ) parentElement, xmlDocument ) );
537+
return Optional.of( new XmlElementW3c( ( org.w3c.dom.Element ) parentElement, factory, xmlDocument ) );
519538
}
520539
finally
521540
{

src/main/java/org/jrivard/xmlchai/XmlFactoryW3c.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,11 @@ public String outputString( final XmlDocument document, final OutputFlag... outp
167167
}
168168
}
169169

170-
static List<XmlElement> nodeListToElementList( final NodeList nodeList, final XmlDocumentW3c xmlDocumentW3c )
170+
static List<XmlElement> nodeListToElementList(
171+
final XmlFactory xmlFactory,
172+
final NodeList nodeList,
173+
final XmlDocumentW3c xmlDocumentW3c
174+
)
171175
{
172176
if ( nodeList != null )
173177
{
@@ -180,7 +184,7 @@ static List<XmlElement> nodeListToElementList( final NodeList nodeList, final Xm
180184
final Node node = nodeList.item( i );
181185
if ( node.getNodeType() == Node.ELEMENT_NODE )
182186
{
183-
returnList.add( new XmlElementW3c( ( org.w3c.dom.Element ) node, xmlDocumentW3c ) );
187+
returnList.add( new XmlElementW3c( ( org.w3c.dom.Element ) node, xmlFactory, xmlDocumentW3c ) );
184188
}
185189
}
186190
return Collections.unmodifiableList( returnList );
@@ -208,6 +212,6 @@ public XmlElement newElement( final String elementName )
208212
final DocumentBuilder documentBuilder = getBuilder();
209213
final org.w3c.dom.Document document = documentBuilder.newDocument();
210214
final org.w3c.dom.Element element = document.createElement( elementName );
211-
return new XmlElementW3c( element, null );
215+
return new XmlElementW3c( element, this, null );
212216
}
213217
}

0 commit comments

Comments
 (0)