code: package de.tu_darmstadt.stg.mudetect.aug.model;
  
  import org.jgrapht.graph.DirectedMultigraph;
  
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Optional;
  import java.util.Set;
  import java.util.stream.Collectors;
  
  public class APIUsageGraph extends DirectedMultigraph<Node, Edge> {
      private static final String FROZEN = "this API-usage graph is frozen";
  
      private boolean frozen;
      private int hash;
      private Set<Node> meaningfullActionNodesCache = null;
      
      public String name;
      public Set<String> interfaces = new HashSet<>();
  
      public boolean isCtor;
      
      // fields used and their joining point
      public Map<String, Node> fieldsUsed = new HashMap<>(); // cached for easy lookup
      
      public APIUsageGraph() {
          super(Edge.class);
      }
      
      
      public APIUsageGraph(APIUsageGraph oldGraph) {
          this();
          for (Node node : oldGraph.vertexSet()) {
          	this.addVertex(node);
          }
          
          for (Edge edge : oldGraph.edgeSet()) {
          	this.addEdge(edge.getSource(), edge.getTarget(), edge);
          }
      }
  
      public int getNodeSize() {
          return vertexSet().size();
      }
  
      public int getEdgeSize() {
          return edgeSet().size();
      }
  
      public int getSize() {
          return getNodeSize() + getEdgeSize();
      }
  
      public Set<Node> incomingNodesOf(Node node) {
          return incomingEdgesOf(node).stream().map(APIUsageGraph.this::getEdgeSource).collect(Collectors.toSet());
      }
  
      public Set<Node> outgoingNodesOf(Node node) {
          return outgoingEdgesOf(node).stream().map(APIUsageGraph.this::getEdgeTarget).collect(Collectors.toSet());
      }
  
      public Set<String> getAPIs() {
          return vertexSet().stream().map(Node::getAPI)
                  .filter(Optional::isPresent).map(Optional::get)
                  .collect(Collectors.toSet());
      }
  
      /**
       * Freezes this API-usage graph. Once the graph is frozen, no elements can be added or removed from it. Trying to do
       * so will result in an {@link UnsupportedOperationException}. Once the graph is frozen, it will start caching its
       * properties, such as the hash code, to reduce performance overhead.
       */
      public void freeze() {
          this.frozen = true;
      }
  
      @Override
      public boolean addEdge(Node sourceVertex, Node targetVertex, Edge edge) {
          if (frozen) throw new UnsupportedOperationException(FROZEN);
          return !hasEdge(sourceVertex, edge.getClass(), targetVertex) && super.addEdge(sourceVertex, targetVertex, edge);
      }
  
      @Override
      public Edge addEdge(Node sourceVertex, Node targetVertex) {
          if (frozen) throw new UnsupportedOperationException(FROZEN);
          return super.addEdge(sourceVertex, targetVertex);
      }
  
      @Override
      public boolean addVertex(Node node) {
          if (frozen) throw new UnsupportedOperationException(FROZEN);
          return super.addVertex(node);
      }
  
      @Override
      protected boolean removeAllEdges(Edge[] edges) {
          if (frozen) throw new UnsupportedOperationException(FROZEN);
          return super.removeAllEdges(edges);
      }
  
      @Override
      public boolean removeAllEdges(Collection<? extends Edge> edges) {
          if (frozen) throw new UnsupportedOperationException(FROZEN);
          return super.removeAllEdges(edges);
      }
  
      @Override
      public Set<Edge> removeAllEdges(Node sourceVertex, Node targetVertex) {
          if (frozen) throw new UnsupportedOperationException(FROZEN);
          return super.removeAllEdges(sourceVertex, targetVertex);
      }
  
      @Override
      public boolean removeAllVertices(Collection<? extends Node> vertices) {
          if (frozen) throw new UnsupportedOperationException(FROZEN);
          return super.removeAllVertices(vertices);
      }
  
      @Override
      public boolean removeEdge(Edge edge) {
          if (frozen) throw new UnsupportedOperationException(FROZEN);
          return super.removeEdge(edge);
      }
  
      @Override
      public Edge removeEdge(Node sourceVertex, Node targetVertex) {
          if (frozen) throw new UnsupportedOperationException(FROZEN);
          return super.removeEdge(sourceVertex, targetVertex);
      }
  
      @Override
      public boolean removeVertex(Node node) {
          if (frozen) throw new UnsupportedOperationException(FROZEN);
          return super.removeVertex(node);
      }
  
      @Override
      public int hashCode() {
          if (!frozen || hash == -1) {
              hash = super.hashCode();
          }
          return hash;
      }
  
      private boolean hasEdge(Node edgeSource, Class<? extends Edge> edgeType, Node edgeTarget) {
          for (Edge edge : outgoingEdgesOf(edgeSource)) {
              if (edgeType.isAssignableFrom(edge.getClass()) && getEdgeTarget(edge) == edgeTarget) {
                  return true;
              }
          }
          return false;
      }
  }
  