//
// Copyright (c) 2021, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 09 Aug 2021 Matthew Giannini Creation
//
**
** Base class for ASN.1 collection types.
**
abstract const class AsnColl : AsnObj
{
//////////////////////////////////////////////////////////////////////////
// Construction
//////////////////////////////////////////////////////////////////////////
protected new make(AsnTag[] tags, Obj val) : super(tags, toItems(val))
{
}
@NoDoc static AsnItem[] toItems(Obj val)
{
items := AsnItem[,]
if (val is Map)
{
m := (Str:AsnObj)val
m.each |v, k| { items.add(AsnItem(v, k)) }
}
else
{
arr := (List)val
if (arr.of.fits(AsnItem#)) items = val
else arr.each { items.add(AsnItem(it)) }
}
return items
}
** Get a [collection builder]`AsnCollBuilder`
static AsnCollBuilder builder() { AsnCollBuilder() }
//////////////////////////////////////////////////////////////////////////
// AsnColl
//////////////////////////////////////////////////////////////////////////
** Is this a 'SEQUENCE'
Bool isSeq() { univTag == AsnTag.univSeq }
** Is this a 'SET'
Bool isSet() { univTag == AsnTag.univSet }
** Get the raw `AsnObj` values in the collection
AsnObj[] vals() { items.map { it.val } }
@NoDoc AsnItem[] items() { val }
** Get the number of items in the collection
Int size() { items.size }
** Is the collection empty
Bool isEmpty() { items.isEmpty }
** Get an item value from the collection.
** - If key is a `sys::Str` then get the named item.
** - If key is an `sys::Int`, then get the item at that zero-based index.
AsnObj? get(Obj key)
{
if (key is Int) return items.getSafe(key)?.val
else if (key is Str) return items.find { it.name == name }?.val
throw ArgErr("invalid key type: ${key.typeof}")
}
//////////////////////////////////////////////////////////////////////////
// AsnObj
//////////////////////////////////////////////////////////////////////////
protected override Str valStr()
{
buf := StrBuf().add("{\n")
indent := 2
items.each |AsnItem item|
{
buf.add("".padl(indent))
if (item.name != null) buf.add("${item.name}: ")
if (item.val is AsnColl)
{
collStr := item.val.toStr
collStr.splitLines.each |line, i|
{
if (i==0) buf.add(line)
else buf.add("".padl(indent)).add(line)
buf.add("\n")
}
}
else buf.add(item.val.toStr).add("\n")
}
buf.add("}")
return buf.toStr
}
}
**************************************************************************
** AsnItem
**************************************************************************
** An item in an ASN.1 collection. An item has a value, and an optional name
** associated with that value. When comparing items, only the values are
** compared; the name is ignored.
final const class AsnItem
{
new make(AsnObj val, Str? name := null)
{
this.name = name
this.val = val
}
const Str? name
const AsnObj val
override Int hash() { val.hash }
override Bool equals(Obj? obj)
{
if (obj == null) return false
that := obj as AsnItem
if (that == null) return false
// name is *not* considered for equality purposes
return this.val == that.val
}
}
**************************************************************************
** AsnSeq
**************************************************************************
**
** Models an ASN.1 'SEQUENCE'
**
const class AsnSeq : AsnColl
{
protected new makeUniv(Obj val) : this.make([AsnTag.univSeq], val)
{
}
protected new make(AsnTag[] tags, Obj val) : super(tags, toItems(val))
{
if (univTag != AsnTag.univSeq) throw ArgErr("Not a sequence: $tags")
}
}
**************************************************************************
** AsnSet
**************************************************************************
**
** Models an ASN.1 'SET'
**
const class AsnSet : AsnColl
{
protected new makeUniv(Obj val) : this.make([AsnTag.univSet], val)
{
if (univTag != AsnTag.univSet) throw ArgErr("Not a set: $tags")
}
protected new make(AsnTag[] tags, Obj val) : super(tags, toItems(val))
{
}
}
**************************************************************************
** AsnCollBuilder
**************************************************************************
**
** `AsnColl` builder.
**
class AsnCollBuilder
{
new make() { }
private AsnItem[] items := [,]
** Convenience to add an `AsnItem` with the given value and name
This add(AsnObj val, Str? name := null) { item(AsnItem(val, name)) }
** Add an `AsnItem` to the collection
This item(AsnItem item)
{
items.add(item)
return this
}
** Build an ASN.1 sequence
AsnColl toSeq(AsnTag? tag := null) { Asn.tag(tag).seq(items) }
** Build an ASN.1 set
AsnColl toSet(AsnTag? tag := null) { Asn.tag(tag).set(items) }
}