/** * GRANITE DATA SERVICES * Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S. * * This file is part of the Granite Data Services Platform. * * Granite Data Services is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * Granite Data Services is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA, or see <http://www.gnu.org/licenses/>. */ package org.granite.messaging.jmf; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.granite.messaging.annotations.Include; import org.granite.messaging.jmf.codec.StandardCodec; import org.granite.messaging.reflect.ClassDescriptor; import org.granite.messaging.reflect.NoopWritableProperty; import org.granite.messaging.reflect.Property; import org.granite.messaging.reflect.Reflection; /** * @author Franck WOLFF */ public class JMFDeserializer implements InputContext { /////////////////////////////////////////////////////////////////////////// // Fields protected final List<String> classNames = new ArrayList<String>(256); protected final List<String> strings = new ArrayList<String>(256); protected final List<Object> objects = new ArrayList<Object>(256); protected final Map<String, ClassDescriptor> classDescriptors = new HashMap<String, ClassDescriptor>(256); protected final InputStream inputStream; protected final SharedContext context; protected final CodecRegistry codecRegistry; /////////////////////////////////////////////////////////////////////////// // Initialization public JMFDeserializer(InputStream is, SharedContext context) { this.inputStream = is; this.context = context; this.codecRegistry = context.getCodecRegistry(); this.classNames.addAll(context.getInitialClassNameDictionary()); } /////////////////////////////////////////////////////////////////////////// // ObjectInput implementation public boolean readBoolean() throws IOException { return codecRegistry.getBooleanCodec().decodePrimitive(this); } public byte readByte() throws IOException { return codecRegistry.getByteCodec().decodePrimitive(this); } public int readUnsignedByte() throws IOException { return readByte() & 0xFF; } public short readShort() throws IOException { return codecRegistry.getShortCodec().decodePrimitive(this); } public int readUnsignedShort() throws IOException { return readShort() & 0xFFFF; } public char readChar() throws IOException { return codecRegistry.getCharacterCodec().decodePrimitive(this); } public int readInt() throws IOException { return codecRegistry.getIntegerCodec().decodePrimitive(this); } public long readLong() throws IOException { return codecRegistry.getLongCodec().decodePrimitive(this); } public float readFloat() throws IOException { return codecRegistry.getFloatCodec().decodePrimitive(this); } public double readDouble() throws IOException { return codecRegistry.getDoubleCodec().decodePrimitive(this); } public String readUTF() throws IOException { int parameterizedJmfType = safeRead(); if (parameterizedJmfType == JMF_NULL) return (String)codecRegistry.getNullCodec().decode(this, parameterizedJmfType); return codecRegistry.getStringCodec().decode(this, parameterizedJmfType); } public Object readObject() throws ClassNotFoundException, IOException { int parameterizedJmfType = safeRead(); StandardCodec<?> codec = codecRegistry.getCodec(parameterizedJmfType); if (codec == null) throw new JMFEncodingException("Unsupported JMF type: " + codecRegistry.extractJmfType(parameterizedJmfType)); try { return codec.decode(this, parameterizedJmfType); } catch (InvocationTargetException e) { throw new IOException(e.getTargetException()); } catch (IllegalAccessException e) { throw new IOException(e); } catch (InstantiationException e) { throw new IOException(e); } catch (NoSuchMethodException e) { throw new IOException(e); } } public int available() throws IOException { return inputStream.available(); } public void close() throws IOException { inputStream.close(); } /////////////////////////////////////////////////////////////////////////// // ObjectInput implementation (unsupported, marked at deprecated) @Deprecated public int read() throws IOException { return readByte(); } @Deprecated public int read(byte[] b) throws IOException { try { byte[] bytes = (byte[])readObject(); int count = Math.min(bytes.length, b.length); System.arraycopy(bytes, 0, b, 0, count); return count; } catch (IOException e) { throw e; } catch (Exception e) { throw new IOException("Could not read byte array", e); } } @Deprecated public int read(byte[] b, int off, int len) throws IOException { try { byte[] bytes = (byte[])readObject(); int count = Math.min(bytes.length, len); System.arraycopy(bytes, 0, b, off, count); return count; } catch (IOException e) { throw e; } catch (Exception e) { throw new IOException("Could not read byte array", e); } } @Deprecated public void readFully(byte[] b) throws IOException { try { byte[] bytes = (byte[])readObject(); if (bytes.length < b.length) throw new IOException("Could not fully read byte array of length: " + b.length); System.arraycopy(bytes, 0, b, 0, b.length); } catch (IOException e) { throw e; } catch (Exception e) { throw new IOException("Could not read byte array", e); } } @Deprecated public void readFully(byte[] b, int off, int len) throws IOException { try { byte[] bytes = (byte[])readObject(); if (bytes.length < len) throw new IOException("Could not fully read byte array of length: " + len); System.arraycopy(bytes, 0, b, off, len); } catch (IOException e) { throw e; } catch (Exception e) { throw new IOException("Could not read byte array", e); } } @Deprecated public String readLine() throws IOException { throw new UnsupportedOperationException(); } @Deprecated public int skipBytes(int n) throws IOException { throw new UnsupportedOperationException(); } @Deprecated public long skip(long n) throws IOException { throw new UnsupportedOperationException(); } /////////////////////////////////////////////////////////////////////////// // InputContext implementation public SharedContext getSharedContext() { return context; } public InputStream getInputStream() { return inputStream; } public int safeRead() throws IOException { int b = inputStream.read(); if (b == -1) throw new EOFException(); return b; } @SuppressWarnings("cast") public long safeReadLong() throws IOException { return (long)safeRead(); } public void safeReadFully(byte[] b) throws IOException { safeReadFully(b, 0, b.length); } public void safeReadFully(byte[] b, int off, int len) throws IOException { if (off < 0 || len < 0 || off + len > b.length) throw new IndexOutOfBoundsException("b.length=" + b.length + ", off=" + off + ", len" + len); if (len == 0) return; do { int read = inputStream.read(b, off, len); if (read == -1) throw new EOFException(); off += read; len -= read; } while (len > 0); } public void safeSkip(long n) throws IOException { while (n > 0) { if (inputStream.read() == -1) throw new EOFException(); n--; } } @Override public int addToClassNames(String cn) { int index = classNames.size(); classNames.add(index, cn); return index; } @Override public String getClassName(int index) { return classNames.get(index); } @Override public ClassDescriptor getClassDescriptor(String className) throws ClassNotFoundException { ClassDescriptor desc = classDescriptors.get(className); if (desc == null) { Class<?> cls = context.getReflection().loadClass(className); desc = context.getReflection().getDescriptor(cls); classDescriptors.put(className, desc); } return desc; } @Override public ClassDescriptor getClassDescriptor(Class<?> cls) { ClassDescriptor desc = classDescriptors.get(cls.getName()); if (desc == null) { desc = context.getReflection().getDescriptor(cls); classDescriptors.put(cls.getName(), desc); } return desc; } public int addToStrings(String s) { int index = strings.size(); strings.add(index, s); return index; } public String getString(int index) { return strings.get(index); } public int addToObjects(Object o) { int index = objects.size(); objects.add(index, o); return index; } public Object getObject(int index) { Object o = objects.get(index); if (o instanceof UnresolvedSharedObject) throw new JMFUnresolvedSharedObjectException("Unresolved shared object: " + o); return o; } public int addToUnresolvedObjects(String className) { int index = objects.size(); objects.add(index, new UnresolvedSharedObject(className, index)); return index; } public Object setUnresolvedObject(int index, Object o) { Object uso = objects.set(index, o); if (!(uso instanceof UnresolvedSharedObject)) throw new JMFUnresolvedSharedObjectException("Not an unresolved shared object: " + uso); return uso; } /////////////////////////////////////////////////////////////////////////// // ExtendedObjectInput implementation public Reflection getReflection() { return context.getReflection(); } public String getAlias(String remoteAlias) { return context.getClassName(remoteAlias); } public void readAndSetProperty(Object obj, Property property) throws IOException, ClassNotFoundException, IllegalAccessException, InvocationTargetException { if (property.isAnnotationPresent(Include.class) && !property.isWritable()) property = new NoopWritableProperty(property.getName(), property.getType()); if (property.getType().isPrimitive()) codecRegistry.getPrimitivePropertyCodec(property.getType()).decodePrimitive(this, obj, property); else property.setObject(obj, readObject()); } static class UnresolvedSharedObject { private final String className; private final int index; public UnresolvedSharedObject(String className, int index) { this.className = className; this.index = index; } public String getClassName() { return className; } public int getIndex() { return index; } @Override public String toString() { return UnresolvedSharedObject.class.getName() + " {className=" + className + ", index=" + index + "}"; } } /////////////////////////////////////////////////////////////////////////// // Debug public String toDumpString() { StringBuilder sb = new StringBuilder(getClass().getName()); sb.append(" {\n"); sb.append(" storedClassNames=[\n"); for (int i = 0; i < classNames.size(); i++) sb.append(" ").append(i).append(": \"").append(classNames.get(i)).append("\"\n"); sb.append(" ],\n"); sb.append(" storedStrings=[\n"); for (int i = 0; i < strings.size(); i++) sb.append(" ").append(i).append(": \"").append(strings.get(i)).append("\"\n"); sb.append(" ],\n"); sb.append(" storedObjects=[\n"); for (int i = 0; i < objects.size(); i++) { Object o = objects.get(i); sb.append(" ").append(i).append(": ").append(o.getClass().getName()) .append("@").append(System.identityHashCode(o)).append("\n"); } sb.append(" ],\n"); sb.append("}"); return sb.toString(); } }