package org.jtheque.metrics.services.impl.utils.count; /* * Copyright JTheque (Baptiste Wicht) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import org.jtheque.core.managers.Managers; import org.jtheque.core.managers.log.ILoggingManager; import org.jtheque.metrics.utils.JavaFileFilter; import org.jtheque.metrics.utils.elements.Class; import org.jtheque.metrics.utils.elements.Constructor; import org.jtheque.metrics.utils.elements.Method; import org.jtheque.metrics.utils.elements.Package; import org.jtheque.metrics.utils.elements.Project; import org.jtheque.utils.io.FileUtils; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.nio.channels.FileChannel; import java.util.Scanner; import java.util.regex.Pattern; import japa.parser.JavaParser; import japa.parser.ParseException; import japa.parser.ast.CompilationUnit; import japa.parser.ast.body.ClassOrInterfaceDeclaration; import japa.parser.ast.body.ConstructorDeclaration; import japa.parser.ast.body.MethodDeclaration; import japa.parser.ast.body.Parameter; import japa.parser.ast.visitor.VoidVisitorAdapter; /** * A counter for the Java project. * * @author Baptiste Wicht */ public final class JavaCounter implements Counter { private final Project project; private Package lastPackage; private Package currentPackage; private Package parentPackage; private final FileFilter filter = new JavaFileFilter(); private String lastParentAbsolutePath; private final Pointers pointers; private static final Pattern PATTERN = Pattern.compile("\\\\"); /** * Construct a new counter for a specific project. * * @param p The project to count. */ public JavaCounter(Project p) { super(); project = p; pointers = new Pointers(); } @Override public void count(File rootFolder) { count(rootFolder, true); } /** * Count the folder. * * @param folder The folder to count. * @param root A boolean flag indicating if the folder is the root folder or not. */ private void count(File folder, boolean root) { resolvePackages(folder, root); File[] files = folder.listFiles(filter); if (files != null) { for (File f : files) { if (f.isDirectory()) { count(f, false); } else { final Class c = new Class(f.getName().replace(".java", "")); currentPackage.addClass(c); countJava(f, c); countLines(f, c); } } } } /** * Resolve the packages for the folder. It seems searching and linking, the current package and the last package. * * @param folder The folder. * @param root A boolean flag indicating if the folder is the root folder or not. */ private void resolvePackages(File folder, boolean root) { String parentAbsolutePath = folder.getParentFile().getAbsolutePath(); if (root) { currentPackage = new Package(folder.getName()); lastPackage = currentPackage; project.setRootPackage(currentPackage); } else { computePackagesPaths(folder, parentAbsolutePath); } lastParentAbsolutePath = parentAbsolutePath; } /** * Compute the packages paths. * * @param folder The current folder. * @param parentAbsolutePath The absolute path of the parent. */ private void computePackagesPaths(File folder, String parentAbsolutePath) { currentPackage = new Package(folder.getName()); int levels = calcLevel(parentAbsolutePath); if (levels < 0) { lastPackage.addPackage(currentPackage); parentPackage = lastPackage; lastPackage = currentPackage; } else if (levels == 0) { parentPackage.addPackage(currentPackage); lastPackage = currentPackage; } else if (levels > 0) { Package parent = parentPackage; for (int i = 0; i <= levels - 1; i++) { parent = parent.getParent(); } parent.addPackage(currentPackage); parentPackage = parent; lastPackage = currentPackage; } } /** * Calc the level of the path. * * @param parentAbsolutePath The path to search the level for. * * @return The level of the path. */ private int calcLevel(String parentAbsolutePath) { int levels = 0; if (parentAbsolutePath.equals(lastParentAbsolutePath)) { levels = 0; } else if (parentAbsolutePath.startsWith(lastParentAbsolutePath)) { levels = -1; } else if (lastParentAbsolutePath.startsWith(parentAbsolutePath)) { String change = lastParentAbsolutePath.replaceFirst(parentAbsolutePath.replace("\\", "\\\\"), ""); levels = PATTERN.split(change).length - 1; } return levels; } /** * Count lines of the file. * * @param f The file. * @param c The class representing the file. */ private void countLines(File f, Class c) { int lines = 0; int commentLines = 0; int current = 1; FileChannel in = null; try { in = new FileInputStream(f).getChannel(); Scanner scanner = new Scanner(in); Pointer pointer = null; int tempCodeLines = 0; int tempCommentLines = 0; int tempPhysicalLines = 0; while (scanner.hasNextLine()) { if (pointer == null) { pointer = pointers.getPointer(current); if (pointer != null) { tempCodeLines = 0; tempCommentLines = 0; tempPhysicalLines = 0; } } String line = scanner.nextLine().trim(); if (!"".equals(line)) { if (isComment(line)) { commentLines++; tempCommentLines++; } else { lines++; tempCodeLines++; } } current++; tempPhysicalLines++; pointer = updatePointerInfos(current, pointer, tempCodeLines, tempCommentLines, tempPhysicalLines); } } catch (FileNotFoundException e) { Managers.getManager(ILoggingManager.class).getLogger(getClass()).error(e); } finally { FileUtils.close(in); } updateClassInfos(c, lines, commentLines, current); } /** * Update the informations of the class. * * @param c The Class to fill. * @param lines The lines. * @param commentLines The lines of comment. * @param physicalLines The number of physical lines. */ private static void updateClassInfos(Class c, int lines, int commentLines, int physicalLines) { c.setCodeLines(lines); c.setCommentLines(commentLines); c.setPhysicalLines(physicalLines - 1); } /** * Update the informations of the line pointer. * * @param current The current line. * @param pointer The current pointer or <code>null</code> if there is no pointer now. * @param tempCodeLines The code lines * @param tempCommentLines The comment lines. * @param tempPhysicalLines The physical lines. * * @return The current pointer or null if the pointer is arrived at the end of his field. */ private static Pointer updatePointerInfos(int current, Pointer pointer, int tempCodeLines, int tempCommentLines, int tempPhysicalLines) { if (pointer != null && pointer.getStopLine() == current) { pointer.setCommentLines(tempCommentLines); pointer.setLinesOfCode(tempCodeLines - 1); pointer.setPhysicalLines(tempPhysicalLines); return null; } return pointer; } /** * Indicate if a line is a comment or not. * * @param line The line to test. * * @return true if the line is a comment else false. */ private static boolean isComment(String line) { return line.startsWith("//") || line.startsWith("/*") || line.startsWith("*") || line.startsWith("*/"); } /** * Count the file for java metrics like methods, constructors. * * @param f The file. * @param c The class representing the file. */ private void countJava(File f, Class c) { FileInputStream fin = null; pointers.clear(); try { fin = new FileInputStream(f); CompilationUnit cu = JavaParser.parse(fin); new ClassVisitorCounter(c).visit(cu, null); } catch (FileNotFoundException e) { Managers.getManager(ILoggingManager.class).getLogger(getClass()).error(e); } catch (ParseException e) { Managers.getManager(ILoggingManager.class).getLogger(getClass()).error(e); } finally { FileUtils.close(fin); } } /** * A visitor to count the java properties of the class. * * @author Baptiste Wicht */ private final class ClassVisitorCounter extends VoidVisitorAdapter<Object> { private final Class c; /** * Construct a new ClassVisitorCounter. * * @param c The class. */ private ClassVisitorCounter(Class c) { super(); this.c = c; } @Override public void visit(ConstructorDeclaration d, Object o) { Constructor constructor = new Constructor(constructName(d)); pointers.addPointer(new ConstructorPointer(constructor, d.getBeginLine(), d.getEndLine())); c.getConstructors().add(constructor); } @Override public void visit(MethodDeclaration d, Object o) { Method m = new Method(constructName(d)); int annotations = 0; if (d.getAnnotations() != null && !d.getAnnotations().isEmpty()) { annotations += d.getAnnotations().size(); } pointers.addPointer(new MethodPointer(m, d.getBeginLine() + annotations, d.getEndLine())); c.getMethods().add(m); } /** * Construct the constructor name. * * @param d The constructor declaration. * * @return The name of the constructor. */ private String constructName(ConstructorDeclaration d) { return constructName(d.getName(), d.getParameters()); } /** * Construct the method name. * * @param d The method declaration. * * @return The name of the method. */ private String constructName(MethodDeclaration d) { return constructName(d.getName(), d.getParameters()); } /** * Construct the name for a method or constructor with parameters. * * @param name The name of the class or constructor. * @param parameters The parameters. * * @return The name. */ private String constructName(String name, Iterable<Parameter> parameters) { StringBuilder builder = new StringBuilder(name); builder.append('('); if (parameters != null) { boolean separated = false; for (Parameter p : parameters) { if (separated) { builder.append(','); } separated = true; builder.append(p.getType().toString()); builder.append(' '); builder.append(p.getId().getName()); } } builder.append(')'); return builder.toString(); } @Override public void visit(ClassOrInterfaceDeclaration d, Object arg) { super.visit(d, arg); if (!d.getName().equals(c.getName())) { currentPackage.addClass(new Class(d.getName())); } } } }