|
| 1 | +package tools.jackson.dataformat.xml; |
| 2 | + |
| 3 | +import java.io.*; |
| 4 | +import java.nio.charset.StandardCharsets; |
| 5 | +import java.util.Arrays; |
| 6 | + |
| 7 | +import com.fasterxml.jackson.annotation.JsonPropertyOrder; |
| 8 | +import tools.jackson.core.*; |
| 9 | +import tools.jackson.databind.AnnotationIntrospector; |
| 10 | +import tools.jackson.databind.type.TypeFactory; |
| 11 | +import tools.jackson.dataformat.xml.annotation.JacksonXmlProperty; |
| 12 | +import tools.jackson.module.jakarta.xmlbind.JakartaXmlBindAnnotationIntrospector; |
| 13 | + |
| 14 | +import static org.junit.jupiter.api.Assertions.*; |
| 15 | + |
| 16 | +public abstract class XmlTestUtil |
| 17 | +{ |
| 18 | + protected static final String DEFAULT_NEW_LINE; |
| 19 | + |
| 20 | + static { |
| 21 | + String newLine = System.getProperty("line.separator"); |
| 22 | + DEFAULT_NEW_LINE = newLine == null ? "\n" : newLine; |
| 23 | + } |
| 24 | + |
| 25 | + @JsonPropertyOrder({ "first", "last", "id" }) |
| 26 | + protected static class NameBean { |
| 27 | + @JacksonXmlProperty(isAttribute=true) |
| 28 | + public int age; |
| 29 | + public String last, first; |
| 30 | + |
| 31 | + public NameBean() { } |
| 32 | + public NameBean(int age, String f, String l) { |
| 33 | + this.age = age; |
| 34 | + first = f; |
| 35 | + last = l; |
| 36 | + } |
| 37 | + } |
| 38 | + |
| 39 | + /** |
| 40 | + * Sample class from Jackson tutorial ("JacksonInFiveMinutes") |
| 41 | + */ |
| 42 | + public static class FiveMinuteUser { |
| 43 | + public enum Gender { MALE, FEMALE }; |
| 44 | + |
| 45 | + public static class Name |
| 46 | + { |
| 47 | + private String _first, _last; |
| 48 | + |
| 49 | + public Name() { } |
| 50 | + public Name(String f, String l) { |
| 51 | + _first = f; |
| 52 | + _last = l; |
| 53 | + } |
| 54 | + |
| 55 | + public String getFirst() { return _first; } |
| 56 | + public String getLast() { return _last; } |
| 57 | + |
| 58 | + public void setFirst(String s) { _first = s; } |
| 59 | + public void setLast(String s) { _last = s; } |
| 60 | + |
| 61 | + @Override |
| 62 | + public boolean equals(Object o) |
| 63 | + { |
| 64 | + if (o == this) return true; |
| 65 | + if (o == null || o.getClass() != getClass()) return false; |
| 66 | + Name other = (Name) o; |
| 67 | + return _first.equals(other._first) && _last.equals(other._last); |
| 68 | + } |
| 69 | + } |
| 70 | + |
| 71 | + private Gender _gender; |
| 72 | + private Name _name; |
| 73 | + private boolean _isVerified; |
| 74 | + private byte[] _userImage; |
| 75 | + |
| 76 | + public FiveMinuteUser() { } |
| 77 | + |
| 78 | + public FiveMinuteUser(String first, String last, boolean verified, Gender g, byte[] data) |
| 79 | + { |
| 80 | + _name = new Name(first, last); |
| 81 | + _isVerified = verified; |
| 82 | + _gender = g; |
| 83 | + _userImage = data; |
| 84 | + } |
| 85 | + |
| 86 | + public Name getName() { return _name; } |
| 87 | + public boolean isVerified() { return _isVerified; } |
| 88 | + public Gender getGender() { return _gender; } |
| 89 | + public byte[] getUserImage() { return _userImage; } |
| 90 | + |
| 91 | + public void setName(Name n) { _name = n; } |
| 92 | + public void setVerified(boolean b) { _isVerified = b; } |
| 93 | + public void setGender(Gender g) { _gender = g; } |
| 94 | + public void setUserImage(byte[] b) { _userImage = b; } |
| 95 | + |
| 96 | + @Override |
| 97 | + public boolean equals(Object o) |
| 98 | + { |
| 99 | + if (o == this) return true; |
| 100 | + if (o == null || o.getClass() != getClass()) return false; |
| 101 | + FiveMinuteUser other = (FiveMinuteUser) o; |
| 102 | + if (_isVerified != other._isVerified) return false; |
| 103 | + if (_gender != other._gender) return false; |
| 104 | + if (!_name.equals(other._name)) return false; |
| 105 | + byte[] otherImage = other._userImage; |
| 106 | + if (otherImage.length != _userImage.length) return false; |
| 107 | + for (int i = 0, len = _userImage.length; i < len; ++i) { |
| 108 | + if (_userImage[i] != otherImage[i]) { |
| 109 | + return false; |
| 110 | + } |
| 111 | + } |
| 112 | + return true; |
| 113 | + } |
| 114 | + } |
| 115 | + |
| 116 | + protected static class StringBean |
| 117 | + { |
| 118 | + public String text; |
| 119 | + |
| 120 | + public StringBean() { this("foobar"); } |
| 121 | + public StringBean(String s) { text = s; } |
| 122 | + |
| 123 | + @Override |
| 124 | + public String toString() { |
| 125 | + if (text == null) return "NULL"; |
| 126 | + return "\""+text+"\""; |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + /** |
| 131 | + * Simple wrapper around String type, usually to test value |
| 132 | + * conversions or wrapping |
| 133 | + */ |
| 134 | + protected static class StringWrapper { |
| 135 | + public String str; |
| 136 | + |
| 137 | + public StringWrapper() { } |
| 138 | + public StringWrapper(String value) { |
| 139 | + str = value; |
| 140 | + } |
| 141 | + } |
| 142 | + |
| 143 | + protected static class IntWrapper { |
| 144 | + public int i; |
| 145 | + |
| 146 | + public IntWrapper() { } |
| 147 | + public IntWrapper(int value) { |
| 148 | + i = value; |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + public static class Point { |
| 153 | + public int x, y; |
| 154 | + |
| 155 | + protected Point() { } // for deser |
| 156 | + public Point(int x0, int y0) { |
| 157 | + x = x0; |
| 158 | + y = y0; |
| 159 | + } |
| 160 | + |
| 161 | + @Override |
| 162 | + public boolean equals(Object o) { |
| 163 | + if (!(o instanceof Point)) { |
| 164 | + return false; |
| 165 | + } |
| 166 | + Point other = (Point) o; |
| 167 | + return (other.x == x) && (other.y == y); |
| 168 | + } |
| 169 | + |
| 170 | + @Override |
| 171 | + public String toString() { |
| 172 | + return String.format("[x=%d, y=%d]", x, y); |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + /* |
| 177 | + /********************************************************** |
| 178 | + /* Some sample documents: |
| 179 | + /********************************************************** |
| 180 | + */ |
| 181 | + |
| 182 | + protected final static int SAMPLE_SPEC_VALUE_WIDTH = 800; |
| 183 | + protected final static int SAMPLE_SPEC_VALUE_HEIGHT = 600; |
| 184 | + protected final static String SAMPLE_SPEC_VALUE_TITLE = "View from 15th Floor"; |
| 185 | + protected final static String SAMPLE_SPEC_VALUE_TN_URL = "http://www.example.com/image/481989943"; |
| 186 | + protected final static int SAMPLE_SPEC_VALUE_TN_HEIGHT = 125; |
| 187 | + protected final static String SAMPLE_SPEC_VALUE_TN_WIDTH = "100"; |
| 188 | + protected final static int SAMPLE_SPEC_VALUE_TN_ID1 = 116; |
| 189 | + protected final static int SAMPLE_SPEC_VALUE_TN_ID2 = 943; |
| 190 | + protected final static int SAMPLE_SPEC_VALUE_TN_ID3 = 234; |
| 191 | + protected final static int SAMPLE_SPEC_VALUE_TN_ID4 = 38793; |
| 192 | + |
| 193 | + protected final static String SAMPLE_DOC_JSON_SPEC = |
| 194 | + "{\n" |
| 195 | + +" \"Image\" : {\n" |
| 196 | + +" \"Width\" : "+SAMPLE_SPEC_VALUE_WIDTH+",\n" |
| 197 | + +" \"Height\" : "+SAMPLE_SPEC_VALUE_HEIGHT+"," |
| 198 | + +"\"Title\" : \""+SAMPLE_SPEC_VALUE_TITLE+"\",\n" |
| 199 | + +" \"Thumbnail\" : {\n" |
| 200 | + +" \"Url\" : \""+SAMPLE_SPEC_VALUE_TN_URL+"\",\n" |
| 201 | + +"\"Height\" : "+SAMPLE_SPEC_VALUE_TN_HEIGHT+",\n" |
| 202 | + +" \"Width\" : \""+SAMPLE_SPEC_VALUE_TN_WIDTH+"\"\n" |
| 203 | + +" },\n" |
| 204 | + +" \"IDs\" : ["+SAMPLE_SPEC_VALUE_TN_ID1+","+SAMPLE_SPEC_VALUE_TN_ID2+","+SAMPLE_SPEC_VALUE_TN_ID3+","+SAMPLE_SPEC_VALUE_TN_ID4+"]\n" |
| 205 | + +" }" |
| 206 | + +"}" |
| 207 | + ; |
| 208 | + |
| 209 | + /* |
| 210 | + /********************************************************** |
| 211 | + /* Construction, factory methods |
| 212 | + /********************************************************** |
| 213 | + */ |
| 214 | + |
| 215 | + protected XmlTestUtil() { |
| 216 | + super(); |
| 217 | + } |
| 218 | + |
| 219 | + protected XmlFactoryBuilder streamFactoryBuilder() { |
| 220 | + return XmlFactory.builder(); |
| 221 | + } |
| 222 | + |
| 223 | + protected static XmlMapper newMapper() { |
| 224 | + return new XmlMapper(); |
| 225 | + } |
| 226 | + |
| 227 | + protected static XmlMapper.Builder mapperBuilder() { |
| 228 | + return XmlMapper.builder(); |
| 229 | + } |
| 230 | + |
| 231 | + protected static XmlMapper.Builder mapperBuilder(XmlFactory f) { |
| 232 | + return XmlMapper.builder(f); |
| 233 | + } |
| 234 | + |
| 235 | + protected XmlMapper xmlMapper(boolean useListWrapping) |
| 236 | + { |
| 237 | + return XmlMapper.builder() |
| 238 | + .defaultUseWrapper(useListWrapping) |
| 239 | + .build(); |
| 240 | + } |
| 241 | + |
| 242 | + protected AnnotationIntrospector jakartaXMLBindAnnotationIntrospector() { |
| 243 | + return new JakartaXmlBindAnnotationIntrospector(); |
| 244 | + } |
| 245 | + |
| 246 | + /* |
| 247 | + /********************************************************** |
| 248 | + /* Additional assertion methods |
| 249 | + /********************************************************** |
| 250 | + */ |
| 251 | + |
| 252 | + protected void assertToken(JsonToken expToken, JsonToken actToken) |
| 253 | + { |
| 254 | + if (actToken != expToken) { |
| 255 | + fail("Expected token "+expToken+", current token "+actToken); |
| 256 | + } |
| 257 | + } |
| 258 | + |
| 259 | + protected void assertToken(JsonToken expToken, JsonParser jp) |
| 260 | + { |
| 261 | + assertToken(expToken, jp.currentToken()); |
| 262 | + } |
| 263 | + |
| 264 | + /** |
| 265 | + * Method that gets textual contents of the current token using |
| 266 | + * available methods, and ensures results are consistent, before |
| 267 | + * returning them |
| 268 | + */ |
| 269 | + protected String getAndVerifyText(JsonParser jp) |
| 270 | + throws IOException |
| 271 | + { |
| 272 | + // Ok, let's verify other accessors |
| 273 | + int actLen = jp.getStringLength(); |
| 274 | + char[] ch = jp.getStringCharacters(); |
| 275 | + String str2 = new String(ch, jp.getStringOffset(), actLen); |
| 276 | + String str = jp.getString(); |
| 277 | + |
| 278 | + if (str.length() != actLen) { |
| 279 | + fail("Internal problem (jp.token == "+jp.currentToken()+"): jp.getText().length() ['"+str+"'] == "+str.length()+"; jp.getTextLength() == "+actLen); |
| 280 | + } |
| 281 | + assertEquals("String access via getText(), getTextXxx() must be the same", str, str2); |
| 282 | + |
| 283 | + return str; |
| 284 | + } |
| 285 | + |
| 286 | + protected void verifyFieldName(JsonParser jp, String expName) |
| 287 | + throws IOException |
| 288 | + { |
| 289 | + assertEquals(expName, jp.getString()); |
| 290 | + assertEquals(expName, jp.currentName()); |
| 291 | + } |
| 292 | + |
| 293 | + protected void verifyException(Throwable e, String... matches) |
| 294 | + { |
| 295 | + String msg = e.getMessage(); |
| 296 | + String lmsg = (msg == null) ? "" : msg.toLowerCase(); |
| 297 | + for (String match : matches) { |
| 298 | + String lmatch = match.toLowerCase(); |
| 299 | + if (lmsg.indexOf(lmatch) >= 0) { |
| 300 | + return; |
| 301 | + } |
| 302 | + } |
| 303 | + fail("Expected an exception with one of substrings ("+Arrays.asList(matches)+"): got one ("+ |
| 304 | + e.getClass().getName()+") with message \""+msg+"\""); |
| 305 | + } |
| 306 | + |
| 307 | + /* |
| 308 | + /********************************************************** |
| 309 | + /* Helper methods, other |
| 310 | + /********************************************************** |
| 311 | + */ |
| 312 | + |
| 313 | + protected static String a2q(String content) { |
| 314 | + return content.replace("'", "\""); |
| 315 | + } |
| 316 | + |
| 317 | + protected byte[] utf8Bytes(String str) { |
| 318 | + return str.getBytes(StandardCharsets.UTF_8); |
| 319 | + } |
| 320 | + |
| 321 | + /** |
| 322 | + * Helper method that tries to remove unnecessary namespace |
| 323 | + * declaration that default JDK XML parser (SJSXP) sees fit |
| 324 | + * to add. |
| 325 | + */ |
| 326 | + protected static String removeSjsxpNamespace(String xml) |
| 327 | + { |
| 328 | + final String match = " xmlns=\"\""; |
| 329 | + int ix = xml.indexOf(match); |
| 330 | + if (ix > 0) { |
| 331 | + xml = xml.substring(0, ix) + xml.substring(ix+match.length()); |
| 332 | + } |
| 333 | + return xml; |
| 334 | + } |
| 335 | + |
| 336 | + protected String readAll(File f) throws IOException |
| 337 | + { |
| 338 | + StringBuilder sb = new StringBuilder(); |
| 339 | + BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8")); |
| 340 | + String line; |
| 341 | + |
| 342 | + while ((line = br.readLine()) != null) { |
| 343 | + sb.append(line).append("\n"); |
| 344 | + } |
| 345 | + br.close(); |
| 346 | + return sb.toString(); |
| 347 | + } |
| 348 | + |
| 349 | + protected byte[] readResource(String ref) |
| 350 | + { |
| 351 | + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
| 352 | + final byte[] buf = new byte[4000]; |
| 353 | + |
| 354 | + InputStream in = getClass().getResourceAsStream(ref); |
| 355 | + if (in != null) { |
| 356 | + try { |
| 357 | + int len; |
| 358 | + while ((len = in.read(buf)) > 0) { |
| 359 | + bytes.write(buf, 0, len); |
| 360 | + } |
| 361 | + in.close(); |
| 362 | + } catch (IOException e) { |
| 363 | + throw new RuntimeException("Failed to read resource '"+ref+"': "+e); |
| 364 | + } |
| 365 | + } |
| 366 | + if (bytes.size() == 0) { |
| 367 | + throw new IllegalArgumentException("Failed to read resource '"+ref+"': empty resource?"); |
| 368 | + } |
| 369 | + return bytes.toByteArray(); |
| 370 | + } |
| 371 | + |
| 372 | + public String jaxbSerialized(Object ob, Class<?>... classes) throws Exception |
| 373 | + { |
| 374 | + StringWriter sw = new StringWriter(); |
| 375 | + if (classes.length == 0) { |
| 376 | + jakarta.xml.bind.JAXB.marshal(ob, sw); |
| 377 | + } else { |
| 378 | + jakarta.xml.bind.JAXBContext.newInstance(classes).createMarshaller().marshal(ob, sw); |
| 379 | + } |
| 380 | + sw.close(); |
| 381 | + return sw.toString(); |
| 382 | + } |
| 383 | +} |
0 commit comments