/* * Copyright 2003-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.mps.persistence.binary; import jetbrains.mps.extapi.model.SModelData; import jetbrains.mps.smodel.DynamicReference; import jetbrains.mps.smodel.DynamicReference.DynamicReferenceOrigin; import jetbrains.mps.smodel.StaticReference; import jetbrains.mps.util.io.ModelInputStream; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.language.SConcept; import org.jetbrains.mps.openapi.language.SContainmentLink; import org.jetbrains.mps.openapi.language.SProperty; import org.jetbrains.mps.openapi.language.SReferenceLink; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeId; import org.jetbrains.mps.openapi.model.SReference; import java.io.IOException; import java.io.ObjectInputStream; import java.util.ArrayList; import java.util.List; /** * Lightweight, straightforward binary serialization of individual {@link org.jetbrains.mps.openapi.model.SNode}s. * @see jetbrains.mps.persistence.binary.BareNodeWriter * @author Artem Tikhomirov */ public class BareNodeReader { protected final SModelReference myModelReference; protected final ModelInputStream myIn; public BareNodeReader(@NotNull SModelReference modelReference, @NotNull ModelInputStream is) { myModelReference = modelReference; myIn = is; } /** * Read nodes and register them as roots into supplied ModelData */ public void readNodesInto(SModelData modelData) throws IOException { for (SNode r : readChildren(null)) { modelData.addRootNode(r); } } /** * Read nodes and register them as children into supplied parent SNode (if any) * @return list of nodes read */ public List<SNode> readChildren(@Nullable SNode parent) throws IOException { int size = myIn.readInt(); ArrayList<SNode> rv = new ArrayList<SNode>(size); while (size-- > 0) { rv.add(readNode(parent)); } return rv; } /** * Read a single node and register it with optional parent */ public final SNode readNode(@Nullable SNode parent) throws IOException { SNode node = instantiate(parent); if (myIn.readByte() != '{') { throw new IOException("bad stream, no '{'"); } readProperties(node); readUserObjects(node); readReferences(node); readChildren(node); if (myIn.readByte() != '}') { throw new IOException("bad stream, no '}'"); } return node; } protected SNode instantiate(@Nullable SNode parent) throws IOException { SConcept c = myIn.readConcept(); SNodeId nid = myIn.readNodeId(); SContainmentLink link = myIn.readContainmentLink(); jetbrains.mps.smodel.SNode node = new jetbrains.mps.smodel.SNode(c, nid); if (parent != null && link != null) { parent.addChild(link, node); } return node; } protected void readProperties(SNode node) throws IOException { int properties = myIn.readShort(); while (properties-- > 0) { final SProperty property = myIn.readProperty(); final String value = myIn.readString(); node.setProperty(property, value); } } protected void readReferences(SNode node) throws IOException { int refs = myIn.readShort(); while (refs-- > 0) { readReference(myIn.readReferenceLink(), node); } } protected SReference readReference(SReferenceLink sref, SNode node) throws IOException { int kind = myIn.readByte(); assert kind >= 1 && kind <= 3; SNodeId targetNodeId = kind == 1 ? myIn.readNodeId() : null; DynamicReferenceOrigin origin = kind == 3 ? new DynamicReferenceOrigin(myIn.readNodePointer(), myIn.readNodePointer()) : null; int targetModelKind = myIn.readByte(); assert targetModelKind == BareNodeWriter.REF_OTHER_MODEL || targetModelKind == BareNodeWriter.REF_THIS_MODEL; final SModelReference modelRef; if (targetModelKind == BareNodeWriter.REF_OTHER_MODEL) { modelRef = myIn.readModelReference(); externalNodeReferenceRead(modelRef, targetNodeId); } else { modelRef = myModelReference; localNodeReferenceRead(targetNodeId); } String resolveInfo = myIn.readString(); if (kind == 1) { SReference reference = new StaticReference( sref, node, modelRef, targetNodeId, resolveInfo); node.setReference(reference.getLink(), reference); return reference; } else if (kind == 2 || kind == 3) { DynamicReference reference = new DynamicReference( sref, node, modelRef, resolveInfo); if (origin != null) { reference.setOrigin(origin); } node.setReference(sref, reference); return reference; } else { throw new IOException("unknown reference type"); } } protected void localNodeReferenceRead(SNodeId nodeId) { // no-op, left for subclasses to override } protected void externalNodeReferenceRead(SModelReference targetModel, SNodeId nodeId) { // no-op, left for subclasses to override } protected void readUserObjects(SNode node) throws IOException { int userObjectCount = myIn.readShort(); for (int i = 0; i < userObjectCount; i += 2) { Object key = readUserObject(); Object value = readUserObject(); if (key != null && value != null) { node.putUserObject(key, value); } } } private Object readUserObject() throws IOException { int id = myIn.readByte(); switch (id) { case BareNodeWriter.USER_NODE_POINTER: return myIn.readNodePointer(); case BareNodeWriter.USER_STRING: return myIn.readString(); case BareNodeWriter.USER_NULL: return null; case BareNodeWriter.USER_NODE_ID: return myIn.readNodeId(); case BareNodeWriter.USER_MODEL_ID: return myIn.readModelID(); case BareNodeWriter.USER_MODEL_REFERENCE: return myIn.readModelReference(); case BareNodeWriter.USER_SERIALIZABLE: ObjectInputStream stream = new ObjectInputStream(myIn); try { return stream.readObject(); } catch (ClassNotFoundException ignore) { // class could be loaded by the other classloader return null; } } throw new IOException("Could not read user object"); } }