О том как я Java в дотнет портировал

в 14:09, , рубрики: .net, java, Программирование

image

Давным давно… ну как давно? вчера! (С), то есть пару лет назад, портировал я одну скромную библиотечку с Java на .NET. И не просто на .NET, а на версию 1.1.

Подход известен — берем в зубы Sharpen (или конвертер из вижуалстудии 2003 года, кому что нравится), и далее — лобзиком.

Про очевидности с итераторами, структурами ("System.Drawing.Size это не объект") и потоками рассказывать не буду — банальщина. А вот про некоторые сюрпризы — добро пожаловать.

Культур-мультур, кто тебя выдумал?

image
Начнем с простейшего, для затравки. С перевода чисел в строки и обратно.

CultureInfo oldCulture   = CurrentThread.CurrentCulture;
CultureInfo oldCultureUI = CurrentThread.CurrentUICulture;
			
CurrentThread.CurrentCulture = new CultureInfo("en-US");
CurrentThread.CurrentUICulture = CurrentThread.CurrentCulture;

try {
	render_impl();
}
finally  {
	CurrentThread.CurrentCulture = oldCulture;
	CurrentThread.CurrentUICulture = oldCultureUI;
}

Ну и зачем этот костыль?

А чтобы 100500 раз писать неправильно, зачем же еще. Вот так чтобы не писать

(float) System.Double.Parse(foo_string, NumberStyles.Float, new CultureInfo("en-US"));

и

new Double(foo_number).ToString(new CultureInfo("en-US"));

Автор лентяй и тунеядец? Отож. А что вы хотели — срок неделя, а радости привалило

find ~/pd4ml/sources  -type f -print | xargs cat | wc -l
420242

Зачем этот хак нужен? Потому что надо генерить PDF, а акробат ридер как-то не уважает национальные запятые в виде разделителей дробной и целой части. Да и в HTML / CSS эти же локалеспецифичности как-то не прижились, хе-хе.

Особенности паранойи в архитектуре

image
Исходная библиотечка унутре содержит неонку построена очевидным образом. Раз надо выдавать пачку разных файлов, а также печатать на принтере и показывать на экране — то отнаследован java.awt.Graphics2D и в наличии пачка PDFDevice, RTFDevice и т. д. Очевидно, куда я пошел что в дотнете этот способ не работает. Дружно скажем спасибо тому индусу, который воткнул sealed куда попало.

Пришлось изобрести свой abstract MegaDevice, в коий принести контракт System.Drawing.Context и от него уже наследоваться. Времени потрачено на это было — караул. И это при том, что вывод на экран и на принтер просто пришлось выбросить.

Закономерен вопрос — а почему был взят дотнетовский контракт, а не от java?

Ответ тут прост — конвертилка сама мастерски поменяла

    g.drawString( prefix + index + " ", x, y );

на

	g.DrawString( prefix + index + " ", 
		SupportClass.GraphicsManager.manager.GetFont(g), 
		SupportClass.GraphicsManager.manager.GetBrush(g), 
		new PointF(x, y));

вот и пришлось под нее подстраиваться. Исходный код принципиально не менялся — а то потом не пойми чего портируется, то ли исходные глюки, то ли привнесенные баги.

Конечные автоматы и signed/unsigned

image
Следующая грабля оказалась с парсером CSS. Точно такого же (com.steadystate.*) на момент портирования на дотнете — не существовало. Да, я знаю — правильно было бы переписать грамматику с JavaCC на ANTLR. Часика эдак за два-три, с учетом сроков и времени, потерянного на предыдущем шаге.

Но моя лень мне подсказала другой вариант — сконвертировать автогенеренную портянку. Она всего-то килобайт триста, несложно ж. И тут — поперло.

Вот такое, биты — они разные:

private static long URShift(long number, int bits) {
    if ( number >= 0)
        return number >> bits;
    else
        return (number >> bits) + (2L << ~bits);
}

и такое — не забываю кастить длинное со знаком в длинное без знака, заодно убираю нагенеренных Identity:

if (((ulong)active0 & (ulong)(0x8000103000000000L)) != 0L) {
    jjmatchedKind = 66;
    return 577;
}

Освежаю в памяти goto и break label, а то генерилка их лепит куда попало:

EOFLoop :
for (; ; ) {
    for (; ; )  {
        ...
        else if ((jjtoSkip[URShift(jjmatchedKind, 6)] & (1L << (jjmatchedKind & 63))) != 0L) {
            if (jjnewLexState[jjmatchedKind] != - 1)
                curLexState = jjnewLexState[jjmatchedKind];
            goto EOFLoop;
        }
        ...
    }
}

И переписываю функцию FillBuf, а то streams — они тоже имеют ма-a-ленькие различия. От которых автомату плохеет и он никогда не достигает конца файла. Если быть совсем точным — доходит до конца и продолжает пытаться читать:

int i;
try {
    if(inputStream == null)
        throw new System.IO.IOException("EOF");

    i = inputStream.ReadBlock(buffer, maxNextCharInd, available - maxNextCharInd);
    if (i <= 0 /* was == -1 */) {
        inputStream.Close();
        inputStream = null;
        throw new IOException();
    }
    else
        maxNextCharInd += i;
    return ;
}
catch (IOException e)	{
    --bufpos;
    backup(0);
    if (tokenBegin == - 1) tokenBegin = bufpos;
    throw e;
}

Вот так вот массовыми заменами в текстовом редакторе решился вопрос с портированием парсера. С «несложно» — я таки сел в лужу, да. День убил, да как убил — на мутную работу. «Выражение хрень было заменено на фигня 132 раза, нажми ОК».

Символы и числа, и неочевидности вокруг них

image
Следующим этапом была поддержка Unicode и right-to-left. И тут меня ждал сюрприз в виде java.lang.Character. Мало того, что между ним и System.Char общего только в названии. Так еще и в дотнете

public static int digit(int codePoint, int radix)

в эпсилон-окрестности не просматривается (и не только).

Пятнадцатиминутное гугление так и не подсказало, на что этот метод можно заменить, и в ход пошла большая дубинка. А именно, был спортирован кусок java.lang.* в дотнет, относящийся к этой функции. То есть java.lang.Character и прилегающие к нему java.lang.CharacterData* со всеми внутренними таблицами.

(иронизирует) И кто сказал, что Java — это негодный опенсорц?

По такому же сценарию был перенесен java.math.BigDecimal. Эти мааленькие различия — они не радуют, особенно если в коде много где наступаешь. Да, я уже говорил — я лентяй и тунеядец? Это снова оно.

С BigDecimal понадобилась магия setScale и правоверный toString():

	BigDecimal d1 = new BigDecimal(currentLineThickness / 2 + x );
	BigDecimal d2 = new BigDecimal(currentLineThickness / 2 + y );
	d1 = d1.SetScale(4, BigDecimal.ROUND_UP);
	d2 = d2.SetScale(4, BigDecimal.ROUND_UP);
	buf.Append(d1.ToString()).Append(" ");
	buf.Append(d2.ToString()).Append(" mn");

Hashtable и ToString

image
Следующий момент, который доставил много неприятных минут — это всем известная банальщина с коллекциями. Однако ж Неизвестный Автор(TM) мастерски сделал ход конем — он построил кэш вокруг HTML аттрибутов, и в качестве ключа использовал ToString().

(мысль вслух) не люблю хашмапы в хашмапу складывать, но вот многим нравится. То ли просветления еще не достиг, то ли еще что, но — не люблю.

Очевидным образом, аттрибуты это набор пар имя = значение. То есть — HashMap. Что делает java с ее toString()? печатает содержимое коллекции. А в дотнете — ну вы знаете. Indian style coding detected?

Решение было очевидным и простым

	public static String ToString(Hashtable map) {
		String hta_str = "[";
		IEnumerator ie = map.Keys.GetEnumerator();
		while(ie.MoveNext()) {
			Object o = map[ie.Current];
			if(o is Array) {
				Array al = (Array)o;
				hta_str += ie.Current + "=[";
				for(int jjk=0;jjk<al.Length;++jjk)
					hta_str += al.GetValue(jjk) + ",";
				hta_str += "]";
			}
			else 
				hta_str += ie.Current + "=" + o + ",";
		}
		hta_str += "]";
		return hta_str;
	}

Я даже не заморачивался чтобы скопировать «как в жаве» — всего лишь нужно было какое-то отображение в текст взамен Hashtable@address.

Сладкое — работа со шрифтами

image
И под занавес — хочу порадовать еще одним перлом — работой со шрифтами. Ибо пол-библиотеки только и занимается магией — подстановки, подборы, переключения слева-направо в справа-налево и наоборот, арабский, китайский, автоматическая смена Arial на Mincho и прочее и прочее.

Без пол-литры такое вкурить — это было что-то с чем-то.

Кстати, безмерно доставляет, что сама Windows OpenType ест со свистом, а вот .NET — увы-с. Хотя он же в вариации «сервелат» сильверлайт — таки да. И это — в 4.0, что уж говорить про архаику 1.1.

Вот к примеру пришлось полностью переписать код по доставанию шрифтов из указанного каталога. В Java там были свои безумства, тоже не радовали.

private static void listFonts(DirectoryInfo dd, String mask, Hashtable listOfFontFaces) {
	FileInfo[] files = dd.GetFiles(mask);
	FontFamily newFN = null;
	bool TtC = mask.Equals("*.ttc");

	Hashtable foundFonts = new Hashtable();
	System.Drawing.Text.PrivateFontCollection tmpPfc = null;
	for(int jjk=0;jjk<files.Length;++jjk) {
		tmpPfc = new System.Drawing.Text.PrivateFontCollection();
		try {
			tmpPfc.AddFontFile(files[jjk].FullName);
		} 
		catch(Exception /* e */) {}

		for(int jjv=0;jjv<tmpPfc.Families.Length;++jjv) {
			newFN = tmpPfc.Families[jjv];
			FontFamilySpec spec = (FontFamilySpec)foundFonts[newFN.Name];
			if(spec == null) {
				spec = new FontFamilySpec(newFN.Name);
				foundFonts[spec.Family] = spec;
			}

			String fn = files[jjk].Name;
			if(TtC) fn += "_" + jjv;
			spec.Files.Add(fn);
			spec.Files.Sort();
		}
		tmpPfc.Dispose();
	}
	...
}

код очевидно грязен, недокументироан и вызывает оторопь, зато работает.

А вот еще одна магия:

char c = content[ 0 ];
UnicodeCategory prevUB = Char.GetUnicodeCategory(c);
int lastCutPosition = 0;
for ( int i = 1; i < content.Length; i++ ) {
	c = content[i];
	if ( c == 0xAD || c == ' ') // soft hyphen
		continue;

	byte dirct = java.lang.Character.getDirectionality((int)c);
	if ( dirct == java.lang.Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC || 
		dirct == java.lang.Character.DIRECTIONALITY_RIGHT_TO_LEFT ||
		dirct == java.lang.Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING ||
		dirct == java.lang.Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE ) 
	// sick of fighting with. 
	// the only expected conflict is a combining of chinese and arabic in a single paragraph
		break;

	UnicodeCategory ub = Char.GetUnicodeCategory(c);
	if ( ub != prevUB ) {
		String pattern = "";
		for ( int j = 0; j < content.Length; j++ ) {
			char ch = content[j];
			if ( prevUB == Char.GetUnicodeCategory(c) ) 
				pattern += ch;
		}
		...
		prevUB = ub;
	}					
}
...

Тут вообще компот вышел — и дотнетовские средства, и портированные внаглую из исходников Java6 — каждой твари по паре. Весьма неочевидные трюки.

Итоги

image
Увы — в отведенную неделю позорно не вложился, прихватил еще и выходные, и пару дней за ними. И потом еще пару раз возвращался с вылавливанием неочевидных грабель.

Клиент потом отправил эту гору костылей и подпорок авторам, и у них вскоре появилась официальная версия под дотнет. Никакого отношения к разработке и т. д. я не имею, так что о сегодняшнем состоянии ничего сказать не могу.

Ни одна зверушка в ходе портирования не пострадала.

Надеюсь, приведенная информация будет кому-то полезной.

Автор: viklequick

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js