Android Question How to return Null from Sub that returns String?

RB Smissaert

Well-Known Member
Licensed User
Longtime User
I wrote a simple class that does the same as TextReader.ReadLine. It returns a string, just like TextReader.ReadLine.
The return value from TextReader.ReadLine can be compared to Null, so for example in a loop:

B4X:
str = oTR.ReadLine
If str = Null Then Exit

Now I am trying to do the same with my ReadLine from my custom class, but so far been unable to do so.
It is not a major problem as I make my class ReadLine return an empty string and do:

B4X:
str = clsTR.ReadLine
If str.Length = 0 Then Exit

But I would like to make the 2 behave the same.
Tried lots of things, eg make it return Null, make it return an un-initialized object etc. but so far no success yet.
Any idea how this can be done?

RBS
 

Sabotto

Active Member
Licensed User
If you set the "ReadLine" method as Object you can return a string or a Null. You should show the ReadLine code.
Actually, even if it is declared as a string it can return Null. What error do you get?
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
If you set the "ReadLine" method as Object you can return a string or a Null. You should show the ReadLine code.
Actually, even if it is declared as a string it can return Null. What error do you get?
TextReader.ReadLine is declared as String return, but still for some reason the return value can be compared to Null.
If I make ReadLine return Null and do:

B4X:
Do While True
str = clsTR.ReadLine
If str = Null Then Exit
Loop

Then there is no error, but the loop doesn't exit.
The same code with TextReader is fine and the loop will exit.

This is the full code of that class:

B4X:
Sub Class_Globals
    
    Private mstrCharSet As String
    Private mbtEndOfLineByte As Byte
    Private bFirstArrayDone As Boolean
    Private lFilePos As Long
    Private lArrayPos As Long
    Private bNoEndOfLineFound As Boolean
    Private bFinalArrayDone As Boolean
    Private miMaxBytes As Int
    Private RAF As RandomAccessFile
    Private iBytes As Int
    Private arrBytes() As Byte
    Private strLine As String

End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(tFF As tFolderAndFile, strCharSet As String, btEndOfLineByte As Byte, lMaxBytes As Long)

    mstrCharSet = strCharSet
    mbtEndOfLineByte = btEndOfLineByte
    miMaxBytes = lMaxBytes
    RAF.Initialize(tFF.strFolder, tFF.strFile, True)
    lFilePos = 0
    lArrayPos = 0
    bNoEndOfLineFound = False
    bFinalArrayDone = False
    
    iBytes = Min(RAF.Size, miMaxBytes)
    
End Sub

Public Sub Close
    RAF.Close
    Dim arrBytes() As Byte 'clear some memory?
End Sub

Public Sub ReadLine As String 'ignore
    
    Dim i As Long
    Dim bRowReturned As Boolean
    
    Do While bRowReturned = False 'to avoid returning an empty string
        bRowReturned = False
        If bFinalArrayDone = False Then
            If bFirstArrayDone = False Then
                'first byte array to process
                Dim arrBytes(iBytes) As Byte
                iBytes = RAF.ReadBytes(arrBytes, 0, iBytes, 0)
                bNoEndOfLineFound = False
                bFirstArrayDone = True
            Else
                If bNoEndOfLineFound Then
                    Dim arrBytes(iBytes) As Byte
                    iBytes = RAF.ReadBytes(arrBytes, 0, iBytes, lFilePos)
                    lArrayPos = 0
                    bNoEndOfLineFound = False
                End If
            End If
        End If
    
        For i = lArrayPos To iBytes - 1
            If arrBytes(i) = mbtEndOfLineByte Then
                If i > 0 Then
                    If arrBytes(i - 1) = 13 Then
                        strLine = BytesToString(arrBytes, lArrayPos, (i - lArrayPos) - 1, mstrCharSet)
                    Else
                        strLine = BytesToString(arrBytes, lArrayPos, i - lArrayPos, mstrCharSet)
                    End If
                    lArrayPos = i + 1
                    bRowReturned = True
                    Return strLine
                End If
            End If
        Next
    
        If bFinalArrayDone Then
            bRowReturned = True
            Return "" 'can we return Null here as in TextReader?
        End If
    
        bNoEndOfLineFound = True
    
        If RAF.Size - lFilePos < miMaxBytes Then
            bFinalArrayDone = True
        Else
            lFilePos = lFilePos + lArrayPos
        End If
    Loop
    
End Sub

RBS
 
Upvote 0

Daestrum

Expert
Licensed User
Longtime User
I seem to recall that if you set a String = Null in B4J the String will actually contain 'null' - this is going back a few years so could have changed by now.
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
You should probably change the order, to this:
B4X:
If Null = str Then Exit
Tried that one, but that gives me this error:
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Object.equals(java.lang.Object)' on a null object reference

Just wondering what the exact return is from TextReader.ReadLine, to make it work to compare a string with Null.

RBS
 
Upvote 0

emexes

Expert
Licensed User
Just wondering what the exact return is from TextReader.ReadLine, to make it work to compare a string with Null.

and to also make StrVar.Length crash after the end-of-file StrVar = TextReader.ReadLine causes StrVar to be Null and not a String.

I have tried all sorts of casting, with no success. The only way I've found to get StrVar to be Null (distinct from "null") is to save a copy of it from an end-of-file TextReader.ReadLine, and even then a Return will cast it back to the Sub's declared return type of String ie "null".

The source code for TextReader.ReadLine would probably explain what and how, but burgered if I can find it. :cool:
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
and to also make StrVar.Length crash after the end-of-file StrVar = TextReader.ReadLine causes StrVar to be Null and not a String.

I have tried all sorts of casting, with no success. The only way I've found to get StrVar to be Null (distinct from "null") is to save a copy of it from an end-of-file TextReader.ReadLine, and even then a Return will cast it back to the Sub's declared return type of String ie "null".

The source code for TextReader.ReadLine would probably explain what and how, but burgered if I can find it. :cool:
>>
and also that makes StrVar.Length crash after the end-of-file strVar =

Did you find a bug there?
How to reproduce this?

RBS
 
Upvote 0

emexes

Expert
Licensed User
and also that makes StrVar.Length crash after the end-of-file strVar =

Did you find a bug there?

It is more a consequence of design than a bug. If you know to sidestep the issue, then it's not a problem.

How to reproduce this?

Use .Length on String variable that is actually the end-of-file Null from .ReadLine :

B4X:
Dim Reader As TextReader
Reader.Initialize(File.OpenInput(File.InternalDir, "1.txt"))

Dim line As String
line = Reader.ReadLine
Do While line <> Null
    Log(line)
    line = Reader.ReadLine
Loop
Reader.Close
 
Log(line.Length)    'hold on to your hat here, because line is Null, not String
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
It is more a consequence of design than a bug. If you know to sidestep the issue, then it's not a problem.



Use .Length on String variable that is actually the end-of-file Null from .ReadLine :

B4X:
Dim Reader As TextReader
Reader.Initialize(File.OpenInput(File.InternalDir, "1.txt"))

Dim line As String
line = Reader.ReadLine
Do While line <> Null
    Log(line)
    line = Reader.ReadLine
Loop
Reader.Close
 
Log(line.Length)    'hold on to your hat here, because line is Null, not String
OK, I get it. I don't think my class returns the final Null byte, but will check.

There was a bug in the posted code as it missed the final line if there was no line break at the end of the file.
Should be like this, but will test further:

B4X:
Sub Class_Globals
    
    Private mstrCharSet As String
    Private mbtEndOfLineByte As Byte
    Private bFirstArrayDone As Boolean
    Private lFilePos As Long
    Private lArrayPos As Long
    Private bNoEndOfLineFound As Boolean
    Private bFinalArrayDone As Boolean
    Private bFinalLineDone As Boolean
    Private miMaxBytes As Int
    Private RAF As RandomAccessFile
    Private iBytes As Int
    Private arrBytes() As Byte
    Private strLine As String

End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(tFF As tFolderAndFile, strCharSet As String, btEndOfLineByte As Byte, lMaxBytes As Long)

    mstrCharSet = strCharSet
    mbtEndOfLineByte = btEndOfLineByte
    miMaxBytes = lMaxBytes
    RAF.Initialize(tFF.strFolder, tFF.strFile, True)
    lFilePos = 0
    lArrayPos = 0
    bNoEndOfLineFound = False
    bFinalArrayDone = False
    bFinalLineDone = False
    
    iBytes = Min(RAF.Size, miMaxBytes)
    
End Sub

Public Sub Close
    RAF.Close
    Dim arrBytes() As Byte 'clear some memory?
End Sub

Public Sub ReadLine As String 'ignore
    
    Dim i As Long
    Dim bRowReturned As Boolean
    
    Do While bRowReturned = False 'to avoid returning an empty string
        bRowReturned = False
        If bFinalArrayDone = False Then
            If bFirstArrayDone = False Then
                'first byte array to process
                Dim arrBytes(iBytes) As Byte
                iBytes = RAF.ReadBytes(arrBytes, 0, iBytes, 0)
                bNoEndOfLineFound = False
                bFirstArrayDone = True
            Else
                If bNoEndOfLineFound Then
                    Dim arrBytes(iBytes) As Byte
                    iBytes = RAF.ReadBytes(arrBytes, 0, iBytes, lFilePos)
                    lArrayPos = 0
                    bNoEndOfLineFound = False
                End If
            End If
        End If
    
        For i = lArrayPos To iBytes - 1
            If arrBytes(i) = mbtEndOfLineByte Then
                If i > 0 Then
                    If arrBytes(i - 1) = 13 Then
                        strLine = BytesToString(arrBytes, lArrayPos, (i - lArrayPos) - 1, mstrCharSet)
                    Else
                        strLine = BytesToString(arrBytes, lArrayPos, i - lArrayPos, mstrCharSet)
                    End If
                    lArrayPos = i + 1
                    bRowReturned = True
                    Return strLine
                End If
            End If
        Next
    
        If bFinalLineDone Then
            bRowReturned = True
            Return "" 'can we return Null here as in TextReader?
        End If
    
        bNoEndOfLineFound = True
    
        If RAF.Size - lFilePos < miMaxBytes Then
            bFinalArrayDone = True
            'Added this to avoid missing a final line
            If lFilePos < RAF.Size - 1 Then
                strLine = BytesToString(arrBytes, lArrayPos, iBytes - lArrayPos, mstrCharSet)
                Log("final line: " & strLine)
                bFinalLineDone = True
                bRowReturned = True
                Return strLine
            End If
        Else
            lFilePos = lFilePos + lArrayPos
        End If
    Loop
    
End Sub

RBS
 
Upvote 0

emexes

Expert
Licensed User
Try
B4X:
If str Is Null Then Exit

The problem is returning that str variable whilst it is a Null.

The Return statement converts the return value to the Sub's return type, which in the case of Sub xxx As String, converts not-a-String value Null to valid 4-character String "null".

I'm interested just to know how TextReader.ReadLine returns a Null as a String, and @RB Smissaert is interested (I think) so that his hand-crafted text file reader can be a drop-in replacement for TextReader.ReadLine.
 
Upvote 0

Daestrum

Expert
Licensed User
Longtime User
It's probably the fact that TextReader is written in java which can return a null value as a String.
(B4J code but principle is the same)
B4X:
...
    If (Me).As(JavaObject).RunMethod("test",Null) <> Null Then
        Log("Not null")
    Else
        Log("It's null")
    End If
...

#if java
public static String test(){
    String s = null;
    System.out.println("s is null = " + (s==null));
    return s;
}
#End If
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
Well, just found another bug:

ID
1
2
3

4
5

Will miss the last 2 lines.
Will fix later.

RBS
Actually this is another reason why doing:

B4X:
If strLine.Length = 0 Then Exit

Is not a great solution.
Doing instead eg:

B4X:
If strLine = "Nil else found" Then Exit

Will be fine, but it will reduce the speed very slightly.

RBS
 
Upvote 0
Top