All Downloads are FREE. Search and download functionalities are using the official Maven repository.

merger.CodeSnipitLocater Maven / Gradle / Ivy

Go to download

JavaForger can create source code from templates using existing java classes as input.

There is a newer version: 2.0.1
Show newest version
/*
 * Copyright 2019 by Daan van den Heuvel.
 *
 * This file is part of JavaForger.
 *
 * 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.
 */
package merger;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.expr.SimpleName;

import generator.JavaForgerException;

/**
 * Determines the location of code to be added, within existing code. Receives code that is already parsed. Ordering is based on the order defined within the
 * {@link NodeComparator}.
 *
 * @author Daan
 */
public class CodeSnipitLocater {

  NodeComparator comparator = new NodeComparator();

  /**
   * Receives two {@link CompilationUnit}s and determines the location of a package, imports, fields, constructors, methods or inner classes. This method will
   * retain the order of Nodes in both existing and new code. It can therefore happen that not all nodes are ordered according to the {@link NodeComparator} if
   * the inserted code was also not ordered like that.
   *
   * @param existingCode {@link CompilationUnit} representing the existing class
   * @param newCode {@link CompilationUnit} representing the code to be added
   * @return An {@link LinkedHashMap} with as keys a {@link CodeSnipitLocation} of code to be added and as value a {@link CodeSnipitLocation} where in the
   *         existing class the code should be added. The map is ordered on increasing insert locations (map values).
   */
  public LinkedHashMap locate(CompilationUnit existingCode, Node newCode) {
    LinkedHashMap locations = recursiveLocator(existingCode.getChildNodes(), newCode.getChildNodes());

    // locations may not be sorted on increasing insertLocation if an existing code block will be overridden and occurs before the last determined insert
    // location. So we need to sort them here.
    locations = locations.entrySet().stream().sorted(Map.Entry.comparingByValue())
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, LinkedHashMap::new));

    return locations;
  }

  /**
   * Calculates the insertion locations for the insertNodes within the existingNodes. Recursively handles classes. The order of the insertNodes is retained,
   * unless an earlier existing node was equal to an insert node.
   *
   * @param existingNodes The nodes from the existing class. May not be empty.
   * @param insertNodes The nodes from the class to be inserted. May not be empty.
   * @return The map of where insertion-code (from) needs to be inserted (to).
   */
  protected LinkedHashMap recursiveLocator(List existingNodes, List insertNodes) {
    LinkedHashMap locations = new LinkedHashMap<>();

    // -1 indicates that the new node needs to be inserted before the first node within the existing nodes.
    int insertAfter = -1;

    List supportedInsertNodes = insertNodes.stream().filter(comparator::nodeTypeIsSupported).collect(Collectors.toList());

    for (Node insertNode : supportedInsertNodes) {
      int equalNodeIndex = findEqualNode(existingNodes, insertNode);
      if (equalNodeIndex >= 0) {
        locations.putAll(handleEqualNodesRecursively(insertNode, existingNodes.get(equalNodeIndex)));
        insertAfter = Integer.max(insertAfter, equalNodeIndex);
      } else {
        insertAfter = findInsertAfterIndex(existingNodes, insertAfter, insertNode);
        if (insertAfter < 0) {
          locations.put(CodeSnipitLocation.of(insertNode), CodeSnipitLocation.before(existingNodes.get(0)));
        } else {
          locations.put(CodeSnipitLocation.of(insertNode), CodeSnipitLocation.after(existingNodes.get(insertAfter)));
        }
      }
    }
    return locations;
  }

  private LinkedHashMap handleEqualNodesRecursively(Node insertNode, Node existingNode) {
    LinkedHashMap loc = new LinkedHashMap<>();
    if (isClass(existingNode) && isClass(insertNode)) {
      List insertNodes = getChildNodes(insertNode);
      List existingNodes = getChildNodes(existingNode);
      if (!insertNodes.isEmpty()) {
        if (existingNodes.isEmpty()) {
          CodeSnipitLocation firstInsertLocation = getFirstInsertLocation((ClassOrInterfaceDeclaration) existingNode);
          loc = insertNodes.stream().collect(Collectors.toMap(CodeSnipitLocation::of, c -> firstInsertLocation, (a, b) -> a, LinkedHashMap::new));
        } else {
          // Recursive call
          loc = recursiveLocator(existingNodes, insertNodes);
        }
      }
    } else {
      loc.put(CodeSnipitLocation.of(insertNode), CodeSnipitLocation.of(existingNode));
    }
    return loc;
  }

  private CodeSnipitLocation getFirstInsertLocation(ClassOrInterfaceDeclaration existingNode) {
    return CodeSnipitLocation.after(getNodeAfterToInsert(existingNode));
  }

  private Node getNodeAfterToInsert(ClassOrInterfaceDeclaration existingNode) {
    return existingNode.getChildNodes().stream().filter(node -> SimpleName.class.isAssignableFrom(node.getClass())).findFirst().orElseThrow(
        () -> new JavaForgerException("Cannot insert code into a class without a simpleName defined. Existing node is: " + existingNode.toString()));
  }

  private boolean isClass(Node existingNode) {
    return ClassOrInterfaceDeclaration.class.isAssignableFrom(existingNode.getClass());
  }

  /**
   * Gets the child nodes and strips off any nodes that are part of the definition of the parent node, such as the name of the class or what it extends.
   *
   * @param node
   * @return
   */
  private List getChildNodes(Node node) {
    return node.getChildNodes().stream().filter(comparator::nodeTypeIsSupported).collect(Collectors.toList());
  }

  /**
   * Returns an index (integer) within existingNodes or -1 if it does not exist, so that we can set the existingIndex to that value if it's higher.
   *
   * @param existingNodes The nodes to check if one is equal to the insertNode.
   * @param insertNode The node to check.
   * @return The index of the node equal to the insertNode if it exists, -1 otherwise.
   */
  private int findEqualNode(List existingNodes, Node insertNode) {
    for (int index = 0; index < existingNodes.size(); index++) {
      if (comparator.compare(existingNodes.get(index), insertNode) == 0) {
        return index;
      }
    }
    return -1;
  }

  /**
   * Finds the next index after which the insertNode needs to be inserted.
   *
   * @param existingNodes The existing nodes we need to compare the insertNode with.
   * @param previousIndex The index used in a previous iteration to insert a next node. This index might be -1 it is the first time a node is inserted for the
   *          input existingNodes.
   * @param insertNode The node for which we need to find an insert location.
   * @return An index between the previousIndex (inclusive) and existingNodes.size (exclusive)
   */
  private int findInsertAfterIndex(List existingNodes, int previousIndex, Node insertNode) {
    int index = previousIndex;

    // Check if the previous index should be before the insertNode
    int compare = comparator.compare(existingNodes.get(Integer.max(0, index)), insertNode);

    // While the current index is before the insertIndex
    while (compare < 0 && index < existingNodes.size() - 1) {
      // compare the next existing node
      compare = comparator.compare(existingNodes.get(index + 1), insertNode);
      // only increment if next existing node should be placed before insertNode
      if (compare < 0) {
        index++;
      }
    }
    return index;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy