/*
* Copyright 2003-2014 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.smodel.DynamicReference;
import jetbrains.mps.smodel.DynamicReference.DynamicReferenceOrigin;
import jetbrains.mps.smodel.StaticReference;
import jetbrains.mps.util.IterableUtil;
import jetbrains.mps.util.io.ModelOutputStream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.language.SProperty;
import org.jetbrains.mps.openapi.model.SModelId;
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.SNodeReference;
import org.jetbrains.mps.openapi.model.SReference;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
/**
* Minimalistic binary persistence, straightforward, to serialize nodes individually.
* Extracted as part of binary persistence refactoring, with the latter becoming full-fledged
* persistence mechanism
* @author Artem Tikhomirov
*/
public class BareNodeWriter {
static final int USER_NODE_POINTER = 0;
static final int USER_STRING = 1;
static final int USER_NULL = 2;
static final int USER_NODE_ID = 3;
static final int USER_MODEL_ID = 4;
static final int USER_MODEL_REFERENCE = 5;
static final int USER_SERIALIZABLE = 6;
static final byte REF_THIS_MODEL = 17;
static final byte REF_OTHER_MODEL = 18;
protected final SModelReference myModelReference;
protected final ModelOutputStream myOut;
public BareNodeWriter(@NotNull SModelReference modelReference, @NotNull ModelOutputStream os) {
myModelReference = modelReference;
myOut = os;
}
public void writeNodes(Collection<SNode> nodes) throws IOException {
myOut.writeInt(nodes.size());
for (SNode n : nodes) {
writeNode(n);
}
}
public final void writeNode(SNode node) throws IOException {
writeNodePrim(node);
myOut.writeByte('{');
writeProperties(node);
writeUserObjects(node);
writeReferences(node);
writeNodes(IterableUtil.asCollection(node.getChildren()));
myOut.writeByte('}');
}
protected void writeNodePrim(SNode node) throws IOException {
myOut.writeConcept(node.getConcept());
myOut.writeNodeId(node.getNodeId());
myOut.writeContainmentLink(node.getContainmentLink());
}
protected void writeReferences(SNode node) throws IOException {
final Collection<SReference> refs = IterableUtil.asCollection(node.getReferences());
myOut.writeShort(refs.size());
for (SReference r : refs) {
myOut.writeReferenceLink(r.getLink());
writeReferenceTarget(r);
}
}
protected void writeReferenceTarget(SReference reference) throws IOException {
SModelReference targetModelReference = reference.getTargetSModelReference();
if (reference instanceof StaticReference) {
myOut.writeByte(1);
myOut.writeNodeId(reference.getTargetNodeId());
} else if (reference instanceof DynamicReference) {
DynamicReferenceOrigin origin = ((DynamicReference) reference).getOrigin();
if (origin != null) {
myOut.writeByte(3);
myOut.writeNodePointer(origin.getTemplate());
myOut.writeNodePointer(origin.getInputNode());
} else {
myOut.writeByte(2);
}
} else {
throw new IOException("cannot store reference: " + reference.toString());
}
if (targetModelReference != null && targetModelReference.equals(myModelReference)) {
myOut.writeByte(REF_THIS_MODEL);
} else {
myOut.writeByte(REF_OTHER_MODEL);
myOut.writeModelReference(targetModelReference);
}
// XXX Revisit ModelWriter9.genResolveInfo uses different approach to obtain resolveInfo
myOut.writeString(((jetbrains.mps.smodel.SReference) reference).getResolveInfo());
}
protected void writeProperties(SNode node) throws IOException {
final Collection<SProperty> props = IterableUtil.asCollection(node.getProperties());
myOut.writeShort(props.size());
for (SProperty p : props) {
myOut.writeProperty(p);
myOut.writeString(node.getProperty(p));
}
}
protected void writeUserObjects(SNode node) throws IOException {
final ArrayList<Object> knownUserObject = new ArrayList<Object>();
for (Object key : node.getUserObjectKeys()) {
Object value = node.getUserObject(key);
if (isKnownUserObject(key) && isKnownUserObject(value)) {
knownUserObject.add(key);
knownUserObject.add(value);
}
}
myOut.writeShort(knownUserObject.size());
for (int i = 0; i < knownUserObject.size(); i += 2) {
writeUserObject(knownUserObject.get(i));
writeUserObject(knownUserObject.get(i + 1));
}
}
protected void writeUserObject(Object object) throws IOException {
if (object == null) {
myOut.writeByte(USER_NULL);
} else if (object instanceof SNodeReference) {
myOut.writeByte(USER_NODE_POINTER);
myOut.writeNodePointer((SNodeReference) object);
} else if (object instanceof String) {
myOut.writeByte(USER_STRING);
myOut.writeString((String) object);
} else if (object instanceof SNodeId) {
myOut.writeByte(USER_NODE_ID);
myOut.writeNodeId((SNodeId) object);
} else if (object instanceof SModelId) {
myOut.writeByte(USER_MODEL_ID);
myOut.writeModelID((SModelId) object);
} else if (object instanceof SModelReference) {
myOut.writeByte(USER_MODEL_REFERENCE);
myOut.writeModelReference((SModelReference) object);
} else if (object instanceof Serializable) {
// two-phase write
try {
ObjectOutputStream dummy = new ObjectOutputStream(new NullOutputStream());
dummy.writeObject(object);
} catch (NotSerializableException ignore) {
object = null;
}
myOut.writeByte(USER_SERIALIZABLE);
ObjectOutputStream objectOutput = new ObjectOutputStream(myOut);
objectOutput.writeObject(object);
}
}
protected boolean isKnownUserObject(Object object) {
return object == null
|| object instanceof SNodeReference
|| object instanceof Serializable
|| object instanceof SNodeId
|| object instanceof SModelId
|| object instanceof SModelReference;
}
}