code: package org.rug.data.characteristics.comps;
  
  import org.apache.tinkerpop.gremlin.process.traversal.P;
  import org.apache.tinkerpop.gremlin.structure.Direction;
  import org.apache.tinkerpop.gremlin.structure.Edge;
  import org.apache.tinkerpop.gremlin.structure.Vertex;
  import org.rug.data.labels.EdgeLabel;
  import org.rug.data.labels.VertexLabel;
  import org.rug.data.project.IVersion;
  
  import java.util.EnumSet;
  import java.util.regex.Pattern;
  
  /**
   * Counts the lines of code of a given vertex (package or class) and saves the results within
   * the vertex node using {@link #name} as key.
   */
  public class NumberOfLinesOfCode extends AbstractComponentCharacteristic {
  
      private SourceCodeRetriever sourceRetriever;
  
      /**
       * Instantiates the calculator of LOC.
       */
      public NumberOfLinesOfCode(){
          super("linesOfCode",
                  VertexLabel.allTypes(),
                  EnumSet.noneOf(EdgeLabel.class));
      }
  
      @Override
      public void calculate(IVersion version) {
          this.sourceRetriever = version.getSourceCodeRetriever();
          super.calculate(version);
      }
  
      /**
       * This method is applied to every vertex that has the label given during instantiation.
       * If the method is invoked on a package vertex, than it triggers the calculation of the lines of code on all the
       * classes belonging to the package.
       *
       * @param vertex the vertex to calculate this characteristic on. The result is stored using {@code this.name}
       *              as property key.
       */
      @Override
      public void calculate(Vertex vertex) {
          if (vertex.property(this.name).isPresent() || sourceRetriever == null)
              return;
  
          long loc;
          if (VertexLabel.getFilesStrings().contains(vertex.label())){
              loc = countLOC(vertex);
          }else if (VertexLabel.getComponentStrings().contains(vertex.label()) &&
                     vertex.edges(Direction.IN, EdgeLabel.BELONGSTO.toString()).hasNext()) {
              vertex.graph().traversal().V(vertex)
                      .in(EdgeLabel.BELONGSTO.toString())
                      .hasNot(this.name)
                      .forEachRemaining(this::calculate);
              loc = vertex.graph().traversal().V(vertex)
                      .in(EdgeLabel.BELONGSTO.toString())
                      .hasLabel(P.within(VertexLabel.getFilesStrings())) // only files in the container
                      .values(this.name)
                      .sum().next().longValue();
          }else {
              return;
          }
          vertex.property(this.name, loc);
  
      }
  
      /**
       * This method is applied to every vertex that has the label given during instantiation.
       *
       * @param edge the edge to calculate this characteristic on. The result must be stored within the edge using
       *             {@link #name} as property key.
       */
      @Override
      protected void calculate(Edge edge) {
  
      }
  
      public void setSourceRetriever(SourceCodeRetriever sourceRetriever) {
          this.sourceRetriever = sourceRetriever;
      }
  
      private Pattern linePattern = Pattern.compile("[^\\s*].*[\\n\\r]+");
      private long countLOC(Vertex element){
          var sourceCode = sourceRetriever.getSource(element);
  
          var matcher = linePattern.matcher(sourceCode);
          var linesOfCode = 0;
          while(matcher.find())
              linesOfCode++;
          return linesOfCode;
      }
  
  }
  