Why Stringbuilder Stops Adding Elements After Using the Null Character

Why stringbuilder stops adding elements after using the null character?

The null character is a reserved character that indicates the end of the string, so if you print something followed by \u0000, nothing else after it will be printed.


Why stringbuilder stops adding elements after using the the null character? It's not really java, it is your terminal that treats \u0000 as the end of the string.

As someone says, with Windows the output can be .a. (i did not test it), this is because windows terminal works differently than Unix-based systems terminal. I'm pretty sure that if you run this code on a OSX machine you will get the same output as the one you get on linux

StringBuilder setLength()

The null character is a reserved character that indicates the end of the string, so if you print something followed by \u0000, nothing else after it will be printed.

Answer is here Why stringbuilder stops adding elements after using the null character?

Include null characters in C# StringBuilder string

Okay, so as Fredou pointed out, StringBuilder strings can have null characters, but something was still getting lost in between the C code and the C# code. Turns out that StringBuilder usually is a fine choice for receiving regular strings, but for byte arrays, using, well, a byte array works better.

Answer here: PInvoke char* in C DLL handled as String in C#. Issue with null characters

Conditionally adding text to a the middle of a string builder between 2 specified points

TL:DR
Your solution

@Override
public List<ProgramTable> getProgramsByAdvancedSearch(SearchModel searchContext) {
System.out.println(searchContext.getName());

String baseStatement = "SELECT p.ProgramId, c.CategoryId, c.CategoryName, p.ProgramName, p.Description, p.Active, p.BaseLocation\n" +
"FROM Program p\n" +
"INNER JOIN Ref_ProgramCategory r on r.ProgramId = p.ProgramId\n" +
"INNER JOIN Category c on c.CategoryId = r.CategoryId\n" +
"WHERE\n";

List<String> clauses = new ArrayList<>();
if(searchContext.getName() != null){
clauses.add("p.ProgramName = " + "'" + searchContext.getName() + "'" + "\n");
}

if(searchContext.getCategory() != null){
clauses.add("c.CategoryName = " + "'" + searchContext.getCategory() + "'" + "\n");
}

StringBuilder sb = new StringBuilder(baseStatement);
for (int i = 0; i < clauses.size(); i++) {
sb.append(clauses.get(i));
if (i < clauses.size() - 1) {
sb.append("AND\n");
}
}

sb.append("ORDER BY p.ProgramName;");

// The line break after WHERE is character number 241
// Would like to do the AND insert here

// Getting "java.lang.NullPointerException: null" here
Query query = entityManager.createNativeQuery(sb.toString());

List<ProgramTable> searchList = query.getResultList();

return searchList;
}

There's quite a few to unpack here, but I will just focus on the main problem (Adding "AND" in the middle of the string between two specific points). Since this method (procedurally) creates the SQL statement, you could do something like this:

if(searchContext.getName() != null){
sb.append("p.ProgramName = " + "'" + searchContext.getName() + "'" + "\n");
}

if(searchContext.getCategory() != null){
if (searchContext.getName() != null) {
sb.append("AND\n");
sb.append("c.CategoryName = " + "'" + searchContext.getCategory() + "'" + "\n");
}

And you can continue that pattern in as many if statements until you reach the ORDER by line. This is the simplest solution, albeit not the most elegant.

Alternatively, you can build the SQL statement completely and call the toString() method on the StringBuilder object and then figure out a way to insert "AND" into the resulting string in as many places you need to.

String sqlStatement = sb.toString();
// Call String's replace(...), replaceFirst(...), or replaceAll(...) here

Obviously, this is more difficult because you will have to know either the precise index where the insertion need to occur, the character sequence to replace, or the regular expression to match to make the insertion at the correct location. This is why the I believe the first option I gave you is the correct way to approach this.

Now.... If you break this down into separate string buffers, you will get more flexibility. For example, this

sb.append("SELECT p.ProgramId, c.CategoryId, c.CategoryName, p.ProgramName, p.Description, p.Active, p.BaseLocation\n" +
"FROM Program p\n" +
"INNER JOIN Ref_ProgramCategory r on r.ProgramId = p.ProgramId\n" +
"INNER JOIN Category c on c.CategoryId = r.CategoryId\n" +
"WHERE\n");

can be its own String since it is all hard-coded.

String baseStatement = "SELECT p.ProgramId, c.CategoryId, c.CategoryName, p.ProgramName, p.Description, p.Active, p.BaseLocation\n" +
"FROM Program p\n" +
"INNER JOIN Ref_ProgramCategory r on r.ProgramId = p.ProgramId\n" +
"INNER JOIN Category c on c.CategoryId = r.CategoryId\n" +
"WHERE\n";

Then, you can capture the subsequent items on a list:

List<String> clauses = new ArrayList<>();
if(searchContext.getName() != null){
clauses.add("p.ProgramName = " + "'" + searchContext.getName() + "'" + "\n");
}

if(searchContext.getCategory() != null){
clauses.add("c.CategoryName = " + "'" + searchContext.getCategory() + "'" + "\n");
}
// many other clauses

Lastly, you can iterate through the list of clauses and add "AND" in between them. This way, you don't need to check if the previous clause exist to add the "AND". This is especially helpful if you have more than two clauses and some clauses in the middle are missing.

StringBuilder sb = new StringBuilder(baseStatement);
for (int i = 0; i < clauses.size(); i++) {
sb.append(clauses.get(i));
if (i < clauses.size() - 1) {
sb.append("AND\n");
}
}

You iterate through the entire list, but stop inserting "AND" at size() - 1 so that you only add "AND" up to the second to last clause. After this, you are ready to call sb.toString().

P.S. There is no way to append() to a StringBuffer at some arbitrary point. You can only append to the end of the buffer. You can insert() or replace() but again, you will need to know the exact index location where you want to insert. This is not an easy task. For cases like that, it works well if you can place weird character patterns that you will know won't exist any other spot in your string and then you can replace all occurrences of that character sequence (pattern) with the string value you want; in this case, "AND".

Java null char in string

I'm going to assume you want to terminate the string at the first null character, as would happen in C. However, you can have null characters inside strings in Java, so they won't terminate the string. I think the following code will produce the behaviour you're after:

StringBuilder sb = new StringBuilder();
for (int i=0; i < mTypeSelection.length; i++){
if(mTypeSelection[i] > -1) {
sb.append(Character.forDigit(mTypeSelection[i], 10));
} else {
break;
}
}
String result = sb.toString();

StringBuilder - Append method stop working at a certain point

It was a big mistake of me, because i didn't know how Eclipse handles String variables in debugging mode. Append was working perfectly fine, but the String was too large for seeing it, and in a case like this, Eclipse shows the string until a certain point, after that, shows "...".

I selected the value of the textBuffer variable in order to change it, for trying to see the real value, and the value was right there, the full String of the file, from the first character to the last.

Huge thanks to @turo for the gigantic effort in helping me.

Java caller can find null character in bytebuffer returned from class but not when iterating through the bytebuffer in the class

You've got a ; after your if statement:

if((char)bbuf.get(i) != '\0');

This is functionally equivalent to

if((char)bbuf.get(i) != '\0') {
}

So, remove that ; and you'll be good to go.

Removed comma from last while working with StringBuilder

Just simple as

for (int i = startRange; i <= endRange; i++) {
sb.append(i);
if(i != endRange)
sb.append(",");
}

String is not adding to itself properly. But is able to compare

Your code has a number of problems with it, but the biggest one (and the one causing your issue) is that you are failing to take into account the actual number of bytes received. This causes null characters to exist in your string, which are then interpreted by some components as the end of the string. (.NET strings are counted, not null-terminated, but there's still lots of code out there that doesn't expect a null character in the middle of a string).

You should change your loop to look more like this:

StringBuilder fullResponse = new StringBuilder();
byte[] buffer = new byte[clientSocket.ReceiveBufferSize];
int bytesRead;

while ((bytesRead = serverStream.Read(buffer, 0, buffer.Length)) > 0)
{
string textRead = System.Text.Encoding.ASCII.GetString(buffer, 0, bytesRead);

fullResponse.Append(textRead);
}

xmlDoc.LoadXml(fullResponse.ToString());

Note: in addition to the issue with the null characters, the above version fixes another problem in your original case, which is that you were using string concatenation in a loop. Doing so can lead to serious performance and memory consumption problems; using StringBuilder is the appropriate way to concatenate text in a loop.

Note: as long as you are sure that your XML has only ASCII characters, the above is okay. But note that UTF8 and UTF16 are common these days, XML is technically one of those formats, and there's a lot of XML out there with non-ASCII characters in it. You might want to double-check the XML's encoding and make sure you're using the right one here.

Note: in the above code, it uses the end-of-stream indication (i.e. the read operation returns a byte count of 0) to terminate the loop. You definitely should check for this in any case. And StringBuilder has no way to check for containment of some specific text, so your previous approach is incompatible with the use of StringBuilder.

Now, you may be thinking to yourself "hey, but there might be more data! why not just accept the performance hit and use string concatenation with Contains()?". Well, the answer to that is: if it is possible for more data to be present after the end of the XML, then you need a more reliable way of detecting the end of the XML than looking for the close tag.

A Stream object has no way to know that it should stop reading your data at the end of the XML, and so the last read operation — the one with the XML's final close tag — can (and probably will) include a portion of the data that follows the XML.

That would mean you'd have two problems:

  1. Your XML is polluted with extra data
  2. That extra data, which is presumably important to something that occurs later, has already been read and won't be available to the code that needs it.

One way to address this would be to precede the XML in the data stream with a byte count, so that your loop knows how many bytes to read before stopping. Another way might be to convert the XML to binary and then to base64 (possibly compressing the binary first), and then delimiting the base64 data with some character you know isn't valid for base64 data (e.g. a space, newline, tab, etc.).

However you address it, you'll know the end of the XML by some mechanism other than the final close tag of the XML itself, and so can still use StringBuilder and won't have to search the string for that close tag.

I would offer more detail on that latter aspect, but you haven't provided enough information in your question to do so. If you need help with that specifically, please post a new question and make sure you provide the appropriate details. See https://stackoverflow.com/help/mcve and https://stackoverflow.com/help/how-to-ask for good advice on how to present your question better.



Related Topics



Leave a reply



Submit