Думаю вам не раз на этапе собеседования приходилось проходить онлайн тест на одном из популярных ресурсов: Codility, Hackerrank.
Методика простая - ознакомившись с условием задачи, вписать свой решение в соответствующей textarea, предварительно выбрав язык. И если вы выбираете javascript, то ваш код обработается сразу браузером.
A как обрабатывается ваш java код? Это нужно сделать на стороне сервера. Средствами javax.tools.
Идея динамической компиляции не нова и используется в Java EE, когда наши jsp - страницы компилируются в сервлеты и загружаеются в сервлет-контейнер. Но писать этот код самому не приходится.
Легким гуглением я нашел пример динамической компиляции "в лоб", который немного приоткрыл завесу тайны. Здесь уже видны основные принципы и API, с которым нужно работать.
DiagnosticCollector diagnostics = new DiagnosticCollector();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
// This sets up the class path that the compiler will use.
// I've added the .jar file that contains the DoStuff interface within in it...
List optionList = new ArrayList();
optionList.add("-classpath");
optionList.add(System.getProperty("java.class.path") + ";dist/InlineCompiler.jar");
Iterable compilationUnit
= fileManager.getJavaFileObjectsFromFiles(Arrays.asList(helloWorldJava));
JavaCompiler.CompilationTask task = compiler.getTask(
null,
fileManager,
diagnostics,
optionList,
null,
compilationUnit);
- Предупреждения и ошибки - они у нас помещаются в DiagnosticCollector
diagnostics - StandardJavaFileManager - файловый менеджер, берем "стандарт" из compiler.getStandardFileManager(diagnostics, null, null)
- compilationUnit - набор файлов для компиляции, которые нам предоставляет наш файловый менеджер(в примере файл мы сами записали предварительно через io)
- optionList co всеми любимым classpath, но можно передавать любые параметры, в том числе версию java
- JavaCompiler, который мы получаем из ToolProvider.getSystemJavaCompiler(), скармливаем ему все перечисленное выше - получаем JavaCompiler.CompilationTask, вызываем call() для компиляции и получаем скомпилированный класс либо же ошики в DiagnosticCollector
Не очень изящный, но вполне рабочий пример. Из недостатков, мы физически создаем *.java файл, и если там код, который нескомпилируется, то у нас будут проблемы с компиляцией всего приложения пока мы этот файл опять таки физически не удалим
Потом я нашел более гибкое решение. Отличная статья.
Самый главный момент здесь - это имплементация FileManagerImpl расширяющая ForwardingJavaFileManager. FileManagerImpl отвечает за привязку имен классов к их исходному либо уже скомпилированному коду. Делается это таким образом - используется кастомная имплементация SimpleJavaFileObject - JavaFileObjectImpl. В JavaFileObjectImpl находится ресурс - исходник или байткод и помечается это флажком Kind.SOURCE или же Kind.CLASS.
А теперь давайте рассмотрим конкретную ситуацию
Есть задача: мы получаем число и нужно его перевести в двоичный вид и найти наибольшее колиство 0 между 1. Нам показан пустой пример, куда нужно вписать имплементацию:
class SolutionImpl implements Solution {
public int solution(int N) {
// write your code in Java SE 8
}
}
Сделаем следующие поправки, для упрощения. Программист, решая задачу не использует дополнительных библиотек. И таким образом наши входные данные - это только тело метода.
Пример тела метода:
String code = " String binary = Integer.toBinaryString(N);\n"
+ " int max = 0;\n"
+ " int temp = 0;\n"
+ " for (char c : binary.toCharArray()) {\n"
+ " if (c == '0') {\n"
+ " temp++;\n"
+ "\n"
+ " } else {\n"
+ " max = Math.max(max, temp);\n"
+ " temp = 0;\n"
+ " }\n"
+ " }\n"
+ " return max;\n";
На нашей стороне в свою очередь уже имеется интерфейс Solution, шаблон будущего кода класса, куда подставляется тело метода (можно также пакет и имя класса).
package $packageName;
public class $className
implements com.getman.grader.Solution {
public int solution(int N) {
$expression
}
}
private String fillTemplate(String packageName, String className, String expression)
throws IOException {
if (template == null)
template = readTemplate();
// simplest "template processor":
String source = template.replace("$packageName", packageName)//
.replace("$className", className)//
.replace("$expression", expression);
return source;
}
Далее наш String отправляется на компиляцию, если проходит успешно мы получаем Class<Solution>, вызываем newInstance() и отправляем экземляр нашего класса на тестирование и получаем результаты. В своем примере я вывожу их просто на экран
0 -> correct
1 -> correct
2 -> correct
1041 -> correct
601 -> correct
600 -> correct
Теперь работа грейдером для меня более понятна. Если есть вопросы - оставляйте комментарии, с радостью отвечу :) Рабочий пример можно скачать здесь
.
Оличная статья
ОтветитьУдалитьОтлично, спасибо!!
ОтветитьУдалить