Страницы

Поиск по вопросам

вторник, 15 января 2019 г.

Условная компиляция

А как в Java реализуется условная компиляция?
Скажем, у меня есть TCP сервер. Мне нужно на этапе отладки складывать в файл пришедшие пакеты. Потом, в релиз версии, этот функционал нужно будет отключить.
Как такое принято реализовывать? В Си это делается так
#ifdef DEBUG savePacket(); #endif
а в Java?


Ответ

Есть ещё один способ эмулировать макросы препроцессора - процессоры аннотаций + Java Compiler API. Этот способ сложнее, но не требует дополнительных библиотек и не выполняет никаких дополнительных действий в рантайме, все изменения в код вносятся в процессе его компиляции, между этапом разбора исходного текста и преобразования его в байткод.
Аннотация для активации отладочных действий:
package com.example;
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE) // Аннотация существует только до компиляции @Target(ElementType.METHOD) public @interface Debug { // Чтобы отключать аннотацию, не убирая её // но можно и без этого boolean value() default true; }
Жертва эксперимента:
import com.example.Debug;
public class SomeServer { private void savePacket() { System.out.println("Packet saved"); }
@Debug private void receivePacket() { System.out.println("Packet received"); }
public static void main(String[] args) { SomeServer srv = new SomeServer(); srv.receivePacket(); } }
Процессор
package com.example;
import com.sun.tools.javac.model.JavacElements; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.annotation.processing.SupportedOptions; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic;
@SupportedOptions("debug") @SupportedAnnotationTypes("com.example.Debug") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class DebugProcessor extends AbstractProcessor { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { if (roundEnv.processingOver()) return false;
// Получаем параметр указывающий на отладочную сборку boolean enabled = Boolean.parseBoolean( processingEnv.getOptions().getOrDefault("debug", "false"));
Context ctx = ((JavacProcessingEnvironment) processingEnv).getContext(); JavacElements elementUtils = (JavacElements) processingEnv.getElementUtils(); TreeMaker treeMaker = TreeMaker.instance(ctx);
// Обходим методы помеченные аннотацией @Debug for (Element element : roundEnv.getElementsAnnotatedWith(Debug.class)) { Debug debug = element.getAnnotation(Debug.class);
if (debug.value() && enabled) { JCTree.JCMethodDecl jcMethodDecl = (JCTree.JCMethodDecl) elementUtils.getTree(element);
// И добавляем в конец метода вызов другого метода treeMaker.pos = jcMethodDecl.pos; jcMethodDecl.body = treeMaker.Block(0, List.of( jcMethodDecl.body, treeMaker.Exec( treeMaker.Apply( List.nil(), treeMaker.Select( treeMaker.Ident( elementUtils.getName("this") ), elementUtils.getName("savePacket") ), List.nil() ) ) )); } }
return false; } }
Собираем аннотацию
$ javac -d build Debug.java
Собираем процессор
$ javac -cp build:"$JAVA_HOME/lib/tools.jar" -d build DebugProcessor.java
А теперь собираем SomeServer с использованием процессора
$ javac -cp build -processor com.example.DebugProcessor -Adebug=true SomeServer.java
Или можно собрать сервисный jar и положить в classpath, чтобы процессор использовался автоматически. Для этого DebugProcessor.class надо упаковать в jar вместе с файлом META-INF/services/javax.annotation.processing.Processor содержащим строку com.example.DebugProcessor. Тогда при сборке останется только указывать параметр -Adebug=true, когда нужны отладочные действия, и просто не указывать в другом случае.
P.S. По-хорошему надо было ещё метод savePacket тоже генерировать на лету, но я несколько притомился. Может дополню ответ в другой день. P.P.S. И возможно, напишу ещё третий ответ - про написание плагинов для компилятора.

Зачем индексировать внешний / вторичный (foreign key) ключ в MySQL?

Есть понятие "ограничитель" в MySQL. Например, это первичный ключ (ключ должен быть уникален, поэтому каждый раз производится поиск "а не было ли этого ключа в предыдущих строках?" для ускорения которого автоматом столбец с первичным ключом индексируется) и ключ-кандидат (та же логика, что и с первичным ключом). То есть, есть логика: будет поиск каждый раз поиск, значит, надо проиндексировать.
Теперь возьмем вторичный ключ. Он ссылается на таблицу-список. При создании новой строки в таблице надо проверить, а есть ли в таблице-списке этот вторичный ключ. То есть, вторичный ключ ограничивает.
ВОПРОС: зачем индексировать вторичный ключ? Это же, например, столбец с номерами преподавателей (а в таблице-списке расшифровывается какой препод какому номеру соответствует). Зачем что-то искать в столбце вида "1,2,5,1,3,10..."?


Ответ

Основная причина - возможность быстрой проверки на "осиротелые вторичные ключи" (orphaned rows) в случае удаления (DELETE) или смены (UPDATE - лучше так не делать) значений первичного ключа.
Пример:
table master
id (PK) val 1 11 2 22 3 33
table slave
id (PK) master_id (FK) val 1 1 111 2 1 112 3 2 311
при удалении:
delete from master where id = 3;
СУБД должна проверить - можно ли удалить PK == 3 для этого проверяются все таблицы содержащие вторичные ключи референцирующие столбец master.id и только если значение 3 не встречается ни в одном из вторичных ключей, тогда удаление разрешается.
Также существует возможность каскадного удаления (ON DELETE CASCADE) или перезаписи соответствующих значений вторичных ключей значением NULL (ON DELETE SET NULL) - в этих случаях нам тоже нужен быстрый (индексированный) доступ ко вторичным ключам.

Активити создается с нуля при возвращении из нового активити

Есть активити MainActivity, содержащее пару фрагментов прописанных через xml. Также есть еще одно активити, запускаемое из MainActivity. При возвращении из это нового активити в основное происходит следующее: сначала вызывается метод onDestroy основного активити который также убивает все фрагменты, а затем все создается заново как при простом запуске.
Может кто ни будь объяснить мне такое поведение андроида и как тогда сохранить между такими пересоздаваниями состояния активити и фрагментов. Например Текст или изображения, заданные не через xml, а в коде или массив данных?
ЗЫ. Руководствовался этой схемой, но вроде бы они все одинаковые в интернете и на всех схемах onDestroy вызывается только при уничтожении активити
// Старт приложения MainActivity.onCreate called
PlayerFragment.onCreate called PlayerFragment.onCreateView called
PlaylistFragment.onCreate called PlaylistFragment.onCreateView called
MainActivity.onResume called
// Запуск нового активити PreferencesActivity.onCreate called
// Жмем кнопку назад MainActivity.onDestroy called // Почему это а не onResume? PlayerFragment.onDestroy called PlaylistFragment.onDestroy called
MainActivity.onCreate called PlayerFragment.onCreate called PlayerFragment.onCreateView called PlaylistFragment.onCreate called PlaylistFragment.onCreateView called
MainActivity.onResume called


Ответ

Если вы делаете переход назад onBackPressed() или finish() покажите ваш манифест. Исходя из вашего ответа вы выставили определенную стратегию в launchMode, аналогичное поведение было бы если бы у вас стояло
android:launchMode="singleInstance"
или
android:launchMode="singleTask"
после этого вы заменили на android:launchMode="singleTop" и это standart condition, те обычное поведеине со стратегией не создавать новый экземплер если он на вершине стека.
Возмоно также вы делали переход с помощью намерениея Intent, здесь тогда все становится намного сложней. Вам кажется что вы сделали переход назад, но по факту вам ещё нужно следить за PreferencesActivity она у вас ещё в стеке, вы выставили singleTop и просто не пересозадил активити с вершиной на стеке, будьте внимательны использую задачи и стек переходов, это очень важно. Вы можете, сделать серфинг по приложению - бесшовным и плавным при этом экономить проц время и объем потреблям трафика.
Здесь есть все для того чтоб понять суть
https://developer.android.com/guide/components/tasks-and-back-stack?hl=ru
удачи

Django Admin, SELECT по полям

Есть модуль UserTask в которой есть очень тяжелые поля и они тормозят запрос, как можно убрать их из запроса для Django Admin.
Они не выводятся на страницу, но Django использует примерно такой запрос:
SELECT * FROM UserTask;
А нужно:
SELECT id, name, is_user FROM UserTask;
P.S. Нужно убрать их именно в админке, вот регистрация модели:
class UserTaskAdmin(admin.ModelAdmin): list_filter = ('id', 'name') list_display = ('id', 'name')
admin.site.register(UserTask, UserTaskAdmin)
кривой перевод на английский


Ответ

Вы можете переопределить get_queryset() и использовать only() метод:
class UserTaskAdmin(admin.ModelAdmin): list_filter = ('id', 'name') list_display = ('id', 'name')
def get_queryset(self, request): return self.model.objects.only('id', 'name')
Немного изменил оригинал, там использовался UserTask.objects.only('id', 'name'), за места self.model.objects.only('id', 'name'), что ограничивало повторное использования кода с другими моделями.
Оригинал от @neverwalkaloner

Проблема с array_rand

if ($sex == 1){ $skin = array(1,2,3,4,5); // Рандомные мужские скины $model = array_rand($skin); } if ($sex == 2){ $skin = array(1,2,3,4,5); // Рандомные женские скины $model = array_rand($skin); }
Помогите, при var_dump($model) пишет NULL


Ответ

$data['sex'] = '1';
switch ($data['sex']) { case 1: $skin = array(1,2,3,4,5); $model = $skin[array_rand($skin)]; break; case 2: $skin = array(1,2,3,4,5); $model = $skin[array_rand($skin)]; break; default: break; }
var_dump($model);
http://sandbox.onlinephpfunctions.com/ - вводим сюда и жмякаем на Execute Code

js, как в строке в каждом слове сделать первую букву в верхний регистр?

как в строке в каждом слове сделать первую букву в верхний регистр ? Точнее как сделать это регуляркой ?


Ответ

let str = 'исходная Строка дЛЯ теста'; let res = str.replace(/(?:^|\s)(\S)/ug, m => m.toUpperCase()); console.log(res);
(?:^|\s) - начало строки или пробельный символ (\S) - непробельный символ с захватом для замены
Насколько я знаю, в js regexp нет аналога \b для unicode.

В некоторых случаях, эта регулярка не подойдёт(из-за отсутствия пробелов): а,б,в
Проще всего использовать класс \p{L}
let str = 'исходная Строка дЛЯ теста(раз,два,три)'; let res = str.replace(/(?:^|[^\p{L}])(\p{L})/ug, m => m.toUpperCase()); console.log(res);

Шаблон регулярного выражения для поиска полного совпадения со строкой

Нужна помощь с регулярным выражением, а точнее с составлением шаблона, для поиска полного совпадения со строкой.
Пример:
import re
word = input() with open('first.txt', 'r') as file1: line = file1.read() if re.search(word, line): #т.к.ищется первое вхождение, то нужно как - то настроить шаблон, и я не понимаю как это сделать print('слово найдено')


Ответ

Правильно ли я вас понял, с учетом строгого соответствия?
import re
word = input() with open('first.txt', 'r') as file1: for line in file1.readlines(): if re.findall('^' + word + '$', line): print('Cлово найдено') else: print('Не найдено')