
Давным давно… ну как давно? вчера! (С), то есть пару лет назад, портировал я одну скромную библиотечку с Java на .NET. И не просто на .NET, а на версию 1.1.
Подход известен — берем в зубы Sharpen (или конвертер из вижуалстудии 2003 года, кому что нравится), и далее — лобзиком.
Про очевидности с итераторами, структурами ("System.Drawing.Size это не объект") и потоками рассказывать не буду — банальщина. А вот про некоторые сюрпризы — добро пожаловать.
Культур-мультур, кто тебя выдумал?

Начнем с простейшего, для затравки. С перевода чисел в строки и обратно.
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 эти же локалеспецифичности как-то не прижились, хе-хе.
Особенности паранойи в архитектуре

Исходная библиотечка
Пришлось изобрести свой 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

Следующая грабля оказалась с парсером 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; }
Вот так вот массовыми заменами в текстовом редакторе решился вопрос с портированием парсера. С «несложно» — я таки сел в лужу, да. День убил, да как убил — на мутную работу.
Символы и числа, и неочевидности вокруг них

Следующим этапом была поддержка 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(" m\n");
Hashtable и ToString

Следующий момент, который доставил много неприятных минут — это всем известная банальщина с коллекциями. Однако ж Неизвестный Автор(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.
Сладкое — работа со шрифтами

И под занавес — хочу порадовать еще одним перлом — работой со шрифтами. Ибо пол-библиотеки только и занимается магией — подстановки, подборы, переключения слева-направо в справа-налево и наоборот, арабский, китайский, автоматическая смена Arial на Mincho и прочее и прочее.
Без пол-литры такое вкурить — это было что-то с чем-то.
Кстати, безмерно доставляет, что сама Windows OpenType ест со свистом, а вот .NET — увы-с. Хотя он же в вариации
Вот к примеру пришлось полностью переписать код по доставанию шрифтов из указанного каталога.
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 — каждой твари по паре. Весьма неочевидные трюки.
Укротить эту рыбу было совсем непросто. Клянусь моей треуголкой! (С)
Итоги

Увы — в отведенную неделю позорно не вложился, прихватил еще и выходные, и пару дней за ними. И потом еще пару раз возвращался с вылавливанием неочевидных грабель.
Клиент потом отправил мои эксперименты авторам, и у них вскоре появилась официальная версия под дотнет. Никакого отношения к дальнейшей разработке и т. д. я не имею.
Общие впечатления от знакомства с оригинальным кодом были весьма положительными. В наше время всеобщей вебкитизации сделать свой рендер HTML 4 с элементами HTML 5, с поддержкой CSS 2.1, и все это pure Java — это real old school.
Ни одна зверушка в ходе портирования не пострадала.
Надеюсь, приведенная информация будет кому-то полезной.
