All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scijava.annotations.legacy.LegacyReader Maven / Gradle / Ivy

/*
 * #%L
 * SciJava Common shared library for SciJava software.
 * %%
 * Copyright (C) 2009 - 2017 Board of Regents of the University of
 * Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
 * Institute of Molecular Cell Biology and Genetics, University of
 * Konstanz, and KNIME GmbH.
 * %%
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * #L%
 */

package org.scijava.annotations.legacy;

import java.io.BufferedInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * Reads legacy annotation indexes.
 * 

* For backwards-compatibility with SezPoz, this reader falls back to a custom * deserializer designed to gracefully handle serialized classes even when the * current class path library is incompatible with the one used to serialize the * data. *

* * @author Johannes Schindelin */ public class LegacyReader { private final static short STREAM_MAGIC = (short) 0xaced; private final static short STREAM_VERSION = 5; private final static byte TC_NULL = (byte) 0x70; private final static byte TC_REFERENCE = (byte) 0x71; private final static byte TC_CLASSDESC = (byte) 0x72; private final static byte TC_OBJECT = (byte) 0x73; private final static byte TC_STRING = (byte) 0x74; private final static byte TC_BLOCKDATA = (byte) 0x77; private final static byte TC_ENDBLOCKDATA = (byte) 0x78; private final static byte SC_WRITE_METHOD = 0x01; // if SC_SERIALIZABLE private final static byte SC_SERIALIZABLE = 0x02; private final static int HANDLE_OFFSET = 0x7e0000; private final InputStream in; private final List references; public LegacyReader(final InputStream in) throws IOException { this.in = new BufferedInputStream(in); short signature = (short) read16(); if (signature != STREAM_MAGIC) { throw new IOException("Unrecognized signature: 0x" + Integer.toHexString(signature)); } int version = read16(); if (version != STREAM_VERSION) { throw new IOException("Unsupported version: " + version); } references = new ArrayList<>(); } public void close() throws IOException { in.close(); } public Object readObject() throws IOException { int c = read8(); if (c == TC_NULL) { return null; } if (c == TC_STRING) { int length = read16(); byte[] bytes = new byte[length]; readFully(bytes); String s = new String(bytes, "UTF-8"); references.add(s); return s; } if (c == TC_REFERENCE) { int handle = read32() - HANDLE_OFFSET; return references.get(handle); } if (c != TC_OBJECT) { throw new IOException("Unexpected token: 0x" + Integer.toHexString(c)); } return readClassDesc().readWithoutClassDesc(); } private ClassDesc readClassDesc() throws IOException { int c = read8(); if (c == TC_REFERENCE) { int handle = read32() - HANDLE_OFFSET; return (ClassDesc) references.get(handle); } else if (c == TC_CLASSDESC) { return newClassDesc(); } else { throw new UnsupportedOperationException("Unexpected token: 0x" + Integer.toHexString(c)); } } private void expectToken(int token) throws IOException { int c = read8(); if (c != token) { throw new UnsupportedOperationException("Unexpected token: 0x" + Integer.toHexString(c)); } } private int read8() throws IOException { return in.read() & 0xff; } private int read16() throws IOException { return (read8() << 8) | read8(); } private int read32() throws IOException { return (read16() << 16) | read16(); } private long read64() throws IOException { return ((read32() & 0xffffffffl) << 32) | read32(); } private String readString() throws IOException { int length = read16(); byte[] array = new byte[length]; readFully(array); return new String(array); } private void readFully(byte[] b) throws IOException { readFully(b, 0, b.length); } private void readFully(byte[] b, int offset, int length) throws IOException { while (length > 0) { int count = in.read(b, offset, length); if (count < 0) { throw new EOFException("Reached EOF " + length + " bytes too early"); } offset += count; length -= count; } } private ClassDesc newClassDesc() throws IOException { String className = readString(); long serialVersionUID = read64(); String rawName = "L" + className.replace('.', '/') + ";"; ClassDesc result = classDescs.get(rawName); if (result == null) { throw new IOException("Could not find class for " + className + ", serial " + serialVersionUID); } references.add(result); int flags = read8(); if ((flags & ~(SC_SERIALIZABLE | SC_WRITE_METHOD)) != 0) { throw new UnsupportedOperationException("Cannot handle flags: 0x" + Integer.toHexString(flags)); } int fieldCount = read16(); if (fieldCount != result.fields.size()) { throw new IOException("Incompatible field count: " + fieldCount + " vs " + result.fields.size()); } result.order = new String[fieldCount]; for (int i = 0; i < fieldCount; i++) { int typeChar = read8(); String fieldName = readString(); if (!result.fields.containsKey(fieldName)) { throw new IOException("Unexpected field " + fieldName); } result.order[i] = fieldName; String type = "" + (char) typeChar; if (typeChar == '[' || typeChar == 'L') { type = (String) readObject(); } if (!type.equals(result.fields.get(fieldName).className)) { throw new IOException(fieldName + " has type " + type + " instead of expected " + result.fields.get(fieldName).className); } } expectToken(TC_ENDBLOCKDATA); int c = read8(); @SuppressWarnings("unused") Object superClassDesc = null; if (c == TC_CLASSDESC) { superClassDesc = newClassDesc(); } else if (c == TC_REFERENCE) { int handle = read32() - HANDLE_OFFSET; superClassDesc = references.get(handle); } else if (c != TC_NULL) { throw new UnsupportedOperationException("Unexpected token: 0x" + Integer.toHexString(c)); } return result; } private abstract class ClassDesc { protected String[] order; protected final String className; protected Map fields; protected ClassDesc(final String className) { this.className = className; classDescs.put(className, this); } protected abstract Object read() throws IOException; protected Object readWithoutClassDesc() throws IOException { return read(); } } private class NonPrimitiveClassDesc extends ClassDesc { protected NonPrimitiveClassDesc(final String className, final Object... fields) { super(className); if ((fields.length % 2) != 0) { throw new RuntimeException("That's odd: " + fields.length); } this.fields = new LinkedHashMap<>(); for (int i = 0; i < fields.length; i += 2) { String name = (String) fields[i]; ClassDesc classDesc; if (fields[i + 1] instanceof ClassDesc) { classDesc = (ClassDesc) fields[i + 1]; } else if (fields[i + 1] instanceof String) { classDesc = classDescs.get(fields[i + 1]); if (classDesc == null) { throw new RuntimeException("Could not find class desc for " + fields[i + 1]); } } else { throw new RuntimeException("Invalid class desc: " + fields[i + 1]); } this.fields.put(name, classDesc); } } @Override protected Object read() throws IOException { int c = read8(); if (c == TC_NULL) { return null; } else if (c != TC_OBJECT) { throw new IOException("Unexpected token: " + Integer.toHexString(c)); } final ClassDesc classDesc = readClassDesc(); if (classDesc != this) { throw new IOException("ClassDesc mismatch: " + className + " was expected, but got " + classDesc.className); } return readWithoutClassDesc(); } @Override protected final Object readWithoutClassDesc() throws IOException { final Map map = new LinkedHashMap<>(); int index = references.size(); references.add(map); for (final String fieldName : order) { final ClassDesc classDesc = fields.get(fieldName); map.put(fieldName, classDesc.read()); } Object o = readExtra(map); if (o != map) { references.set(index, o); } return o; } /** * @throws IOException If something goes wrong reading the map. */ protected Object readExtra(Map o) throws IOException { return o; } } private class BoxedPrimitiveClassDesc extends NonPrimitiveClassDesc { private final Class clazz; public BoxedPrimitiveClassDesc(final Class clazz, final String simpleName) { super(toSimpleName(clazz), "value", simpleName); this.clazz = clazz == Double.class || clazz == Float.class ? Double.class : clazz == Boolean.class ? Boolean.class : Long.class; } @Override public Object readExtra(final Map map) throws IOException { return clazz.cast(map.get("value")); } } private class InterfaceClassDesc extends NonPrimitiveClassDesc { protected InterfaceClassDesc(final Class clazz) { super(toSimpleName(clazz)); } @Override public Object read() throws IOException { return readObject(); } } public static String toSimpleName(Class clazz) { return "L" + clazz.getName().replace('.', '/') + ";"; } private final Map classDescs = new HashMap<>(); { new ClassDesc("B") { @Override public Object read() throws IOException { return (long) (byte) read8(); } }; new ClassDesc("C") { @Override public Object read() throws IOException { return "" + (char) read16(); } }; new ClassDesc("D") { @Override public Object read() throws IOException { return Double.longBitsToDouble(read64()); } }; new ClassDesc("F") { @Override public Object read() throws IOException { return (double) Float.intBitsToFloat(read32()); } }; new ClassDesc("I") { @Override public Object read() throws IOException { return (long) read32(); } }; new ClassDesc("J") { @Override public Object read() throws IOException { return read64(); } }; new ClassDesc("S") { @Override public Object read() throws IOException { return (long) (short) read16(); } }; new ClassDesc("Z") { @Override public Object read() throws IOException { return read8() != 0; } }; new ClassDesc("Ljava/lang/String;") { @Override public Object read() throws IOException { return readObject(); } }; new BoxedPrimitiveClassDesc(Boolean.class, "Z"); new BoxedPrimitiveClassDesc(Byte.class, "B"); new BoxedPrimitiveClassDesc(Short.class, "S"); new BoxedPrimitiveClassDesc(Integer.class, "I"); new BoxedPrimitiveClassDesc(Long.class, "J"); new BoxedPrimitiveClassDesc(Float.class, "F"); new BoxedPrimitiveClassDesc(Double.class, "D"); new InterfaceClassDesc(Number.class); new InterfaceClassDesc(Comparator.class); new NonPrimitiveClassDesc(toSimpleName(Character.class), "value", "C") { @Override public Object readExtra(final Map map) { return map.get("value"); } }; new NonPrimitiveClassDesc(toSimpleName(TreeMap.class), "comparator", "Ljava/util/Comparator;") { // implements serialVersionUID = 919286545866124006L @Override public Map readExtra(final Map map) throws IOException { if (map.size() != 1 || !map.containsKey("comparator")) { throw new IOException("Unexpected comparator"); } map.clear(); // might just as well re-use it expectToken(TC_BLOCKDATA); expectToken(4); int size = read32(); for (int i = 0; i < size; i++) { final String key = (String) readObject(); final Object value = readObject(); map.put(key, value); } expectToken(TC_ENDBLOCKDATA); return map; } }; new NonPrimitiveClassDesc(toSimpleName(ArrayList.class), "size", "I") { // implements serialVersionUID 8683452581122892189L @Override public Object readExtra(final Map map) throws IOException { int size = (int) (long) (Long) map.get("size"); map.clear(); // might just as well re-use it expectToken(TC_BLOCKDATA); expectToken(4); int capacity = read32(); final List list = new ArrayList<>(capacity); for (int i = 0; i < size; i++) { list.add(readObject()); } expectToken(TC_ENDBLOCKDATA); return list; } }; new NonPrimitiveClassDesc("Lnet/java/sezpoz/impl/SerAnnotatedElement;", "isMethod", "Z", "className", "Ljava/lang/String;", "memberName", "Ljava/lang/String;", "values", "Ljava/util/TreeMap;") { @Override public Object readExtra(final Map map) throws IOException { map.put("class", map.get("className")); return map; } }; new NonPrimitiveClassDesc("Lnet/java/sezpoz/impl/SerAnnConst;", "name", "Ljava/lang/String;", "values", "Ljava/util/TreeMap;") { @Override public Object readExtra(final Map map) throws IOException { return map.get("values"); } }; new NonPrimitiveClassDesc("Lnet/java/sezpoz/impl/SerEnumConst;", "enumName", "Ljava/lang/String;", "constName", "Ljava/lang/String;") { @Override public Object readExtra(final Map map) throws IOException { map.put("enum", map.get("enumName")); map.put("value", map.get("constName")); return map; } }; new NonPrimitiveClassDesc("Lnet/java/sezpoz/impl/SerTypeConst;", "name", "Ljava/lang/String;") { @Override public Object readExtra(final Map map) throws IOException { return map.get("name"); } }; } }