model/stop_area.go

Summary

Maintainability
C
1 day
Test Coverage
package model

import (
    "encoding/json"
    "fmt"
    "math/rand"
    "sync"
    "time"

    "bitbucket.org/enroute-mobi/ara/clock"
    "bitbucket.org/enroute-mobi/ara/logger"
    "bitbucket.org/enroute-mobi/ara/uuid"
)

type StopAreaId ModelId

type StopArea struct {
    Collectable
    CollectedUntil time.Time
    model          Model
    References     References
    CodeConsumer
    Origins           *StopAreaOrigins
    Attributes        Attributes
    ReferentId        StopAreaId `json:",omitempty"`
    id                StopAreaId
    ParentId          StopAreaId `json:",omitempty"`
    Name              string
    LineIds           StopAreaLineIds `json:"Lines,omitempty"`
    Latitude          float64         `json:",omitempty"`
    Longitude         float64         `json:",omitempty"`
    CollectChildren   bool
    CollectSituations bool
    CollectedAlways   bool
    Monitored         bool
}

func NewStopArea(model Model) *StopArea {
    stopArea := &StopArea{
        model:           model,
        Origins:         NewStopAreaOrigins(),
        Attributes:      NewAttributes(),
        References:      NewReferences(),
        CollectedAlways: true,
    }
    stopArea.codes = make(Codes)
    return stopArea
}

func (stopArea *StopArea) modelId() ModelId {
    return ModelId(stopArea.id)
}

func (stopArea *StopArea) copy() *StopArea {
    return &StopArea{
        Collectable: Collectable{
            nextCollectAt: stopArea.nextCollectAt,
            collectedAt:   stopArea.collectedAt,
        },
        CodeConsumer:      stopArea.CodeConsumer.Copy(),
        model:             stopArea.model,
        id:                stopArea.id,
        ParentId:          stopArea.ParentId,
        ReferentId:        stopArea.ReferentId,
        CollectedUntil:    stopArea.CollectedUntil,
        CollectedAlways:   stopArea.CollectedAlways,
        CollectSituations: stopArea.CollectSituations,
        Monitored:         stopArea.Monitored,
        Origins:           stopArea.Origins.Copy(),
        Name:              stopArea.Name,
        LineIds:           stopArea.LineIds.Copy(),
        CollectChildren:   stopArea.CollectChildren,
        Attributes:        stopArea.Attributes.Copy(),
        References:        stopArea.References.Copy(),
        Longitude:         stopArea.Longitude,
        Latitude:          stopArea.Latitude,
    }
}

func (stopArea *StopArea) Id() StopAreaId {
    return stopArea.id
}

func (stopArea *StopArea) MarshalJSON() ([]byte, error) {
    type Alias StopArea
    aux := struct {
        References     map[string]Reference `json:",omitempty"`
        Codes          Codes                `json:",omitempty"`
        NextCollectAt  *time.Time           `json:",omitempty"`
        CollectedAt    *time.Time           `json:",omitempty"`
        CollectedUntil *time.Time           `json:",omitempty"`
        Attributes     Attributes           `json:",omitempty"`
        *Alias
        Id StopAreaId
    }{
        Id:    stopArea.id,
        Alias: (*Alias)(stopArea),
    }

    if !stopArea.Codes().Empty() {
        aux.Codes = stopArea.Codes()
    }
    if !stopArea.Attributes.IsEmpty() {
        aux.Attributes = stopArea.Attributes
    }
    if !stopArea.References.IsEmpty() {
        aux.References = stopArea.References.GetReferences()
    }
    if !stopArea.nextCollectAt.IsZero() {
        aux.NextCollectAt = &stopArea.nextCollectAt
    }
    if !stopArea.collectedAt.IsZero() {
        aux.CollectedAt = &stopArea.collectedAt
    }
    if !stopArea.CollectedAlways && !stopArea.CollectedUntil.IsZero() {
        aux.CollectedUntil = &stopArea.CollectedUntil
    }

    return json.Marshal(&aux)
}

func (stopArea *StopArea) UnmarshalJSON(data []byte) error {
    type Alias StopArea
    aux := &struct {
        Codes      map[string]string
        References map[string]Reference
        Origins    map[string]bool
        *Alias
    }{
        Alias: (*Alias)(stopArea),
    }
    err := json.Unmarshal(data, aux)
    if err != nil {
        return err
    }

    if aux.Codes != nil {
        stopArea.CodeConsumer.codes = NewCodesFromMap(aux.Codes)
    }

    if aux.References != nil {
        stopArea.References.SetReferences(aux.References)
    }

    if aux.Origins != nil {
        stopArea.Origins.SetOriginsFromMap(aux.Origins)
    }

    return nil
}

func (stopArea *StopArea) Attribute(key string) (value string, present bool) {
    value, present = stopArea.Attributes[key]
    return
}

func (stopArea *StopArea) Reference(key string) (Reference, bool) {
    value, present := stopArea.References.Get(key)
    return value, present
}

func (stopArea *StopArea) Lines() (lines []*Line) {
    for _, lineId := range stopArea.LineIds {
        foundLine, ok := stopArea.model.Lines().Find(lineId)
        if ok {
            lines = append(lines, foundLine)
        }
    }
    return
}

func (stopArea *StopArea) Parent() (*StopArea, bool) {
    return stopArea.model.StopAreas().Find(stopArea.ParentId)
}

func (stopArea *StopArea) Referent() (*StopArea, bool) {
    return stopArea.model.StopAreas().Find(stopArea.ReferentId)
}

func (stopArea *StopArea) ReferentOrSelfCode(codeSpace string) (Code, bool) {
    ref, ok := stopArea.Referent()
    if ok {
        code, ok := ref.Code(codeSpace)
        if ok {
            return code, true
        }
    }
    code, ok := stopArea.Code(codeSpace)
    if ok {
        return code, true
    }
    return Code{}, false
}

/*
Returns true if we need to send the StopArea in a SPD

We only send the StopArea if it has no referent with a correct codeSpace.
If that's the case, we'll send the Referent instead
*/
func (stopArea *StopArea) SPDCode(codeSpace string) (Code, bool) {
    ref, ok := stopArea.Referent()
    if ok {
        _, ok := ref.Code(codeSpace)
        if ok {
            return Code{}, false
        }
    }
    code, ok := stopArea.Code(codeSpace)
    if ok {
        return code, true
    }
    return Code{}, false
}

func (stopArea *StopArea) SetPartnerStatus(partner string, status bool) {
    stopArea.Origins.SetPartnerStatus(partner, status)
    stopArea.Monitored = stopArea.Origins.Monitored()
}

func (stopArea *StopArea) Save() bool {
    return stopArea.model.StopAreas().Save(stopArea)
}

type MemoryStopAreas struct {
    uuid.UUIDConsumer

    model *MemoryModel

    mutex        *sync.RWMutex
    byIdentifier map[StopAreaId]*StopArea
    byParent     *Index
    byReferent   *Index
    byCode       *CodeIndex

    broadcastEvent func(event StopMonitoringBroadcastEvent)
}

type StopAreas interface {
    uuid.UUIDInterface

    New() *StopArea
    Find(StopAreaId) (*StopArea, bool)
    FindByCode(Code) (*StopArea, bool)
    FindByLineId(LineId) []*StopArea
    FindByOrigin(string) []StopAreaId
    FindAll() []*StopArea
    FindFamily(StopAreaId) []StopAreaId
    FindAscendants(StopAreaId) []*StopArea
    FindAscendantsWithCodeSpace(StopAreaId, string) []Code
    Save(*StopArea) bool
    Delete(*StopArea) bool
}

func NewMemoryStopAreas() *MemoryStopAreas {
    referentExtractor := func(instance ModelInstance) ModelId { return ModelId((instance.(*StopArea)).ReferentId) }
    parentExtractor := func(instance ModelInstance) ModelId { return ModelId((instance.(*StopArea)).ParentId) }

    return &MemoryStopAreas{
        mutex:        &sync.RWMutex{},
        byIdentifier: make(map[StopAreaId]*StopArea),
        byReferent:   NewIndex(referentExtractor),
        byParent:     NewIndex(parentExtractor),
        byCode:       NewCodeIndex(),
    }
}

func (manager *MemoryStopAreas) New() *StopArea {
    return NewStopArea(manager.model)
}

func (manager *MemoryStopAreas) Find(id StopAreaId) (*StopArea, bool) {
    manager.mutex.RLock()
    stopArea, ok := manager.byIdentifier[id]
    manager.mutex.RUnlock()

    if ok {
        return stopArea.copy(), true
    }
    return &StopArea{}, false
}

func (manager *MemoryStopAreas) FindByCode(code Code) (*StopArea, bool) {
    manager.mutex.RLock()
    defer manager.mutex.RUnlock()

    id, ok := manager.byCode.Find(code)
    if ok {
        return manager.byIdentifier[StopAreaId(id)].copy(), true
    }

    return &StopArea{}, false
}

func (manager *MemoryStopAreas) FindByLineId(id LineId) (stopAreas []*StopArea) {
    manager.mutex.RLock()

    for _, stopArea := range manager.byIdentifier {
        if stopArea.LineIds.Contains(id) {
            stopAreas = append(stopAreas, stopArea.copy())
        }
    }

    manager.mutex.RUnlock()
    return
}

func (manager *MemoryStopAreas) FindByOrigin(origin string) (stopAreas []StopAreaId) {
    manager.mutex.RLock()

    for _, stopArea := range manager.byIdentifier {
        if _, ok := stopArea.Origins.Origin(origin); ok {
            stopAreas = append(stopAreas, stopArea.Id())
        }
    }

    manager.mutex.RUnlock()
    return
}

func (manager *MemoryStopAreas) FindByReferentId(id StopAreaId) (stopAreas []*StopArea) {
    manager.mutex.RLock()

    ids, _ := manager.byReferent.Find(ModelId(id))

    for _, id := range ids {
        sa := manager.byIdentifier[StopAreaId(id)]
        stopAreas = append(stopAreas, sa.copy())
    }

    manager.mutex.RUnlock()
    return
}

func (manager *MemoryStopAreas) FindByParentId(id StopAreaId) (stopAreas []*StopArea) {
    manager.mutex.RLock()

    ids, _ := manager.byParent.Find(ModelId(id))

    for _, id := range ids {
        sa := manager.byIdentifier[StopAreaId(id)]
        stopAreas = append(stopAreas, sa.copy())
    }

    manager.mutex.RUnlock()
    return
}

func (manager *MemoryStopAreas) FindAll() (stopAreas []*StopArea) {
    manager.mutex.RLock()

    for _, stopArea := range manager.byIdentifier {
        stopAreas = append(stopAreas, stopArea.copy())
    }

    manager.mutex.RUnlock()
    return
}

func (manager *MemoryStopAreas) Save(stopArea *StopArea) bool {
    if stopArea.Id() == "" {
        stopArea.id = StopAreaId(manager.NewUUID())
    }

    manager.mutex.Lock()

    stopArea.model = manager.model
    manager.byIdentifier[stopArea.Id()] = stopArea
    manager.byReferent.Index(stopArea)
    manager.byParent.Index(stopArea)
    manager.byCode.Index(stopArea)

    manager.mutex.Unlock()

    event := StopMonitoringBroadcastEvent{
        ModelId:   string(stopArea.id),
        ModelType: "StopArea",
    }

    if manager.broadcastEvent != nil {
        manager.broadcastEvent(event)
    }

    return true
}

func (manager *MemoryStopAreas) Delete(stopArea *StopArea) bool {
    manager.mutex.Lock()
    defer manager.mutex.Unlock()

    delete(manager.byIdentifier, stopArea.Id())
    manager.byReferent.Delete(ModelId(stopArea.id))
    manager.byParent.Delete(ModelId(stopArea.id))
    manager.byCode.Delete(ModelId(stopArea.id))

    return true
}

func (manager *MemoryStopAreas) FindFamily(stopAreaId StopAreaId) (stopAreaIds []StopAreaId) {
    manager.mutex.RLock()

    stopAreaIds = manager.findFamily(stopAreaId)

    manager.mutex.RUnlock()

    return
}

func (manager *MemoryStopAreas) findFamily(stopAreaId StopAreaId) (stopAreaIds []StopAreaId) {
    stopAreaIds = []StopAreaId{stopAreaId}

    ids, _ := manager.byParent.Find(ModelId(stopAreaId))
    for _, id := range ids {
        stopAreaIds = append(stopAreaIds, manager.findFamily(StopAreaId(id))...)
    }
    ids, _ = manager.byReferent.Find(ModelId(stopAreaId))
    for _, id := range ids {
        stopAreaIds = append(stopAreaIds, manager.findFamily(StopAreaId(id))...)
    }

    return
}

func (manager *MemoryStopAreas) FindAscendants(stopAreaId StopAreaId) (stopAreas []*StopArea) {
    manager.mutex.RLock()

    var count uint8

    stopAreas = manager.findAscendants(stopAreaId, count)

    manager.mutex.RUnlock()

    return
}

func (manager *MemoryStopAreas) findAscendants(stopAreaId StopAreaId, count uint8) (stopAreas []*StopArea) {
    if count >= 20 {
        logger.Log.Printf("Loop in StopAreas when finding Ascendants: %v", stopAreaId)
        return
    }
    count++

    sa, ok := manager.byIdentifier[stopAreaId]
    if !ok {
        return
    }
    stopAreas = []*StopArea{sa.copy()}
    if sa.ParentId != "" {
        stopAreas = append(stopAreas, manager.findAscendants(sa.ParentId, count)...)
    }
    if sa.ReferentId != "" {
        stopAreas = append(stopAreas, manager.findAscendants(sa.ReferentId, count)...)
    }

    return
}

func (manager *MemoryStopAreas) FindAscendantsWithCodeSpace(stopAreaId StopAreaId, kind string) (stopAreaCodes []Code) {
    manager.mutex.RLock()

    stopAreaCodes = manager.findAscendantsWithCodeSpace(stopAreaId, kind)

    manager.mutex.RUnlock()

    return
}

func (manager *MemoryStopAreas) findAscendantsWithCodeSpace(stopAreaId StopAreaId, kind string) (stopAreaCodes []Code) {
    sa, ok := manager.byIdentifier[stopAreaId]
    if !ok {
        return
    }

    id, ok := sa.Code(kind)
    if ok {
        stopAreaCodes = []Code{id}
    }

    if sa.ParentId != "" {
        stopAreaCodes = append(stopAreaCodes, manager.findAscendantsWithCodeSpace(sa.ParentId, kind)...)
    }
    if sa.ReferentId != "" {
        stopAreaCodes = append(stopAreaCodes, manager.findAscendantsWithCodeSpace(sa.ReferentId, kind)...)
    }

    return
}

func (manager *MemoryStopAreas) Load(referentialSlug string) error {
    var selectStopAreas []SelectStopArea
    modelName := manager.model.Date()

    sqlQuery := fmt.Sprintf("select * from stop_areas where referential_slug = '%s' and model_name = '%s'", referentialSlug, modelName.String())
    _, err := Database.Select(&selectStopAreas, sqlQuery)
    if err != nil {
        return err
    }

    for _, sa := range selectStopAreas {
        stopArea := manager.New()
        stopArea.id = StopAreaId(sa.Id)
        if sa.Name.Valid {
            stopArea.Name = sa.Name.String
        }
        if sa.ParentId.Valid {
            stopArea.ParentId = StopAreaId(sa.ParentId.String)
        }
        if sa.ReferentId.Valid {
            stopArea.ReferentId = StopAreaId(sa.ReferentId.String)
        }
        if sa.CollectedAlways.Valid {
            stopArea.CollectedAlways = sa.CollectedAlways.Bool
        }
        if stopArea.CollectedAlways { // To prevent too much spam when initializing
            rand_duration := time.Duration(rand.Intn(30)) * time.Second
            stopArea.NextCollect(clock.DefaultClock().Now().Add(rand_duration))
        }
        if sa.CollectChildren.Valid {
            stopArea.CollectChildren = sa.CollectChildren.Bool
        }
        if sa.CollectSituations.Valid {
            stopArea.CollectSituations = sa.CollectSituations.Bool
        }

        if sa.LineIds.Valid && len(sa.LineIds.String) > 0 {
            var lineIds []string
            if err = json.Unmarshal([]byte(sa.LineIds.String), &lineIds); err != nil {
                return err
            }
            for i := range lineIds {
                stopArea.LineIds = append(stopArea.LineIds, LineId(lineIds[i]))
            }
        }

        if sa.Attributes.Valid && len(sa.Attributes.String) > 0 {
            if err = json.Unmarshal([]byte(sa.Attributes.String), &stopArea.Attributes); err != nil {
                return err
            }
        }

        if sa.References.Valid && len(sa.References.String) > 0 {
            references := make(map[string]Reference)
            if err = json.Unmarshal([]byte(sa.References.String), &references); err != nil {
                return err
            }
            stopArea.References.SetReferences(references)
        }

        if sa.Codes.Valid && len(sa.Codes.String) > 0 {
            codeMap := make(map[string]string)
            if err = json.Unmarshal([]byte(sa.Codes.String), &codeMap); err != nil {
                return err
            }
            stopArea.codes = NewCodesFromMap(codeMap)
        }

        manager.Save(stopArea)
    }
    return nil
}