1 package com.fasterxml.jackson.databind.interop; 2 3 import java.io.*; 4 5 import com.fasterxml.jackson.databind.*; 6 7 /** 8 * Simple test to ensure that we can make POJOs use Jackson 9 * for JDK serialization, via {@link Externalizable} 10 * 11 * @since 2.1 12 */ 13 public class TestExternalizable extends BaseMapTest 14 { 15 /* Not pretty, but needed to make ObjectMapper accessible from 16 * static context (alternatively could use ThreadLocal). 17 */ 18 static class MapperHolder { 19 private final ObjectMapper mapper = new ObjectMapper(); 20 private final static MapperHolder instance = new MapperHolder(); mapper()21 public static ObjectMapper mapper() { return instance.mapper; } 22 } 23 24 /** 25 * Helper class we need to adapt {@link ObjectOutput} as 26 * {@link OutputStream} 27 */ 28 final static class ExternalizableInput extends InputStream 29 { 30 private final ObjectInput in; 31 ExternalizableInput(ObjectInput in)32 public ExternalizableInput(ObjectInput in) { 33 this.in = in; 34 } 35 36 @Override available()37 public int available() throws IOException { 38 return in.available(); 39 } 40 41 @Override close()42 public void close() throws IOException { 43 in.close(); 44 } 45 46 @Override markSupported()47 public boolean markSupported() { 48 return false; 49 } 50 51 @Override read()52 public int read() throws IOException { 53 return in.read(); 54 } 55 56 @Override read(byte[] buffer)57 public int read(byte[] buffer) throws IOException { 58 return in.read(buffer); 59 } 60 61 @Override read(byte[] buffer, int offset, int len)62 public int read(byte[] buffer, int offset, int len) throws IOException { 63 return in.read(buffer, offset, len); 64 } 65 66 @Override skip(long n)67 public long skip(long n) throws IOException { 68 return in.skip(n); 69 } 70 } 71 72 /** 73 * Helper class we need to adapt {@link ObjectOutput} as 74 * {@link OutputStream} 75 */ 76 final static class ExternalizableOutput extends OutputStream 77 { 78 private final ObjectOutput out; 79 ExternalizableOutput(ObjectOutput out)80 public ExternalizableOutput(ObjectOutput out) { 81 this.out = out; 82 } 83 84 @Override flush()85 public void flush() throws IOException { 86 out.flush(); 87 } 88 89 @Override close()90 public void close() throws IOException { 91 out.close(); 92 } 93 94 @Override write(int ch)95 public void write(int ch) throws IOException { 96 out.write(ch); 97 } 98 99 @Override write(byte[] data)100 public void write(byte[] data) throws IOException { 101 out.write(data); 102 } 103 104 @Override write(byte[] data, int offset, int len)105 public void write(byte[] data, int offset, int len) throws IOException { 106 out.write(data, offset, len); 107 } 108 } 109 110 // @com.fasterxml.jackson.annotation.JsonFormat(shape=com.fasterxml.jackson.annotation.JsonFormat.Shape.ARRAY) 111 @SuppressWarnings("resource") 112 static class MyPojo implements Externalizable 113 { 114 public int id; 115 public String name; 116 public int[] values; 117 MyPojo()118 public MyPojo() { } // for deserialization MyPojo(int id, String name, int[] values)119 public MyPojo(int id, String name, int[] values) 120 { 121 this.id = id; 122 this.name = name; 123 this.values = values; 124 } 125 126 @Override readExternal(ObjectInput in)127 public void readExternal(ObjectInput in) throws IOException 128 { 129 // MapperHolder.mapper().readValue( 130 MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in)); 131 } 132 133 @Override writeExternal(ObjectOutput oo)134 public void writeExternal(ObjectOutput oo) throws IOException 135 { 136 MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this); 137 } 138 139 @Override equals(Object o)140 public boolean equals(Object o) 141 { 142 if (o == this) return true; 143 if (o == null) return false; 144 if (o.getClass() != getClass()) return false; 145 146 MyPojo other = (MyPojo) o; 147 148 if (other.id != id) return false; 149 if (!other.name.equals(name)) return false; 150 151 if (other.values.length != values.length) return false; 152 for (int i = 0, end = values.length; i < end; ++i) { 153 if (values[i] != other.values[i]) return false; 154 } 155 return true; 156 } 157 } 158 159 /* 160 /********************************************************** 161 /* Actual tests 162 /********************************************************** 163 */ 164 165 // Comparison, using JDK native 166 static class MyPojoNative implements Serializable 167 { 168 private static final long serialVersionUID = 1L; 169 170 public int id; 171 public String name; 172 public int[] values; 173 MyPojoNative(int id, String name, int[] values)174 public MyPojoNative(int id, String name, int[] values) 175 { 176 this.id = id; 177 this.name = name; 178 this.values = values; 179 } 180 } 181 182 @SuppressWarnings("unused") testSerializeAsExternalizable()183 public void testSerializeAsExternalizable() throws Exception 184 { 185 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 186 ObjectOutputStream obs = new ObjectOutputStream(bytes); 187 final MyPojo input = new MyPojo(13, "Foobar", new int[] { 1, 2, 3 } ); 188 obs.writeObject(input); 189 obs.close(); 190 byte[] ser = bytes.toByteArray(); 191 192 // Ok: just verify it contains stuff it should 193 byte[] json = MapperHolder.mapper().writeValueAsBytes(input); 194 195 int ix = indexOf(ser, json); 196 if (ix < 0) { 197 fail("Serialization ("+ser.length+") does NOT contain JSON (of "+json.length+")"); 198 } 199 200 // Sanity check: 201 if (false) { 202 bytes = new ByteArrayOutputStream(); 203 obs = new ObjectOutputStream(bytes); 204 MyPojoNative p = new MyPojoNative(13, "Foobar", new int[] { 1, 2, 3 } ); 205 obs.writeObject(p); 206 obs.close(); 207 System.out.println("Native size: "+bytes.size()+", vs JSON: "+ser.length); 208 } 209 210 // then read back! 211 ObjectInputStream ins = new ObjectInputStream(new ByteArrayInputStream(ser)); 212 MyPojo output = (MyPojo) ins.readObject(); 213 ins.close(); 214 assertNotNull(output); 215 216 assertEquals(input, output); 217 } 218 219 /* 220 /********************************************************** 221 /* Helper methods 222 /********************************************************** 223 */ 224 indexOf(byte[] full, byte[] fragment)225 private int indexOf(byte[] full, byte[] fragment) 226 { 227 final byte first = fragment[0]; 228 for (int i = 0, end = full.length-fragment.length; i < end; ++i) { 229 if (full[i] != first) continue; 230 if (matches(full, i, fragment)) { 231 return i; 232 } 233 } 234 return -1; 235 } 236 matches(byte[] full, int index, byte[] fragment)237 private boolean matches(byte[] full, int index, byte[] fragment) 238 { 239 for (int i = 1, end = fragment.length; i < end; ++i) { 240 if (fragment[i] != full[index+i]) { 241 return false; 242 } 243 } 244 return true; 245 } 246 } 247