src/main/java/de/uniks/networkparser/ext/FileClassModel.java
package de.uniks.networkparser.ext;
import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.uniks.networkparser.SimpleEvent;
import de.uniks.networkparser.buffer.CharacterBuffer;
import de.uniks.networkparser.ext.io.FileBuffer;
import de.uniks.networkparser.graph.Association;
import de.uniks.networkparser.graph.Clazz;
import de.uniks.networkparser.graph.ClazzSet;
import de.uniks.networkparser.graph.Feature;
import de.uniks.networkparser.graph.GraphMetric;
import de.uniks.networkparser.graph.GraphModel;
import de.uniks.networkparser.graph.GraphUtil;
import de.uniks.networkparser.graph.Method;
import de.uniks.networkparser.graph.MethodSet;
import de.uniks.networkparser.graph.Modifier;
import de.uniks.networkparser.graph.Parameter;
import de.uniks.networkparser.interfaces.ObjectCondition;
import de.uniks.networkparser.list.SimpleKeyValueList;
import de.uniks.networkparser.list.SimpleList;
import de.uniks.networkparser.list.SimpleSet;
import de.uniks.networkparser.logic.FeatureCondition;
import de.uniks.networkparser.parser.ParserEntity;
import de.uniks.networkparser.parser.SimpleReverseEngineering;
import de.uniks.networkparser.parser.SymTabEntry;
import de.uniks.networkparser.xml.HTMLEntity;
/*String string = model.toString(new DotConverter().withShowAssocInfo(false).withShowSimpleNodeInfo(true));
FileBuffer.writeFile("model2.data", string.getBytes()); */
public class FileClassModel extends ClassModel {
/**
* The suffix of a java file as constant for easer use
*/
private static final String JAVA_FILE_SUFFIX = ".java";
public static final String RECURSIVE = "rekursive";
private SimpleSet<ParserEntity> error = new SimpleSet<ParserEntity>();
private SimpleSet<ParserEntity> list = new SimpleSet<ParserEntity>();
private SimpleKeyValueList<String, SimpleList<ParserEntity>> packageList = new SimpleKeyValueList<String, SimpleList<ParserEntity>>();
private boolean parseFile = true;
private ObjectCondition reverseEngineering;
public FileClassModel(String packageName) {
with(packageName);
}
public FileClassModel withParseFile(boolean value) {
this.parseFile = value;
return this;
}
public boolean readFiles(String path, ObjectCondition... conditions) {
return readFiles(path, null, conditions);
}
public boolean readFiles(String path, String type, ObjectCondition... conditions) {
ObjectCondition condition = null;
if (conditions != null && conditions.length > 0) {
condition = conditions[0];
}
String name = this.getName();
String pkgName = name.replace('.', '/');
String parent = "";
if (name.indexOf('.') > 0) {
parent = name.substring(0, name.lastIndexOf('.') + 1);
}
if (path != null) {
if ((path.endsWith("/") || path.endsWith("\\")) == false) {
path += "/";
}
} else {
path = "";
}
getFiles(new File(path + pkgName), condition, parent);
if (parseFile) {
for (ParserEntity item : list) {
analyse(item);
}
}
return true;
}
public boolean finishReverseEngineering() {
SimpleEvent event = new SimpleEvent(this, "reverseengineering", null, this.list);
boolean update = getReverseEngineering().update(event);
this.fixClassModel();
return update;
}
public SimpleList<String> analyseJavaDoc(boolean fullCheck) {
SimpleList<String> errors = new SimpleList<String>();
for (ParserEntity item : list) {
errors.addAll(analyseJavaDoc(item, fullCheck));
}
return errors;
}
/**
* Validates a single java file for the java doc
*
* @param entity Analyse JavaDoc
* @param fullCheck FullCheck
* @return List f Warnings and Errors
*/
public SimpleList<String> analyseJavaDoc(ParserEntity entity, boolean fullCheck) {
CharacterBuffer content = FileBuffer.readFile(entity.getFileName());
content.replace('\t', ' ');
/* Create a string */
SimpleList<String> lines = new SimpleList<String>();
while (content.isEnd() == false) {
lines.add(content.readLine().toString());
}
SimpleList<String> msg = new SimpleList<String>();
String currentPackage = null;
for (String s : lines) {
if (s.contains("package")) {
currentPackage = s.replace("package", "");
break;
}
}
if (currentPackage != null) {
currentPackage = currentPackage.substring(0, currentPackage.length() - 1);
}
/* Set the current file name */
String currentFileName = entity.getFileName();
/* Check the class java doc */
msg.addAll(checkClassJavaDoc(content, lines, fullCheck, currentPackage, currentFileName));
/* Check all method java doc */
msg.addAll(checkMethodsJavaDoc(content, lines, fullCheck, currentPackage, currentFileName));
return msg;
}
/**
* Checks if there is a java doc over the class declaration
*
* @param text The text which should be checked
* @param lines All lines of the current checked file
* @param fullCheck Should founded warnings or error
* @param currentPackage PackageName
* @param currentFileName FileName
* @return All Messages
*/
private SimpleList<String> checkClassJavaDoc(CharacterBuffer text, SimpleList<String> lines, boolean fullCheck,
String currentPackage, String currentFileName) {
SimpleList<String> msg = new SimpleList<String>();
/*
* First check if the class has java doc, first get the index of "public class"
*/
int start = text.indexOf("public class");
/* if the start -1 the class could be abstract */
if (start == -1) {
/* Get the index of "public abstract class" */
start = text.indexOf("public abstract class");
}
/* If the start -1 the class is a enumeration */
if (start == -1) {
/* Get the index of "public enum" */
start = text.indexOf("public enum");
}
/* If the start -1 the class is a interface */
if (start == -1) {
/* Get the index of "public interface" */
start = text.indexOf("public interface");
}
/* Maybe there is just a class */
if (start == -1) {
/* Get the index of "class" */
start = text.indexOf("class ");
}
/* Get the line of the class definition to jump there if there is no comment */
int lineClass = -1;
for (String s : lines) {
if (s.contains("public class")) {
lineClass = lines.indexOf(s) + 1;
break;
}
}
/* Check if line class is still -1, this means class could be abstract */
for (String s : lines) {
if (s.contains("public abstract class")) {
lineClass = lines.indexOf(s) + 1;
break;
}
}
/* Check if line class is still -1, this means class is a interface */
if (lineClass == -1) {
for (String s : lines) {
if (s.contains("public interface")) {
lineClass = lines.indexOf(s) + 1;
break;
}
}
}
/* Check if line class is still -1, this means class is a enumeration */
if (lineClass == -1) {
for (String s : lines) {
if (s.contains("public enum")) {
lineClass = lines.indexOf(s) + 1;
break;
}
}
}
/* Maybe there is just a class */
if (lineClass == -1) {
for (String s : lines) {
if (s.contains("class ")) {
lineClass = lines.indexOf(s) + 1;
break;
}
}
}
/* Check if the char before the method declaration is a end of a comment */
if (lines.get(lineClass - 2).contains("*/")) {
/* Get the java doc comment, 0 because the first comment is the class comment */
String classDoc = extractJavaDocComment(text, start);
/* There is no comment or a wrong one */
if (classDoc.isEmpty() && fullCheck) {
/* Put the missing java doc to the trace informations */
msg.add("ERROR:" + currentPackage + ".missing.ClassDoc(" + currentFileName + ":" + lineClass + ")");
} else {
/* Check if there is a line like * some text */
if (Pattern.compile("\\u002A \\w+").matcher(classDoc).find() == false) {
/* There is no text, put a missing doc description error */
msg.add("WARNING:" + currentPackage + ".missing.ClassDocText(" + currentFileName + ":" + lineClass
+ ")");
}
/* Check if there is the @author tag with some text */
if (classDoc.split("@author").length == 1) {
/* There is no tag and no text */
msg.add("ERROR:" + currentPackage + ".missing.AuthorTag(" + currentFileName + ":" + lineClass
+ ")");
} else if (classDoc.split("@author [a-zA-Z]+").length == 1) {
/* There is no text after the tag, create warning */
msg.add("WARNING:" + currentPackage + ".missing.AuthoTagText(" + currentFileName + ":" + lineClass
+ ")");
}
}
/* Return we found a doc or a incomplete doc */
return msg;
}
/* Only if full check is enable */
if (fullCheck) {
/*
* There is no comment in any case over the class definition, therefore create
* error
*/
msg.add("ERROR:" + currentPackage + ".missing.ClassDoc(" + currentFileName + ":" + lineClass + ")");
}
return msg;
}
/**
* Checks if there is a java doc over all method declarations
*
* @param text The text which should be checked
* @param lines All lines of the current checked file
* @param fullCheck Should founded warnings or error
* @param currentPackage PackageName
* @param currentFileName FileName
* @return All Messages
*/
private SimpleList<String> checkMethodsJavaDoc(CharacterBuffer text, SimpleList<String> lines, boolean fullCheck,
String currentPackage, String currentFileName) {
SimpleList<String> msg = new SimpleList<String>();
/* Create a matcher to find all method declarations */
Matcher methodPattern = Pattern.compile(
"((public|private|protected|static|final|native|synchronized|abstract|transient)+\\s)+[\\$_\\w\\<\\>\\[\\]]*\\s+[\\$_\\w]+\\([^\\)]*\\)?\\s*\\{?")
.matcher(text);
/* Go through all matches */
while (methodPattern.find()) {
/* Get the current match */
String match = methodPattern.group();
/* Get the index of the current match in the text */
int start = methodPattern.start();
/* Get the line of the method definition to jump there if there is no comment */
int lineMethod = -1;
for (String s : lines) {
if (s.contains(match)) {
lineMethod = lines.indexOf(s) + 1;
break;
}
}
/*
* If lineMethod still -1 the { is in the next row therefore cut at \n and try
* again
*/
if (lineMethod == -1) {
for (String s : lines) {
if (s.contains(match.split("\n")[0])) {
lineMethod = lines.indexOf(s) + 1;
break;
}
}
}
if (lineMethod == -1) {
continue;
}
/*
* Check if there is a annotation over the method, if so go to the next method
*/
if (lines.get(lineMethod - 2).contains("@")) {
continue;
}
/* Check if the char before the method declaration is a end of a comment */
if (lines.get(lineMethod - 2).contains("*/")) {
/* Get the java doc comment */
String methodDoc = extractJavaDocComment(text, start);
/* There is no comment or a wrong one */
if (methodDoc.isEmpty() && fullCheck) {
/* Put the missing java doc to the trace informations */
msg.add("ERROR:" + currentPackage + ".missing.MethodDoc(" + currentFileName + ":" + lineMethod
+ ")");
} else {
/* Check if there is a line like * some text */
if (Pattern.compile("\\u002A \\s*[\\w+<]").matcher(methodDoc).find() == false) {
if (match.contains(" get") || match.contains(" set") || match.contains(" is")
|| match.contains(" with")) {
/* no nessessary Comment for getter and Setter */
continue;
}
/* There is no text, put a missing doc description error */
msg.add("WARNING:" + currentPackage + ".missing.MethodDocText(" + currentFileName + ":"
+ lineMethod + ")");
}
/* Check if there are parameters in the method declaration */
if (match.contains("()") == false) {
/* Get all parameters from method declaration */
String temp = match.substring(match.indexOf("(") + 1, match.indexOf(")"))
.replaceAll("<[^\\)]*>", "");
String[] parameters = temp.split(",");
/* Go through all parameters and check the @param tag */
for (String s : parameters) {
String[] param = s.trim().split(" ");
String parameterName = null;
if (param.length > 1) {
parameterName = param[1];
}
/* Check if there is a @param tag with the current parameter name */
if (methodDoc.split("@param " + parameterName).length == 1) {
/* There is no tag and no text */
msg.add("ERROR:" + currentPackage + ".missing.ParamTag(" + currentFileName + ":"
+ lineMethod + ")");
} else if (methodDoc.split("@param " + parameterName + "[ ]+[a-zA-Z*\r]+").length == 1) {
/* There is no text after the tag, create warning */
msg.add("WARNING:" + currentPackage + ".missing.ParamTagText(" + currentFileName + ":"
+ lineMethod + ")");
}
}
}
/* Check if there is a return type in the method */
if (match.contains("void") == false) {
/* There is no tag and text */
if (methodDoc.split("@return").length == 1) {
/* There is no tag and no text */
msg.add("ERROR:" + currentPackage + ".missing.ReturnTag(" + currentFileName + ":"
+ lineMethod + ")");
} else if (methodDoc.split("@return[ \t]+[a-zA-Z]+").length == 1) {
/* There is no text after the tag, create warning */
msg.add("WARNING:" + currentPackage + ".missing.ReturnTagText(" + currentFileName + ":"
+ lineMethod + ")");
}
}
}
/* Look at the next match */
continue;
}
/* Only if full check is enable */
if (fullCheck) {
/*
* There is no comment in any case over the method definition, therefore create
* error
*/
msg.add("ERROR:" + currentPackage + ".missing.MethodDoc(" + currentFileName + ":" + lineMethod + ")");
}
}
return msg;
}
/**
* Extract a java doc comment from a given string
*
* @param extractFrom The text where the comment should be extracted from
* @param searchIndex The index from which the search will start
* @return The extracted comment as string
*/
private String extractJavaDocComment(CharacterBuffer extractFrom, int searchIndex) {
/* Initialize attribute which describe the range of the doc */
int begin = -1;
int end = -1;
/* Go back from the searchIndex */
for (int i = searchIndex; i > 0; i--) {
/* Found the start of the java doc */
if (extractFrom.charAt(i) == '*' && extractFrom.charAt(i - 1) == '*' && extractFrom.charAt(i - 2) == '/') {
/* The start of the comment */
begin = i - 2;
/* Found the end of the java doc */
} else if (extractFrom.charAt(i) == '/' && extractFrom.charAt(i - 1) == '*') {
/* The end of the comment */
end = i;
}
/* If both values found break */
if (begin != -1 && end != -1) {
break;
}
}
/* Wrong comment just a /* or there is no comment */
if (begin == -1) {
return "";
}
return extractFrom.substring(begin, end + 2);
}
public SimpleKeyValueList<String, SimpleList<ParserEntity>> getPackageList() {
return packageList;
}
public static ParserEntity createParserEntity(File file, ObjectCondition condition) {
return new ParserEntity().withFile(file.getAbsolutePath()).withCondition(condition);
}
public ParserEntity analyse(ParserEntity entity) {
CharacterBuffer content = FileBuffer.readFile(entity.getFileName());
try {
entity.parse(content);
} catch (Exception e) {
this.error.add(entity);
}
return entity;
}
public ClassModel analyseSymTabEntry(ClassModel model) {
if (model == null) {
model = this;
}
for (ParserEntity element : list) {
this.add(element.getClazz());
element.addMemberToModel(false);
}
return model;
}
public ClassModel analyseBounds(ClassModel model) {
if (model == null) {
model = this;
}
String search = "import " + model.getName();
SimpleKeyValueList<Clazz, SimpleList<String>> assocs = new SimpleKeyValueList<Clazz, SimpleList<String>>();
ClazzSet set = new ClazzSet();
for (ParserEntity element : list) {
Clazz clazz = element.getClazz();
SimpleList<SymTabEntry> imports = element.getSymbolEntries(SymTabEntry.TYPE_IMPORT);
SimpleList<String> assoc = new SimpleList<String>();
for (SymTabEntry item : imports) {
if (item.getName().startsWith(search)) {
String ref = item.getName().substring(7);
ref = ref.substring(ref.lastIndexOf('.') + 1);
if (ref.equals("//") == false) {
assoc.add(ref);
}
}
}
set.add(clazz);
assocs.add(clazz, assoc);
}
for (int i = 0; i < assocs.size(); i++) {
Clazz clazz = assocs.getKeyByIndex(i);
SimpleList<String> assocValue = assocs.getValueByIndex(i);
if (assocValue.size() > 0) {
model.add(clazz);
}
for (String item : assocValue) {
Clazz target = (Clazz) this.getChildByName(item, Clazz.class);
if (target == null) {
target = set.getClazz(item);
}
if (target == null) {
target = model.createClazz(item);
}
clazz.createBidirectional(target, "use", Association.ONE, "use", Association.ONE);
}
}
for (int i = 0; i < packageList.size(); i++) {
SimpleList<ParserEntity> parserEntities = packageList.getValueByIndex(i);
for (int p = 0; p < parserEntities.size(); p++) {
ParserEntity parserEntity = parserEntities.get(p);
CharacterBuffer content = parserEntity.getCode().getContent();
Clazz clazz = parserEntity.getClazz();
for (int e = 0; e < parserEntities.size(); e++) {
if (e == p) {
continue;
}
Clazz targetClazz = parserEntities.get(e).getClazz();
String targetName = targetClazz.getName();
if (content.indexOf(targetName) > 0) {
clazz.createBidirectional(targetClazz, "use", Association.ONE, "use", Association.ONE);
}
}
}
}
return model;
}
private void getFiles(File directory, ObjectCondition condition, String parent) {
if (directory.exists() && directory.isDirectory()) {
File[] items = directory.listFiles();
if (items == null) {
return;
}
SimpleList<ParserEntity> packageEntity = new SimpleList<ParserEntity>();
ObjectCondition con = null;
boolean isRekusive = true;
if(condition instanceof FeatureCondition) {
Feature feature = ((FeatureCondition) condition).getFeature(null);
if(feature != null && RECURSIVE.equalsIgnoreCase(feature.getName())){
isRekusive = false;
}
}
if(isRekusive) {
con = condition;
}
for (File file : items) {
if (file.getName().endsWith(JAVA_FILE_SUFFIX)) {
ParserEntity element = createParserEntity(file, con);
list.add(element);
packageEntity.add(element);
} else if (file.getName().equalsIgnoreCase("test") == false && file.isDirectory()) {
if(isRekusive == false){
continue;
}
getFiles(file, condition, parent + directory.getName() + ".");
}
}
if (packageEntity.size() > 0) {
packageList.put(parent + directory.getName(), packageEntity);
}
}
}
public ClassModel analyseInBoundLinks(ClassModel model) {
if (model == null) {
model = this;
}
for (int i = 0; i < list.size(); i++) {
ParserEntity parserEntity = list.get(i);
CharacterBuffer content = parserEntity.getCode().getContent();
Clazz clazz = parserEntity.getClazz();
model.add(clazz);
for (int p = 0; p < list.size(); p++) {
if (p == i) {
continue;
}
Clazz targetClazz = list.get(p).getClazz();
String targetName = targetClazz.getName();
if (content.indexOf("protected " + targetName + " ") > 0) {
clazz.createBidirectional(targetClazz, "use", Association.ONE, "use", Association.ONE);
continue;
}
if (content.indexOf("public " + targetName + " ") > 0) {
clazz.createBidirectional(targetClazz, "use", Association.ONE, "use", Association.ONE);
continue;
}
MethodSet methods = clazz.getMethods();
for (Method m : methods) {
if (m.getModifier().has(Modifier.PROTECTED) || m.getModifier().has(Modifier.PUBLIC)) {
for (Parameter param : methods.getParameters()) {
if (param.getType().equals(targetClazz)) {
clazz.createBidirectional(targetClazz, "use", Association.ONE, "use", Association.ONE);
break;
}
}
}
}
}
}
return model;
}
public SimpleSet<ParserEntity> getErros() {
return error;
}
public int analyseMcCabe(Object item) {
String methodBody = null;
Method owner = null;
if (item instanceof Method) {
owner = (Method) item;
methodBody = owner.getBody();
methodBody = methodBody.toLowerCase();
methodBody = methodBody.replace("\n", "");
methodBody = methodBody.replaceAll("\t", "");
methodBody = methodBody.replaceAll(" ", "");
methodBody = methodBody.replace('<', '(');
methodBody = methodBody.replace('>', ')');
} else if (item instanceof String) {
methodBody = (String) item;
}
int mcCabe = 1;
if (methodBody != null) {
mcCabe += check(methodBody, "if(");
mcCabe += check(methodBody, "do{");
mcCabe += check(methodBody, "while(");
mcCabe += check(methodBody, "&&");
if (owner != null) {
GraphMetric metric = GraphMetric.create(owner);
metric.withMcCabe(mcCabe);
}
}
return mcCabe;
}
private int check(String body, String search) {
int mcCabe = 0;
int index = 0;
while (index >= 0) {
index = body.indexOf(search, index);
if (index == -1) {
break;
}
if (checkQuotes(body, index)) {
mcCabe++;
index += 1;
} else {
index += 1;
}
}
return mcCabe;
}
public boolean checkQuotes(String allText, int index) {
int quote = 0;
for (int i = 0; i < index; i++) {
char nextChar = allText.charAt(i);
if (nextChar == '\"')
quote++;
}
if (quote % 2 == 0) {
return true;
} else {
return false;
}
}
public GraphMetric analyseLoC(Object item) {
String methodBody = null;
Method owner = null;
if (item instanceof GraphModel) {
GraphModel model = (GraphModel) item;
GraphMetric modelMetric = GraphMetric.create(model);
ClazzSet clazzes = model.getClazzes();
for (Clazz clazz : clazzes) {
GraphMetric clazzMetric = GraphMetric.create(clazz);
MethodSet methods = clazz.getMethods();
for (Method m : methods) {
GraphMetric methodMetric = analyseLoC(m);
clazzMetric.merge(methodMetric);
}
modelMetric.merge(clazzMetric);
}
return modelMetric;
}
if (item instanceof Method) {
owner = (Method) item;
methodBody = owner.getBody();
} else if (item instanceof String) {
methodBody = (String) item;
}
GraphMetric metric = GraphMetric.create(owner);
if (methodBody == null) {
return metric;
}
int emptyLine = 0, commentCount = 0, methodheader = 0, annotation = 0, linesOfCode = 0;
String[] lines = methodBody.split("\n");
for (String line : lines) {
String simple = line.trim();
if (simple.length() < 1) {
emptyLine++;
continue;
}
if (simple.indexOf("/*") >= 0 || simple.indexOf("*/") >= 0 || simple.indexOf("//") >= 0
|| simple.startsWith("*")) {
commentCount++;
continue;
}
if ("{}".indexOf(simple) >= 0) {
methodheader++;
continue;
}
if (simple.startsWith("@")) {
annotation++;
continue;
}
linesOfCode++;
}
metric.withLoc(emptyLine, commentCount, methodheader, annotation, linesOfCode);
return metric;
}
public ParserEntity readFile(String fileName, ObjectCondition condition) {
File file = new File(fileName);
if (file.getName().endsWith(".java")) {
ParserEntity element = createParserEntity(file, condition);
SimpleList<ParserEntity> packageEntity = new SimpleList<ParserEntity>();
list.add(element);
packageEntity.add(element);
if (packageEntity.size() > 0) {
packageList.put(file.getParent(), packageEntity);
}
return analyse(element);
}
return null;
}
public ObjectCondition getReverseEngineering() {
if (reverseEngineering == null) {
reverseEngineering = new SimpleReverseEngineering();
}
return reverseEngineering;
}
public FileClassModel withReverseEngineering(ObjectCondition reverseEngineering) {
this.reverseEngineering = reverseEngineering;
return this;
}
@Override
public Object getValue(String attribute) {
if (PROPERTY_FILETYPE.equalsIgnoreCase(attribute)) {
return getClass().getSuperclass().getSimpleName().toLowerCase();
}
return super.getValue(attribute);
}
@Override
public HTMLEntity dumpHTML(String diagramName, boolean... write) {
finishReverseEngineering();
return super.dumpHTML(diagramName, write);
}
public static final FeatureCondition createFeature(String key) {
FeatureCondition condition=new FeatureCondition();
Feature item= GraphUtil.createFeature(key);
condition.withFeature(item);
return condition;
}
}