code: package org.rug.data.characteristics.comps;
  
  import org.apache.tinkerpop.gremlin.process.traversal.P;
  import org.apache.tinkerpop.gremlin.structure.Edge;
  import org.apache.tinkerpop.gremlin.structure.Vertex;
  import org.rug.data.characteristics.IComponentCharacteristic;
  import org.rug.data.labels.EdgeLabel;
  import org.rug.data.labels.VertexLabel;
  import org.rug.data.project.IVersion;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.util.EnumSet;
  import java.util.List;
  import java.util.stream.Collectors;
  
  public abstract class AbstractComponentCharacteristic implements IComponentCharacteristic {
  
      private final static Logger logger = LoggerFactory.getLogger(AbstractComponentCharacteristic.class);
  
      protected String name;
      protected List<String> eLabels;
      protected List<String> vLabels;
  
      public AbstractComponentCharacteristic(String name, EnumSet<VertexLabel> vLabels, EnumSet<EdgeLabel> eLabels) {
          this.name = name;
          this.vLabels = vLabels.stream().map(VertexLabel::toString).collect(Collectors.toList());
          this.eLabels = eLabels.stream().map(EdgeLabel::toString).collect(Collectors.toList());
      }
  
      /**
       * Returns the name of this characteristic.
       *
       * @return the name.
       */
      @Override
      public String getName() {
          return name;
      }
  
      /**
       * Calculate the characteristic on the graph of this version and save it as a node, vertex or graph property.
       * @param version the version from which to retrieve the components
       */
      @Override
      public void calculate(IVersion version) {
          logger.debug("Computing {} characteristic", name);
          var graph = version.getGraph();
          if (!vLabels.isEmpty()) {
              graph.traversal().V().hasLabel(P.within(vLabels)).forEachRemaining(this::calculate);
          }
          if (!eLabels.isEmpty()) {
              graph.traversal().E().hasLabel(P.within(eLabels)).forEachRemaining(this::calculate);
          }
      }
  
      /**
       * This method is applied to every vertex that has the label given during instantiation.
       * @param vertex the vertex to calculate this characteristic on. The result must be stored within the vertex using
       *               {@link #name} as property key.
       */
      protected abstract void calculate(Vertex vertex);
  
      /**
       * 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.
       */
      protected abstract void calculate(Edge edge);
  
      /**
       * Returns the label used for the main operations on the given graph.
       *
       * @return an array of labels.
       */
      @Override
      public List<String> getOperatingEdgeLabels() {
          return eLabels;
      }
  
      /**
       * Returns the label used for the main operations on the given graph.
       *
       * @return an array of labels.
       */
      @Override
      public List<String> getOperatingVertexLabels() {
          return vLabels;
      }
  }
  