![image](https://habrastorage.org/getpro/habr/post_images/029/6bd/882/0296bd882063d7ada6b6676b33d06e09.png)
Давным давно… ну как давно? вчера! (С), то есть пару лет назад, портировал я одну скромную библиотечку с Java на .NET. И не просто на .NET, а на версию 1.1.
Подход известен — берем в зубы Sharpen (или конвертер из вижуалстудии 2003 года, кому что нравится), и далее — лобзиком.
Про очевидности с итераторами, структурами ("System.Drawing.Size это не объект") и потоками рассказывать не буду — банальщина. А вот про некоторые сюрпризы — добро пожаловать.
Культур-мультур, кто тебя выдумал?
![image](https://habrastorage.org/getpro/habr/post_images/c07/98f/9e6/c0798f9e62071b92bac2cb0b50874787.png)
Начнем с простейшего, для затравки. С перевода чисел в строки и обратно.
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](https://habrastorage.org/getpro/habr/post_images/643/5b2/5a6/6435b25a61899aafc80d5cddf38ff508.png)
Исходная библиотечка
Пришлось изобрести свой 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](https://habrastorage.org/getpro/habr/post_images/e97/e11/17f/e97e1117f59454e7d4c6165c9f8767e9.png)
Следующая грабля оказалась с парсером 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;
}
Вот так вот массовыми заменами в текстовом редакторе решился вопрос с портированием парсера. С «несложно» — я таки сел в лужу, да. День убил, да как убил — на мутную работу.
Символы и числа, и неочевидности вокруг них
![image](http://openclipart.org/image/250px/svg_to_png/172244/Birne.png)
Следующим этапом была поддержка 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
![image](http://openclipart.org/image/250px/svg_to_png/172073/exploration.png)
Следующий момент, который доставил много неприятных минут — это всем известная банальщина с коллекциями. Однако ж Неизвестный Автор(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](http://openclipart.org/image/250px/svg_to_png/172104/sextant.png)
И под занавес — хочу порадовать еще одним перлом — работой со шрифтами. Ибо пол-библиотеки только и занимается магией — подстановки, подборы, переключения слева-направо в справа-налево и наоборот, арабский, китайский, автоматическая смена 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 — каждой твари по паре. Весьма неочевидные трюки.
Укротить эту рыбу было совсем непросто. Клянусь моей треуголкой! (С)
Итоги
![image](http://openclipart.org/image/250px/svg_to_png/172012/prize.png)
Увы — в отведенную неделю позорно не вложился, прихватил еще и выходные, и пару дней за ними. И потом еще пару раз возвращался с вылавливанием неочевидных грабель.
Клиент потом отправил мои эксперименты авторам, и у них вскоре появилась официальная версия под дотнет. Никакого отношения к дальнейшей разработке и т. д. я не имею.
Общие впечатления от знакомства с оригинальным кодом были весьма положительными. В наше время всеобщей вебкитизации сделать свой рендер HTML 4 с элементами HTML 5, с поддержкой CSS 2.1, и все это pure Java — это real old school.
Ни одна зверушка в ходе портирования не пострадала.
Надеюсь, приведенная информация будет кому-то полезной.