Merge branch '12-heuristic' into 'master'

Resolve "Структура классов для эвристических методов поиска определенных типов классов проекта"

Closes #12

See merge request romanov73/git-extractor!8
merge-requests/18/head
Anton Romanov 3 years ago
commit b25a1dbab9

@ -1,3 +1,8 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
buildscript {
ext {
versionSpringBoot = '2.3.9.RELEASE'
@ -53,7 +58,9 @@ dependencies {
compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-hibernate5'
compile group: 'org.postgresql', name: 'postgresql', version: '9.4.1212'
compile group: 'org.liquibase', name: 'liquibase-core', version: '4.3.1'
implementation group: 'commons-io', name: 'commons-io', version: '2.6'
compile group: 'commons-io', name: 'commons-io', version: '2.6'
compile group: 'net.sourceforge.htmlunit', name: 'htmlunit', version: '2.35.0'
compile group: 'com.github.javaparser', name: 'javaparser-core', version: '3.20.2'
compile group: 'io.springfox', name: 'springfox-boot-starter', version: '3.0.0'

@ -0,0 +1,95 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.heuristic.api;
import ru.ulstu.extractor.heuristic.component.BuildTool;
import ru.ulstu.extractor.heuristic.component.ProgrammingLanguage;
import ru.ulstu.extractor.heuristic.model.StructuralUnit;
import ru.ulstu.extractor.heuristic.service.DetectorService;
import ru.ulstu.extractor.heuristic.service.ProgrammingLanguageService;
import ru.ulstu.extractor.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static ru.ulstu.extractor.heuristic.service.DetectorService.LangDetectScrupulousness.HIGH;
import static ru.ulstu.extractor.heuristic.service.DetectorService.LangDetectScrupulousness.LOW;
public abstract class StructuralUnitIdentifier {
public List<StructuralUnit> getEntityClasses(String projectPath, List<File> projectFiles, List<File> rootProjectFiles) {
String subDirectory = getSourceDirectory(rootProjectFiles);
return getEntityClasses(projectPath, subDirectory, projectFiles);
}
public abstract boolean canAppliedToCode(String sourceCode);
public abstract boolean canAppliedToFile(File projectFile);
public abstract boolean canAppliedToProject(String projectPath, List<File> projectFiles, List<File> rootProjectFiles);
public abstract boolean isEntityClass(File file);
public abstract boolean isEntityClass(String sourceCode);
protected abstract boolean isBusinessLogicClass(File file);
public abstract List<StructuralUnit> getBusinessLogicClasses();
public abstract boolean isMultiModuleProject();
public abstract Optional<BuildTool> getBuildTool(List<File> rootDirectoryFiles);
protected abstract List<StructuralUnit> getEntityClasses(String projectPath, String subDirectory, List<File> projectFiles);
protected abstract String getSourceDirectory(List<File> rootProjectFiles);
protected abstract DetectorService getDetectorService();
protected abstract ProgrammingLanguageService getProgrammingLanguageService();
protected abstract ProgrammingLanguage getProgrammingLanguage();
protected Optional<ProgrammingLanguage> getMainProgrammingLanguage(String projectPath, List<File> projectFiles, List<File> rootProjectFiles) {
String subDirectory = getSourceDirectory(rootProjectFiles);
Map<ProgrammingLanguage, Integer> projectFileLanguageFrequency = new HashMap<>();
projectFiles.stream()
.filter(file -> StringUtils.fileInSubdirectory(file.getPath(), projectPath, subDirectory))
.forEach(projectFile -> {
Optional<ProgrammingLanguage> detectedLanguage = getMainProgrammingLanguage(projectFile, LOW);
detectedLanguage.ifPresent(programmingLanguage -> projectFileLanguageFrequency.put(programmingLanguage, projectFileLanguageFrequency.getOrDefault(programmingLanguage, 0) + 1));
});
Optional<Map.Entry<ProgrammingLanguage, Integer>> mostFrequentLanguageEntry = projectFileLanguageFrequency
.entrySet()
.stream()
.max(Map.Entry.comparingByValue());
if (mostFrequentLanguageEntry.isEmpty()) {
return Optional.empty();
}
return Optional.of(mostFrequentLanguageEntry.get().getKey());
}
protected Optional<ProgrammingLanguage> getMainProgrammingLanguage(File projectFile, DetectorService.LangDetectScrupulousness scrupulousness) {
String fileContent = "";
if (scrupulousness == HIGH) {
try {
fileContent = new String(Files.readAllBytes(projectFile.toPath()));
} catch (IOException e) {
e.printStackTrace();
}
}
return getProgrammingLanguageService().getByName(getDetectorService().getDetectedLanguage(
projectFile.getName(),
fileContent,
scrupulousness));
}
}

@ -0,0 +1,25 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.heuristic.component;
import java.io.File;
import java.util.List;
public abstract class BuildTool {
private final String name;
public BuildTool(String name) {
this.name = name;
}
public abstract boolean canAppliedToProject(List<File> rootProjectFiles);
public String getName() {
return name;
}
public abstract String getSourceDirectoryPath();
}

@ -0,0 +1,30 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.heuristic.component;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.List;
@Component
public class GradleBuildTool extends BuildTool {
public GradleBuildTool() {
super("Gradle");
}
@Override
public boolean canAppliedToProject(List<File> rootProjectFiles) {
return rootProjectFiles.stream()
.anyMatch(file -> file.getName().equals("build.gradle") || file.getName().equals("settings.gradle"));
}
@Override
public String getSourceDirectoryPath() {
return "src";
}
}

@ -0,0 +1,15 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.heuristic.component;
import org.springframework.stereotype.Component;
@Component
public class JavaProgrammingLanguage extends ProgrammingLanguage {
public JavaProgrammingLanguage() {
super("java");
}
}

@ -0,0 +1,30 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.heuristic.component;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.List;
@Component
public class MavenBuildTool extends BuildTool {
public MavenBuildTool() {
super("Maven");
}
@Override
public boolean canAppliedToProject(List<File> rootProjectFiles) {
return rootProjectFiles.stream()
.anyMatch(file -> file.getName().equals("pom.xml"));
}
@Override
public String getSourceDirectoryPath() {
return "src";
}
}

@ -0,0 +1,28 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.heuristic.component;
import java.util.Locale;
public abstract class ProgrammingLanguage {
private final String name;
public ProgrammingLanguage(String name) {
this.name = name;
}
public String getName() {
return name;
}
public boolean canMappedByName(String programmingLanguageName) {
if (programmingLanguageName == null || programmingLanguageName.isBlank()) {
return false;
}
return getName().toLowerCase(Locale.ROOT).equals(programmingLanguageName.toLowerCase(Locale.ROOT))
|| programmingLanguageName.toLowerCase(Locale.ROOT).contains(getName().toLowerCase(Locale.ROOT));
}
}

@ -0,0 +1,36 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.heuristic.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.ulstu.extractor.heuristic.model.StructuralUnit;
import ru.ulstu.extractor.heuristic.service.StructuralUnitService;
import ru.ulstu.extractor.service.GitRepositoryService;
import java.io.File;
import java.io.IOException;
import java.util.List;
@RestController
@RequestMapping("StructuralUnitController")
public class StructuralUnitController {
private final StructuralUnitService structuralUnitService;
private final GitRepositoryService gitRepositoryService;
public StructuralUnitController(StructuralUnitService structuralUnitService,
GitRepositoryService gitRepositoryService) {
this.structuralUnitService = structuralUnitService;
this.gitRepositoryService = gitRepositoryService;
}
@GetMapping("get-entities")
public List<StructuralUnit> getEntities(String repositoryUrl) throws IOException {
File rootPath = gitRepositoryService.getProjectDirectoryFile(repositoryUrl);
return structuralUnitService.getEntities(rootPath);
}
}

@ -0,0 +1,15 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.heuristic.model;
import java.io.File;
public class EntityUnit extends StructuralUnit {
public EntityUnit(String projectPath, File file) {
super(projectPath, file);
}
}

@ -0,0 +1,28 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.heuristic.model;
import java.io.File;
import static ru.ulstu.extractor.util.StringUtils.removePathPrefix;
public abstract class StructuralUnit {
private final String pathToFile;
private final String unitName;
public StructuralUnit(String projectPath, File file) {
this.pathToFile = removePathPrefix(file.getPath(), projectPath);
this.unitName = file.getName();
}
public String getPathToFile() {
return pathToFile;
}
public String getUnitName() {
return unitName;
}
}

@ -0,0 +1,26 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.heuristic.service;
import org.springframework.stereotype.Service;
import ru.ulstu.extractor.heuristic.component.BuildTool;
import java.io.File;
import java.util.List;
import java.util.Optional;
@Service
public class BuildToolService {
private final List<BuildTool> buildTools;
public BuildToolService(List<BuildTool> buildTools) {
this.buildTools = buildTools;
}
public Optional<BuildTool> getProjectBuildTool(List<File> rootProjectFiles) {
return buildTools.stream().filter(buildTool -> buildTool.canAppliedToProject(rootProjectFiles)).findAny();
}
}

@ -0,0 +1,59 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.heuristic.service;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.DomElement;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import static ru.ulstu.extractor.heuristic.service.DetectorService.LangDetectScrupulousness.LOW;
@Service
public class DetectorService {
public enum LangDetectScrupulousness {LOW, HIGH}
private final static String BASE_URL = "http://localhost:8080/lang-detector.html";
public String getDetectedLanguage(String fileName, String code, LangDetectScrupulousness scrupulousness) {
if (scrupulousness == LOW) {
return DirectoryService.getFileExtension(fileName).orElse("");
}
return getDetectedLanguage(code);
}
private String getDetectedLanguage(String code) {
String selectedLang = null;
try (WebClient webClient = new WebClient()) {
webClient.setJavaScriptTimeout(60 * 1000);
webClient.getOptions().setThrowExceptionOnScriptError(false);
final HtmlPage page = webClient.getPage(BASE_URL);
DomElement codeElement = page.getElementById("input");
codeElement.setTextContent(code);
DomElement button = page.getElementById("btn");
button.click();
DomElement output = page.getElementById("output");
String outputString = output.getTextContent();
List<String> langsStrings = Arrays.asList(outputString.split("\n"));
int max = 0;
for (String langString : langsStrings) {
int currentVal = Integer.parseInt(langString.split(":")[1].trim());
if (currentVal > max) {
max = currentVal;
selectedLang = langString.split(":")[0].trim();
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
return selectedLang.toLowerCase(Locale.ROOT);
}
}

@ -0,0 +1,63 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.heuristic.service;
import com.sun.istack.NotNull;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* Сервис для работы с каталогом файловой системы. Для задач проекта нужно получать список файлов.
*/
@Service
public class DirectoryService {
private static final String FILE_EXTENSION_DELIMITER = ".";
/**
* Получить список файлов, рекурсивно обойдя все дерево каталогов проекта.
*
* @param directory корневой каталог
* @return список всех файлов
* @throws IOException при возникновении исключения
*/
public List<File> getFilesRecursively(@NotNull File directory) throws IOException {
return Files.find(directory.toPath(),
Integer.MAX_VALUE,
(filePath, fileAttr) -> fileAttr.isRegularFile())
.map(Path::toFile)
.collect(Collectors.toList());
}
/**
* Получить список файлов только текущего каталога
*
* @param directory текущий каталог
* @return список файлов
*/
public List<File> getDirectoryFiles(@NotNull Path directory) {
if (directory.toFile().listFiles() != null) {
return Arrays.asList(Objects.requireNonNull(directory.toFile().listFiles()));
}
return Collections.emptyList();
}
public static Optional<String> getFileExtension(String fileName) {
if (fileName == null || fileName.isEmpty() || fileName.lastIndexOf(FILE_EXTENSION_DELIMITER) < 0) {
return Optional.empty();
}
return Optional.of(fileName.substring(fileName.lastIndexOf(FILE_EXTENSION_DELIMITER) + 1));
}
}

@ -0,0 +1,157 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.heuristic.service;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseResult;
import com.github.javaparser.ast.CompilationUnit;
import org.springframework.stereotype.Service;
import ru.ulstu.extractor.heuristic.api.StructuralUnitIdentifier;
import ru.ulstu.extractor.heuristic.component.BuildTool;
import ru.ulstu.extractor.heuristic.component.JavaProgrammingLanguage;
import ru.ulstu.extractor.heuristic.component.ProgrammingLanguage;
import ru.ulstu.extractor.heuristic.model.EntityUnit;
import ru.ulstu.extractor.heuristic.model.StructuralUnit;
import ru.ulstu.extractor.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static ru.ulstu.extractor.heuristic.service.DetectorService.LangDetectScrupulousness.LOW;
@Service
public class JavaIdentifier extends StructuralUnitIdentifier {
private static final String ENTITY_ANNOTATION = "@Entity";
private final DetectorService detectorService;
private final BuildToolService buildToolService;
private final ProgrammingLanguageService programmingLanguageService;
public JavaIdentifier(DetectorService detectorService,
BuildToolService buildToolService,
ProgrammingLanguageService programmingLanguageService) {
this.detectorService = detectorService;
this.buildToolService = buildToolService;
this.programmingLanguageService = programmingLanguageService;
}
@Override
public boolean canAppliedToProject(String projectPath, List<File> projectFiles, List<File> rootProjectFiles) {
return /*getBuildTool() instanceof GradleBuildTool
&&*/ getMainProgrammingLanguage(projectPath, projectFiles, rootProjectFiles).orElse(null) instanceof JavaProgrammingLanguage;
}
public boolean canAppliedToFile(File projectFile) {
return getMainProgrammingLanguage(projectFile, LOW).orElse(null) instanceof JavaProgrammingLanguage;
}
public boolean canAppliedToCode(String sourceCode) {
return getMainProgrammingLanguage(sourceCode).orElse(null) instanceof JavaProgrammingLanguage;
}
@Override
public List<StructuralUnit> getEntityClasses(String projectPath, String subDirectory, List<File> projectFiles) {
return projectFiles.stream()
.filter(file -> StringUtils.fileInSubdirectory(file.getPath(), projectPath, subDirectory))
.filter(this::isEntityClass)
.map(file -> new EntityUnit(projectPath, file))
.collect(Collectors.toList());
}
protected Optional<ProgrammingLanguage> getMainProgrammingLanguage(String sourceCode) {
return sourceCodeContainsClass(sourceCode)
? Optional.of(getProgrammingLanguage())
: Optional.empty();
}
@Override
public List<StructuralUnit> getBusinessLogicClasses() {
return null;
}
@Override
public boolean isMultiModuleProject() {
return false;
}
@Override
public Optional<BuildTool> getBuildTool(List<File> rootDirectoryFiles) {
return buildToolService.getProjectBuildTool(rootDirectoryFiles);
}
@Override
protected DetectorService getDetectorService() {
return detectorService;
}
@Override
protected ProgrammingLanguageService getProgrammingLanguageService() {
return programmingLanguageService;
}
@Override
protected ProgrammingLanguage getProgrammingLanguage() {
return new JavaProgrammingLanguage();
}
@Override
public boolean isEntityClass(File file) {
try {
return file.getName().endsWith("java") && classContainsAnnotation(file, ENTITY_ANNOTATION);
} catch (Exception ex) {
ex.printStackTrace();
}
return false;
}
@Override
public boolean isEntityClass(String sourceCode) {
try {
return classContainsAnnotation(sourceCode, ENTITY_ANNOTATION);
} catch (Exception ex) {
ex.printStackTrace();
}
return false;
}
private boolean classContainsAnnotation(File file, String annotationDeclaration) throws IOException {
return classContainsAnnotation(new String(Files.readAllBytes(file.toPath())), annotationDeclaration);
}
private boolean classContainsAnnotation(String sourceCode, String annotationDeclaration) {
JavaParser parser = new JavaParser();
ParseResult<CompilationUnit> parseResult = parser.parse(sourceCode);
if (parseResult.getResult().isPresent() && parseResult.getResult().get().findCompilationUnit().isPresent()) {
return parseResult.getResult().get().getTypes().stream()
.anyMatch(clazz -> clazz.getAnnotations().stream().anyMatch(annotation -> annotation.toString().startsWith(annotationDeclaration)));
}
return false;
}
private boolean sourceCodeContainsClass(String sourceCode) {
JavaParser parser = new JavaParser();
ParseResult<CompilationUnit> parseResult = parser.parse(sourceCode);
if (parseResult.getResult().isPresent() && parseResult.getResult().get().findCompilationUnit().isPresent()) {
return parseResult.getResult().get().getTypes().stream().findAny().isPresent();
}
return false;
}
@Override
protected String getSourceDirectory(List<File> rootDirectoryFiles) {
return buildToolService.getProjectBuildTool(rootDirectoryFiles)
.map(BuildTool::getSourceDirectoryPath)
.orElse("src");
}
@Override
protected boolean isBusinessLogicClass(File file) {
return false;
}
}

@ -0,0 +1,25 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.heuristic.service;
import org.springframework.stereotype.Service;
import ru.ulstu.extractor.heuristic.component.ProgrammingLanguage;
import java.util.List;
import java.util.Optional;
@Service
public class ProgrammingLanguageService {
private final List<ProgrammingLanguage> programmingLanguages;
public ProgrammingLanguageService(List<ProgrammingLanguage> programmingLanguages) {
this.programmingLanguages = programmingLanguages;
}
public Optional<ProgrammingLanguage> getByName(String programmingLanguageName) {
return programmingLanguages.stream().filter(lang -> lang.canMappedByName(programmingLanguageName)).findAny();
}
}

@ -0,0 +1,60 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.heuristic.service;
import org.springframework.stereotype.Service;
import ru.ulstu.extractor.heuristic.api.StructuralUnitIdentifier;
import ru.ulstu.extractor.heuristic.model.StructuralUnit;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
@Service
public class StructuralUnitService {
private final DirectoryService directoryService;
private final List<StructuralUnitIdentifier> structuralUnitIdentifiers;
public StructuralUnitService(DirectoryService directoryService,
List<StructuralUnitIdentifier> structuralUnitIdentifiers) {
this.directoryService = directoryService;
this.structuralUnitIdentifiers = structuralUnitIdentifiers;
}
public List<StructuralUnit> getEntities(File rootPath) throws IOException {
List<File> projectFiles = directoryService.getFilesRecursively(rootPath);
List<File> rootProjectFiles = directoryService.getDirectoryFiles(rootPath.toPath());
return getStructuralUnitIdentifier(
structuralUnitIdentifier -> structuralUnitIdentifier.canAppliedToProject(
rootPath.getPath(),
projectFiles,
rootProjectFiles))
.orElseThrow(() -> new RuntimeException("Identifier not found"))
.getEntityClasses(rootPath.getPath(), projectFiles, rootProjectFiles);
}
public boolean containsEntity(File projectFile) {
return getStructuralUnitIdentifier(
structuralUnitIdentifier -> structuralUnitIdentifier.canAppliedToFile(projectFile))
.map(identifier -> identifier.isEntityClass(projectFile))
.orElse(false);
}
public boolean containsEntity(String sourceCode) {
return getStructuralUnitIdentifier(
structuralUnitIdentifier -> structuralUnitIdentifier.canAppliedToCode(sourceCode))
.map(identifier -> identifier.isEntityClass(sourceCode))
.orElse(false);
}
private Optional<StructuralUnitIdentifier> getStructuralUnitIdentifier(Predicate<StructuralUnitIdentifier> predicate) {
return structuralUnitIdentifiers.stream()
.filter(predicate)
.findAny();
}
}

@ -1,3 +1,8 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.model;
import org.hibernate.annotations.Fetch;
@ -84,4 +89,12 @@ public class Commit extends BaseEntity {
public void setBranch(Branch branch) {
this.branch = branch;
}
public boolean containsEntity() {
return fileChanges != null && fileChanges.stream().anyMatch(
fileChange -> fileChange != null
&& fileChange.isContainsEntity() != null
&& fileChange.isContainsEntity()
);
}
}

@ -1,3 +1,8 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.model;
import org.hibernate.annotations.Fetch;
@ -20,6 +25,8 @@ public class FileChange extends BaseEntity {
@Transient
private boolean added;
private Boolean containsEntity;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "file_change_id", unique = true)
@Fetch(FetchMode.SUBSELECT)
@ -71,4 +78,12 @@ public class FileChange extends BaseEntity {
public boolean isAdded() {
return added;
}
public Boolean isContainsEntity() {
return containsEntity;
}
public void setContainsEntity(boolean containsEntity) {
this.containsEntity = containsEntity;
}
}

@ -12,10 +12,15 @@ import org.eclipse.jgit.api.ListBranchCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import ru.ulstu.extractor.heuristic.service.StructuralUnitService;
import ru.ulstu.extractor.model.Author;
import ru.ulstu.extractor.model.Branch;
import ru.ulstu.extractor.model.Commit;
@ -28,6 +33,7 @@ import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
@ -44,6 +50,12 @@ public class GitRepositoryService {
@Value("${extractor.custom-projects-dir}")
private String customProjectsDir;
private final StructuralUnitService structuralUnitService;
public GitRepositoryService(StructuralUnitService structuralUnitService) {
this.structuralUnitService = structuralUnitService;
}
public List<Branch> getRemoteBranches(String url) throws GitAPIException, IOException {
cloneOrUpdateRepo(url);
Repository localRepo = new FileRepository(getProjectGitDirectory(url));
@ -113,6 +125,12 @@ public class GitRepositoryService {
return list;
}
public File getProjectDirectoryFile(String url) {
validateUrl(url);
return Path.of(getProjectDirectory(url))
.toFile();
}
public void remove(String repositoryUrl) throws IOException {
FileUtils.deleteDirectory(getProjectDirectoryFile(repositoryUrl));
}
@ -143,12 +161,6 @@ public class GitRepositoryService {
return getProjectDirectory(url) + "/.git";
}
private File getProjectDirectoryFile(String url) {
validateUrl(url);
return Path.of(getProjectDirectory(url))
.toFile();
}
private void validateUrl(String url) {
if (url == null || url.isEmpty()) {
throw new RuntimeException("Repository url must not empty");
@ -177,10 +189,10 @@ public class GitRepositoryService {
} catch (IOException e) {
throw new RuntimeException("Error occurred during diff computation. Message: " + e.getMessage());
}
return parseOutputDiff(output);
return parseOutputDiff(output, localRepo, laterCommit);
}
private List<FileChange> parseOutputDiff(String output) {
private List<FileChange> parseOutputDiff(String output, Repository repository, RevCommit commit) {
List<FileChange> changes = new ArrayList<>();
String[] strings = output.split("\n");
FileChange fileChange = new FileChange();
@ -191,6 +203,9 @@ public class GitRepositoryService {
if (maybeFileName.isPresent()) {
fileChange = new FileChange();
fileChange.setFile(maybeFileName.get());
fileChange.setContainsEntity(
structuralUnitService.containsEntity(getContent(repository, commit, maybeFileName.get()))
);
/// вытащить другие изменения из коммита
changes.add(fileChange);
}
@ -226,6 +241,22 @@ public class GitRepositoryService {
return changes;
}
private String getContent(Repository repository, RevCommit commit, String path) {
try (TreeWalk treeWalk = TreeWalk.forPath(repository, path, commit.getTree())) {
if (treeWalk != null) {
ObjectId blobId = treeWalk.getObjectId(0);
try (ObjectReader objectReader = repository.newObjectReader()) {
ObjectLoader objectLoader = objectReader.open(blobId);
byte[] bytes = objectLoader.getBytes();
return new String(bytes, StandardCharsets.UTF_8);
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
return "";
}
private Optional<String> getFileName(String commitString) {
String startString = "diff --git a/";
if (commitString.startsWith(startString)) {

@ -0,0 +1,28 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*/
package ru.ulstu.extractor.util;
import java.nio.file.FileSystems;
public class StringUtils {
public final static String EMPTY_STRING = "";
private final static String PATH_SEPARATOR = FileSystems.getDefault().getSeparator();
public static String addPathSeparator(String path) {
return path + PATH_SEPARATOR;
}
public static String removePathPrefix(String path, String prefix) {
if (prefix.endsWith(PATH_SEPARATOR)) {
return path.replace(prefix, EMPTY_STRING);
}
return path.replace(addPathSeparator(prefix), EMPTY_STRING);
}
public static boolean fileInSubdirectory(String filePath, String projectPath, String subDirectory) {
return filePath.startsWith(projectPath + PATH_SEPARATOR + subDirectory);
}
}

@ -0,0 +1,15 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<!--
~ Copyright (C) 2021 Anton Romanov - All Rights Reserved
~ You may use, distribute and modify this code, please write to: romanov73@gmail.com.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="orion" id="20210412-100000-1">
<addColumn tableName="file_change">
<column name="contains_entity" type="boolean"/>
</addColumn>
</changeSet>
</databaseChangeLog>

@ -1,4 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
~ Copyright (C) 2021 Anton Romanov - All Rights Reserved
~ You may use, distribute and modify this code, please write to: romanov73@gmail.com.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
@ -6,4 +11,5 @@
<include file="db/changelog-20210317_140000-schema.xml"/>
<include file="db/changelog-20210326_170000-schema.xml"/>
<include file="db/changelog-20210329_120000-schema.xml"/>
<include file="db/changelog-20210412_100000-schema.xml"/>
</databaseChangeLog>

@ -0,0 +1,30 @@
.error-popup-container {
background: #999999;
width: 600px;
max-width: 85vw;
left: 50%;
top: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
text-align: center;
font-size: 25px;
position: fixed;
box-shadow: 0px 0px 100px #999999;
border-radius: 60px;
border: 1px solid black;
z-index: 2147483646;
}
.error-popup-message {
color: #690000;
font-weight: bold;
padding: 20px;
}
.more-games-logo {
position: absolute;
margin: 8px;
z-index: 100000000;
left: 0;
top: 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 845 B

@ -0,0 +1,84 @@
/* globals hljs */
var allLanguages = hljs.listLanguages();
var commonLanguages = ["cpp", "cs", "css", "javascript", "java", "objectivec", "perl", "php", "python", "ruby", "sql", "xml", "autohotkey", "lua", "actionscript", "swift", "vbscript"];
var highlightResult;
document.getElementById("commonLanguageTitle").title = commonLanguages.toString().replace(/,/g, ", ");
document.getElementById("highlight").onclick = function() {
var code = document.getElementById("pasteCode").value;
document.getElementById("languageOutput").hidden = true;
document.getElementById("option2").hidden = true;
document.getElementById("option2Languages").hidden = true;
document.getElementById("option2SelectTd").hidden = true;
document.getElementById("option2Select").disabled = false;
document.getElementById("option2Select").innerText = "Select";
document.getElementById("highlightCode").innerHTML = "";
document.getElementById("highlightCode").className = "";
document.getElementById("option1Select").onclick = null;
document.getElementById("option2Select").onclick = null;
document.getElementById("error").hidden = true;
if (document.getElementById("commonLanguagesOnly").checked) {
highlightResult = hljs.highlightAuto(code, commonLanguages);
} else {
highlightResult = hljs.highlightAuto(code, allLanguages);
}
if (typeof highlightResult.language != "undefined") {
document.getElementById("highlightCode").innerHTML = highlightResult.value;
document.getElementById("pasteCode").value = "";
var languageObj = hljs.getLanguage(highlightResult.language);
var languages = [];
if (typeof languageObj.aliases != "undefined") {
languages = languageObj.aliases.slice();
}
languages.unshift(highlightResult.language);
document.getElementById("languageOutput").hidden = false;
document.getElementById("option1Languages").innerText = languages.toString().replace(/,/g, ", ");
document.getElementById("option1Select").disabled = true;
document.getElementById("option1Select").innerText = "Selected";
document.getElementById("option1Select").onclick = function() {
document.getElementById("option1Select").disabled = true;
document.getElementById("option1Select").innerText = "Selected";
document.getElementById("option2Select").disabled = false;
document.getElementById("option2Select").innerText = "Select";
document.getElementById("highlightCode").innerHTML = highlightResult.value;
};
if (typeof highlightResult.second_best != "undefined") {
var languageObj = hljs.getLanguage(highlightResult.second_best.language);
var languages = [];
if (typeof languageObj.aliases != "undefined") {
languages = languageObj.aliases.slice();
}
languages.unshift(highlightResult.second_best.language);
document.getElementById("option2Languages").innerText = languages.toString().replace(/,/g, ", ");
document.getElementById("option2Select").onclick = function() {
document.getElementById("option1Select").disabled = false;
document.getElementById("option1Select").innerText = "Select";
document.getElementById("option2Select").disabled = true;
document.getElementById("option2Select").innerText = "Selected";
document.getElementById("highlightCode").innerHTML = highlightResult.second_best.value;
};
document.getElementById("option2").hidden = false;
document.getElementById("option2Languages").hidden = false;
document.getElementById("option2SelectTd").hidden = false;
}
document.getElementById("highlightCode").className = "hljs";
} else {
document.getElementById("error").hidden = false;
if (document.getElementById("pasteCode").value.length == 0) {
document.getElementById("error").innerText = "Error: No code entered. Please paste your code above and try again.";
} else {
if (document.getElementById("commonLanguagesOnly").checked) {
document.getElementById("error").innerText = 'Error: Unable to identify the programming language. Please add more code or uncheck the "Common Languages Only" option.';
} else {
document.getElementById("error").innerText = "Error: Unable to identify the programming language. Please add more code to increase the accuracy of the detection.";
}
}
}
};

@ -0,0 +1,116 @@
/*
* Visual Studio 2015 dark style
* Author: Nicolas LLOBERA <nllobera@gmail.com>
*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
background: #1E1E1E;
color: #DCDCDC;
}
.hljs-keyword,
.hljs-literal,
.hljs-symbol,
.hljs-name {
color: #569CD6;
}
.hljs-link {
color: #569CD6;
text-decoration: underline;
}
.hljs-built_in,
.hljs-type {
color: #4EC9B0;
}
.hljs-number,
.hljs-class {
color: #B8D7A3;
}
.hljs-string,
.hljs-meta-string {
color: #D69D85;
}
.hljs-regexp,
.hljs-template-tag {
color: #9A5334;
}
.hljs-subst,
.hljs-function,
.hljs-title,
.hljs-params,
.hljs-formula {
color: #DCDCDC;
}
.hljs-comment,
.hljs-quote {
color: #57A64A;
font-style: italic;
}
.hljs-doctag {
color: #608B4E;
}
.hljs-meta,
.hljs-meta-keyword,
.hljs-tag {
color: #9B9B9B;
}
.hljs-variable,
.hljs-template-variable {
color: #BD63C5;
}
.hljs-attr,
.hljs-attribute,
.hljs-builtin-name {
color: #9CDCFE;
}
.hljs-section {
color: gold;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
/*.hljs-code {
font-family:'Monospace';
}*/
.hljs-bullet,
.hljs-selector-tag,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #D7BA7D;
}
.hljs-addition {
background-color: #144212;
display: inline-block;
width: 100%;
}
.hljs-deletion {
background-color: #600;
display: inline-block;
width: 100%;
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,127 @@
<!DOCTYPE html>
<!-- saved from url=(0050)https://creativetechguy.com/utilities/codedetector -->
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="keywords"
content="programming, language, detector, identifier, highlighter, syntax, code, ctg, Creative Tech Guy, Jason O&#39;Neill">
<meta name="description" content="Automatically detect a programming language by pasting a snippet of code.">
<meta name="author" content="Jason O&#39;Neill">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Code Detector &amp; Formatter</title>
<link rel="apple-touch-icon" sizes="180x180" href="https://creativetechguy.com/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="https://creativetechguy.com/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="https://creativetechguy.com/favicon-16x16.png">
<link rel="manifest" href="https://creativetechguy.com/manifest.json">
<link rel="mask-icon" href="https://creativetechguy.com/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="https://creativetechguy.com/favicon.ico">
<meta name="apple-mobile-web-app-title" content="Code Detector">
<meta name="application-name" content="Code Detector">
<meta name="theme-color" content="#ffffff">
<meta property="og:title" content="Code Detector &amp; Formatter">
<meta property="og:type" content="website">
<meta property="og:locale" content="en_US">
<meta property="og:url" content="https://creativetechguy.com/utilities/codedetector">
<meta property="og:description" content="Automatically detect a programming language by pasting a snippet of code.">
<meta property="og:image" content="https://creativetechguy.com/images/logo.png">
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@JasonONeillCTG">
<meta name="twitter:title" content="Code Detector &amp; Formatter">
<meta name="twitter:description"
content="Automatically detect a programming language by pasting a snippet of code.">
<meta name="twitter:image" content="https://creativetechguy.com/images/logo.png">
<link rel="stylesheet" href="./Code Detector &amp; Formatter_files/games.css" type="text/css">
<link rel="stylesheet" href="./Code Detector &amp; Formatter_files/vs2015.css">
<style>
table, tbody, tr, th, td {
border: 1px solid black;
border-collapse: collapse;
}
th {
border-bottom: 2px solid black;
}
td, th {
padding-left: 5px;
padding-right: 5px;
}
.center,
.center * {
text-align: center;
margin-left: auto;
margin-right: auto;
}
</style>
</head>
<body>
<a href="https://creativetechguy.com/utilities" id="homeLogo" title="Back to Utilities">
<img src="./Code Detector &amp; Formatter_files/more arrow.png" class="more-games-logo" alt="More Utilities">
</a>
<div class="center">
<h1 style="margin-top: 0px;">Code Detector &amp; Formatter</h1>
<p>Paste code below and click "Detect Code" to format and analyze the code snippet.<br>The more code that is
included, the more accurate the analysis will be.</p>
<textarea id="pasteCode"
style="width: 100%; max-width: 600px; height: 350px; text-align: left; resize: vertical; box-sizing: border-box;"></textarea><br>
<div id="commonLanguageTitle"
title="cpp, cs, css, javascript, java, objectivec, perl, php, python, ruby, sql, xml, autohotkey, lua, actionscript, swift, vbscript">
Common Languages Only: <input type="checkbox" id="commonLanguagesOnly" checked=""><br>
</div>
<button id="highlight">Detect Code</button>
<br>
<br>
<p id="error" hidden=""></p>
<table id="languageOutput" hidden="">
<tbody style="text-align: center;">
<tr>
<th id="option1">Option 1</th>
<th id="option2">Option 2</th>
</tr>
<tr>
<td id="option1Languages"></td>
<td id="option2Languages"></td>
</tr>
<tr>
<td id="option1SelectTd" style="width: 100px;">
<button id="option1Select">Select</button>
</td>
<td id="option2SelectTd" style="width: 100px;">
<button id="option2Select">Select</button>
</td>
</tr>
</tbody>
</table>
</div>
<pre><code id="highlightCode" style="max-height: 400px; overflow: auto;"></code></pre>
<hr style="border-color:#000000; border-top-width: 5px; border-style: solid; height: 0px; margin-top: 4px; max-width: 600px;">
<p style="font-size: 12px; margin: 5px; text-align: center;">Powered by <a href="https://highlightjs.org/"
target="_blank"
rel="noopener">highlight.js</a><br>Copyright
<span id="copyrightYear">2021</span> Jason O'Neill</p>
<script>
document.getElementById("copyrightYear").innerText = new Date().getFullYear();
</script>
<script src="./Code Detector &amp; Formatter_files/highlight.pack.js.Без названия"></script>
<script src="./Code Detector &amp; Formatter_files/script.js.Без названия"></script>
<noscript>
<div class="error-popup-container">
<p class="error-popup-message">This page, along with all of my other web games and utilities, require
JavaScript.<br>Please enable JavaScript in your browser and refresh to continue.</p>
</div>
</noscript>
</body>
<style id="stylish-1" class="stylish" type="text/css">td, div {
font-family: arial, sans-serif !important;
}</style>
</html>

@ -0,0 +1,153 @@
<!DOCTYPE html>
<!-- saved from url=(0041)https://hosein2398.github.io/lang-detect/ -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>lang-detector</title>
<script defer="" src="bundle.js"></script>
</head>
<body>
<a target="_blank" title="lang-detector on github" href="https://github.com/ts95/lang-detector" class="github-corner">
<svg width="80" height="80" viewBox="0 0 250 250" style="position: absolute; top: 0; border: 0; right: 0;">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="white" style="transform-origin: 130px 106px;" class="octo-arm"></path>
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="white" class="octo-body"></path>
</svg>
</a>
<h1 id="title">lang-detector <a target="_blank" title="lang-detector on github"
href="https://github.com/ts95/lang-detector"><img src="lang-detector.svg"></a>
</h1>
<div class="wrapper">
<textarea id="input" autofocus="" placeholder="Write some code to detect here."></textarea>
<button id="btn">Detect</button>
<pre id="output" disabled="">JavaScript: 0
C: 3
C++: 3
Python: 0
Java: 8
HTML: 0
CSS: 0
Ruby: 2
Go: 0
PHP: 2
Unknown: 1</pre>
</div>
<footer class="copyright">Toni Sučić ©</footer>
<style>
@import url(http://fonts.googleapis.com/css?family=Roboto:700,400,300);
body {
margin: 20px;
font-family: 'Roboto', sans-serif;
background-color: #f0f8ff;
}
textarea {
color: #555;
font-size: 1em;
width: 600px;
height: 200px;
border: 4px solid #eee;
padding: 10px;
outline: none;
overflow: scroll;
}
textarea,
textarea[disabled] {
background-color: white;
}
pre {
color: #555;
font-size: 1em;
width: 600px;
height: 150px;
border: 4px solid #eee;
padding: 10px;
outline: none;
overflow: scroll;
}
button {
color: #999;
background: none;
border: 4px solid #eee;
padding: 7px 12px 7px 12px;
margin: 0;
font-size: 1.2em;
outline: none;
margin: auto;
display: block;
}
.butt {
text-align: center;
margin: auto 0;
}
button:hover {
color: #777;
cursor: pointer;
border: 4px solid #ccc;
}
button:active {
color: #555;
background-color: #f8f8f8;
border: 4px solid #bbb;
}
#title {
color: #aaa;
font-size: 2.5em;
text-align: center;
font-weight: bold;
padding: 10px;
}
#input {
resize: none;
overflow-x: hidden;
}
#output {
resize: none;
border: none;
overflow: hidden;
height: auto;
font-size: 22px
}
#btn {
display: block;
}
.wrapper {
width: 600px;
margin: 0 auto;
}
.copyright {
color: #bbb;
padding: 30px;
text-align: center;
}
</style>
</body>
<style id="stylish-1" class="stylish" type="text/css">td, div {
font-family: arial, sans-serif !important;
}</style>
</html>

@ -0,0 +1,32 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="76" height="20">
<style>a:hover #llink{fill:url(#b);stroke:#ccc}a:hover #rlink{fill:#4183c4}</style>
<linearGradient id="a" x2="0" y2="100%">
<stop offset="0" stop-color="#fcfcfc" stop-opacity="0"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#ccc" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<g stroke="#d5d5d5">
<rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="48" height="19" rx="2"/>
<rect x="54.5" y="0.5" width="21" height="19" rx="2" fill="#fafafa"/>
<rect x="54" y="7.5" width="0.5" height="5" stroke="#fafafa"/>
<path d="M54.5 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/>
</g>
<image x="5" y="3" width="14" height="14"
xlink:href="data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMTgxNzE3IiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+R2l0SHViIGljb248L3RpdGxlPjxwYXRoIGQ9Ik0xMiAuMjk3Yy02LjYzIDAtMTIgNS4zNzMtMTIgMTIgMCA1LjMwMyAzLjQzOCA5LjggOC4yMDUgMTEuMzg1LjYuMTEzLjgyLS4yNTguODItLjU3NyAwLS4yODUtLjAxLTEuMDQtLjAxNS0yLjA0LTMuMzM4LjcyNC00LjA0Mi0xLjYxLTQuMDQyLTEuNjFDNC40MjIgMTguMDcgMy42MzMgMTcuNyAzLjYzMyAxNy43Yy0xLjA4Ny0uNzQ0LjA4NC0uNzI5LjA4NC0uNzI5IDEuMjA1LjA4NCAxLjgzOCAxLjIzNiAxLjgzOCAxLjIzNiAxLjA3IDEuODM1IDIuODA5IDEuMzA1IDMuNDk1Ljk5OC4xMDgtLjc3Ni40MTctMS4zMDUuNzYtMS42MDUtMi42NjUtLjMtNS40NjYtMS4zMzItNS40NjYtNS45MyAwLTEuMzEuNDY1LTIuMzggMS4yMzUtMy4yMi0uMTM1LS4zMDMtLjU0LTEuNTIzLjEwNS0zLjE3NiAwIDAgMS4wMDUtLjMyMiAzLjMgMS4yMy45Ni0uMjY3IDEuOTgtLjM5OSAzLS40MDUgMS4wMi4wMDYgMi4wNC4xMzggMyAuNDA1IDIuMjgtMS41NTIgMy4yODUtMS4yMyAzLjI4NS0xLjIzLjY0NSAxLjY1My4yNCAyLjg3My4xMiAzLjE3Ni43NjUuODQgMS4yMyAxLjkxIDEuMjMgMy4yMiAwIDQuNjEtMi44MDUgNS42MjUtNS40NzUgNS45Mi40Mi4zNi44MSAxLjA5Ni44MSAyLjIyIDAgMS42MDYtLjAxNSAyLjg5Ni0uMDE1IDMuMjg2IDAgLjMxNS4yMS42OS44MjUuNTdDMjAuNTY1IDIyLjA5MiAyNCAxNy41OTIgMjQgMTIuMjk3YzAtNi42MjctNS4zNzMtMTItMTItMTIiLz48L3N2Zz4="/>
<g aria-hidden="false" fill="#333" text-anchor="middle" font-family="Helvetica Neue,Helvetica,Arial,sans-serif"
text-rendering="geometricPrecision" font-weight="700" font-size="110px" line-height="14px">
<a target="_blank" xlink:href="https://github.com/ts95/lang-detector">
<text aria-hidden="true" x="325" y="150" fill="#fff" transform="scale(.1)" textLength="210">Star</text>
<text x="325" y="140" transform="scale(.1)" textLength="210">Star</text>
<rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="48" height="19" rx="2"/>
</a>
<a target="_blank" xlink:href="https://github.com/ts95/lang-detector/stargazers">
<rect width="22" x="54" height="20" fill="rgba(0,0,0,0)"/>
<text aria-hidden="true" x="645" y="150" fill="#fff" transform="scale(.1)" textLength="130">28</text>
<text id="rlink" x="645" y="140" transform="scale(.1)" textLength="130">28</text>
</a>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

@ -86,6 +86,7 @@
<th scope="col">Автор</th>
<th scope="col" style="width: 30%">Дата</th>
<th scope="col">Сообщение</th>
<th scope="col" align="center">Содержит сущность</th>
</tr>
</thead>
<tbody>
@ -93,6 +94,12 @@
<td th:text="${commit.author.name}"></td>
<td th:text="${commit.date}"></td>
<td th:text="${commit.message}"></td>
<td align="center">
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" th:checked="${commit.containsEntity()}"
disabled>
</div>
</td>
</tr>
</tbody>
</table>

Loading…
Cancel
Save