Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/main/java/ru/spbau/mit/Implementor.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
*/
public interface Implementor {


/**
* Имплементор по данной папке с class файлами java ищет в ней java класс, которые требуется реализовать.
* Класс записывает реализацию в папку `outputDirectory` (учитывая пакеты)
Expand All @@ -52,7 +53,7 @@ public interface Implementor {


/**
* Имплементор ищет java класс/интерфейс из стандартной библеотеки, которые требуется реализовать.
* Имплементор ищет java класс/интерфейс из стандартной библиотеки, которые требуется реализовать.
* Класс записывает реализацию в папку `outputDirectory`.
* Реализация должна находится в default пакете.
*
Expand Down
192 changes: 192 additions & 0 deletions src/main/java/ru/spbau/mit/SimpleImplementor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package ru.spbau.mit;


import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.stream.Collectors;

public class SimpleImplementor implements Implementor {

private static final String TAB = " ";

private String outputDirectory;

public SimpleImplementor(String outputDirectory) {
this.outputDirectory = outputDirectory;
}

@Override
public String implementFromDirectory(String directoryPath, String className) throws ImplementorException {
Class<?> classToExtend = loadClassFromDirectory(directoryPath, className);
return implement(classToExtend, true);
}

@Override
public String implementFromStandardLibrary(String className) throws ImplementorException {
Class<?> classToExtend = getClassFromStandardLibrary(className);
return implement(classToExtend, false);
}

private String implement(Class<?> classToExtend, boolean packageNeeded) throws ImplementorException {
File outputFile = createOutputFile(classToExtend, packageNeeded);
checkClassNotFinal(classToExtend);
return doGenerate(classToExtend, outputFile, packageNeeded);
}

private static void checkClassNotFinal(Class<?> clazz) throws ImplementorException {
if (Modifier.isFinal(clazz.getModifiers())) {
throw new ImplementorException("Class is final.");
}
}

private String doGenerate(Class<?> classToExtend, File outputFile, Boolean packageNeeded)
throws ImplementorException {
try (BufferedWriter output = new BufferedWriter(new FileWriter(outputFile))) {
generatePackage(classToExtend, packageNeeded, output);
generateGeneratedWarning(output);
generateClassBegin(classToExtend, output);
for (Method method : classToExtend.getMethods()) {
if (!Modifier.isStatic(method.getModifiers()) && !Modifier.isFinal(method.getModifiers())) {
generateMethod(output, method);
}
}
for (Method method : classToExtend.getDeclaredMethods()) {
if (Modifier.isProtected(method.getModifiers())
&& !Modifier.isStatic(method.getModifiers())
&& !Modifier.isFinal(method.getModifiers())) {
generateMethod(output, method);
}
}
generateClassEnd(output);
} catch (IOException ex) {
throw new ImplementorException("Cannot write to output file.", ex);
}
return getGeneratedClassName(classToExtend, packageNeeded);
}

private void generateMethod(BufferedWriter output, Method method) throws IOException, ImplementorException {
generateMethodBegin(output, method);
generateMethodBody(output, method);
generateMethodEnd(output);
}

private void generateMethodBegin(BufferedWriter output, Method method)
throws IOException, ImplementorException {
output.write(TAB);
if (Modifier.isPublic(method.getModifiers())) {
output.write(" public ");
} else if (Modifier.isProtected(method.getModifiers())) {
output.write(" protected ");
} else {
throw new ImplementorException("Invalid input.");
}
output.write(method.getReturnType().getCanonicalName() + " ");
output.write(method.getName());
String params =
Arrays.stream(method.getParameters())
.map(param -> param.getType().getCanonicalName() + " " + param.getName())
.collect(Collectors.joining(", "));
output.write("(" + params + ") {");
output.newLine();
}

private void generateMethodBody(BufferedWriter output, Method method) throws IOException {
output.write(TAB + TAB);
Class<?> returnType = method.getReturnType();
if (returnType.equals(void.class)) {
output.write("return;");
} else if (returnType.equals(boolean.class)) {
output.write("return false;");
} else if (returnType.isPrimitive()) {
output.write("return 0;");
} else {
output.write("return null;");
}
output.newLine();
}

private void generateMethodEnd(BufferedWriter output) throws IOException {
output.write(TAB + "}");
output.newLine();
}

private void generateClassBegin(Class<?> classToExtend, BufferedWriter output) throws IOException {
output.write("public class " + getGeneratedClassName(classToExtend, false));
if (classToExtend.isInterface()) {
output.write(" implements " + classToExtend.getCanonicalName() + " {");
} else {
output.write(" extends " + classToExtend.getCanonicalName() + " {");
}
output.newLine();
}

private void generateClassEnd(BufferedWriter output) throws IOException {
output.write("}");
output.newLine();
}

private void generatePackage(Class<?> classToExtend, Boolean packageNeeded, BufferedWriter output)
throws IOException {
if (packageNeeded && classToExtend.getPackage() != null) {
output.write("package " + classToExtend.getPackage().getName() + ";");
output.newLine();
}
}

private void generateGeneratedWarning(BufferedWriter output) throws IOException {
output.write("// this code is generated.");
output.newLine();
}

private File createOutputFile(Class<?> classToExtend, Boolean packageNeeded) throws ImplementorException {
String generatedClassPath =
getGeneratedClassName(classToExtend, packageNeeded)
.replaceAll("\\.", File.separator) + ".java";
File generatedFile = new File(outputDirectory, generatedClassPath);
try {
generatedFile.getParentFile().mkdirs();
generatedFile.createNewFile();
} catch (IOException ex) {
throw new ImplementorException("Cannot create output file.", ex);
}
return generatedFile;
}

private static String getGeneratedClassName(Class<?> classToGenerate, Boolean packageNeeded) {
if (packageNeeded) {
return classToGenerate.getCanonicalName() + "Impl";
} else {
return classToGenerate.getSimpleName() + "Impl";
}
}

private Class<?> getClassFromStandardLibrary(String className) throws ImplementorException {
try {
return Class.forName(className);
} catch (ClassNotFoundException ex) {
throw new ImplementorException("Cannot access the class.", ex);
}
}

private Class<?> loadClassFromDirectory(String directoryPath, String className) throws ImplementorException {
File dir = new File(directoryPath);
if (!dir.isDirectory()) {
throw new ImplementorException("Invalid directory.");
}

try {
URL jar = new URL("file://" + dir.getCanonicalPath() + File.separator);
ClassLoader cl = new URLClassLoader(new URL[]{jar});
return cl.loadClass(className);
} catch (IOException | ClassNotFoundException ex) {
throw new ImplementorException("Cannot access the class.", ex);
}
}
}
104 changes: 100 additions & 4 deletions src/test/java/ru/spbau/mit/ImplementorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import org.junit.rules.TemporaryFolder;

import javax.tools.*;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
Expand Down Expand Up @@ -75,10 +77,104 @@ public void cleanUp() {
deleteFolderContent(new File(getOutputDirectoryPath()), false);
}

// @Test
// public void implementCloneable() throws Exception {
// checkInterfaceImplementationFromStandardLibrary("java.lang.Cloneable");
// }
//
// @Test
// public void implementSimpleInterface() throws Exception {
// String packageFolder = String.join(File.separator, new String[]{"ru", "au", "java"});
// File dir = new File(getTestsDirectoryPath(), packageFolder);
// dir.mkdirs();
// File interfaze = new File(dir, "AnInterface.java");
// interfaze.createNewFile();
//
// try (BufferedWriter output = new BufferedWriter(new FileWriter(interfaze))) {
// output.write("package ru.au.java;");
// output.newLine();
// output.write("public interface AnInterface {");
// output.newLine();
// output.write("int someMethod();");
// output.newLine();
// output.write("}");
// output.newLine();
// }
// compileFile(interfaze.getAbsolutePath());
// checkInterfaceImplementationFromFolder("ru.au.java.AnInterface");
// }

// @Test
// public void implementEmptyInterface() throws Exception {
// String packageFolder = String.join(File.separator, new String[]{"ru", "au", "java"});
// File dir = new File(getTestsDirectoryPath(), packageFolder);
// dir.mkdirs();
// File interfaze = new File(dir, "EmptyInterface.java");
// interfaze.createNewFile();
//
// try (BufferedWriter output = new BufferedWriter(new FileWriter(interfaze))) {
// output.write("package ru.au.java;");
// output.newLine();
// output.write("public interface EmptyInterface {");
// output.newLine();
// output.write("}");
// output.newLine();
// }
// compileFile(interfaze.getAbsolutePath());
// checkInterfaceImplementationFromFolder("ru.au.java.EmptyInterface");
// }

@Test
public void implementCloneable() throws Exception {
checkInterfaceImplementationFromStandardLibrary("java.lang.Cloneable");
public void implementAbstractClassExtendingInterface() throws Exception {
String packageFolder = String.join(File.separator, new String[]{"ru", "au", "java"});
File dir = new File(getTestsDirectoryPath(), packageFolder);
dir.mkdirs();
File interfaze = new File(dir, "AnInterface.java");
interfaze.createNewFile();

try (BufferedWriter output = new BufferedWriter(new FileWriter(interfaze))) {
output.write("package ru.au.java;");
output.newLine();
output.write("public interface AnInterface {");
output.newLine();
output.write("int someMethod();");
output.newLine();
output.write("}");
output.newLine();
}
compileFile(interfaze.getAbsolutePath());

File abstractClass = new File(dir, "AbstractClass.java");
abstractClass.createNewFile();

try (BufferedWriter output = new BufferedWriter(new FileWriter(abstractClass))) {
output.write("package ru.au.java;");
output.newLine();
output.write("public abstract class AbstractClass implements AnInterface {");
output.newLine();
output.write("protected abstract int someOtherMethod();");
output.newLine();
output.write("}");
output.newLine();
}
compileFile(abstractClass.getAbsolutePath());
checkAbstractClassImplementationFromFolder("ru.au.java.AbstractClass");
}
//
// @Test
// public void implementAbstractMap() throws Exception {
// checkAbstractClassImplementationFromStandardLibrary("java.util.AbstractMap");
// }
//
// @Test
// public void implementSocket() throws Exception {
// checkAbstractClassImplementationFromStandardLibrary("java.net.Socket");
// }
//
// @Test
// public void implementMap() throws Exception {
// checkInterfaceImplementationFromStandardLibrary("java.util.Map");
// }

private void checkInterfaceImplementationFromFolder(String className) throws Exception {
Implementor implementor = newImplementor();
Expand Down Expand Up @@ -114,7 +210,7 @@ private void compileAndCheckAbstractClassImplementation(String className, String
final Class<?> outputClass = compileAndLoadClass(implClassName);
checkExtendsAbstractClass(className, outputClass);
}

private void checkExtendsAbstractClass(String className, Class<?> outputClass) {
assertThat(outputClass.getSuperclass().getCanonicalName(), is(className));
}
Expand Down Expand Up @@ -153,7 +249,7 @@ private boolean compileFile(String absolutePath) throws IOException {
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromStrings(
Arrays.asList(absolutePath));
List<String> options = new ArrayList<>();
options.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path") + getTestsDirectoryPath()));
options.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path") + ":" + getTestsDirectoryPath()));
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options,
null, compilationUnits);
boolean success = task.call();
Expand Down