Package aterm.pure.binary

Source Code of aterm.pure.binary.BinaryWriter$ATermMapping

/*
* Copyright (c) 2002-2007, CWI and INRIA
*
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * 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.
*     * Neither the name of the University of California, Berkeley nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS AND 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.
*/

package aterm.pure.binary;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jjtraveler.VisitFailure;
import aterm.AFun;
import aterm.ATerm;
import aterm.ATermAppl;
import aterm.ATermBlob;
import aterm.ATermFwdVoid;
import aterm.ATermInt;
import aterm.ATermList;
import aterm.ATermLong;
import aterm.ATermPlaceholder;
import aterm.ATermReal;

/**
* Writes the given ATerm to a (streamable) binary format. Supply the constructor of this class with
* a ATerm and keep calling the serialize method until the finished() method returns true.<br />
* <br />
* For example (yes I know this code is crappy, but it's simple):<blockquote><pre>
* ByteBuffer buffer = ByteBuffer.allocate(8192);
BinaryWriter bw = new BinaryWriter(aterm);
while(!bw.isFinished()) {
   buffer.clear();
   bw.serialize(buffer);
   while(buffer.hasRemaining()) channel.write(buffer); // Write the chunk of data to a channel
}
* </pre></blockquote>
*
* @author Arnold Lankamp
*/
public class BinaryWriter extends ATermFwdVoid{
  private final static int ISSHAREDFLAG = 0x00000080;
  private final static int ANNOSFLAG = 0x00000010;

  private final static int ISFUNSHARED = 0x00000040;
  private final static int APPLQUOTED = 0x00000020;

  private final static int STACKSIZE = 256;

  private final static int MINIMUMFREESPACE = 10;

  private final Map<ATerm, Integer> sharedTerms;
  private int currentKey;
  private final Map<AFun, Integer> applSignatures;
  private int sigKey;

  private ATermMapping[] stack;
  private int stackPosition;
  private ATerm currentTerm;
  private int indexInTerm;
  private byte[] tempNameWriteBuffer;

  private ByteBuffer currentBuffer;

  /**
   * Constructor.
   *
   * @param root
   *            The ATerm that needs to be serialized.
   */
  public BinaryWriter(ATerm root){
    super();

    sharedTerms = new HashMap<ATerm, Integer>();
    currentKey = 0;
    applSignatures = new HashMap<AFun, Integer>();
    sigKey = 0;

    stack = new ATermMapping[STACKSIZE];
    stackPosition = 0;

    ATermMapping tm = new ATermMapping();
    tm.term = root;

    stack[stackPosition] = tm;
    currentTerm = root;
   
    indexInTerm = 0;
    tempNameWriteBuffer = null;
  }

  /**
   * Serializes the term from the position where it left of the last time this method was called.
   * Note that the buffer will be flipped before returned.
   *
   * @param buffer
   *            The buffer that will be filled with data.
   * @throws VisitFailure
   *             Never thrown.
   */
  public void serialize(ByteBuffer buffer) throws VisitFailure{
    currentBuffer = buffer;

    while(currentTerm != null){
      if(buffer.remaining() < MINIMUMFREESPACE) break;

      Integer id = sharedTerms.get(currentTerm);
      if(id != null){
        buffer.put((byte) ISSHAREDFLAG);
        writeInt(id.intValue());

        stackPosition--; // Pop the term from the stack, since it's subtree is shared.
      }else{
        visit(currentTerm);
       
        if(currentTerm.getType() == ATerm.LIST) stack[stackPosition].nextPartOfList = (ATermList) currentTerm; // <- for ATermList->next optimizaton.

        // Don't add the term to the shared list until we are completely done with it.
        if(indexInTerm == 0) sharedTerms.put(currentTerm, new Integer(currentKey++));
        else break;
      }

      currentTerm = getNextTerm();
    }

    buffer.flip();
  }

  /**
   * Checks if we are done serializing.
   *
   * @return true when we are done serializing; false otherwise.
   */
  public boolean isFinished(){
    return (currentTerm == null);
  }

  /**
   * Finds the next term we are going to serialize, based on the current state of the stack.
   *
   * @return The next term we are going to serialize.
   */
  private ATerm getNextTerm(){
    ATerm next = null;
   
    // Make sure the stack remains large enough
    ensureStackCapacity();

    while(next == null && stackPosition > -1){
      ATermMapping current = stack[stackPosition];
      ATerm term = current.term;

      if(term.getChildCount() > current.subTermIndex + 1){
        if(term.getType() != ATerm.LIST){
          next = (ATerm) term.getChildAt(++current.subTermIndex);
        }else{
          ATermList nextList = current.nextPartOfList;
          next = nextList.getFirst();
          current.nextPartOfList = nextList.getNext();
         
          current.subTermIndex++;
        }
       
        ATermMapping child = new ATermMapping();
        child.term = next;
        stack[++stackPosition] = child;
      }else if(!current.annosDone && term.hasAnnotations()){
        next = term.getAnnotations();

        ATermMapping annos = new ATermMapping();
        annos.term = next;
        stack[++stackPosition] = annos;

        current.annosDone = true;
      }else{
        stackPosition--;
      }
    }
   
    return next;
  }
 
  /**
   * Resizes the stack when needed. When we're running low on stack space the capacity will be
   * doubled.
   */
  private void ensureStackCapacity(){
    int stackSize = stack.length;
    if(stackPosition + 1 == stackSize){
      ATermMapping[] newStack = new ATermMapping[(stackSize << 1)];
      System.arraycopy(stack, 0, newStack, 0, stack.length);
      stack = newStack;
    }
  }

  /**
   * Returns a header for the given term.
   *
   * @param term
   *            The term we are requesting a header for.
   * @return The constructed header.
   */
  private byte getHeader(ATerm term){
    byte header = (byte) term.getType();
    if(term.hasAnnotations()) header = (byte) (header | ANNOSFLAG);

    return header;
  }

  /**
   * Structure that holds information about the state of the contained term.
   *
   * @author Arnold Lankamp
   */
  private static class ATermMapping{
    public ATerm term;
    public int subTermIndex = -1;
    public boolean annosDone = false;
   
    public ATermList nextPartOfList = null; // This is for a ATermList 'nextTerm' optimalization only.
  }

  /**
   * Serializes the given appl. The function name of the appl can be serialized in chunks.
   *
   * @see aterm.ATermFwdVoid#voidVisitAppl(ATermAppl)
   * @throws VisitFailure
   *             Never thrown.
   */
  public void voidVisitAppl(ATermAppl arg) throws VisitFailure{
    if(indexInTerm == 0){
      byte header = getHeader(arg);

      AFun fun = arg.getAFun();
      Integer key = applSignatures.get(fun);
      if(key == null){
        if(arg.isQuoted()) header = (byte) (header | APPLQUOTED);
        currentBuffer.put(header);

        writeInt(arg.getArity());
       
        String name = fun.getName();
        byte[] nameBytes = name.getBytes();
        int length = nameBytes.length;
        writeInt(length);

        int endIndex = length;
        int remaining = currentBuffer.remaining();
        if(remaining < endIndex) endIndex = remaining;
       
        currentBuffer.put(nameBytes, 0, endIndex);

        if(endIndex != length){
          indexInTerm = endIndex;
          tempNameWriteBuffer = nameBytes;
        }

        applSignatures.put(fun, new Integer(sigKey++));
      }else{
        header = (byte) (header | ISFUNSHARED);
        currentBuffer.put(header);

        writeInt(key.intValue());
      }
    }else{
      int length = tempNameWriteBuffer.length;
     
      int endIndex = length;
      int remaining = currentBuffer.remaining();
      if((indexInTerm + remaining) < endIndex) endIndex = (indexInTerm + remaining);
     
      currentBuffer.put(tempNameWriteBuffer, indexInTerm, (endIndex - indexInTerm));
      indexInTerm = endIndex;

      if(indexInTerm == length){
        indexInTerm = 0;
        tempNameWriteBuffer = null;
      }
    }
  }

  /**
   * Serializes the given blob. A blob can be serialized in chunks.
   *
   * @see aterm.ATermFwdVoid#voidVisitBlob(ATermBlob)
   * @throws VisitFailure
   *             Never thrown.
   */
  public void voidVisitBlob(ATermBlob arg) throws VisitFailure{
    int size = arg.getBlobSize();
    if(indexInTerm == 0) {
      currentBuffer.put(getHeader(arg));

      writeInt(size);
    }

    byte[] blobBytes = arg.getBlobData();

    int bytesToWrite = size - indexInTerm;
    int remaining = currentBuffer.remaining();
    if(remaining < bytesToWrite){
      bytesToWrite = remaining;
    }

    currentBuffer.put(blobBytes, indexInTerm, bytesToWrite);
    indexInTerm += bytesToWrite;

    if(indexInTerm == size) indexInTerm = 0;
  }

  /**
   * Serializes the given int. Ints will always be serialized in one piece.
   *
   * @see aterm.ATermFwdVoid#voidVisitInt(ATermInt)
   * @throws VisitFailure
   *             Never thrown.
   */
  public void voidVisitInt(ATermInt arg) throws VisitFailure{
    currentBuffer.put(getHeader(arg));

    writeInt(arg.getInt());
  }

  /**
   * Serializes the given long.
   *
   * @see aterm.ATermFwdVoid#voidVisitLong(ATermLong)
   * @throws VisitFailure
   *             Never thrown.
   */
  public void voidVisitLong(ATermLong arg) throws VisitFailure{
    currentBuffer.put(getHeader(arg));

    writeLong(arg.getLong());
  }

  /**
   * Serializes the given list. List information will always be serialized in one piece.
   *
   * @see aterm.ATermFwdVoid#voidVisitList(ATermList)
   * @throws VisitFailure
   *             Never thrown.
   */
  public void voidVisitList(ATermList arg) throws VisitFailure{
    byte header = getHeader(arg);
    currentBuffer.put(header);

    writeInt(arg.getLength());
  }

  /**
   * Serializes the given placeholder. Placeholders will always be serialized in one piece.
   *
   * @see aterm.ATermFwdVoid#voidVisitPlaceholder(ATermPlaceholder)
   * @throws VisitFailure
   *             Never thrown.
   */
  public void voidVisitPlaceholder(ATermPlaceholder arg) throws VisitFailure{
    currentBuffer.put(getHeader(arg));

    // Do nothing, serializing its header is enough.
  }

  /**
   * Serializes the given real. Reals will always be serialized in one peice.
   *
   * @see aterm.ATermFwdVoid#voidVisitReal(ATermReal)
   * @throws VisitFailure
   *             Never thrown.
   */
  public void voidVisitReal(ATermReal arg) throws VisitFailure{
    currentBuffer.put(getHeader(arg));

    writeDouble(arg.getReal());
  }

  private final static int SEVENBITS = 0x0000007f;
  private final static int SIGNBIT = 0x00000080;
  private final static int LONGBITS = 8;

  /**
   * Splits the given integer in separate bytes and writes it to the buffer. It will occupy the
   * smallest amount of bytes possible. This is done in the following way: the sign bit will be
   * used to indicate that more bytes coming, if this is set to 0 we know we are done. Since we
   * are mostly writing small values, this will save a considerable amount of space. On the other
   * hand a large number will occupy 5 bytes instead of the regular 4.
   *
   * @param value
   *            The integer that needs to be split and written.
   */
  private void writeInt(int value){
    int intValue = value;
   
    if((intValue & 0xffffff80) == 0){
      currentBuffer.put((byte) (intValue & SEVENBITS));
      return;
    }
    currentBuffer.put((byte) ((intValue & SEVENBITS) | SIGNBIT));
   
    if((intValue & 0xffffc000) == 0){
      currentBuffer.put((byte) ((intValue >>> 7) & SEVENBITS));
      return;
    }
    currentBuffer.put((byte) (((intValue >>> 7) & SEVENBITS) | SIGNBIT));
   
    if((intValue & 0xffe00000) == 0){
      currentBuffer.put((byte) ((intValue >>> 14) & SEVENBITS));
      return;
    }
    currentBuffer.put((byte) (((intValue >>> 14) & SEVENBITS) | SIGNBIT));
   
    if((intValue & 0xf0000000) == 0){
      currentBuffer.put((byte) ((intValue >>> 21) & SEVENBITS));
      return;
    }
    currentBuffer.put((byte) (((intValue >>> 21) & SEVENBITS) | SIGNBIT));
   
    currentBuffer.put((byte) ((intValue >>> 28) & SEVENBITS));
  }

  /**
   * Splits the given double in separate bytes and writes it to the buffer. Doubles will always
   * occupy 8 bytes, since the convertion of a floating point number to a long will always cause
   * the high order bits to be occupied.
   *
   * @param value
   *            The integer that needs to be split and written.
   */
  private void writeDouble(double value){
    long longValue = Double.doubleToLongBits(value);
    writeLong(longValue);
  }

  private void writeLong(long value){
    for(int i = 0; i < LONGBITS; i++) {
      currentBuffer.put((byte) (value >>> (i * 8)));
    }
  }
 
  /**
   * Writes the given aterm to the given file. Blocks of 65536 bytes will be used.
   *
   * @param aTerm
   *             The ATerm that needs to be writen to file.
   * @param file
   *             The file to write to.
   * @throws IOException
   *             Thrown when an error occurs while writing to the given file.
   * @throws VisitFailure
   *             This never happens
   */
  public static void writeTermToSAFFile(ATerm aTerm, File file) throws IOException, VisitFailure{
    BinaryWriter binaryWriter = new BinaryWriter(aTerm);
   
    ByteBuffer byteBuffer = ByteBuffer.allocate(65536);
    ByteBuffer sizeBuffer = ByteBuffer.allocate(2);
   
    FileOutputStream fos = null;
    FileChannel fc = null;
    try{
      fos = new FileOutputStream(file);
      fc = fos.getChannel();
     
      byteBuffer.put((byte) '?');
      byteBuffer.flip();
      fc.write(byteBuffer);
     
      do{
        byteBuffer.clear();
        binaryWriter.serialize(byteBuffer);
       
        int blockSize = byteBuffer.limit();
        sizeBuffer.clear();
        sizeBuffer.put((byte) (blockSize & 0x000000ff));
        sizeBuffer.put((byte) ((blockSize >>> 8) & 0x000000ff));
        sizeBuffer.flip();
       
        fc.write(sizeBuffer);
       
        fc.write(byteBuffer);
      }while(!binaryWriter.isFinished());
    }finally{
      if(fc != null){
        fc.close();
      }
      if(fos != null){
        fos.close();
      }
    }
  }
 
  /**
   * Writes the given aterm to a byte array. The reason this method returns a byte array instead
   * of a Java String is because otherwise the binary data would be corrupted, since it would be
   * automatically encoded in UTF-16 format (which would completely mess everything up). Blocks
   * of 65536 bytes will be used.
   *
   * @param aTerm
   *             The ATerm that needs to be written to a byte array.
   * @return The serialized representation of the given ATerm contained in a byte array.
   * @throws VisitFailure
   *             This never happens
   */
  public static byte[] writeTermToSAFString(ATerm aTerm) throws VisitFailure{
    List<ByteBuffer> buffers = new ArrayList<ByteBuffer>();
    int totalBytesWritten = 0;
   
    BinaryWriter binaryWriter = new BinaryWriter(aTerm);
    do{
      ByteBuffer byteBuffer = ByteBuffer.allocate(65536);
      binaryWriter.serialize(byteBuffer);
     
      buffers.add(byteBuffer);
      totalBytesWritten += byteBuffer.limit() + 2; // Increment by: buffer size + 2 bytes length spec.
    }while(!binaryWriter.isFinished());
   
    byte[] data = new byte[totalBytesWritten];
    int position = 0;
    int numberOfBuffers = buffers.size();
    for(int i = 0; i < numberOfBuffers; i++){
      ByteBuffer buffer = buffers.get(i);
      int blockSize = buffer.limit();
      data[position++] = (byte)(blockSize & 0x000000ff);
      data[position++] = (byte)((blockSize >>> 8) & 0x000000ff);
     
      System.arraycopy(buffer.array(), 0, data, position, blockSize);
      position += blockSize;
    }
   
    return data;
  }
}
TOP

Related Classes of aterm.pure.binary.BinaryWriter$ATermMapping

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.