/*
Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
и других спецификаций для функционирования компактных приложений на языке
Java (мидлетов) в среде программного обеспечения Малик Эмулятор.
Copyright © 2016–2017, 2019–2023 Малик Разработчик
Это свободная программа: вы можете перераспространять ее и/или изменять
ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
общественной лицензии GNU.
Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<https://www.gnu.org/licenses/>.
*/
package malik.emulator.fileformats.text.mapped;
import java.io.*;
import malik.emulator.fileformats.*;
import malik.emulator.i18n.*;
import malik.emulator.util.*;
public final class PropertiesDecoder extends CustomKeyValueList
{
public static final String ENCODING = "ISO 8859-1";
private static int skipWhiteSpace(char[] stream, int offset) {
int i = offset;
for(int c; (c = stream[i]) > '\0' && c <= ' ' && c != '\r' && c != '\n'; i++);
return i;
}
private static int skipEmptySpace(char[] stream, int offset) {
int i = offset;
for(int c; (c = stream[i]) > '\0' && c <= ' '; i++);
return i;
}
private static int skipComment(char[] stream, int offset) {
int c;
int i = offset + 1;
for(; (c = stream[i]) > '\0' && c != '\r' && c != '\n'; i++);
if(c > '\0') i += c == '\r' && stream[i + 1] == '\n' ? 2 : 1;
return i;
}
private String parsed;
private final Object monitor;
public PropertiesDecoder() {
this.monitor = new Object();
}
public void loadFromInputStream(InputStream stream) throws IOException {
loadFromInputStream(stream, ENCODING);
}
public void loadFromInputStream(InputStream stream, String encoding) throws IOException {
int size;
char[] s;
byte[] b;
stream.read(b = new byte[(size = stream.available()) + 4], 0, size);
size = (s = Helper.byteToCharArray(b, 0, size + 4, encoding)).length;
synchronized(monitor)
{
clear();
try
{
String key;
String value;
for(int i = size >= 1 && s[0] == '\ufeff' ? 1 : 0; s[i = skipEmptySpace(s, i)] != 0; set(key, value))
{
int c;
while((c = s[i]) == '!' || c == '#') i = skipEmptySpace(s, skipComment(s, i));
if(i >= size || s[i] == 0) break;
if((c = s[i = skipWhiteSpace(s, parseKey(s, i))]) == ':' || c == '=') i = skipWhiteSpace(s, i + 1);
key = parsed.intern();
i = parseValue(s, i);
value = parsed.intern();
}
}
finally
{
parsed = null;
}
}
}
public void loadFromDataStream(ExtendedDataInputStream stream) throws IOException {
loadFromInputStream(stream, ENCODING);
}
public void loadFromDataStream(ExtendedDataInputStream stream, String encoding) throws IOException {
loadFromInputStream(stream, encoding);
}
private int parseKey(char[] stream, int offset) throws InvalidDataFormatException {
int i = offset;
StringBuilder result = new StringBuilder();
for(; ; )
{
int c;
int u;
for(; (c = stream[i]) > ' ' && c != '\\' && c != ':' && c != '='; i++);
if(i > offset) result.append(stream, offset, i - offset);
if(c != '\\') break;
switch(c = stream[++i])
{
default:
break;
case '\r':
if(stream[i + 1] == '\n') i++;
/* fall through */
case '\n':
i = skipWhiteSpace(stream, i + 1);
break;
case 'u':
i++;
u = 0;
for(int j = 4; j-- > 0; i++)
{
if((c = stream[i]) >= '0' && c <= '9')
{
u = (u << 4) + (c - '0');
}
else if(c >= 'A' && c <= 'F')
{
u = (u << 4) + (c - ('A' - 0x0a));
}
else if(c >= 'a' && c <= 'f')
{
u = (u << 4) + (c - ('a' - 0x0a));
}
else
{
break;
}
}
result.append((char) u);
break;
case '0':
i++;
result.append('\0');
break;
case 'b':
i++;
result.append('\b');
break;
case 't':
i++;
result.append('\t');
break;
case 'n':
i++;
result.append('\n');
break;
case 'f':
i++;
result.append('\f');
break;
case 'r':
i++;
result.append('\r');
break;
case '\\':
case '\"':
case '\'':
case '!':
case '#':
case ' ':
case ':':
case '=':
i++;
result.append((char) c);
break;
}
offset = i;
}
parsed = result.toString();
return i;
}
private int parseValue(char[] stream, int offset) {
int i = offset;
StringBuilder result = new StringBuilder();
for(; ; )
{
int c;
int u;
for(; (c = stream[i]) >= ' ' && c != '\\'; i++);
if(i > offset) result.append(stream, offset, i - offset);
if(c != '\\') break;
switch(c = stream[++i])
{
default:
break;
case '\r':
if(stream[i + 1] == '\n') i++;
/* fall through */
case '\n':
i = skipWhiteSpace(stream, i + 1);
break;
case 'u':
i++;
u = 0;
for(int j = 4; j-- > 0; i++)
{
if((c = stream[i]) >= '0' && c <= '9')
{
u = (u << 4) + (c - '0');
}
else if(c >= 'A' && c <= 'F')
{
u = (u << 4) + (c - ('A' - 0x0a));
}
else if(c >= 'a' && c <= 'f')
{
u = (u << 4) + (c - ('a' - 0x0a));
}
else
{
break;
}
}
result.append((char) u);
break;
case '0':
i++;
result.append('\0');
break;
case 'b':
i++;
result.append('\b');
break;
case 't':
i++;
result.append('\t');
break;
case 'n':
i++;
result.append('\n');
break;
case 'f':
i++;
result.append('\f');
break;
case 'r':
i++;
result.append('\r');
break;
case '\\':
case '\"':
case '\'':
i++;
result.append((char) c);
break;
}
offset = i;
}
parsed = result.toString();
return i;
}
}