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