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

com.io7m.smfj.format.binary.SMFFormatBinary Maven / Gradle / Ivy

The newest version!
/*
 * Copyright © 2016  http://io7m.com
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package com.io7m.smfj.format.binary;

import com.io7m.jaffirm.core.Invariants;
import com.io7m.jaffirm.core.Preconditions;
import com.io7m.jnull.NullCheck;
import com.io7m.smfj.core.SMFAttributeName;
import com.io7m.smfj.core.SMFFormatDescription;
import com.io7m.smfj.core.SMFFormatVersion;
import com.io7m.smfj.parser.api.SMFParserEventsType;
import com.io7m.smfj.parser.api.SMFParserProviderType;
import com.io7m.smfj.parser.api.SMFParserRandomAccessType;
import com.io7m.smfj.parser.api.SMFParserSequentialType;
import com.io7m.smfj.serializer.api.SMFSerializerProviderType;
import com.io7m.smfj.serializer.api.SMFSerializerType;
import javaslang.collection.SortedSet;
import javaslang.collection.TreeSet;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

/**
 * The implementation of the binary format.
 */

@Component
public final class SMFFormatBinary implements SMFParserProviderType,
  SMFSerializerProviderType
{
  private static final Logger LOG;
  private static final SMFFormatDescription FORMAT;
  private static final byte[] MAGIC_NUMBER;
  private static final SortedSet SUPPORTED_VERSIONS;

  static {
    LOG = LoggerFactory.getLogger(SMFFormatBinary.class);

    {
      final SMFFormatDescription.Builder b = SMFFormatDescription.builder();
      b.setDescription("A binary encoding of SMF data");
      b.setMimeType("application/vnd.io7m.smf");
      b.setName("smfb");
      b.setRandomAccess(true);
      b.setSuffix("smfb");
      FORMAT = b.build();
    }

    {
      SUPPORTED_VERSIONS = TreeSet.of(SMFFormatVersion.of(1, 0));
    }

    {
      MAGIC_NUMBER = new byte[8];
      MAGIC_NUMBER[0] = (byte) (0x89 & 0xff);
      MAGIC_NUMBER[1] = (byte) 'S';
      MAGIC_NUMBER[2] = (byte) 'M';
      MAGIC_NUMBER[3] = (byte) 'F';
      MAGIC_NUMBER[4] = (byte) 0x0D;
      MAGIC_NUMBER[5] = (byte) 0x0A;
      MAGIC_NUMBER[6] = (byte) 0x1A;
      MAGIC_NUMBER[7] = (byte) 0x0A;
    }
  }

  /**
   * Construct a binary format provider.
   */

  public SMFFormatBinary()
  {

  }

  /**
   * @return The magic bytes at the start of every SMF file
   */

  public static byte[] magicNumber()
  {
    return MAGIC_NUMBER.clone();
  }

  private static boolean magicNumberIsValid(
    final byte[] data)
  {
    Preconditions.checkPreconditionI(
      data.length,
      data.length == 8,
      len -> String.format("Length %d must be 8 bytes", Integer.valueOf(len)));
    return Arrays.equals(MAGIC_NUMBER, data);
  }

  @Override
  public SMFFormatDescription parserFormat()
  {
    return FORMAT;
  }

  @Override
  public SortedSet parserSupportedVersions()
  {
    return SUPPORTED_VERSIONS;
  }

  @Override
  public SMFParserSequentialType parserCreateSequential(
    final SMFParserEventsType in_events,
    final Path in_path,
    final InputStream in_stream)
  {
    NullCheck.notNull(in_events, "Events");
    NullCheck.notNull(in_path, "Path");
    NullCheck.notNull(in_stream, "Stream");

    return new ParserSequential(
      in_events,
      SMFBDataStreamReader.create(in_path, in_stream),
      new AtomicReference<>(SMFBAbstractParserSequential.ParserState.STATE_INITIAL));
  }

  @Override
  public SMFParserRandomAccessType parserCreateRandomAccess(
    final SMFParserEventsType in_events,
    final Path in_path,
    final FileChannel in_channel)
    throws UnsupportedOperationException
  {
    NullCheck.notNull(in_events, "Events");
    NullCheck.notNull(in_path, "Path");
    NullCheck.notNull(in_channel, "Channel");

    return new ParserRandom(
      in_events,
      new SMFBDataFileChannelReader(in_path, in_channel),
      new AtomicReference<>(SMFBAbstractParserRandomAccess.ParserState.STATE_INITIAL));
  }

  @Override
  public SMFFormatDescription serializerFormat()
  {
    return FORMAT;
  }

  @Override
  public SortedSet serializerSupportedVersions()
  {
    return TreeSet.of(SMFFormatVersion.of(1, 0));
  }

  @Override
  public SMFSerializerType serializerCreate(
    final SMFFormatVersion version,
    final Path path,
    final OutputStream stream)
    throws UnsupportedOperationException
  {
    if (SUPPORTED_VERSIONS.contains(version)) {
      return new SMFBV1Serializer(version, path, stream);
    }

    throw new UnsupportedOperationException(
      String.format(
        "Version %d.%d is not supported",
        Integer.valueOf(version.major()),
        Integer.valueOf(version.minor())));
  }

  private static final class ParserRandom extends SMFBAbstractParserRandomAccess
  {
    private Optional parser;

    ParserRandom(
      final SMFParserEventsType in_events,
      final SMFBDataFileChannelReader in_reader,
      final AtomicReference in_state)
    {
      super(in_events, in_reader, in_state);
      this.parser = Optional.empty();
    }

    @Override
    protected Logger log()
    {
      return LOG;
    }

    @Override
    public void parseHeader()
    {
      switch (super.state.get()) {
        case STATE_INITIAL: {
          super.events.onStart();

          try {
            this.parser = this.parseMagicNumberAndVersion();

            if (this.parser.isPresent()) {
              this.parser.get().parseHeader();
            } else {
              Invariants.checkInvariant(
                super.state.get(),
                super.state.get() == ParserState.STATE_FAILED,
                s -> String.format(
                  "State %s must be %s", s, ParserState.STATE_FAILED));
            }

          } catch (final Exception e) {
            super.fail(e.getMessage(), Optional.of(e));
          }
          break;
        }
        case STATE_PARSED_HEADER: {
          throw new IllegalStateException("Header has already been parsed");
        }
        case STATE_FAILED: {
          throw new IllegalStateException("Parser has already failed");
        }
      }
    }

    @Override
    public void parseAttributeData(
      final SMFAttributeName name)
    {
      switch (super.state.get()) {
        case STATE_INITIAL: {
          throw new IllegalStateException("Header has not yet been parsed");
        }
        case STATE_PARSED_HEADER: {
          Preconditions.checkPrecondition(
            this.parser.isPresent(), "Parser must be present");

          this.parser.get().parseAttributeData(name);
          break;
        }
        case STATE_FAILED: {
          throw new IllegalStateException("Parser has already failed");
        }
      }
    }

    @Override
    public void parseTriangles()
    {
      switch (super.state.get()) {
        case STATE_INITIAL: {
          throw new IllegalStateException("Header has not yet been parsed");
        }
        case STATE_PARSED_HEADER: {
          Preconditions.checkPrecondition(
            this.parser.isPresent(), "Parser must be present");

          this.parser.get().parseTriangles();
          break;
        }
        case STATE_FAILED: {
          throw new IllegalStateException("Parser has already failed");
        }
      }
    }

    @Override
    public void parseMetadata()
      throws IllegalStateException
    {
      switch (super.state.get()) {
        case STATE_INITIAL: {
          throw new IllegalStateException("Header has not yet been parsed");
        }
        case STATE_PARSED_HEADER: {
          Preconditions.checkPrecondition(
            this.parser.isPresent(), "Parser must be present");

          this.parser.get().parseMetadata();
          break;
        }
        case STATE_FAILED: {
          throw new IllegalStateException("Parser has already failed");
        }
      }
    }

    private Optional parseMagicNumberAndVersion()
    {
      try {
        final byte[] buffer8 = new byte[8];
        super.reader.readBytes(
          Optional.of("magic number"),
          buffer8,
          SMFBV1Offsets.offsetMagicNumber());
        if (magicNumberIsValid(buffer8)) {
          return this.parseVersion();
        }

        super.failExpectedGot(
          "Bad magic number.",
          DatatypeConverter.printHexBinary(MAGIC_NUMBER),
          DatatypeConverter.printHexBinary(buffer8));
        return Optional.empty();
      } catch (final IOException e) {
        super.fail("I/O error: " + e.getMessage(), Optional.of(e));
        return Optional.empty();
      }
    }

    @Override
    public void close()
      throws IOException
    {
      LOG.debug("closing parser");
      super.events.onFinish();
    }

    private Optional parseVersion()
      throws IOException
    {
      final long major = super.reader.readUnsigned32(
        Optional.of("major version"), SMFBV1Offsets.offsetVersionMajor());
      final long minor = super.reader.readUnsigned32(
        Optional.of("minor version"), SMFBV1Offsets.offsetVersionMinor());

      final SMFFormatVersion version =
        SMFFormatVersion.of((int) major, (int) minor);

      super.events.onVersionReceived(version);

      switch ((int) major) {
        case 1: {
          LOG.debug("instantiating parser for 1.*");
          return Optional.of(
            new SMFBV1ParserRandomAccess(
              super.events,
              super.reader,
              super.state));
        }

        default: {
          LOG.debug("no parser for version {}", version);
          super.fail("Unsupported version", Optional.empty());
          return Optional.empty();
        }
      }
    }
  }

  private static final class ParserSequential extends
    SMFBAbstractParserSequential
  {
    private Optional parser;

    ParserSequential(
      final SMFParserEventsType in_events,
      final SMFBDataStreamReaderType in_reader,
      final AtomicReference in_state)
    {
      super(in_events, in_reader, in_state);
      this.parser = Optional.empty();
    }

    @Override
    protected Logger log()
    {
      return LOG;
    }

    private Optional parseMagicNumberAndVersion()
    {
      try {
        final byte[] buffer8 = new byte[8];
        super.reader.readBytes(Optional.of("magic number"), buffer8);
        if (magicNumberIsValid(buffer8)) {
          return this.parseVersion();
        }

        super.failExpectedGot(
          "Bad magic number.",
          DatatypeConverter.printHexBinary(MAGIC_NUMBER),
          DatatypeConverter.printHexBinary(buffer8));
        return Optional.empty();
      } catch (final IOException e) {
        super.fail("I/O error: " + e.getMessage(), Optional.of(e));
        return Optional.empty();
      }
    }

    @Override
    public void close()
      throws IOException
    {
      LOG.debug("closing parser");
      super.events.onFinish();
    }

    private Optional parseVersion()
      throws IOException
    {
      final long major =
        super.reader.readU32(Optional.of("major version"));
      final long minor =
        super.reader.readU32(Optional.of("minor version"));

      final SMFFormatVersion version =
        SMFFormatVersion.of((int) major, (int) minor);

      super.events.onVersionReceived(version);

      switch ((int) major) {
        case 1: {
          LOG.debug("instantiating parser for 1.*");
          return Optional.of(
            new SMFBV1ParserSequential(
              super.events,
              super.reader,
              super.state));
        }

        default: {
          LOG.debug("no parser for version {}", version);
          super.fail("Unsupported version", Optional.empty());
          return Optional.empty();
        }
      }
    }

    @Override
    public void parseHeader()
    {
      switch (super.state.get()) {
        case STATE_INITIAL: {
          super.events.onStart();

          try {
            this.parser = this.parseMagicNumberAndVersion();

            if (this.parser.isPresent()) {
              this.parser.get().parseHeader();
            } else {
              Invariants.checkInvariant(
                super.state.get(),
                super.state.get() == ParserState.STATE_FAILED,
                s -> String.format(
                  "State %s must be %s", s, ParserState.STATE_FAILED));
            }

          } catch (final Exception e) {
            super.fail(e.getMessage(), Optional.of(e));
          }
          break;
        }

        case STATE_PARSED_HEADER: {
          throw new IllegalStateException("Header has already been parsed");
        }
        case STATE_FAILED: {
          throw new IllegalStateException("Parser has already failed");
        }
        case STATE_FINISHED: {
          throw new IllegalStateException("Parser has already finished");
        }
      }
    }

    @Override
    public void parseData()
      throws IllegalStateException
    {
      switch (super.state.get()) {
        case STATE_INITIAL: {
          throw new IllegalStateException("Header has not been parsed");
        }

        case STATE_PARSED_HEADER: {
          if (this.parser.isPresent()) {
            this.parser.get().parseData();
          } else {
            Invariants.checkInvariant(
              super.state.get(),
              super.state.get() == ParserState.STATE_FAILED,
              s -> String.format(
                "State %s must be %s", s, ParserState.STATE_FAILED));
          }
          break;
        }

        case STATE_FAILED: {
          throw new IllegalStateException("Parser has already failed");
        }
        case STATE_FINISHED: {
          throw new IllegalStateException("Parser has already finished");
        }
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy