BaseNCoder.st
author Claus Gittinger <cg@exept.de>
Mon, 18 Mar 2019 16:24:14 +0100
changeset 4885 f23f8cf58b0c
parent 4842 6926d1490469
child 4893 8c2146846b19
permissions -rw-r--r--
mingw

"{ Encoding: utf8 }"

"
 COPYRIGHT (c) 2002 by eXept Software AG
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
"{ Package: 'stx:libbasic2' }"

"{ NameSpace: Smalltalk }"

ObjectCoder subclass:#BaseNCoder
	instanceVariableNames:'buffer bits charCount peekByte atEnd lineLimit mapping
		reverseMapping'
	classVariableNames:''
	poolDictionaries:''
	category:'System-Storage'
!

!BaseNCoder class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 2002 by eXept Software AG
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
!

documentation
"
    Abstract superclass of Base64Coder and Base32Coder
    Their main entry point API is 
        <BaseNCoder> encode:aStringOrBytes
    and
        <BaseNCoder> decode:aString

    If the decoder should return a string, use
        <BaseNCoder> decodeAsString:aString.

    [examples:]
        Base64Coder encode:'helloWorld'
        
        Base64Coder decode:'aGVsbG9Xb3JsZA=='
        
        Base64Coder decodeAsString:'aGVsbG9Xb3JsZA=='
        
    [author:]
        Stefan Vogel (stefan@zwerg)

    [instance variables:]
        buffer          SmallInteger   buffered data
        bits            SmallInteger   Number of valid bits in buffer
        charCount       SmallInteger   Number of characters since last cr
        atEnd           Boolean        true if end of Base64 string reached

    [class variables:]

    [see also:]

"
! !

!BaseNCoder class methodsFor:'initialization'!

initializeMappings
    self subclassResponsibility

    "
     self withAllSubclassesDo:#initialize
    "

    "Modified (comment): / 30-09-2018 / 15:39:16 / Claus Gittinger"
!

mapping
    self subclassResponsibility

    "Created: / 30-09-2018 / 15:30:08 / Claus Gittinger"
!

reverseMapping
    self subclassResponsibility

    "Created: / 30-09-2018 / 15:30:11 / Claus Gittinger"
!

reverseMappingFor:mapping
    "initialize class variables"

    |revMapping|
    
    revMapping := ByteArray new:128 withAll:255.
    mapping keysAndValuesDo:[:idx :char|
        revMapping at:char codePoint put:idx-1.
    ].
    ^ revMapping

    "Created: / 30-09-2018 / 15:34:37 / Claus Gittinger"
! !

!BaseNCoder class methodsFor:'instance creation'!

new
   self initializeMappings.
   ^ self basicNew initialize
! !

!BaseNCoder class methodsFor:'decoding'!

decodeAsString:encodedString
    "decode a base-n encoded string.
     We already expect a string instead of a ByteArray"

    ^ (self on:encodedString readStream) stringUpToEnd
! !

!BaseNCoder class methodsFor:'queries'!

isAbstract
    "Return if this class is an abstract class.
     True is returned here for myself only; false for subclasses.
     Abstract subclasses must redefine this again."

    ^ self == BaseNCoder.
! !

!BaseNCoder methodsFor:'accessing'!

lineLimit:something
    "set the line length of the encoded output.
     Default is a line length of 76 characters.

     If nil, no line breaks will be done."

    lineLimit := something.
! !

!BaseNCoder methodsFor:'decoding'!

next
    "answer the next decoded byte"

    |b|

    peekByte notNil ifTrue:[
        b := peekByte.
        peekByte := nil.
        ^ b
    ].
    ^ self basicNext.
!

next:count
    "return the next count bytes of the stream as ByteArray"

    |answerStream 
     cnt  "{ Class: SmallInteger }" |

    cnt := count.
    answerStream := WriteStream on:(ByteArray new:cnt).
    answerStream signalAtEnd:true.
    1 to:cnt do:[:index |
        |next|

        next := self next.
        next isNil ifTrue:[
            "if next did not raise EndOfStreamError, we have to do it"
            EndOfStreamError raiseRequestFrom:self.
            "if you proceed, you get what we have already collected"
            ^ answerStream contents
        ].
        answerStream nextPut:next.
    ].
    ^ answerStream contents
!

peek
    "answer the next decoded byte. Do not consume this byte"

    peekByte isNil ifTrue:[
        peekByte := self basicNext.
    ].
    ^ peekByte
! !

!BaseNCoder methodsFor:'encoding'!

visitByteArray:aByteArray with:aParameter 
    ^ self
        nextPutBytes:aByteArray;
        flush.

    "
      Base64Coder encodingOf:#[1 2 3 4 5 6 255]
    "
!

visitObject:anObject with:aParameter
    "not defined. Use nextPut or nextPutAll:.
     Could encode the printString here"

    ^ self shouldNotImplement
!

visitStream:aStream with:aParameter
    aStream copyToEndInto:self.
    self flush.

    "
      Base64Coder encodingOf:#[1 2 3 4 5 6 255]
      Base64Coder encodingOf:#[1 2 3 4 5 6 255] readStream
    "
!

visitString:aString with:aParameter 
    ^ self
        nextPutAll:aString;
        flush.

    "
      |encoded decoded decoder|

      encoded := Base64Coder encode:'hello world'.  
      decoded := #[] writeStream.
      decoder := Base64Coder on:encoded readStream.
      [decoder atEnd] whileFalse:[
          decoded nextPut:(decoder next).
      ].
      decoded := decoded contents.
      decoded asString.    
    "
! !

!BaseNCoder methodsFor:'initialization'!

emptyWriteStream
    "answer an empty stream. We encode as string"
    
    ^ WriteStream on:(String new:64)
!

initialize

    <modifier: #super> "must be called if redefined"

    buffer := bits := charCount := 0.
    lineLimit := 76.   "RFC 2045 says: max 76 characters in one line"
    atEnd := false.

    mapping := self class mapping.
    reverseMapping := self class reverseMapping.

    "Modified: / 08-02-2017 / 00:33:07 / cg"
    "Modified: / 30-09-2018 / 15:29:41 / Claus Gittinger"
! !

!BaseNCoder methodsFor:'misc'!

reset
    "reset to initial state"

    super reset.
    buffer := bits := charCount := 0.
    atEnd := false.
    peekByte := nil.
! !

!BaseNCoder methodsFor:'private'!

basicNext
    "answer the next decoded byte. 
     No peekByte handling is done here."

    |b|

    bits == 0 ifTrue:[
        self fillBuffer.
        bits == 0 ifTrue:[
            ^ stream pastEndRead.
        ]
    ].
    b := (buffer bitShift:(8 - bits)) bitAnd:16rFF.
    bits := bits - 8.

    ^ b.
! !

!BaseNCoder methodsFor:'queries'!

atEnd
    "answer true, if no more bytes can be read"

    bits == 0 ifTrue:[
        atEnd ifTrue:[^ true].
        self fillBuffer.
        bits == 0 ifTrue:[^ true].
    ].
    ^ false.
!

binary
    "switch to binary mode - nothing is done here.
     Defined for compatibility with ExternalStream."

    ^ self
!

binary:beBinaryBool
    "ExternalStream protocol compatibility"

    ^ false         "/ no-op, I am not in binary mode

    "Created: / 13-03-2019 / 19:15:17 / Stefan Vogel"
!

isStream
    "we simulate a stream"

    ^ true
! !

!BaseNCoder methodsFor:'stream compatibility'!

bufferSizeForBulkCopy
    ^ 1024

    "Created: / 14-03-2019 / 12:58:47 / Stefan Vogel"
!

nextBytesInto:anObject startingAt:offset
    "copy bytes into anObject starting at offset"

    |off|

    off := offset.
    [self atEnd] whileFalse:[
        anObject at:off put:self next.
        off := off + 1.
    ].
    ^ off - offset
!

nextPut:aByte
    "encode aByte on the output stream"

    ^ self nextPutByte:aByte asInteger.
!

nextPutAll:aCollection startingAt:first to:last
    "append the elements with index from first to last
     of the argument, aCollection onto the receiver."

    aCollection from:first to:last do:[:element |
        self nextPutByte:element
    ].
    ^ aCollection
!

nextPutBytes:aCollectionOfBytes
    "encode all objects from the argument"

    aCollectionOfBytes do:[:o |
        self nextPutByte:o
    ]
!

stringUpToEnd
    "return a collection of the elements up-to the end"

    |answerStream|

    answerStream := WriteStream on:(String new:128).
    peekByte notNil ifTrue:[
        answerStream nextPut:(Character codePoint:peekByte).
        peekByte := nil.
    ].
    [
        [bits >= 8] whileTrue:[
            answerStream nextPut:(Character codePoint:((buffer bitShift:(8 - bits)) bitAnd:16rFF)).
            bits := bits - 8.
        ].
        atEnd ifTrue:[
            bits ~~ 0 ifTrue:[
                answerStream nextPut:(Character codePoint:(buffer bitAnd:16rFF)).
                bits := 0.
            ]
        ] ifFalse:[
            self fillBuffer.
        ].
    ] doWhile:[bits > 0].

    ^ answerStream contents
!

upToEnd
    "return a collection of the elements up-to the end"

    |answerStream|

    answerStream := WriteStream on:(ByteArray new:128).
    peekByte notNil ifTrue:[
        answerStream nextPut:peekByte.
        peekByte := nil.
    ].
    [
        [bits >= 8] whileTrue:[
            answerStream nextPut:((buffer bitShift:(8 - bits)) bitAnd:16rFF).
            bits := bits - 8.
        ].
        atEnd ifFalse:[
            self fillBuffer.
        ].
    ] doWhile:[bits > 0].

    ^ answerStream contents

    "Modified: / 30-09-2018 / 16:37:03 / Claus Gittinger"
! !

!BaseNCoder methodsFor:'subclass responsibility'!

fillBuffer
    "fill buffer with next n characters each representing m bits"

    ^ self subclassResponsibility.
!

flush
    "flush the remaining bits of buffer. 
     The number of bits in buffer is not a multiple of m, so we pad
     the buffer and signal that padding has been done via $= characters."

    ^ self subclassResponsibility.
!

nextPutByte:aByte
    "encode aByte on the output stream"

    ^ self subclassResponsibility.
! !

!BaseNCoder class methodsFor:'documentation'!

version
    ^ '$Header$'
!

version_CVS
    ^ '$Header$'
! !