<commit_before>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 by Daniel Eichhorn
 * Copyright (c) 2016 by Fabrice Weinberg
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "SSD1306Ui.h"

SSD1306Ui::SSD1306Ui(SSD1306 *display) {
  this->display = display;
}

void SSD1306Ui::init() {
  this->display->init();
}

void SSD1306Ui::setTargetFPS(uint8_t fps){
  float oldInterval = this->updateInterval;
  this->updateInterval = ((float) 1.0 / (float) fps) * 1000;

  // Calculate new ticksPerFrame
  float changeRatio = oldInterval / (float) this->updateInterval;
  this->ticksPerFrame *= changeRatio;
  this->ticksPerTransition *= changeRatio;
}

// -/------ Automatic controll ------\-

void SSD1306Ui::enableAutoTransition(){
  this->autoTransition = true;
}
void SSD1306Ui::disableAutoTransition(){
  this->autoTransition = false;
}
void SSD1306Ui::setAutoTransitionForwards(){
  this->state.frameTransitionDirection = 1;
  this->lastTransitionDirection = 1;
}
void SSD1306Ui::setAutoTransitionBackwards(){
  this->state.frameTransitionDirection = -1;
  this->lastTransitionDirection = -1;
}
void SSD1306Ui::setTimePerFrame(uint16_t time){
  this->ticksPerFrame = (int) ( (float) time / (float) updateInterval);
}
void SSD1306Ui::setTimePerTransition(uint16_t time){
  this->ticksPerTransition = (int) ( (float) time / (float) updateInterval);
}

// -/------ Customize indicator position and style -------\-
void SSD1306Ui::enableIndicator(){
  this->state.isIndicatorDrawen = true;
}

void SSD1306Ui::disableIndicator(){
  this->state.isIndicatorDrawen = false;
}

void SSD1306Ui::setIndicatorPosition(IndicatorPosition pos) {
  this->indicatorPosition = pos;
}
void SSD1306Ui::setIndicatorDirection(IndicatorDirection dir) {
  this->indicatorDirection = dir;
}
void SSD1306Ui::setActiveSymbol(const char* symbol) {
  this->activeSymbol = symbol;
}
void SSD1306Ui::setInactiveSymbol(const char* symbol) {
  this->inactiveSymbol = symbol;
}

// -/----- Frame settings -----\-
void SSD1306Ui::setFrameAnimation(AnimationDirection dir) {
  this->frameAnimationDirection = dir;
}
void SSD1306Ui::setFrames(FrameCallback* frameFunctions, uint8_t frameCount) {
  this->frameCount     = frameCount;
  this->frameFunctions = frameFunctions;
}

// -/----- Overlays ------\-
void SSD1306Ui::setOverlays(OverlayCallback* overlayFunctions, uint8_t overlayCount){
  this->overlayCount     = overlayCount;
  this->overlayFunctions = overlayFunctions;
}

// -/----- Loading Process -----\-

void SSD1306Ui::runLoadingProcess(LoadingStage* stages, uint8_t stagesCount) {
  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->setFont(ArialMT_Plain_10);

  uint8_t progress = 0;
  uint8_t increment = 100 / stagesCount;

  for (uint8_t i = 0; i < stagesCount; i++) {
    display->clear();
    this->loadingDrawFunction(this->display, &stages[i], progress);
    display->display();

    stages[i].callback();

    progress += increment;
    yield();
  }

  display->clear();
  this->loadingDrawFunction(this->display, &stages[stagesCount-1], progress);
  display->display();

  delay(150);
}

// -/----- Manuel control -----\-
void SSD1306Ui::nextFrame() {
  if (this->state.frameState != IN_TRANSITION) {
    this->state.manuelControll = true;
    this->state.frameState = IN_TRANSITION;
    this->state.ticksSinceLastStateSwitch = 0;
    this->lastTransitionDirection = this->state.frameTransitionDirection;
    this->state.frameTransitionDirection = 1;
  }
}
void SSD1306Ui::previousFrame() {
  if (this->state.frameState != IN_TRANSITION) {
    this->state.manuelControll = true;
    this->state.frameState = IN_TRANSITION;
    this->state.ticksSinceLastStateSwitch = 0;
    this->lastTransitionDirection = this->state.frameTransitionDirection;
    this->state.frameTransitionDirection = -1;
  }
}

// -/----- State information -----\-
SSD1306UiState* SSD1306Ui::getUiState(){
  return &this->state;
}

int8_t SSD1306Ui::update(){
  int8_t timeBudget = this->updateInterval - (millis() - this->state.lastUpdate);
  if ( timeBudget <= 0) {
    // Implement frame skipping to ensure time budget is keept
    if (this->autoTransition && this->state.lastUpdate != 0) this->state.ticksSinceLastStateSwitch += ceil(-timeBudget / this->updateInterval);

    this->state.lastUpdate = millis();
    this->tick();
  }
  return timeBudget;
}

void SSD1306Ui::tick() {
  this->state.ticksSinceLastStateSwitch++;

  switch (this->state.frameState) {
    case IN_TRANSITION:
        if (this->state.ticksSinceLastStateSwitch >= this->ticksPerTransition){
          this->state.frameState = FIXED;
          this->state.currentFrame = getNextFrameNumber();
          this->state.ticksSinceLastStateSwitch = 0;
        }
      break;
    case FIXED:
      // Revert manuelControll
      if (this->state.manuelControll) {
        this->state.frameTransitionDirection = this->lastTransitionDirection;
        this->state.manuelControll = false;
      }
      if (this->state.ticksSinceLastStateSwitch >= this->ticksPerFrame){
          if (this->autoTransition){
            this->state.frameState = IN_TRANSITION;
          }
          this->state.ticksSinceLastStateSwitch = 0;
      }
      break;
  }

  this->display->clear();
  this->drawFrame();
  this->drawIndicator();
  this->drawOverlays();
  this->display->display();
}

void SSD1306Ui::drawFrame(){
  switch (this->state.frameState){
     case IN_TRANSITION: {
       float progress = (float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition;
       int16_t x, y, x1, y1;
       switch(this->frameAnimationDirection){
        case SLIDE_LEFT:
          x = -128 * progress;
          y = 0;
          x1 = x + 128;
          y1 = 0;
          break;
        case SLIDE_RIGHT:
          x = 128 * progress;
          y = 0;
          x1 = x - 128;
          y1 = 0;
          break;
        case SLIDE_UP:
          x = 0;
          y = -64 * progress;
          x1 = 0;
          y1 = y + 64;
          break;
        case SLIDE_DOWN:
          x = 0;
          y = 64 * progress;
          x1 = 0;
          y1 = y - 64;
          break;
       }

       // Invert animation if direction is reversed.
       int8_t dir = this->state.frameTransitionDirection >= 0 ? 1 : -1;
       x *= dir; y *= dir; x1 *= dir; y1 *= dir;

       bool drawenCurrentFrame;

       // Prope each frameFunction for the indicator Drawen state
       this->enableIndicator();
       (this->frameFunctions[this->state.currentFrame])(this->display, &this->state, x, y);
       drawenCurrentFrame = this->state.isIndicatorDrawen;

       this->enableIndicator();
       (this->frameFunctions[this->getNextFrameNumber()])(this->display, &this->state, x1, y1);

       // Build up the indicatorDrawState
       if (drawenCurrentFrame && !this->state.isIndicatorDrawen) {
         // Drawen now but not next
         this->indicatorDrawState = 2;
       } else if (!drawenCurrentFrame && this->state.isIndicatorDrawen) {
         // Not drawen now but next
         this->indicatorDrawState = 1;
       } else if (!drawenCurrentFrame && !this->state.isIndicatorDrawen) {
         // Not drawen in both frames
         this->indicatorDrawState = 3;
       }

       // If the indicator isn't draw in the current frame
       // reflect it in state.isIndicatorDrawen
       if (!drawenCurrentFrame) this->state.isIndicatorDrawen = false;

       break;
     }
     case FIXED:
      // Always assume that the indicator is drawn!
      // And set indicatorDrawState to "not known yet"
      this->indicatorDrawState = 0;
      this->enableIndicator();
      (this->frameFunctions[this->state.currentFrame])(this->display, &this->state, 0, 0);
      break;
  }
}

void SSD1306Ui::drawIndicator() {

    // Only draw if the indicator is invisible
    // for both frames or
    // the indiactor is shown and we are IN_TRANSITION
    if (this->indicatorDrawState == 3 || (!this->state.isIndicatorDrawen && this->state.frameState != IN_TRANSITION)) {
      return;
    }

    uint8_t posOfHighlightFrame;
    float indicatorFadeProgress = 0;

    // if the indicator needs to be slided in we want to
    // highlight the next frame in the transition
    uint8_t frameToHighlight = this->indicatorDrawState == 1 ? this->getNextFrameNumber() : this->state.currentFrame;

    // Calculate the frame that needs to be highlighted
    // based on the Direction the indiactor is drawn
    switch (this->indicatorDirection){
      case LEFT_RIGHT:
        posOfHighlightFrame = frameToHighlight;
        break;
      case RIGHT_LEFT:
        posOfHighlightFrame = this->frameCount - frameToHighlight;
        break;
    }

    switch (this->indicatorDrawState) {
      case 1: // Indicator was not drawn in this frame but will be in next
        // Slide IN
        indicatorFadeProgress = 1 - ((float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition);
        break;
      case 2: // Indicator was drawn in this frame but not in next
        // Slide OUT
        indicatorFadeProgress = ((float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition);
        break;
    }

    uint16_t frameStartPos = (12 * frameCount / 2);
    const char *image;
    uint16_t x,y;
    for (byte i = 0; i < this->frameCount; i++) {

      switch (this->indicatorPosition){
        case TOP:
          y = 0 - (8 * indicatorFadeProgress);
          x = 64 - frameStartPos + 12 * i;
          break;
        case BOTTOM:
          y = 56 + (8 * indicatorFadeProgress);
          x = 64 - frameStartPos + 12 * i;
          break;
        case RIGHT:
          x = 120 + (8 * indicatorFadeProgress);
          y = 32 - frameStartPos + 12 * i;
          break;
        case LEFT:
          x = 0 - (8 * indicatorFadeProgress);
          y = 32 - frameStartPos + 12 * i;
          break;
      }

      if (posOfHighlightFrame == i) {
         image = this->activeSymbol;
      } else {
         image = this->inactiveSymbol;
      }

      this->display->drawFastImage(x, y, 8, 8, image);
    }
}

void SSD1306Ui::drawOverlays() {
 for (uint8_t i=0;i<this->overlayCount;i++){
    (this->overlayFunctions[i])(this->display, &this->state);
 }
}

uint8_t SSD1306Ui::getNextFrameNumber(){
  int8_t nextFrame = (this->state.currentFrame + this->state.frameTransitionDirection) % this->frameCount;
  if (nextFrame < 0){
    nextFrame = this->frameCount + nextFrame;
  }
  return nextFrame;
}
<commit_msg>Quick hack to set currentFrame at 0 when calling setFrames. Allows multilevel navigation.<commit_after>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 by Daniel Eichhorn
 * Copyright (c) 2016 by Fabrice Weinberg
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "SSD1306Ui.h"

SSD1306Ui::SSD1306Ui(SSD1306 *display) {
  this->display = display;
}

void SSD1306Ui::init() {
  this->display->init();
}

void SSD1306Ui::setTargetFPS(uint8_t fps){
  float oldInterval = this->updateInterval;
  this->updateInterval = ((float) 1.0 / (float) fps) * 1000;

  // Calculate new ticksPerFrame
  float changeRatio = oldInterval / (float) this->updateInterval;
  this->ticksPerFrame *= changeRatio;
  this->ticksPerTransition *= changeRatio;
}

// -/------ Automatic controll ------\-

void SSD1306Ui::enableAutoTransition(){
  this->autoTransition = true;
}
void SSD1306Ui::disableAutoTransition(){
  this->autoTransition = false;
}
void SSD1306Ui::setAutoTransitionForwards(){
  this->state.frameTransitionDirection = 1;
  this->lastTransitionDirection = 1;
}
void SSD1306Ui::setAutoTransitionBackwards(){
  this->state.frameTransitionDirection = -1;
  this->lastTransitionDirection = -1;
}
void SSD1306Ui::setTimePerFrame(uint16_t time){
  this->ticksPerFrame = (int) ( (float) time / (float) updateInterval);
}
void SSD1306Ui::setTimePerTransition(uint16_t time){
  this->ticksPerTransition = (int) ( (float) time / (float) updateInterval);
}

// -/------ Customize indicator position and style -------\-
void SSD1306Ui::enableIndicator(){
  this->state.isIndicatorDrawen = true;
}

void SSD1306Ui::disableIndicator(){
  this->state.isIndicatorDrawen = false;
}

void SSD1306Ui::setIndicatorPosition(IndicatorPosition pos) {
  this->indicatorPosition = pos;
}
void SSD1306Ui::setIndicatorDirection(IndicatorDirection dir) {
  this->indicatorDirection = dir;
}
void SSD1306Ui::setActiveSymbol(const char* symbol) {
  this->activeSymbol = symbol;
}
void SSD1306Ui::setInactiveSymbol(const char* symbol) {
  this->inactiveSymbol = symbol;
}

// -/----- Frame settings -----\-
void SSD1306Ui::setFrameAnimation(AnimationDirection dir) {
  this->frameAnimationDirection = dir;
}
void SSD1306Ui::setFrames(FrameCallback* frameFunctions, uint8_t frameCount) {
  this->frameCount     = frameCount;
  this->frameFunctions = frameFunctions;
  this->state.currentFrame = 0;
}

// -/----- Overlays ------\-
void SSD1306Ui::setOverlays(OverlayCallback* overlayFunctions, uint8_t overlayCount){
  this->overlayCount     = overlayCount;
  this->overlayFunctions = overlayFunctions;
}

// -/----- Loading Process -----\-

void SSD1306Ui::runLoadingProcess(LoadingStage* stages, uint8_t stagesCount) {
  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->setFont(ArialMT_Plain_10);

  uint8_t progress = 0;
  uint8_t increment = 100 / stagesCount;

  for (uint8_t i = 0; i < stagesCount; i++) {
    display->clear();
    this->loadingDrawFunction(this->display, &stages[i], progress);
    display->display();

    stages[i].callback();

    progress += increment;
    yield();
  }

  display->clear();
  this->loadingDrawFunction(this->display, &stages[stagesCount-1], progress);
  display->display();

  delay(150);
}

// -/----- Manuel control -----\-
void SSD1306Ui::nextFrame() {
  if (this->state.frameState != IN_TRANSITION) {
    this->state.manuelControll = true;
    this->state.frameState = IN_TRANSITION;
    this->state.ticksSinceLastStateSwitch = 0;
    this->lastTransitionDirection = this->state.frameTransitionDirection;
    this->state.frameTransitionDirection = 1;
  }
}
void SSD1306Ui::previousFrame() {
  if (this->state.frameState != IN_TRANSITION) {
    this->state.manuelControll = true;
    this->state.frameState = IN_TRANSITION;
    this->state.ticksSinceLastStateSwitch = 0;
    this->lastTransitionDirection = this->state.frameTransitionDirection;
    this->state.frameTransitionDirection = -1;
  }
}

// -/----- State information -----\-
SSD1306UiState* SSD1306Ui::getUiState(){
  return &this->state;
}

int8_t SSD1306Ui::update(){
  int8_t timeBudget = this->updateInterval - (millis() - this->state.lastUpdate);
  if ( timeBudget <= 0) {
    // Implement frame skipping to ensure time budget is keept
    if (this->autoTransition && this->state.lastUpdate != 0) this->state.ticksSinceLastStateSwitch += ceil(-timeBudget / this->updateInterval);

    this->state.lastUpdate = millis();
    this->tick();
  }
  return timeBudget;
}

void SSD1306Ui::tick() {
  this->state.ticksSinceLastStateSwitch++;

  switch (this->state.frameState) {
    case IN_TRANSITION:
        if (this->state.ticksSinceLastStateSwitch >= this->ticksPerTransition){
          this->state.frameState = FIXED;
          this->state.currentFrame = getNextFrameNumber();
          this->state.ticksSinceLastStateSwitch = 0;
        }
      break;
    case FIXED:
      // Revert manuelControll
      if (this->state.manuelControll) {
        this->state.frameTransitionDirection = this->lastTransitionDirection;
        this->state.manuelControll = false;
      }
      if (this->state.ticksSinceLastStateSwitch >= this->ticksPerFrame){
          if (this->autoTransition){
            this->state.frameState = IN_TRANSITION;
          }
          this->state.ticksSinceLastStateSwitch = 0;
      }
      break;
  }

  this->display->clear();
  this->drawFrame();
  this->drawIndicator();
  this->drawOverlays();
  this->display->display();
}

void SSD1306Ui::drawFrame(){
  switch (this->state.frameState){
     case IN_TRANSITION: {
       float progress = (float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition;
       int16_t x, y, x1, y1;
       switch(this->frameAnimationDirection){
        case SLIDE_LEFT:
          x = -128 * progress;
          y = 0;
          x1 = x + 128;
          y1 = 0;
          break;
        case SLIDE_RIGHT:
          x = 128 * progress;
          y = 0;
          x1 = x - 128;
          y1 = 0;
          break;
        case SLIDE_UP:
          x = 0;
          y = -64 * progress;
          x1 = 0;
          y1 = y + 64;
          break;
        case SLIDE_DOWN:
          x = 0;
          y = 64 * progress;
          x1 = 0;
          y1 = y - 64;
          break;
       }

       // Invert animation if direction is reversed.
       int8_t dir = this->state.frameTransitionDirection >= 0 ? 1 : -1;
       x *= dir; y *= dir; x1 *= dir; y1 *= dir;

       bool drawenCurrentFrame;

       // Prope each frameFunction for the indicator Drawen state
       this->enableIndicator();
       (this->frameFunctions[this->state.currentFrame])(this->display, &this->state, x, y);
       drawenCurrentFrame = this->state.isIndicatorDrawen;

       this->enableIndicator();
       (this->frameFunctions[this->getNextFrameNumber()])(this->display, &this->state, x1, y1);

       // Build up the indicatorDrawState
       if (drawenCurrentFrame && !this->state.isIndicatorDrawen) {
         // Drawen now but not next
         this->indicatorDrawState = 2;
       } else if (!drawenCurrentFrame && this->state.isIndicatorDrawen) {
         // Not drawen now but next
         this->indicatorDrawState = 1;
       } else if (!drawenCurrentFrame && !this->state.isIndicatorDrawen) {
         // Not drawen in both frames
         this->indicatorDrawState = 3;
       }

       // If the indicator isn't draw in the current frame
       // reflect it in state.isIndicatorDrawen
       if (!drawenCurrentFrame) this->state.isIndicatorDrawen = false;

       break;
     }
     case FIXED:
      // Always assume that the indicator is drawn!
      // And set indicatorDrawState to "not known yet"
      this->indicatorDrawState = 0;
      this->enableIndicator();
      (this->frameFunctions[this->state.currentFrame])(this->display, &this->state, 0, 0);
      break;
  }
}

void SSD1306Ui::drawIndicator() {

    // Only draw if the indicator is invisible
    // for both frames or
    // the indiactor is shown and we are IN_TRANSITION
    if (this->indicatorDrawState == 3 || (!this->state.isIndicatorDrawen && this->state.frameState != IN_TRANSITION)) {
      return;
    }

    uint8_t posOfHighlightFrame;
    float indicatorFadeProgress = 0;

    // if the indicator needs to be slided in we want to
    // highlight the next frame in the transition
    uint8_t frameToHighlight = this->indicatorDrawState == 1 ? this->getNextFrameNumber() : this->state.currentFrame;

    // Calculate the frame that needs to be highlighted
    // based on the Direction the indiactor is drawn
    switch (this->indicatorDirection){
      case LEFT_RIGHT:
        posOfHighlightFrame = frameToHighlight;
        break;
      case RIGHT_LEFT:
        posOfHighlightFrame = this->frameCount - frameToHighlight;
        break;
    }

    switch (this->indicatorDrawState) {
      case 1: // Indicator was not drawn in this frame but will be in next
        // Slide IN
        indicatorFadeProgress = 1 - ((float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition);
        break;
      case 2: // Indicator was drawn in this frame but not in next
        // Slide OUT
        indicatorFadeProgress = ((float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition);
        break;
    }

    uint16_t frameStartPos = (12 * frameCount / 2);
    const char *image;
    uint16_t x,y;
    for (byte i = 0; i < this->frameCount; i++) {

      switch (this->indicatorPosition){
        case TOP:
          y = 0 - (8 * indicatorFadeProgress);
          x = 64 - frameStartPos + 12 * i;
          break;
        case BOTTOM:
          y = 56 + (8 * indicatorFadeProgress);
          x = 64 - frameStartPos + 12 * i;
          break;
        case RIGHT:
          x = 120 + (8 * indicatorFadeProgress);
          y = 32 - frameStartPos + 12 * i;
          break;
        case LEFT:
          x = 0 - (8 * indicatorFadeProgress);
          y = 32 - frameStartPos + 12 * i;
          break;
      }

      if (posOfHighlightFrame == i) {
         image = this->activeSymbol;
      } else {
         image = this->inactiveSymbol;
      }

      this->display->drawFastImage(x, y, 8, 8, image);
    }
}

void SSD1306Ui::drawOverlays() {
 for (uint8_t i=0;i<this->overlayCount;i++){
    (this->overlayFunctions[i])(this->display, &this->state);
 }
}

uint8_t SSD1306Ui::getNextFrameNumber(){
  int8_t nextFrame = (this->state.currentFrame + this->state.frameTransitionDirection) % this->frameCount;
  if (nextFrame < 0){
    nextFrame = this->frameCount + nextFrame;
  }
  return nextFrame;
}
<|endoftext|>


<commit_before>/*
 * Copyright(c) Sophist Solutions, Inc. 1990-2017.  All rights reserved
 */
#include "../../../StroikaPreComp.h"

#include "../../../Characters/FloatConversion.h"
#include "../../../Characters/Format.h"
#include "../../../Characters/String2Int.h"
#include "../../../Characters/String_Constant.h"
#include "../../../Streams/TextReader.h"
#include "../../BadFormatException.h"

#include "Reader.h"

using namespace Stroika::Foundation;
using namespace Stroika::Foundation::DataExchange;

using Characters::Character;
using Memory::Byte;
using Memory::Optional;
using Characters::String_Constant;

// Comment this in to turn on aggressive noisy DbgTrace in this module
//#define   USE_NOISY_TRACE_IN_THIS_MODULE_       1

namespace {

    /*
     * Parse strategy:
     *      o   Pre-pass to map all input to UNICODE - and then just handle a sequence of unicode strings
     *      o   Classic resursive decent parser.
     *      o   Inline lexical analysis (cuz very simple)
     */

    enum ReadState_ {
        eNeutral_,
        eInObject_,
        eInArray_,
        eInNumber_,
        eInString_,
    };

    /*
     * Utilities to treat string iterator/end ptr as a 'stream pointer' - and get next char
     */
    inline wchar_t NextChar_ (const Streams::InputStream<Character>& in)
    {
        Require (not in.IsAtEOF ());
        return in.Read ()->As<wchar_t> ();
    }

    VariantValue Reader_value_ (const Streams::InputStream<Character>& in);

    // throw if bad hex digit
    unsigned int HexChar2Num_ (char c)
    {
        if ('0' <= c and c <= '9') {
            return c - '0';
        }
        if ('A' <= c and c <= 'F') {
            return (c - 'A') + 10;
        }
        if ('a' <= c and c <= 'f') {
            return (c - 'a') + 10;
        }
        Execution::Throw (BadFormatException (String_Constant{L"JSON: bad hex digit after \\u"}));
    }

    // 'in' is positioned to the start of string, and we read, leaving in possitioned just after end of string
    VariantValue Reader_String_ (const Streams::InputStream<Character>& in)
    {
        Require (in != nullptr);
        Require (not in.IsAtEOF ());
        wchar_t c = NextChar_ (in);
        if (c != '\"') {
            Execution::Throw (BadFormatException (String_Constant{L"JSON: Expected quoted string"}));
        }
        // accumulate chars, and check for close-quote
        wstring result;
        while (true) {
            if (in.IsAtEOF ()) {
                Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected EOF reading string (looking for close quote)"}));
            }
            c = NextChar_ (in);
            if (c == '\"') {
                return VariantValue (result);
            }
            else if (c == '\\') {
                // quoted character read...
                if (in.IsAtEOF ()) {
                    Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected EOF reading string (looking for close quote)"}));
                }
                c = NextChar_ (in);
                switch (c) {
                    case 'b':
                        c = '\b';
                        break;
                    case 'f':
                        c = '\f';
                        break;
                    case 'n':
                        c = '\n';
                        break;
                    case 'r':
                        c = '\r';
                        break;
                    case 't':
                        c = '\t';
                        break;
                    case 'u': {
                        // Not sure this is right -- But I hope so ... -- LGP 2012-11-29
                        wchar_t newC = '\0';
                        for (int n = 0; n < 4; ++n) {
                            if (in.IsAtEOF ()) {
                                Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected EOF reading string (looking for close quote)"}));
                            }
                            newC += HexChar2Num_ (static_cast<char> (NextChar_ (in)));
                            if (n != 3) {
                                newC <<= 4;
                            }
                        }
                        c = newC;
                    } break;
                    default: {
                        // if we have \N for any unrecognized N, just treat it as N
                    }
                }
            }
            Containers::ReserveSpeedTweekAdd1 (result);
            result += c; // must handle other character quoting (besides \u which was preflighted)
        }
    }

    // 'in' is positioned to the start of number, and we read, leaving in positioned just after end of number
    VariantValue Reader_Number_ (const Streams::InputStream<Character>& in)
    {
        Require (in != nullptr);
        Require (not in.IsAtEOF ());

        bool containsDot = false;
        // ACCUMULATE STRING, and then call builtin number parsing functions...
        // This accumulation is NOT as restrictive as it could be - but should accept all valid numbers
        wstring tmp;
        while (not in.IsAtEOF ()) {
            wchar_t c = NextChar_ (in);
            if (iswdigit (c) or c == '.' or c == 'e' or c == 'E' or c == '+' or c == '-') {
                Containers::ReserveSpeedTweekAdd1 (tmp);
                tmp += c;
                if (c == '.') {
                    containsDot = true;
                }
            }
            else {
                // any other character signals end of number (not a syntax error)
                // but if we read anything at all, backup - don't consume next character - not part of number
                if (!tmp.empty ()) {
                    in.Seek (Streams::Whence::eFromCurrent, -1);
                }
                break;
            }
        }
        // if got not valid characters, THATS not a valid # - so error
        if (tmp.empty ()) {
            Execution::Throw (BadFormatException (String_Constant{L"JSON: no valid number found"}));
        }
        if (containsDot) {
            return VariantValue (Characters::String2Float<long double> (tmp));
        }
        else {
            // if no - use unsigned since has wider range (if no -)
            return Characters::String (tmp).LTrim ().StartsWith (String_Constant{L"-"}) ? VariantValue (Characters::String2Int<long long int> (tmp)) : VariantValue (Characters::String2Int<unsigned long long int> (tmp));
        }
    }

    // NOTE: THIS STARTS SEEKED JUST PAST OPENING '{'
    VariantValue Reader_Object_ (const Streams::InputStream<Character>& in)
    {
        Require (in != nullptr);
        Require (not in.IsAtEOF ());
        Mapping<String, VariantValue> result;

        // accumulate elements, and check for close-array
        enum LookingFor { eName,
                          eValue,
                          eColon,
                          eComma };
        LookingFor lf = eName;

        Optional<String> curName;
        while (true) {
            Optional<Character> oNextChar = in.Read ();
            if (oNextChar.IsMissing ()) {
                in.Seek (Streams::Whence::eFromCurrent, -1);
                Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected EOF reading string (looking for '}')"}));
            }
            wchar_t nextChar = oNextChar->As<wchar_t> ();
            if (nextChar == '}') {
                if (lf == eName or lf == eComma) {
                    // skip char
                    return VariantValue (result);
                }
                else {
                    in.Seek (Streams::Whence::eFromCurrent, -1);
                    Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected '}' reading object"}));
                }
            }
            else if (iswspace (nextChar)) {
                // skip char
            }
            else if (nextChar == ',') {
                if (lf == eComma) {
                    // skip char
                    lf = eName; // next elt
                }
                else {
                    in.Seek (Streams::Whence::eFromCurrent, -1);
                    Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected ',' reading object"}));
                }
            }
            else if (nextChar == ':') {
                if (lf == eColon) {
                    // skip char
                    lf = eValue; // next elt
                }
                else {
                    in.Seek (Streams::Whence::eFromCurrent, -1);
                    Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected ':' reading object"}));
                }
            }
            else {
                in.Seek (Streams::Whence::eFromCurrent, -1);
                if (lf == eName) {
                    curName = Reader_String_ (in).As<wstring> ();
                    lf      = eColon;
                }
                else if (lf == eValue) {
                    Assert (curName.IsPresent ());
                    result.Add (*curName, Reader_value_ (in));
                    curName.clear ();
                    lf = eComma;
                }
                else {
                    Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected character looking for colon or comma reading object"}));
                }
            }
        }
    }

    // NOTE - called with OPENING '[' already read
    VariantValue Reader_Array_ (const Streams::InputStream<Characters::Character>& in)
    {
        Require (in != nullptr);
        Require (not in.IsAtEOF ());
        vector<VariantValue> result;

        // accumulate elements, and check for close-array
        bool lookingForElt = true;
        while (true) {
            if (in.IsAtEOF ()) {
                Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected EOF reading string (looking for ']')"}));
            }
            if (in.Peek () == ']') {
                if (lookingForElt) {
                    // allow ending ',' - harmless - could  be more aggressive - but if so - careful of zero-sized array special case
                }
                NextChar_ (in); // skip char
                return VariantValue (result);
            }
            else if (in.Peek () == ',') {
                if (lookingForElt) {
                    Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected second ',' in reading array"}));
                }
                else {
                    lookingForElt = true;
                }
                NextChar_ (in); // skip char
            }
            else if (iswspace (in.Peek ()->As<wchar_t> ())) {
                NextChar_ (in); // skip char
            }
            else {
                // not looking at whitespace, in midst of array, and array not terminated, so better be looking at a value
                if (lookingForElt) {
                    Containers::ReserveSpeedTweekAdd1 (result);
                    result.push_back (Reader_value_ (in));
                    lookingForElt = false;
                }
                else {
                    Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected character (missing ',' ?) in reading array"}));
                }
            }
        }
    }

    VariantValue Reader_SpecialToken_ (wchar_t initialChar, const Streams::InputStream<Characters::Character>& in)
    {
        Require (in != nullptr);
        Streams::SeekOffsetType savedPos = in.GetOffset ();
        switch (initialChar) {
            case 'f': {
                Character buf[4];
                if (in.ReadAll (begin (buf), end (buf)) == 4 and
                    buf[0] == 'a' and
                    buf[1] == 'l' and
                    buf[2] == 's' and
                    buf[3] == 'e') {
                    return VariantValue{false};
                }
            } break;
            case 't': {
                Character buf[3];
                if (in.ReadAll (begin (buf), end (buf)) == 3 and
                    buf[0] == 'r' and
                    buf[1] == 'u' and
                    buf[2] == 'e') {
                    return VariantValue{true};
                }
            } break;
            case 'n': {
                Character buf[3];
                if (in.ReadAll (begin (buf), end (buf)) == 3 and
                    buf[0] == 'u' and
                    buf[1] == 'l' and
                    buf[2] == 'l') {
                    return VariantValue{};
                }
            } break;
        }
        in.Seek (savedPos - 1); // because handed initial char, and seeked past it
        Execution::Throw (BadFormatException (String_Constant{L"JSON: Unrecognized token"}));
    }

    VariantValue Reader_value_ (const Streams::InputStream<Characters::Character>& in)
    {
        // Skip initial whitespace, and look for any value:
        //      string
        //      number
        //      object
        //      array
        //      true
        //      false
        //      null
        for (Optional<Character> oc = in.Read (); oc; oc = in.Read ()) {
            switch (oc->As<wchar_t> ()) {
                case '\"':
                    in.Seek (Streams::Whence::eFromCurrent, -1);
                    return Reader_String_ (in);

                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                case '-':
                    in.Seek (Streams::Whence::eFromCurrent, -1);
                    return Reader_Number_ (in);

                case '{':
                    return Reader_Object_ (in);
                case '[':
                    return Reader_Array_ (in);

                case 't':
                case 'f':
                case 'n':
                    return Reader_SpecialToken_ (oc->As<wchar_t> (), in);

                default: {
                    if (iswspace (oc->As<wchar_t> ())) {
                        // ignore
                    }
                    else {
                        Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected character looking for start of value"}));
                    }
                }
            }
        }
        // if we get here - nothing found
        Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected EOF looking for value"}));
    }
}

/*
 ********************************************************************************
 ************************* Variant::JSON::Reader ********************************
 ********************************************************************************
 */
class Variant::JSON::Reader::Rep_ : public Variant::Reader::_IRep {
public:
    virtual _SharedPtrIRep Clone () const override
    {
        return make_shared<Rep_> (); // no instance data
    }
    virtual String GetDefaultFileSuffix () const override
    {
        return String_Constant{L".json"};
    }
    virtual VariantValue Read (const Streams::InputStream<Byte>& in) override
    {
        constexpr bool kSeekable_{true};
        return Read (Streams::TextReader (in, kSeekable_));
    }
    virtual VariantValue Read (const Streams::InputStream<Characters::Character>& in) override
    {
#if USE_NOISY_TRACE_IN_THIS_MODULE_
        Debug::TraceContextBumper ctx ("DataExchange::JSON::Reader::Rep_::Read");
#endif
        Require (in.IsSeekable ());
        return Reader_value_ (in);
    }
};
Variant::JSON::Reader::Reader ()
    : inherited (make_shared<Rep_> ())
{
}
<commit_msg>JSON Parser: use StringBuilder isntead of wstring (seemed to make no diff)<commit_after>/*
 * Copyright(c) Sophist Solutions, Inc. 1990-2017.  All rights reserved
 */
#include "../../../StroikaPreComp.h"

#include "../../../Characters/FloatConversion.h"
#include "../../../Characters/Format.h"
#include "../../../Characters/String2Int.h"
#include "../../../Characters/StringBuilder.h"
#include "../../../Characters/String_Constant.h"
#include "../../../Streams/TextReader.h"
#include "../../BadFormatException.h"

#include "Reader.h"

using namespace Stroika::Foundation;
using namespace Stroika::Foundation::DataExchange;

using Characters::Character;
using Characters::StringBuilder;
using Memory::Byte;
using Memory::Optional;
using Characters::String_Constant;

// Comment this in to turn on aggressive noisy DbgTrace in this module
//#define   USE_NOISY_TRACE_IN_THIS_MODULE_       1

namespace {

    /*
     * Parse strategy:
     *      o   Pre-pass to map all input to UNICODE - and then just handle a sequence of unicode strings
     *      o   Classic resursive decent parser.
     *      o   Inline lexical analysis (cuz very simple)
     */

    enum ReadState_ {
        eNeutral_,
        eInObject_,
        eInArray_,
        eInNumber_,
        eInString_,
    };

    /*
     * Utilities to treat string iterator/end ptr as a 'stream pointer' - and get next char
     */
    inline wchar_t NextChar_ (const Streams::InputStream<Character>& in)
    {
        Require (not in.IsAtEOF ());
        return in.Read ()->As<wchar_t> ();
    }

    VariantValue Reader_value_ (const Streams::InputStream<Character>& in);

    // throw if bad hex digit
    unsigned int HexChar2Num_ (char c)
    {
        if ('0' <= c and c <= '9') {
            return c - '0';
        }
        if ('A' <= c and c <= 'F') {
            return (c - 'A') + 10;
        }
        if ('a' <= c and c <= 'f') {
            return (c - 'a') + 10;
        }
        Execution::Throw (BadFormatException (String_Constant{L"JSON: bad hex digit after \\u"}));
    }

    // 'in' is positioned to the start of string, and we read, leaving in possitioned just after end of string
    VariantValue Reader_String_ (const Streams::InputStream<Character>& in)
    {
        Require (in != nullptr);
        Require (not in.IsAtEOF ());
        wchar_t c = NextChar_ (in);
        if (c != '\"') {
            Execution::Throw (BadFormatException (String_Constant{L"JSON: Expected quoted string"}));
        }
        // accumulate chars, and check for close-quote
        StringBuilder result;
        while (true) {
            if (in.IsAtEOF ()) {
                Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected EOF reading string (looking for close quote)"}));
            }
            c = NextChar_ (in);
            if (c == '\"') {
                return VariantValue (result.str ());
            }
            else if (c == '\\') {
                // quoted character read...
                if (in.IsAtEOF ()) {
                    Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected EOF reading string (looking for close quote)"}));
                }
                c = NextChar_ (in);
                switch (c) {
                    case 'b':
                        c = '\b';
                        break;
                    case 'f':
                        c = '\f';
                        break;
                    case 'n':
                        c = '\n';
                        break;
                    case 'r':
                        c = '\r';
                        break;
                    case 't':
                        c = '\t';
                        break;
                    case 'u': {
                        // Not sure this is right -- But I hope so ... -- LGP 2012-11-29
                        wchar_t newC = '\0';
                        for (int n = 0; n < 4; ++n) {
                            if (in.IsAtEOF ()) {
                                Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected EOF reading string (looking for close quote)"}));
                            }
                            newC += HexChar2Num_ (static_cast<char> (NextChar_ (in)));
                            if (n != 3) {
                                newC <<= 4;
                            }
                        }
                        c = newC;
                    } break;
                    default: {
                        // if we have \N for any unrecognized N, just treat it as N
                    }
                }
            }
            result += c; // must handle other character quoting (besides \u which was preflighted)
        }
    }

    // 'in' is positioned to the start of number, and we read, leaving in positioned just after end of number
	static constexpr Character kDash_{ '-' };
	VariantValue Reader_Number_ (const Streams::InputStream<Character>& in)
    {
        Require (in != nullptr);
        Require (not in.IsAtEOF ());

        bool containsDot = false;
        // ACCUMULATE STRING, and then call builtin number parsing functions...
        // This accumulation is NOT as restrictive as it could be - but should accept all valid numbers
        StringBuilder tmp;
        while (not in.IsAtEOF ()) {
            wchar_t c = NextChar_ (in);
            if (iswdigit (c) or c == '.' or c == 'e' or c == 'E' or c == '+' or c == '-') {
                tmp += c;
                if (c == '.') {
                    containsDot = true;
                }
            }
            else {
                // any other character signals end of number (not a syntax error)
                // but if we read anything at all, backup - don't consume next character - not part of number
                if (!tmp.empty ()) {
                    in.Seek (Streams::Whence::eFromCurrent, -1);
                }
                break;
            }
        }
        // if got not valid characters, THATS not a valid # - so error
        if (tmp.empty ()) {
            Execution::Throw (BadFormatException (String_Constant{L"JSON: no valid number found"}));
        }
        if (containsDot) {
            return VariantValue (Characters::String2Float<long double> (tmp.str ()));
        }
        else {
            // if no - use unsigned since has wider range (if no -)
            String                     t = tmp.str ();
            return t.LTrim ().StartsWith (kDash_) ? VariantValue (Characters::String2Int<long long int> (t)) : VariantValue (Characters::String2Int<unsigned long long int> (t));
        }
    }

    // NOTE: THIS STARTS SEEKED JUST PAST OPENING '{'
    VariantValue Reader_Object_ (const Streams::InputStream<Character>& in)
    {
        Require (in != nullptr);
        Require (not in.IsAtEOF ());
        Mapping<String, VariantValue> result;

        // accumulate elements, and check for close-array
        enum LookingFor { eName,
                          eValue,
                          eColon,
                          eComma };
        LookingFor lf = eName;

        Optional<String> curName;
        while (true) {
            Optional<Character> oNextChar = in.Read ();
            if (oNextChar.IsMissing ()) {
                in.Seek (Streams::Whence::eFromCurrent, -1);
                Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected EOF reading string (looking for '}')"}));
            }
            wchar_t nextChar = oNextChar->As<wchar_t> ();
            if (nextChar == '}') {
                if (lf == eName or lf == eComma) {
                    // skip char
                    return VariantValue (result);
                }
                else {
                    in.Seek (Streams::Whence::eFromCurrent, -1);
                    Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected '}' reading object"}));
                }
            }
            else if (iswspace (nextChar)) {
                // skip char
            }
            else if (nextChar == ',') {
                if (lf == eComma) {
                    // skip char
                    lf = eName; // next elt
                }
                else {
                    in.Seek (Streams::Whence::eFromCurrent, -1);
                    Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected ',' reading object"}));
                }
            }
            else if (nextChar == ':') {
                if (lf == eColon) {
                    // skip char
                    lf = eValue; // next elt
                }
                else {
                    in.Seek (Streams::Whence::eFromCurrent, -1);
                    Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected ':' reading object"}));
                }
            }
            else {
                in.Seek (Streams::Whence::eFromCurrent, -1);
                if (lf == eName) {
                    curName = Reader_String_ (in).As<wstring> ();
                    lf      = eColon;
                }
                else if (lf == eValue) {
                    Assert (curName.IsPresent ());
                    result.Add (*curName, Reader_value_ (in));
                    curName.clear ();
                    lf = eComma;
                }
                else {
                    Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected character looking for colon or comma reading object"}));
                }
            }
        }
    }

    // NOTE - called with OPENING '[' already read
    VariantValue Reader_Array_ (const Streams::InputStream<Characters::Character>& in)
    {
        Require (in != nullptr);
        Require (not in.IsAtEOF ());
        vector<VariantValue> result;

        // accumulate elements, and check for close-array
        bool lookingForElt = true;
        while (true) {
            if (in.IsAtEOF ()) {
                Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected EOF reading string (looking for ']')"}));
            }
            if (in.Peek () == ']') {
                if (lookingForElt) {
                    // allow ending ',' - harmless - could  be more aggressive - but if so - careful of zero-sized array special case
                }
                NextChar_ (in); // skip char
                return VariantValue (result);
            }
            else if (in.Peek () == ',') {
                if (lookingForElt) {
                    Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected second ',' in reading array"}));
                }
                else {
                    lookingForElt = true;
                }
                NextChar_ (in); // skip char
            }
            else if (iswspace (in.Peek ()->As<wchar_t> ())) {
                NextChar_ (in); // skip char
            }
            else {
                // not looking at whitespace, in midst of array, and array not terminated, so better be looking at a value
                if (lookingForElt) {
                    Containers::ReserveSpeedTweekAdd1 (result);
                    result.push_back (Reader_value_ (in));
                    lookingForElt = false;
                }
                else {
                    Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected character (missing ',' ?) in reading array"}));
                }
            }
        }
    }

    VariantValue Reader_SpecialToken_ (wchar_t initialChar, const Streams::InputStream<Characters::Character>& in)
    {
        Require (in != nullptr);
        Streams::SeekOffsetType savedPos = in.GetOffset ();
        switch (initialChar) {
            case 'f': {
                Character buf[4];
                if (in.ReadAll (begin (buf), end (buf)) == 4 and
                    buf[0] == 'a' and
                    buf[1] == 'l' and
                    buf[2] == 's' and
                    buf[3] == 'e') {
                    return VariantValue{false};
                }
            } break;
            case 't': {
                Character buf[3];
                if (in.ReadAll (begin (buf), end (buf)) == 3 and
                    buf[0] == 'r' and
                    buf[1] == 'u' and
                    buf[2] == 'e') {
                    return VariantValue{true};
                }
            } break;
            case 'n': {
                Character buf[3];
                if (in.ReadAll (begin (buf), end (buf)) == 3 and
                    buf[0] == 'u' and
                    buf[1] == 'l' and
                    buf[2] == 'l') {
                    return VariantValue{};
                }
            } break;
        }
        in.Seek (savedPos - 1); // because handed initial char, and seeked past it
        Execution::Throw (BadFormatException (String_Constant{L"JSON: Unrecognized token"}));
    }

    VariantValue Reader_value_ (const Streams::InputStream<Characters::Character>& in)
    {
        // Skip initial whitespace, and look for any value:
        //      string
        //      number
        //      object
        //      array
        //      true
        //      false
        //      null
        for (Optional<Character> oc = in.Read (); oc; oc = in.Read ()) {
            switch (oc->As<wchar_t> ()) {
                case '\"':
                    in.Seek (Streams::Whence::eFromCurrent, -1);
                    return Reader_String_ (in);

                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                case '-':
                    in.Seek (Streams::Whence::eFromCurrent, -1);
                    return Reader_Number_ (in);

                case '{':
                    return Reader_Object_ (in);
                case '[':
                    return Reader_Array_ (in);

                case 't':
                case 'f':
                case 'n':
                    return Reader_SpecialToken_ (oc->As<wchar_t> (), in);

                default: {
                    if (iswspace (oc->As<wchar_t> ())) {
                        // ignore
                    }
                    else {
                        Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected character looking for start of value"}));
                    }
                }
            }
        }
        // if we get here - nothing found
        Execution::Throw (BadFormatException (String_Constant{L"JSON: Unexpected EOF looking for value"}));
    }
}

/*
 ********************************************************************************
 ************************* Variant::JSON::Reader ********************************
 ********************************************************************************
 */
class Variant::JSON::Reader::Rep_ : public Variant::Reader::_IRep {
public:
    virtual _SharedPtrIRep Clone () const override
    {
        return make_shared<Rep_> (); // no instance data
    }
    virtual String GetDefaultFileSuffix () const override
    {
        return String_Constant{L".json"};
    }
    virtual VariantValue Read (const Streams::InputStream<Byte>& in) override
    {
        constexpr bool kSeekable_{true};
        return Read (Streams::TextReader (in, kSeekable_));
    }
    virtual VariantValue Read (const Streams::InputStream<Characters::Character>& in) override
    {
#if USE_NOISY_TRACE_IN_THIS_MODULE_
        Debug::TraceContextBumper ctx ("DataExchange::JSON::Reader::Rep_::Read");
#endif
        Require (in.IsSeekable ());
        return Reader_value_ (in);
    }
};
Variant::JSON::Reader::Reader ()
    : inherited (make_shared<Rep_> ())
{
}
<|endoftext|>


<commit_before>// This file is a part of the IncludeOS unikernel - www.includeos.org
//
// Copyright 2017 IncludeOS AS, Oslo, Norway
//
// 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.
/**
 * Master thesis
 * by Alf-Andre Walla 2016-2017
 *
**/
#include "liveupdate.hpp"

#include <cassert>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <unistd.h>
#include <util/elf.h>
#include "storage.hpp"
#include <kernel/os.hpp>
#include <hw/devices.hpp>

//#define LPRINT(x, ...) printf(x, ##__VA_ARGS__);
#define LPRINT(x, ...) /** x **/

static const int SECT_SIZE   = 512;
static const int ELF_MINIMUM = 164;

extern "C"
void solo5_exec(const char*, size_t);
static void* HOTSWAP_AREA = (void*) 0x8000;
extern "C" void  hotswap(const char*, int, char*, uintptr_t, void*);
extern "C" char  __hotswap_length;
extern "C" void  hotswap64(char*, const char*, int, uintptr_t, void*);
extern uint32_t  hotswap64_len;
extern "C" void* __os_store_soft_reset(const void*, size_t);
// kernel area
extern char _ELF_START_;
extern char _end;
// heap area
extern char* heap_begin;
extern char* heap_end;
// turn this off to reduce liveupdate times at the cost of extra checks
bool LIVEUPDATE_PERFORM_SANITY_CHECKS = true;

using namespace liu;

static size_t update_store_data(void* location, const buffer_t*);

// serialization callbacks
static std::unordered_map<std::string, LiveUpdate::storage_func> storage_callbacks;

void LiveUpdate::register_serialization_callback(std::string key, storage_func callback)
{
  auto it = storage_callbacks.find(key);
  if (it == storage_callbacks.end())
  {
    storage_callbacks.emplace(std::piecewise_construct,
              std::forward_as_tuple(std::move(key)),
              std::forward_as_tuple(std::move(callback)));
  }
  else {
    throw std::runtime_error("Storage key '" + key + "' already used");
  }
}

template <typename Class>
inline bool validate_header(const Class* hdr)
{
    return hdr->e_ident[0] == 0x7F &&
           hdr->e_ident[1] == 'E'  &&
           hdr->e_ident[2] == 'L'  &&
           hdr->e_ident[3] == 'F';
}

void LiveUpdate::begin(buffer_t  blob)
{
  void* location = OS::liveupdate_storage_area();
  LPRINT("LiveUpdate::begin(%p, %p:%d, ...)\n", location, blob.data(), (int) blob.size());
  // 1. turn off interrupts
  asm volatile("cli");

  // use area provided to us directly, which we will assume
  // is far enough into heap to not get overwritten by hotswap.
  // even then, it's still guaranteed to work: the copy mechanism
  // is implemented in hotswap.cpp and copies forwards. the
  // blobs are separated by at least one old kernel size and
  // some early heap allocations, which is at least 1mb, while
  // the copy mechanism just copies single bytes.
  const char* update_area  = blob.data();
  char* storage_area = (char*) location;

  // validate not overwriting heap, kernel area and other things
  if (storage_area < (char*) 0x200) {
    throw std::runtime_error("LiveUpdate storage area is (probably) a null pointer");
  }
  if (storage_area >= &_ELF_START_ && storage_area < &_end) {
    throw std::runtime_error("LiveUpdate storage area is inside kernel area");
  }
  if (storage_area >= heap_begin && storage_area < heap_end) {
    throw std::runtime_error("LiveUpdate storage area is inside the heap area");
  }
  if (storage_area >= (char*) OS::heap_max()) {
    throw std::runtime_error("LiveUpdate storage area is outside physical memory");
  }
  if (storage_area >= (char*) OS::heap_max() - 0x10000) {
    throw std::runtime_error("LiveUpdate storage area needs at least 64kb memory");
  }

  // search for ELF header
  LPRINT("* Looking for ELF header at %p\n", update_area);
  const char* binary  = &update_area[0];
  const auto* hdr = (const Elf32_Ehdr*) binary;
  if (!validate_header<Elf32_Ehdr>(hdr))
  {
    /// try again with 1 sector offset (skip bootloader)
    binary   = &update_area[SECT_SIZE];
    hdr      = (const Elf32_Ehdr*) binary;

    if (!validate_header<Elf32_Ehdr>(hdr))
    {
      /// failed to find elf header at sector 0 and 1
      /// simply return
      throw std::runtime_error("Could not find any ELF header in blob");
    }
  }
  LPRINT("* Found ELF header\n");

  size_t    expected_total = 0;
  uintptr_t start_offset = 0;

  const char* bin_data  = nullptr;
  int         bin_len   = 0;
  char*       phys_base = nullptr;

  if (hdr->e_ident[EI_CLASS] == ELFCLASS32)
  {
    /// note: this assumes section headers are at the end
    expected_total =
        hdr->e_shnum * hdr->e_shentsize +
        hdr->e_shoff;
    /// program entry point
    start_offset = hdr->e_entry;
    // get offsets for the new service from program header
    auto* phdr = (Elf32_Phdr*) &binary[hdr->e_phoff];
    bin_data  = &binary[phdr->p_offset];
    bin_len   = phdr->p_filesz;
    phys_base = (char*) (uintptr_t) phdr->p_paddr;
  }
  else {
    auto* ehdr = (Elf64_Ehdr*) hdr;
    /// note: this assumes section headers are at the end
    expected_total =
        ehdr->e_shnum * ehdr->e_shentsize +
        ehdr->e_shoff;
    /// program entry point
    start_offset = ehdr->e_entry;
    // get offsets for the new service from program header
    auto* phdr = (Elf64_Phdr*) &binary[ehdr->e_phoff];
    bin_data  = &binary[phdr->p_offset];
    bin_len   = phdr->p_filesz;
    phys_base = (char*) phdr->p_paddr;
  }

  if (blob.size() < expected_total || expected_total < ELF_MINIMUM)
  {
    fprintf(stderr,
        "*** There was a mismatch between blob length and expected ELF file size:\n");
    fprintf(stderr,
        "EXPECTED: %u byte\n",  (uint32_t) expected_total);
    fprintf(stderr,
        "ACTUAL:   %u bytes\n", (uint32_t) blob.size());
    throw std::runtime_error("ELF file was incomplete");
  }
  LPRINT("* Validated ELF header\n");

  // _start() entry point
  LPRINT("* _start is located at %#x\n", start_offset);

  // save ourselves if function passed
  update_store_data(storage_area, &blob);

  // 2. flush all devices with flush() interface
  hw::Devices::flush_all();
  // 3. deactivate all PCI devices and mask all MSI-X vectors
  // NOTE: there are some nasty side effects from calling this
  //hw::Devices::deactivate_all();

  // store soft-resetting stuff
#ifdef PLATFORM_x86_solo5
  void* sr_data = nullptr;
#else
  extern const std::pair<const char*, size_t> get_rollback_location();
  const auto rollback = get_rollback_location();
  void* sr_data = __os_store_soft_reset(rollback.first, rollback.second);
#endif

  // get offsets for the new service from program header
  if (bin_data == nullptr ||
      phys_base == nullptr || bin_len <= 64) {
    throw std::runtime_error("ELF program header malformed");
  }

  //char* phys_base = (char*) (start_offset & 0xffff0000);
  LPRINT("* Physical base address is %p...\n", phys_base);

  // replace ourselves and reset by jumping to _start
  LPRINT("* Replacing self with %d bytes and jumping to %#x\n", bin_len, start_offset);

#ifdef PLATFORM_x86_solo5
  solo5_exec(blob.data(), blob.size());
  throw std::runtime_error("solo5_exec returned");
#else
# ifdef ARCH_i686
    // copy hotswapping function to sweet spot
    memcpy(HOTSWAP_AREA, (void*) &hotswap, &__hotswap_length - (char*) &hotswap);
    /// the end
    ((decltype(&hotswap)) HOTSWAP_AREA)(bin_data, bin_len, phys_base, start_offset, sr_data);
# elif defined(ARCH_x86_64)
    // copy hotswapping function to sweet spot
    memcpy(HOTSWAP_AREA, (void*) &hotswap64, hotswap64_len);
    /// the end
    ((decltype(&hotswap64)) HOTSWAP_AREA)(phys_base, bin_data, bin_len, start_offset, sr_data);
# else
#   error "Unimplemented architecture"
# endif
#endif
}
void LiveUpdate::restore_environment()
{
  // enable interrupts again
  asm volatile("sti");
}
buffer_t LiveUpdate::store()
{
  char* location = (char*) OS::liveupdate_storage_area();
  size_t size = update_store_data(location, nullptr);
  return buffer_t(location, location + size);
}

size_t LiveUpdate::stored_data_length()
{
  return stored_data_length(OS::liveupdate_storage_area());
}
size_t LiveUpdate::stored_data_length(void* location)
{
  auto* storage = (storage_header*) location;

  if (LIVEUPDATE_PERFORM_SANITY_CHECKS)
  {
    /// sanity check
    if (storage->validate() == false)
        throw std::runtime_error("Failed sanity check on LiveUpdate storage area");
  }

  /// return length of the whole area
  return storage->total_bytes();
}

size_t update_store_data(void* location, const buffer_t* blob)
{
  // create storage header in the fixed location
  new (location) storage_header();
  auto* storage = (storage_header*) location;

  Storage wrapper(*storage);
  /// callback for storing stuff, if provided
  for (const auto& pair : storage_callbacks)
  {
    // create partition
    int p = storage->create_partition(pair.first);
    // run serialization process
    pair.second(wrapper, blob);
    // add end for partition
    storage->finish_partition(p);
  }

  /// finalize
  storage->finalize();

  /// return length (and perform sanity check)
  return LiveUpdate::stored_data_length(location);
}

/// struct Storage

void Storage::put_marker(uid id)
{
  hdr.add_marker(id);
}
void Storage::add_int(uid id, int value)
{
  hdr.add_int(id, value);
}
void Storage::add_string(uid id, const std::string& str)
{
  hdr.add_string(id, str);
}
void Storage::add_buffer(uid id, const buffer_t& blob)
{
  hdr.add_buffer(id, blob.data(), blob.size());
}
void Storage::add_buffer(uid id, const void* buf, size_t len)
{
  hdr.add_buffer(id, (const char*) buf, len);
}
void Storage::add_vector(uid id, const void* buf, size_t count, size_t esize)
{
  hdr.add_vector(id, buf, count, esize);
}
void Storage::add_string_vector(uid id, const std::vector<std::string>& vec)
{
  hdr.add_string_vector(id, vec);
}

#include "serialize_tcp.hpp"
void Storage::add_connection(uid id, Connection_ptr conn)
{
  hdr.add_struct(TYPE_TCP, id,
  [&conn] (char* location) -> int {
    // return size of all the serialized data
    return conn->serialize_to(location);
  });
}
<commit_msg>liu: Added missing unordered_map include<commit_after>// This file is a part of the IncludeOS unikernel - www.includeos.org
//
// Copyright 2017 IncludeOS AS, Oslo, Norway
//
// 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.
/**
 * Master thesis
 * by Alf-Andre Walla 2016-2017
 *
**/
#include "liveupdate.hpp"

#include <cassert>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <unistd.h>
#include <util/elf.h>
#include "storage.hpp"
#include <kernel/os.hpp>
#include <hw/devices.hpp>

//#define LPRINT(x, ...) printf(x, ##__VA_ARGS__);
#define LPRINT(x, ...) /** x **/

static const int SECT_SIZE   = 512;
static const int ELF_MINIMUM = 164;

extern "C"
void solo5_exec(const char*, size_t);
static void* HOTSWAP_AREA = (void*) 0x8000;
extern "C" void  hotswap(const char*, int, char*, uintptr_t, void*);
extern "C" char  __hotswap_length;
extern "C" void  hotswap64(char*, const char*, int, uintptr_t, void*);
extern uint32_t  hotswap64_len;
extern "C" void* __os_store_soft_reset(const void*, size_t);
// kernel area
extern char _ELF_START_;
extern char _end;
// heap area
extern char* heap_begin;
extern char* heap_end;
// turn this off to reduce liveupdate times at the cost of extra checks
bool LIVEUPDATE_PERFORM_SANITY_CHECKS = true;

using namespace liu;

static size_t update_store_data(void* location, const buffer_t*);

// serialization callbacks
static std::unordered_map<std::string, LiveUpdate::storage_func> storage_callbacks;

void LiveUpdate::register_serialization_callback(std::string key, storage_func callback)
{
  auto it = storage_callbacks.find(key);
  if (it == storage_callbacks.end())
  {
    storage_callbacks.emplace(std::piecewise_construct,
              std::forward_as_tuple(std::move(key)),
              std::forward_as_tuple(std::move(callback)));
  }
  else {
    throw std::runtime_error("Storage key '" + key + "' already used");
  }
}

template <typename Class>
inline bool validate_header(const Class* hdr)
{
    return hdr->e_ident[0] == 0x7F &&
           hdr->e_ident[1] == 'E'  &&
           hdr->e_ident[2] == 'L'  &&
           hdr->e_ident[3] == 'F';
}

void LiveUpdate::begin(buffer_t  blob)
{
  void* location = OS::liveupdate_storage_area();
  LPRINT("LiveUpdate::begin(%p, %p:%d, ...)\n", location, blob.data(), (int) blob.size());
  // 1. turn off interrupts
  asm volatile("cli");

  // use area provided to us directly, which we will assume
  // is far enough into heap to not get overwritten by hotswap.
  // even then, it's still guaranteed to work: the copy mechanism
  // is implemented in hotswap.cpp and copies forwards. the
  // blobs are separated by at least one old kernel size and
  // some early heap allocations, which is at least 1mb, while
  // the copy mechanism just copies single bytes.
  const char* update_area  = blob.data();
  char* storage_area = (char*) location;

  // validate not overwriting heap, kernel area and other things
  if (storage_area < (char*) 0x200) {
    throw std::runtime_error("LiveUpdate storage area is (probably) a null pointer");
  }
  if (storage_area >= &_ELF_START_ && storage_area < &_end) {
    throw std::runtime_error("LiveUpdate storage area is inside kernel area");
  }
  if (storage_area >= heap_begin && storage_area < heap_end) {
    throw std::runtime_error("LiveUpdate storage area is inside the heap area");
  }
  if (storage_area >= (char*) OS::heap_max()) {
    throw std::runtime_error("LiveUpdate storage area is outside physical memory");
  }
  if (storage_area >= (char*) OS::heap_max() - 0x10000) {
    throw std::runtime_error("LiveUpdate storage area needs at least 64kb memory");
  }

  // search for ELF header
  LPRINT("* Looking for ELF header at %p\n", update_area);
  const char* binary  = &update_area[0];
  const auto* hdr = (const Elf32_Ehdr*) binary;
  if (!validate_header<Elf32_Ehdr>(hdr))
  {
    /// try again with 1 sector offset (skip bootloader)
    binary   = &update_area[SECT_SIZE];
    hdr      = (const Elf32_Ehdr*) binary;

    if (!validate_header<Elf32_Ehdr>(hdr))
    {
      /// failed to find elf header at sector 0 and 1
      /// simply return
      throw std::runtime_error("Could not find any ELF header in blob");
    }
  }
  LPRINT("* Found ELF header\n");

  size_t    expected_total = 0;
  uintptr_t start_offset = 0;

  const char* bin_data  = nullptr;
  int         bin_len   = 0;
  char*       phys_base = nullptr;

  if (hdr->e_ident[EI_CLASS] == ELFCLASS32)
  {
    /// note: this assumes section headers are at the end
    expected_total =
        hdr->e_shnum * hdr->e_shentsize +
        hdr->e_shoff;
    /// program entry point
    start_offset = hdr->e_entry;
    // get offsets for the new service from program header
    auto* phdr = (Elf32_Phdr*) &binary[hdr->e_phoff];
    bin_data  = &binary[phdr->p_offset];
    bin_len   = phdr->p_filesz;
    phys_base = (char*) (uintptr_t) phdr->p_paddr;
  }
  else {
    auto* ehdr = (Elf64_Ehdr*) hdr;
    /// note: this assumes section headers are at the end
    expected_total =
        ehdr->e_shnum * ehdr->e_shentsize +
        ehdr->e_shoff;
    /// program entry point
    start_offset = ehdr->e_entry;
    // get offsets for the new service from program header
    auto* phdr = (Elf64_Phdr*) &binary[ehdr->e_phoff];
    bin_data  = &binary[phdr->p_offset];
    bin_len   = phdr->p_filesz;
    phys_base = (char*) phdr->p_paddr;
  }

  if (blob.size() < expected_total || expected_total < ELF_MINIMUM)
  {
    fprintf(stderr,
        "*** There was a mismatch between blob length and expected ELF file size:\n");
    fprintf(stderr,
        "EXPECTED: %u byte\n",  (uint32_t) expected_total);
    fprintf(stderr,
        "ACTUAL:   %u bytes\n", (uint32_t) blob.size());
    throw std::runtime_error("ELF file was incomplete");
  }
  LPRINT("* Validated ELF header\n");

  // _start() entry point
  LPRINT("* _start is located at %#x\n", start_offset);

  // save ourselves if function passed
  update_store_data(storage_area, &blob);

  // 2. flush all devices with flush() interface
  hw::Devices::flush_all();
  // 3. deactivate all PCI devices and mask all MSI-X vectors
  // NOTE: there are some nasty side effects from calling this
  //hw::Devices::deactivate_all();

  // store soft-resetting stuff
#ifdef PLATFORM_x86_solo5
  void* sr_data = nullptr;
#else
  extern const std::pair<const char*, size_t> get_rollback_location();
  const auto rollback = get_rollback_location();
  void* sr_data = __os_store_soft_reset(rollback.first, rollback.second);
#endif

  // get offsets for the new service from program header
  if (bin_data == nullptr ||
      phys_base == nullptr || bin_len <= 64) {
    throw std::runtime_error("ELF program header malformed");
  }

  //char* phys_base = (char*) (start_offset & 0xffff0000);
  LPRINT("* Physical base address is %p...\n", phys_base);

  // replace ourselves and reset by jumping to _start
  LPRINT("* Replacing self with %d bytes and jumping to %#x\n", bin_len, start_offset);

#ifdef PLATFORM_x86_solo5
  solo5_exec(blob.data(), blob.size());
  throw std::runtime_error("solo5_exec returned");
#else
# ifdef ARCH_i686
    // copy hotswapping function to sweet spot
    memcpy(HOTSWAP_AREA, (void*) &hotswap, &__hotswap_length - (char*) &hotswap);
    /// the end
    ((decltype(&hotswap)) HOTSWAP_AREA)(bin_data, bin_len, phys_base, start_offset, sr_data);
# elif defined(ARCH_x86_64)
    // copy hotswapping function to sweet spot
    memcpy(HOTSWAP_AREA, (void*) &hotswap64, hotswap64_len);
    /// the end
    ((decltype(&hotswap64)) HOTSWAP_AREA)(phys_base, bin_data, bin_len, start_offset, sr_data);
# else
#   error "Unimplemented architecture"
# endif
#endif
}
void LiveUpdate::restore_environment()
{
  // enable interrupts again
  asm volatile("sti");
}
buffer_t LiveUpdate::store()
{
  char* location = (char*) OS::liveupdate_storage_area();
  size_t size = update_store_data(location, nullptr);
  return buffer_t(location, location + size);
}

size_t LiveUpdate::stored_data_length()
{
  return stored_data_length(OS::liveupdate_storage_area());
}
size_t LiveUpdate::stored_data_length(void* location)
{
  auto* storage = (storage_header*) location;

  if (LIVEUPDATE_PERFORM_SANITY_CHECKS)
  {
    /// sanity check
    if (storage->validate() == false)
        throw std::runtime_error("Failed sanity check on LiveUpdate storage area");
  }

  /// return length of the whole area
  return storage->total_bytes();
}

size_t update_store_data(void* location, const buffer_t* blob)
{
  // create storage header in the fixed location
  new (location) storage_header();
  auto* storage = (storage_header*) location;

  Storage wrapper(*storage);
  /// callback for storing stuff, if provided
  for (const auto& pair : storage_callbacks)
  {
    // create partition
    int p = storage->create_partition(pair.first);
    // run serialization process
    pair.second(wrapper, blob);
    // add end for partition
    storage->finish_partition(p);
  }

  /// finalize
  storage->finalize();

  /// return length (and perform sanity check)
  return LiveUpdate::stored_data_length(location);
}

/// struct Storage

void Storage::put_marker(uid id)
{
  hdr.add_marker(id);
}
void Storage::add_int(uid id, int value)
{
  hdr.add_int(id, value);
}
void Storage::add_string(uid id, const std::string& str)
{
  hdr.add_string(id, str);
}
void Storage::add_buffer(uid id, const buffer_t& blob)
{
  hdr.add_buffer(id, blob.data(), blob.size());
}
void Storage::add_buffer(uid id, const void* buf, size_t len)
{
  hdr.add_buffer(id, (const char*) buf, len);
}
void Storage::add_vector(uid id, const void* buf, size_t count, size_t esize)
{
  hdr.add_vector(id, buf, count, esize);
}
void Storage::add_string_vector(uid id, const std::vector<std::string>& vec)
{
  hdr.add_string_vector(id, vec);
}

#include "serialize_tcp.hpp"
void Storage::add_connection(uid id, Connection_ptr conn)
{
  hdr.add_struct(TYPE_TCP, id,
  [&conn] (char* location) -> int {
    // return size of all the serialized data
    return conn->serialize_to(location);
  });
}
<|endoftext|>


<commit_before>/*************************************************************************
 *
 *  OpenOffice.org - a multi-platform office productivity suite
 *
 *  $RCSfile: namecreator.hxx,v $
 *
 *  $Revision: 1.2 $
 *
 *  last change: $Author: rt $ $Date: 2005-09-08 03:50:42 $
 *
 *  The Contents of this file are made available subject to
 *  the terms of GNU Lesser General Public License Version 2.1.
 *
 *
 *    GNU Lesser General Public License Version 2.1
 *    =============================================
 *    Copyright 2005 by Sun Microsystems, Inc.
 *    901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License version 2.1, as published by the Free Software Foundation.
 *
 *    This library 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., 59 Temple Place, Suite 330, Boston,
 *    MA  02111-1307  USA
 *
 ************************************************************************/

#ifndef INCLUDED_CONFIGMGR_NAMECREATOR_HXX
#define INCLUDED_CONFIGMGR_NAMECREATOR_HXX

#ifndef CONFIGMGR_CONFIGPATH_HXX_
#include "configpath.hxx"
#endif

namespace configmgr
{
    // -----------------------------------------------------------------------------
    class Change;
    class SubtreeChange;
    // -----------------------------------------------------------------------------
    using configuration::Name;
    using configuration::AbsolutePath;
    using configuration::RelativePath;
    // -----------------------------------------------------------------------------

    class ONameCreator
    {
    public:
        typedef configuration::Path::Component  FullName;
        typedef configuration::Path::Rep        PathRep;
        typedef std::vector< FullName > NameList;
    public:
        ONameCreator() {}

        void pushName(const FullName &_aName) { m_aNameList.push_back(_aName); }
        void popName()                       { m_aNameList.pop_back(); }

        void clear() { m_aNameList.clear(); }

        RelativePath buildPath() const;
        RelativePath buildPath(const FullName &_aPlusName) const;

        static FullName createName(Change const& _rChange, SubtreeChange const* _pParent);
    private:
        NameList     m_aNameList;
    };

    template <class PathClass>
    class OPathCreator : public ONameCreator
    {
    public:
        typedef PathClass PathType;
    public:
        OPathCreator() : m_aBasePath( PathRep() ) {}
        OPathCreator(PathClass const & _aBasePath) : m_aBasePath(_aBasePath) {}

        void init(PathClass const & _aBasePath)
        { clear(); m_aBasePath = _aBasePath; }

        PathClass createPath() const
        { return m_aBasePath.compose( ONameCreator::buildPath() ); }

        PathClass createPath(const FullName & _aPlusName) const
        { return m_aBasePath.compose( ONameCreator::buildPath(_aPlusName) ); }
    private:
        PathClass m_aBasePath;
    };
    // -----------------------------------------------------------------------------

} // namespace configmgr

#endif

<commit_msg>INTEGRATION: CWS changefileheader (1.2.130); FILE MERGED 2008/04/01 12:27:24 thb 1.2.130.2: #i85898# Stripping all external header guards 2008/03/31 12:22:44 rt 1.2.130.1: #i87441# Change license header to LPGL v3.<commit_after>/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * Copyright 2008 by Sun Microsystems, Inc.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * $RCSfile: namecreator.hxx,v $
 * $Revision: 1.3 $
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org 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 version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

#ifndef INCLUDED_CONFIGMGR_NAMECREATOR_HXX
#define INCLUDED_CONFIGMGR_NAMECREATOR_HXX

#include "configpath.hxx"

namespace configmgr
{
    // -----------------------------------------------------------------------------
    class Change;
    class SubtreeChange;
    // -----------------------------------------------------------------------------
    using configuration::Name;
    using configuration::AbsolutePath;
    using configuration::RelativePath;
    // -----------------------------------------------------------------------------

    class ONameCreator
    {
    public:
        typedef configuration::Path::Component  FullName;
        typedef configuration::Path::Rep        PathRep;
        typedef std::vector< FullName > NameList;
    public:
        ONameCreator() {}

        void pushName(const FullName &_aName) { m_aNameList.push_back(_aName); }
        void popName()                       { m_aNameList.pop_back(); }

        void clear() { m_aNameList.clear(); }

        RelativePath buildPath() const;
        RelativePath buildPath(const FullName &_aPlusName) const;

        static FullName createName(Change const& _rChange, SubtreeChange const* _pParent);
    private:
        NameList     m_aNameList;
    };

    template <class PathClass>
    class OPathCreator : public ONameCreator
    {
    public:
        typedef PathClass PathType;
    public:
        OPathCreator() : m_aBasePath( PathRep() ) {}
        OPathCreator(PathClass const & _aBasePath) : m_aBasePath(_aBasePath) {}

        void init(PathClass const & _aBasePath)
        { clear(); m_aBasePath = _aBasePath; }

        PathClass createPath() const
        { return m_aBasePath.compose( ONameCreator::buildPath() ); }

        PathClass createPath(const FullName & _aPlusName) const
        { return m_aBasePath.compose( ONameCreator::buildPath(_aPlusName) ); }
    private:
        PathClass m_aBasePath;
    };
    // -----------------------------------------------------------------------------

} // namespace configmgr

#endif

<|endoftext|>


<commit_before>/*

Copyright (c) 2015, Project OSRM contributors
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.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER OR 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.

*/

#ifndef HIDDEN_MARKOV_MODEL
#define HIDDEN_MARKOV_MODEL

#include "../util/integer_range.hpp"

#include <boost/assert.hpp>

#include <cmath>

#include <limits>
#include <vector>

namespace osrm
{
namespace matching
{
// FIXME this value should be a table based on samples/meter (or samples/min)
constexpr static const double log_2_pi = 1.837877066409346; // std::log(2. * M_PI);

constexpr static const double IMPOSSIBLE_LOG_PROB = -std::numeric_limits<double>::infinity();
constexpr static const double MINIMAL_LOG_PROB = std::numeric_limits<double>::lowest();
constexpr static const std::size_t INVALID_STATE = std::numeric_limits<std::size_t>::max();
} // namespace matching
} // namespace osrm

// closures to precompute log -> only simple floating point operations
struct EmissionLogProbability
{
    double sigma_z;
    double log_sigma_z;

    EmissionLogProbability(const double sigma_z) : sigma_z(sigma_z), log_sigma_z(std::log(sigma_z))
    {
    }

    double operator()(const double distance) const
    {
        return -0.5 * (osrm::matching::log_2_pi + (distance / sigma_z) * (distance / sigma_z)) -
               log_sigma_z;
    }
};

struct TransitionLogProbability
{
    double beta;
    double log_beta;
    TransitionLogProbability(const double beta) : beta(beta), log_beta(std::log(beta)) {}

    double operator()(const double d_t) const { return -log_beta - d_t / beta; }
};

template <class CandidateLists> struct HiddenMarkovModel
{
    std::vector<std::vector<double>> viterbi;
    std::vector<std::vector<std::pair<unsigned, unsigned>>> parents;
    std::vector<std::vector<float>> path_lengths;
    std::vector<std::vector<bool>> pruned;
    std::vector<bool> breakage;

    const CandidateLists &candidates_list;
    const EmissionLogProbability &emission_log_probability;

    HiddenMarkovModel(const CandidateLists &candidates_list,
                      const EmissionLogProbability &emission_log_probability)
        : breakage(candidates_list.size()), candidates_list(candidates_list),
          emission_log_probability(emission_log_probability)
    {
        for (const auto &l : candidates_list)
        {
            viterbi.emplace_back(l.size());
            parents.emplace_back(l.size());
            path_lengths.emplace_back(l.size());
            pruned.emplace_back(l.size());
        }

        clear(0);
    }

    void clear(std::size_t initial_timestamp)
    {
        BOOST_ASSERT(viterbi.size() == parents.size() && parents.size() == path_lengths.size() &&
                     path_lengths.size() == pruned.size() && pruned.size() == breakage.size());

        for (const auto t : osrm::irange(initial_timestamp, viterbi.size()))
        {
            std::fill(viterbi[t].begin(), viterbi[t].end(), osrm::matching::IMPOSSIBLE_LOG_PROB);
            std::fill(parents[t].begin(), parents[t].end(), std::make_pair(0u, 0u));
            std::fill(path_lengths[t].begin(), path_lengths[t].end(), 0);
            std::fill(pruned[t].begin(), pruned[t].end(), true);
        }
        std::fill(breakage.begin() + initial_timestamp, breakage.end(), true);
    }

    std::size_t initialize(std::size_t initial_timestamp)
    {
        BOOST_ASSERT(initial_timestamp < candidates_list.size());

        do
        {
            for (const auto s : osrm::irange<std::size_t>(0u, viterbi[initial_timestamp].size()))
            {
                viterbi[initial_timestamp][s] =
                    emission_log_probability(candidates_list[initial_timestamp][s].second);
                parents[initial_timestamp][s] = std::make_pair(initial_timestamp, s);
                pruned[initial_timestamp][s] =
                    viterbi[initial_timestamp][s] < osrm::matching::MINIMAL_LOG_PROB;

                breakage[initial_timestamp] =
                    breakage[initial_timestamp] && pruned[initial_timestamp][s];
            }

            ++initial_timestamp;
        } while (breakage[initial_timestamp - 1]);

        if (initial_timestamp >= viterbi.size())
        {
            return osrm::matching::INVALID_STATE;
        }

        BOOST_ASSERT(initial_timestamp > 0);
        --initial_timestamp;

        BOOST_ASSERT(breakage[initial_timestamp] == false);

        return initial_timestamp;
    }
};

#endif // HIDDEN_MARKOV_MODEL
<commit_msg>remove constexpr qualifier as numeric_limits is not yet constexpr on MSVC compiler<commit_after>/*

Copyright (c) 2015, Project OSRM contributors
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.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER OR 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.

*/

#ifndef HIDDEN_MARKOV_MODEL
#define HIDDEN_MARKOV_MODEL

#include "../util/integer_range.hpp"

#include <boost/assert.hpp>

#include <cmath>

#include <limits>
#include <vector>

namespace osrm
{
namespace matching
{
// FIXME this value should be a table based on samples/meter (or samples/min)
static const double log_2_pi = std::log(2. * M_PI);
static const double IMPOSSIBLE_LOG_PROB = -std::numeric_limits<double>::infinity();
static const double MINIMAL_LOG_PROB = std::numeric_limits<double>::lowest();
static const std::size_t INVALID_STATE = std::numeric_limits<std::size_t>::max();
} // namespace matching
} // namespace osrm

// closures to precompute log -> only simple floating point operations
struct EmissionLogProbability
{
    double sigma_z;
    double log_sigma_z;

    EmissionLogProbability(const double sigma_z) : sigma_z(sigma_z), log_sigma_z(std::log(sigma_z))
    {
    }

    double operator()(const double distance) const
    {
        return -0.5 * (osrm::matching::log_2_pi + (distance / sigma_z) * (distance / sigma_z)) -
               log_sigma_z;
    }
};

struct TransitionLogProbability
{
    double beta;
    double log_beta;
    TransitionLogProbability(const double beta) : beta(beta), log_beta(std::log(beta)) {}

    double operator()(const double d_t) const { return -log_beta - d_t / beta; }
};

template <class CandidateLists> struct HiddenMarkovModel
{
    std::vector<std::vector<double>> viterbi;
    std::vector<std::vector<std::pair<unsigned, unsigned>>> parents;
    std::vector<std::vector<float>> path_lengths;
    std::vector<std::vector<bool>> pruned;
    std::vector<bool> breakage;

    const CandidateLists &candidates_list;
    const EmissionLogProbability &emission_log_probability;

    HiddenMarkovModel(const CandidateLists &candidates_list,
                      const EmissionLogProbability &emission_log_probability)
        : breakage(candidates_list.size()), candidates_list(candidates_list),
          emission_log_probability(emission_log_probability)
    {
        for (const auto &l : candidates_list)
        {
            viterbi.emplace_back(l.size());
            parents.emplace_back(l.size());
            path_lengths.emplace_back(l.size());
            pruned.emplace_back(l.size());
        }

        clear(0);
    }

    void clear(std::size_t initial_timestamp)
    {
        BOOST_ASSERT(viterbi.size() == parents.size() && parents.size() == path_lengths.size() &&
                     path_lengths.size() == pruned.size() && pruned.size() == breakage.size());

        for (const auto t : osrm::irange(initial_timestamp, viterbi.size()))
        {
            std::fill(viterbi[t].begin(), viterbi[t].end(), osrm::matching::IMPOSSIBLE_LOG_PROB);
            std::fill(parents[t].begin(), parents[t].end(), std::make_pair(0u, 0u));
            std::fill(path_lengths[t].begin(), path_lengths[t].end(), 0);
            std::fill(pruned[t].begin(), pruned[t].end(), true);
        }
        std::fill(breakage.begin() + initial_timestamp, breakage.end(), true);
    }

    std::size_t initialize(std::size_t initial_timestamp)
    {
        BOOST_ASSERT(initial_timestamp < candidates_list.size());

        do
        {
            for (const auto s : osrm::irange<std::size_t>(0u, viterbi[initial_timestamp].size()))
            {
                viterbi[initial_timestamp][s] =
                    emission_log_probability(candidates_list[initial_timestamp][s].second);
                parents[initial_timestamp][s] = std::make_pair(initial_timestamp, s);
                pruned[initial_timestamp][s] =
                    viterbi[initial_timestamp][s] < osrm::matching::MINIMAL_LOG_PROB;

                breakage[initial_timestamp] =
                    breakage[initial_timestamp] && pruned[initial_timestamp][s];
            }

            ++initial_timestamp;
        } while (breakage[initial_timestamp - 1]);

        if (initial_timestamp >= viterbi.size())
        {
            return osrm::matching::INVALID_STATE;
        }

        BOOST_ASSERT(initial_timestamp > 0);
        --initial_timestamp;

        BOOST_ASSERT(breakage[initial_timestamp] == false);

        return initial_timestamp;
    }
};

#endif // HIDDEN_MARKOV_MODEL
<|endoftext|>