39 |
|
|
40 |
package java.io; |
package java.io; |
41 |
|
|
|
import gnu.classpath.Configuration; |
|
42 |
import gnu.java.io.ObjectIdentityWrapper; |
import gnu.java.io.ObjectIdentityWrapper; |
43 |
|
|
44 |
import java.lang.reflect.Array; |
import java.lang.reflect.Array; |
127 |
* @exception IOException Exception from underlying |
* @exception IOException Exception from underlying |
128 |
* <code>InputStream</code>. |
* <code>InputStream</code>. |
129 |
*/ |
*/ |
130 |
public final Object readObject() throws ClassNotFoundException, IOException |
public final Object readObject() |
131 |
|
throws ClassNotFoundException, IOException |
132 |
{ |
{ |
133 |
if (this.useSubclassMethod) |
if (this.useSubclassMethod) |
134 |
return readObjectOverride(); |
return readObjectOverride(); |
138 |
Object ret_val; |
Object ret_val; |
139 |
was_deserializing = this.isDeserializing; |
was_deserializing = this.isDeserializing; |
140 |
|
|
|
boolean is_consumed = false; |
|
141 |
boolean old_mode = setBlockDataMode(false); |
boolean old_mode = setBlockDataMode(false); |
142 |
|
|
143 |
this.isDeserializing = true; |
this.isDeserializing = true; |
150 |
|
|
151 |
try |
try |
152 |
{ |
{ |
153 |
switch (marker) |
ret_val = parseContent(marker); |
154 |
{ |
} |
155 |
case TC_ENDBLOCKDATA: |
finally |
156 |
{ |
{ |
157 |
ret_val = null; |
setBlockDataMode(old_mode); |
158 |
is_consumed = true; |
|
159 |
break; |
this.isDeserializing = was_deserializing; |
160 |
} |
|
161 |
|
depth -= 2; |
162 |
case TC_BLOCKDATA: |
|
163 |
case TC_BLOCKDATALONG: |
if (! was_deserializing) |
164 |
{ |
{ |
165 |
if (marker == TC_BLOCKDATALONG) |
if (validators.size() > 0) |
166 |
{ if(dump) dumpElementln("BLOCKDATALONG"); } |
invokeValidators(); |
167 |
else |
} |
168 |
{ if(dump) dumpElementln("BLOCKDATA"); } |
} |
169 |
readNextBlock(marker); |
|
170 |
throw new StreamCorruptedException("Unexpected blockData"); |
return ret_val; |
171 |
} |
} |
172 |
|
|
173 |
case TC_NULL: |
/** |
174 |
{ |
* Handles a content block within the stream, which begins with a marker |
175 |
if(dump) dumpElementln("NULL"); |
* byte indicating its type. |
176 |
ret_val = null; |
* |
177 |
break; |
* @param marker the byte marker. |
178 |
} |
* @return an object which represents the parsed content. |
179 |
|
* @throws ClassNotFoundException if the class of an object being |
180 |
case TC_REFERENCE: |
* read in cannot be found. |
181 |
{ |
* @throws IOException if invalid data occurs or one is thrown by the |
182 |
if(dump) dumpElement("REFERENCE "); |
* underlying <code>InputStream</code>. |
183 |
Integer oid = new Integer(this.realInputStream.readInt()); |
*/ |
184 |
if(dump) dumpElementln(Integer.toHexString(oid.intValue())); |
private Object parseContent(byte marker) |
185 |
ret_val = ((ObjectIdentityWrapper) |
throws ClassNotFoundException, IOException |
186 |
this.objectLookupTable.get(oid)).object; |
{ |
187 |
break; |
Object ret_val; |
188 |
} |
boolean is_consumed = false; |
189 |
|
|
190 |
case TC_CLASS: |
switch (marker) |
191 |
|
{ |
192 |
|
case TC_ENDBLOCKDATA: |
193 |
|
{ |
194 |
|
ret_val = null; |
195 |
|
is_consumed = true; |
196 |
|
break; |
197 |
|
} |
198 |
|
|
199 |
|
case TC_BLOCKDATA: |
200 |
|
case TC_BLOCKDATALONG: |
201 |
|
{ |
202 |
|
if (marker == TC_BLOCKDATALONG) |
203 |
|
{ if(dump) dumpElementln("BLOCKDATALONG"); } |
204 |
|
else |
205 |
|
{ if(dump) dumpElementln("BLOCKDATA"); } |
206 |
|
readNextBlock(marker); |
207 |
|
throw new StreamCorruptedException("Unexpected blockData"); |
208 |
|
} |
209 |
|
|
210 |
|
case TC_NULL: |
211 |
|
{ |
212 |
|
if(dump) dumpElementln("NULL"); |
213 |
|
ret_val = null; |
214 |
|
break; |
215 |
|
} |
216 |
|
|
217 |
|
case TC_REFERENCE: |
218 |
|
{ |
219 |
|
if(dump) dumpElement("REFERENCE "); |
220 |
|
Integer oid = new Integer(this.realInputStream.readInt()); |
221 |
|
if(dump) dumpElementln(Integer.toHexString(oid.intValue())); |
222 |
|
ret_val = ((ObjectIdentityWrapper) |
223 |
|
this.objectLookupTable.get(oid)).object; |
224 |
|
break; |
225 |
|
} |
226 |
|
|
227 |
|
case TC_CLASS: |
228 |
|
{ |
229 |
|
if(dump) dumpElementln("CLASS"); |
230 |
|
ObjectStreamClass osc = (ObjectStreamClass)readObject(); |
231 |
|
Class clazz = osc.forClass(); |
232 |
|
assignNewHandle(clazz); |
233 |
|
ret_val = clazz; |
234 |
|
break; |
235 |
|
} |
236 |
|
|
237 |
|
case TC_PROXYCLASSDESC: |
238 |
|
{ |
239 |
|
if(dump) dumpElementln("PROXYCLASS"); |
240 |
|
int n_intf = this.realInputStream.readInt(); |
241 |
|
String[] intfs = new String[n_intf]; |
242 |
|
for (int i = 0; i < n_intf; i++) |
243 |
|
{ |
244 |
|
intfs[i] = this.realInputStream.readUTF(); |
245 |
|
System.out.println(intfs[i]); |
246 |
|
} |
247 |
|
|
248 |
|
boolean oldmode = setBlockDataMode(true); |
249 |
|
Class cl = resolveProxyClass(intfs); |
250 |
|
setBlockDataMode(oldmode); |
251 |
|
|
252 |
|
ObjectStreamClass osc = lookupClass(cl); |
253 |
|
assignNewHandle(osc); |
254 |
|
|
255 |
|
if (!is_consumed) |
256 |
|
{ |
257 |
|
byte b = this.realInputStream.readByte(); |
258 |
|
if (b != TC_ENDBLOCKDATA) |
259 |
|
throw new IOException("Data annotated to class was not consumed." + b); |
260 |
|
} |
261 |
|
else |
262 |
|
is_consumed = false; |
263 |
|
ObjectStreamClass superosc = (ObjectStreamClass)readObject(); |
264 |
|
osc.setSuperclass(superosc); |
265 |
|
ret_val = osc; |
266 |
|
break; |
267 |
|
} |
268 |
|
|
269 |
|
case TC_CLASSDESC: |
270 |
|
{ |
271 |
|
ObjectStreamClass osc = readClassDescriptor(); |
272 |
|
|
273 |
|
if (!is_consumed) |
274 |
|
{ |
275 |
|
byte b = this.realInputStream.readByte(); |
276 |
|
if (b != TC_ENDBLOCKDATA) |
277 |
|
throw new IOException("Data annotated to class was not consumed." + b); |
278 |
|
} |
279 |
|
else |
280 |
|
is_consumed = false; |
281 |
|
|
282 |
|
osc.setSuperclass ((ObjectStreamClass)readObject()); |
283 |
|
ret_val = osc; |
284 |
|
break; |
285 |
|
} |
286 |
|
|
287 |
|
case TC_STRING: |
288 |
|
case TC_LONGSTRING: |
289 |
|
{ |
290 |
|
if(dump) dumpElement("STRING="); |
291 |
|
String s = this.realInputStream.readUTF(); |
292 |
|
if(dump) dumpElementln(s); |
293 |
|
ret_val = processResolution(null, s, assignNewHandle(s)); |
294 |
|
break; |
295 |
|
} |
296 |
|
|
297 |
|
case TC_ARRAY: |
298 |
|
{ |
299 |
|
if(dump) dumpElementln("ARRAY"); |
300 |
|
ObjectStreamClass osc = (ObjectStreamClass)readObject(); |
301 |
|
Class componentType = osc.forClass().getComponentType(); |
302 |
|
if(dump) dumpElement("ARRAY LENGTH="); |
303 |
|
int length = this.realInputStream.readInt(); |
304 |
|
if(dump) dumpElementln (length + "; COMPONENT TYPE=" + componentType); |
305 |
|
Object array = Array.newInstance(componentType, length); |
306 |
|
int handle = assignNewHandle(array); |
307 |
|
readArrayElements(array, componentType); |
308 |
|
if(dump) |
309 |
|
for (int i = 0, len = Array.getLength(array); i < len; i++) |
310 |
|
dumpElementln(" ELEMENT[" + i + "]=" + Array.get(array, i)); |
311 |
|
ret_val = processResolution(null, array, handle); |
312 |
|
break; |
313 |
|
} |
314 |
|
|
315 |
|
case TC_OBJECT: |
316 |
|
{ |
317 |
|
if(dump) dumpElementln("OBJECT"); |
318 |
|
ObjectStreamClass osc = (ObjectStreamClass)readObject(); |
319 |
|
Class clazz = osc.forClass(); |
320 |
|
|
321 |
|
if (!osc.realClassIsSerializable) |
322 |
|
throw new NotSerializableException |
323 |
|
(clazz + " is not Serializable, and thus cannot be deserialized."); |
324 |
|
|
325 |
|
if (osc.realClassIsExternalizable) |
326 |
{ |
{ |
327 |
if(dump) dumpElementln("CLASS"); |
Externalizable obj = osc.newInstance(); |
|
ObjectStreamClass osc = (ObjectStreamClass)readObject(); |
|
|
Class clazz = osc.forClass(); |
|
|
assignNewHandle(clazz); |
|
|
ret_val = clazz; |
|
|
break; |
|
|
} |
|
|
|
|
|
case TC_PROXYCLASSDESC: |
|
|
{ |
|
|
if(dump) dumpElementln("PROXYCLASS"); |
|
|
int n_intf = this.realInputStream.readInt(); |
|
|
String[] intfs = new String[n_intf]; |
|
|
for (int i = 0; i < n_intf; i++) |
|
|
{ |
|
|
intfs[i] = this.realInputStream.readUTF(); |
|
|
System.out.println(intfs[i]); |
|
|
} |
|
328 |
|
|
329 |
boolean oldmode = setBlockDataMode(true); |
int handle = assignNewHandle(obj); |
|
Class cl = resolveProxyClass(intfs); |
|
|
setBlockDataMode(oldmode); |
|
330 |
|
|
331 |
ObjectStreamClass osc = lookupClass(cl); |
boolean read_from_blocks = ((osc.getFlags() & SC_BLOCK_DATA) != 0); |
|
assignNewHandle(osc); |
|
332 |
|
|
333 |
if (!is_consumed) |
boolean oldmode = this.readDataFromBlock; |
334 |
{ |
if (read_from_blocks) |
335 |
byte b = this.realInputStream.readByte(); |
setBlockDataMode(true); |
|
if (b != TC_ENDBLOCKDATA) |
|
|
throw new IOException("Data annotated to class was not consumed." + b); |
|
|
} |
|
|
else |
|
|
is_consumed = false; |
|
|
ObjectStreamClass superosc = (ObjectStreamClass)readObject(); |
|
|
osc.setSuperclass(superosc); |
|
|
ret_val = osc; |
|
|
break; |
|
|
} |
|
|
|
|
|
case TC_CLASSDESC: |
|
|
{ |
|
|
ObjectStreamClass osc = readClassDescriptor(); |
|
336 |
|
|
337 |
if (!is_consumed) |
obj.readExternal(this); |
338 |
{ |
|
339 |
byte b = this.realInputStream.readByte(); |
if (read_from_blocks) |
340 |
if (b != TC_ENDBLOCKDATA) |
{ |
341 |
throw new IOException("Data annotated to class was not consumed." + b); |
setBlockDataMode(oldmode); |
342 |
|
if (!oldmode) |
343 |
|
if (this.realInputStream.readByte() != TC_ENDBLOCKDATA) |
344 |
|
throw new IOException("No end of block data seen for class with readExternal (ObjectInputStream) method."); |
345 |
} |
} |
|
else |
|
|
is_consumed = false; |
|
|
|
|
|
osc.setSuperclass ((ObjectStreamClass)readObject()); |
|
|
ret_val = osc; |
|
|
break; |
|
|
} |
|
|
|
|
|
case TC_STRING: |
|
|
case TC_LONGSTRING: |
|
|
{ |
|
|
if(dump) dumpElement("STRING="); |
|
|
String s = this.realInputStream.readUTF(); |
|
|
if(dump) dumpElementln(s); |
|
|
ret_val = processResolution(null, s, assignNewHandle(s)); |
|
|
break; |
|
|
} |
|
|
|
|
|
case TC_ARRAY: |
|
|
{ |
|
|
if(dump) dumpElementln("ARRAY"); |
|
|
ObjectStreamClass osc = (ObjectStreamClass)readObject(); |
|
|
Class componentType = osc.forClass().getComponentType(); |
|
|
if(dump) dumpElement("ARRAY LENGTH="); |
|
|
int length = this.realInputStream.readInt(); |
|
|
if(dump) dumpElementln (length + "; COMPONENT TYPE=" + componentType); |
|
|
Object array = Array.newInstance(componentType, length); |
|
|
int handle = assignNewHandle(array); |
|
|
readArrayElements(array, componentType); |
|
|
if(dump) |
|
|
for (int i = 0, len = Array.getLength(array); i < len; i++) |
|
|
dumpElementln(" ELEMENT[" + i + "]=" + Array.get(array, i)); |
|
|
ret_val = processResolution(null, array, handle); |
|
|
break; |
|
|
} |
|
|
|
|
|
case TC_OBJECT: |
|
|
{ |
|
|
if(dump) dumpElementln("OBJECT"); |
|
|
ObjectStreamClass osc = (ObjectStreamClass)readObject(); |
|
|
Class clazz = osc.forClass(); |
|
|
|
|
|
if (!osc.realClassIsSerializable) |
|
|
throw new NotSerializableException |
|
|
(clazz + " is not Serializable, and thus cannot be deserialized."); |
|
|
|
|
|
if (osc.realClassIsExternalizable) |
|
|
{ |
|
|
Externalizable obj = osc.newInstance(); |
|
|
|
|
|
int handle = assignNewHandle(obj); |
|
|
|
|
|
boolean read_from_blocks = ((osc.getFlags() & SC_BLOCK_DATA) != 0); |
|
|
|
|
|
boolean oldmode = this.readDataFromBlock; |
|
|
if (read_from_blocks) |
|
|
setBlockDataMode(true); |
|
|
|
|
|
obj.readExternal(this); |
|
|
|
|
|
if (read_from_blocks) |
|
|
{ |
|
|
setBlockDataMode(oldmode); |
|
|
if (!oldmode) |
|
|
if (this.realInputStream.readByte() != TC_ENDBLOCKDATA) |
|
|
throw new IOException("No end of block data seen for class with readExternal (ObjectInputStream) method."); |
|
|
} |
|
|
|
|
|
ret_val = processResolution(osc, obj, handle); |
|
|
break; |
|
|
} // end if (osc.realClassIsExternalizable) |
|
346 |
|
|
347 |
Object obj = newObject(clazz, osc.firstNonSerializableParentConstructor); |
ret_val = processResolution(osc, obj, handle); |
348 |
|
break; |
|
int handle = assignNewHandle(obj); |
|
|
Object prevObject = this.currentObject; |
|
|
ObjectStreamClass prevObjectStreamClass = this.currentObjectStreamClass; |
|
|
|
|
|
this.currentObject = obj; |
|
|
ObjectStreamClass[] hierarchy = |
|
|
inputGetObjectStreamClasses(clazz); |
|
349 |
|
|
350 |
for (int i = 0; i < hierarchy.length; i++) |
} // end if (osc.realClassIsExternalizable) |
351 |
{ |
|
352 |
this.currentObjectStreamClass = hierarchy[i]; |
Object obj = newObject(clazz, osc.firstNonSerializableParentConstructor); |
353 |
|
|
354 |
if(dump) dumpElementln("Reading fields of " + this.currentObjectStreamClass.getName ()); |
int handle = assignNewHandle(obj); |
355 |
|
Object prevObject = this.currentObject; |
356 |
// XXX: should initialize fields in classes in the hierarchy |
ObjectStreamClass prevObjectStreamClass = this.currentObjectStreamClass; |
357 |
// that aren't in the stream |
|
358 |
// should skip over classes in the stream that aren't in the |
this.currentObject = obj; |
359 |
// real classes hierarchy |
ObjectStreamClass[] hierarchy = |
360 |
|
inputGetObjectStreamClasses(clazz); |
361 |
Method readObjectMethod = this.currentObjectStreamClass.readObjectMethod; |
|
362 |
if (readObjectMethod != null) |
for (int i = 0; i < hierarchy.length; i++) |
363 |
{ |
{ |
364 |
fieldsAlreadyRead = false; |
this.currentObjectStreamClass = hierarchy[i]; |
365 |
boolean oldmode = setBlockDataMode(true); |
if(dump) dumpElementln("Reading fields of " + this.currentObjectStreamClass.getName ()); |
366 |
callReadMethod(readObjectMethod, this.currentObjectStreamClass.forClass(), obj); |
|
367 |
setBlockDataMode(oldmode); |
// XXX: should initialize fields in classes in the hierarchy |
368 |
} |
// that aren't in the stream |
369 |
else |
// should skip over classes in the stream that aren't in the |
370 |
{ |
// real classes hierarchy |
371 |
readFields(obj, currentObjectStreamClass); |
|
372 |
} |
Method readObjectMethod = this.currentObjectStreamClass.readObjectMethod; |
373 |
|
if (readObjectMethod != null) |
374 |
if (this.currentObjectStreamClass.hasWriteMethod()) |
{ |
375 |
{ |
fieldsAlreadyRead = false; |
376 |
if(dump) dumpElement("ENDBLOCKDATA? "); |
boolean oldmode = setBlockDataMode(true); |
377 |
try |
callReadMethod(readObjectMethod, this.currentObjectStreamClass.forClass(), obj); |
378 |
{ |
setBlockDataMode(oldmode); |
379 |
// FIXME: XXX: This try block is to |
} |
380 |
// catch EOF which is thrown for some |
else |
381 |
// objects. That indicates a bug in |
{ |
382 |
// the logic. |
readFields(obj, currentObjectStreamClass); |
383 |
|
} |
384 |
if (this.realInputStream.readByte() != TC_ENDBLOCKDATA) |
|
385 |
throw new IOException |
if (this.currentObjectStreamClass.hasWriteMethod()) |
386 |
("No end of block data seen for class with readObject (ObjectInputStream) method."); |
{ |
387 |
if(dump) dumpElementln("yes"); |
if(dump) dumpElement("ENDBLOCKDATA? "); |
388 |
} |
try |
389 |
// catch (EOFException e) |
{ |
390 |
// { |
/* Read blocks until an end marker */ |
391 |
// if(dump) dumpElementln("no, got EOFException"); |
byte writeMarker = this.realInputStream.readByte(); |
392 |
// } |
while (writeMarker != TC_ENDBLOCKDATA) |
393 |
catch (IOException e) |
{ |
394 |
{ |
parseContent(writeMarker); |
395 |
if(dump) dumpElementln("no, got IOException"); |
writeMarker = this.realInputStream.readByte(); |
396 |
} |
} |
397 |
|
if(dump) dumpElementln("yes"); |
398 |
|
} |
399 |
|
catch (EOFException e) |
400 |
|
{ |
401 |
|
throw (IOException) new IOException |
402 |
|
("No end of block data seen for class with readObject (ObjectInputStream) method.").initCause(e); |
403 |
} |
} |
404 |
} |
} |
|
|
|
|
this.currentObject = prevObject; |
|
|
this.currentObjectStreamClass = prevObjectStreamClass; |
|
|
ret_val = processResolution(osc, obj, handle); |
|
|
|
|
|
break; |
|
405 |
} |
} |
406 |
|
|
407 |
case TC_RESET: |
this.currentObject = prevObject; |
408 |
if(dump) dumpElementln("RESET"); |
this.currentObjectStreamClass = prevObjectStreamClass; |
409 |
clearHandles(); |
ret_val = processResolution(osc, obj, handle); |
410 |
ret_val = readObject(); |
|
411 |
break; |
break; |
412 |
|
} |
|
case TC_EXCEPTION: |
|
|
{ |
|
|
if(dump) dumpElement("EXCEPTION="); |
|
|
Exception e = (Exception)readObject(); |
|
|
if(dump) dumpElementln(e.toString()); |
|
|
clearHandles(); |
|
|
throw new WriteAbortedException("Exception thrown during writing of stream", e); |
|
|
} |
|
|
|
|
|
default: |
|
|
throw new IOException("Unknown marker on stream: " + marker); |
|
|
} |
|
|
} |
|
|
finally |
|
|
{ |
|
|
setBlockDataMode(old_mode); |
|
413 |
|
|
414 |
this.isDeserializing = was_deserializing; |
case TC_RESET: |
415 |
|
if(dump) dumpElementln("RESET"); |
416 |
|
clearHandles(); |
417 |
|
ret_val = readObject(); |
418 |
|
break; |
419 |
|
|
420 |
depth -= 2; |
case TC_EXCEPTION: |
421 |
|
{ |
422 |
|
if(dump) dumpElement("EXCEPTION="); |
423 |
|
Exception e = (Exception)readObject(); |
424 |
|
if(dump) dumpElementln(e.toString()); |
425 |
|
clearHandles(); |
426 |
|
throw new WriteAbortedException("Exception thrown during writing of stream", e); |
427 |
|
} |
428 |
|
|
429 |
if (! was_deserializing) |
default: |
430 |
{ |
throw new IOException("Unknown marker on stream: " + marker); |
|
if (validators.size() > 0) |
|
|
invokeValidators(); |
|
|
} |
|
431 |
} |
} |
|
|
|
432 |
return ret_val; |
return ret_val; |
433 |
} |
} |
434 |
|
|
828 |
|
|
829 |
/** |
/** |
830 |
* Reconstruct class hierarchy the same way |
* Reconstruct class hierarchy the same way |
831 |
* {@link java.io.ObjectStreamClass.getObjectStreamClasses(java.lang.Class)} does |
* {@link java.io.ObjectStreamClass#getObjectStreamClasses(Class)} does |
832 |
* but using lookupClass instead of ObjectStreamClass.lookup. This |
* but using lookupClass instead of ObjectStreamClass.lookup. This |
833 |
* dup is necessary localize the lookup table. Hopefully some future |
* dup is necessary localize the lookup table. Hopefully some future |
834 |
* rewritings will be able to prevent this. |
* rewritings will be able to prevent this. |
897 |
} |
} |
898 |
else |
else |
899 |
for (int i = 0; i < intfs.length; i++) |
for (int i = 0; i < intfs.length; i++) |
900 |
clss[i] = cl.loadClass(intfs[i]); |
clss[i] = Class.forName(intfs[i], false, cl); |
901 |
try |
try |
902 |
{ |
{ |
903 |
return Proxy.getProxyClass(cl, clss); |
return Proxy.getProxyClass(cl, clss); |
1218 |
* This method should be called by a method called 'readObject' in the |
* This method should be called by a method called 'readObject' in the |
1219 |
* deserializing class (if present). It cannot (and should not)be called |
* deserializing class (if present). It cannot (and should not)be called |
1220 |
* outside of it. Its goal is to read all fields in the real input stream |
* outside of it. Its goal is to read all fields in the real input stream |
1221 |
* and keep them accessible through the {@link #GetField} class. Calling |
* and keep them accessible through the {@link GetField} class. Calling |
1222 |
* this method will not alter the deserializing object. |
* this method will not alter the deserializing object. |
1223 |
* |
* |
1224 |
* @return A valid freshly created 'GetField' instance to get access to |
* @return A valid freshly created 'GetField' instance to get access to |
1931 |
System.out.print (Thread.currentThread() + ": "); |
System.out.print (Thread.currentThread() + ": "); |
1932 |
} |
} |
1933 |
|
|
|
static |
|
|
{ |
|
|
if (Configuration.INIT_LOAD_LIBRARY) |
|
|
{ |
|
|
System.loadLibrary ("javaio"); |
|
|
} |
|
|
} |
|
|
|
|
1934 |
// used to keep a prioritized list of object validators |
// used to keep a prioritized list of object validators |
1935 |
private static final class ValidatorAndPriority implements Comparable |
private static final class ValidatorAndPriority implements Comparable |
1936 |
{ |
{ |