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

io.sundr.dsl.internal.graph.functions.Nodes Maven / Gradle / Ivy

/*
 * Copyright 2016 The original authors.
 *
 *    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 io.sundr.dsl.internal.graph.functions;

import static io.sundr.dsl.internal.Constants.BEGIN_SCOPE;
import static io.sundr.dsl.internal.Constants.CARDINALITY_MULTIPLE;
import static io.sundr.dsl.internal.Constants.END_SCOPE;
import static io.sundr.dsl.internal.Constants.IS_GENERATED;
import static io.sundr.dsl.internal.Constants.KEYWORDS;
import static io.sundr.dsl.internal.Constants.SCOPE_SUFFIX;
import static io.sundr.dsl.internal.utils.GraphUtils.exclusion;
import static io.sundr.dsl.internal.utils.GraphUtils.isSatisfied;
import static io.sundr.dsl.internal.utils.TypeDefUtils.isBeginScope;
import static io.sundr.dsl.internal.utils.TypeDefUtils.isEndScope;
import static io.sundr.dsl.internal.utils.TypeDefUtils.isEntryPoint;
import static io.sundr.dsl.internal.utils.TypeDefUtils.isTerminal;
import static io.sundr.dsl.internal.utils.TypeDefUtils.isTransition;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

import io.sundr.adapter.apt.AptContext;
import io.sundr.dsl.internal.graph.Node;
import io.sundr.dsl.internal.graph.NodeContext;
import io.sundr.dsl.internal.processor.DslContextManager;
import io.sundr.dsl.internal.type.functions.Combine;
import io.sundr.dsl.internal.type.functions.Generics;
import io.sundr.dsl.internal.type.functions.Generify;
import io.sundr.dsl.internal.utils.GraphUtils;
import io.sundr.dsl.internal.utils.TypeDefUtils;
import io.sundr.model.ClassRef;
import io.sundr.model.Kind;
import io.sundr.model.Method;
import io.sundr.model.TypeDef;
import io.sundr.model.TypeDefBuilder;
import io.sundr.model.functions.GetDefinition;

public class Nodes {

  /**
   * {@link Function} that accepts a set of {@link TypeDef} and creates a the transition graph.
   * The graph is structured as a set of Trees (tree-like to be more accurate as there might be circles).
   */
  public static final Function, Set>> TO_GRAPH = new Function, Set>>() {
    public Set> apply(Set clazzes) {
      Set> nodes = new LinkedHashSet>();
      Set all = new LinkedHashSet(clazzes);
      for (TypeDef clazz : clazzes) {
        if (isEntryPoint(clazz)) {
          nodes.add(TO_TREE.apply(NodeContext.builder().withItem(clazz).withAll(all).build()));
        }
      }
      return nodes;
    }
  };

  public static final Function> TO_TREE = new Function>() {
    public Node apply(NodeContext context) {
      Set> nextVertices = new LinkedHashSet>();

      //visited and path are the same only in the first iteration. see bellow:
      Set visited = new LinkedHashSet(context.getVisited());
      List nextCandidates = new ArrayList(TO_NEXT.apply(context));
      Collections.sort(nextCandidates, new CandidateComparator(context));

      for (TypeDef next : nextCandidates) {
        NodeContext nextContext = context.contextOfChild(next).addToVisited(visited).addToVisited(next).build();

        Node subGraph = TO_TREE.apply(nextContext);
        //Let's keep track of types used so far in the loop so that we avoid using the same types, in different branches of the tree:
        //This is required so that we avoid extending the same generic interface with different parameters.
        visited.add(subGraph.getItem());
        if (subGraph.getTransitions().size() > 0 || isTerminal(subGraph.getItem()) || isEndScope(subGraph.getItem())) {
          nextVertices.add(subGraph);
        }
      }
      return DslContextManager.getContext().getNodeRepository().getOrCreateNode(context.getItem(), nextVertices);
    }
  };

  /**
   * {@link Function} that accepts a {@link NodeContext} and finds which classes can follow next.
   */
  public static final Function> TO_NEXT = new Function>() {
    public Set apply(NodeContext context) {
      Set result = new LinkedHashSet();
      Boolean inScope = !context.getActiveScopes().isEmpty();

      if (inScope && isEndScope(context.getItem()) || isTerminal(context.getItem())) {
        return result;
      }

      List currentPath = context.getPathTypes();
      currentPath.add(context.getItem());
      for (TypeDef candidate : exclusion(context.getAll(), context.getVisitedTypes())) {
        if (!isEntryPoint(candidate) && isSatisfied(candidate, currentPath)) {
          result.add(candidate);
        }
      }
      result.remove(context.getItem());
      return result;
    }
  };

  public static final Function, TypeDef> TO_ROOT = new Function, TypeDef>() {
    public TypeDef apply(Node item) {
      List interfaces = new ArrayList();

      for (Node child : item.getTransitions()) {
        ClassRef transitionInterface = TO_TRANSITION.apply(child);
        interfaces.add(transitionInterface);
        AptContext.getContext().getDefinitionRepository().register(child.getItem(), IS_GENERATED);
      }

      TypeDef rootType = new TypeDefBuilder(item.getItem()).withExtendsList(interfaces).withParameters().withMethods().build();

      return new TypeDefBuilder(Generics.UNWRAP.apply(rootType)).withMethods(new ArrayList()).build();
    }
  };

  public static final Function, ClassRef> TO_TRANSITION = new Function, ClassRef>() {
    public ClassRef apply(Node current) {
      if (current.getTransitions().isEmpty()) {
        return current.getItem().toInternalReference();
      } else {
        TypeDef clazz = current.getItem();
        Set toCombine = new LinkedHashSet();

        for (Node v : current.getTransitions()) {
          toCombine.add(apply(v));
        }

        ClassRef nextClazz;
        TypeDef nextClazzDef;

        if (toCombine.size() != 1) {
          nextClazzDef = Combine.TYPEREFS.apply(Generify.CLASSREFS.apply(toCombine));
          nextClazz = nextClazzDef.toInternalReference();
        } else {
          nextClazz = toCombine.iterator().next();
          nextClazzDef = null;
        }

        if (TypeDefUtils.isCardinalityMultiple(clazz)) {
          //1st pass create the self ref
          final ClassRef selfRef = transition(clazz, nextClazz);
          Set toReCombine = new LinkedHashSet(toCombine);
          toReCombine.add(selfRef);
          TypeDef reCombinedType = Combine.TYPEREFS.apply(toReCombine);
          final ClassRef reCombinedRef = reCombinedType.toInternalReference();
          DslContextManager.getContext().getDefinitionRepository().register(reCombinedType, IS_GENERATED);

          reCombinedType = new TypeDefBuilder(reCombinedType).accept(TypeDefBuilder.class, builder -> {
            List updatedInterfaces = new ArrayList();
            for (ClassRef interfaceRef : builder.buildExtendsList()) {
              if (interfaceRef.equals(selfRef)) {
                updatedInterfaces.add(GetDefinition.of(selfRef).toReference(reCombinedRef));
              } else {
                updatedInterfaces.add(interfaceRef);
              }
            }
            builder.withExtendsList(updatedInterfaces);
          }).build();

          ClassRef reCombined = reCombinedType.toInternalReference();
          //2nd pass recreate the combination
          ClassRef updatedSelfRef = transition(clazz, reCombined);
          toReCombine = new LinkedHashSet(toCombine);
          toReCombine.remove(selfRef);
          toReCombine.add(updatedSelfRef);
          //          reCombinedType = Combine.TYPEREFS.apply(toReCombine);
          reCombinedType = new TypeDefBuilder(reCombinedType).withExtendsList(new ArrayList<>(toReCombine)).build();
          reCombined = reCombinedType.toInternalReference();
          DslContextManager.getContext().getDefinitionRepository().register(reCombinedType, IS_GENERATED);

          //          DslContextManager.getContext().getDefinitionRepository().register(nextClazz.getDefinition(), IS_GENERATED);
          return transition(clazz, reCombined);
        } else {
          //If we have a couple of classes to combine that are non-multiple
          // we may end up with intermediate garbage in the registry, which are masking the real thing
          if (!isTransition(nextClazz) && nextClazzDef != null && DslContextManager.getContext().getDefinitionRepository()
              .getDefinition(nextClazz.getFullyQualifiedName()) == null) {
            DslContextManager.getContext().getDefinitionRepository().register(nextClazzDef, IS_GENERATED);
          }
          return transition(clazz, nextClazz);
        }
      }
    }

    public ClassRef transition(TypeDef from, ClassRef to) {
      return from.toReference(to);

    }
  };

  public static final Function, Node> TO_UNCYCLIC = new Function, Node>() {
    public Node apply(Node node) {
      visit(node, new LinkedHashSet>());
      return node;
    }

    private boolean visit(Node node, Set> visited) {
      if (visited.add(node)) {
        for (Node child : new LinkedHashSet>(node.getTransitions())) {
          Set> branchNodes = new LinkedHashSet>(visited);
          if (!visit(child, branchNodes)) {
            node.getTransitions().remove(child);
          }
        }
        return true;
      } else {
        return false;
      }
    }
  };

  public static final Function> TO_UNWRAPPED = new Function>() {
    public Node apply(NodeContext ctx) {
      Node current = DslContextManager.getContext().getNodeRepository().get(ctx.getItem());
      Set> next = new LinkedHashSet>();

      for (Node candidate : current.getTransitions()) {
        List currentPath = ctx.getPathTypes();
        currentPath.add(ctx.getItem());

        if (GraphUtils.isSatisfied(candidate.getItem(), currentPath)) {
          Node subGraph = apply(ctx.contextOfChild(candidate.getItem()).build());
          if (subGraph.getTransitions().size() > 0 || isTerminal(subGraph.getItem()) || isEndScope(subGraph.getItem())) {
            next.add(subGraph);
          }
        }
      }
      return DslContextManager.getContext().getNodeRepository().createNode(ctx.getItem(), next);
    }
  };

  public static Function, Set> TO_SCOPE = new Function, Set>() {
    public Set apply(Set clazzes) {
      Set result = new LinkedHashSet(clazzes);
      Set all = new LinkedHashSet(clazzes);
      for (TypeDef clazz : clazzes) {
        if (isBeginScope(clazz)) {

          Boolean multiple = TypeDefUtils.isCardinalityMultiple(clazz);
          TypeDef current;

          if (multiple) {
            current = new TypeDefBuilder(clazz).addToAttributes(CARDINALITY_MULTIPLE, false).build();

            all.remove(clazz);
            all.add(current);
          } else {
            current = clazz;
          }

          Node node = TO_TREE.apply(NodeContext.builder().withItem(current).withAll(all).build());

          Set scopeClasses = scopeClasses(node);
          for (TypeDef scopeClass : scopeClasses) {
            DslContextManager.getContext().getDefinitionRepository().register(scopeClass, IS_GENERATED);
          }
          ClassRef scopeInterface = TO_TRANSITION.apply(node);

          result.removeAll(scopeClasses);
          TypeDef typeDef = new TypeDefBuilder(clazz).withKind(Kind.INTERFACE)
              .withPackageName(scopeInterface.getPackageName())
              .withName(scopeInterface.getName() + SCOPE_SUFFIX).withExtendsList(scopeInterface).withMethods()
              .addToAttributes(CARDINALITY_MULTIPLE, multiple).addToAttributes(KEYWORDS, scopeKeywords(scopeClasses))
              .accept(TypeDefBuilder.class, builder -> {
                builder.getAttributes().remove(BEGIN_SCOPE);
                builder.getAttributes().remove(END_SCOPE);
              }).build();
          DslContextManager.getContext().getDefinitionRepository().register(typeDef, IS_GENERATED);
          result.add(typeDef);
        }
      }
      return result;
    }

    public Set scopeClasses(Node node) {
      Set result = new LinkedHashSet();
      result.add(node.getItem());
      for (Node transition : node.getTransitions()) {
        result.addAll(scopeClasses(transition));
      }

      return result;
    }

    public Set scopeKeywords(Collection clazzes) {
      Set result = new LinkedHashSet();
      for (TypeDef clazz : clazzes) {
        Set keywords = (Set) clazz.getAttributes().get(KEYWORDS);
        result.addAll(keywords != null ? keywords : Collections. emptySet());
      }
      return result;
    }
  };

  private static class CandidateComparator implements Comparator {

    private final NodeContext nodeContext;

    private CandidateComparator(NodeContext nodeContext) {
      this.nodeContext = nodeContext;
    }

    public int compare(TypeDef left, TypeDef right) {
      Set leftSet = TO_NEXT.apply(nodeContext.contextOfChild(left).addToVisited(left).build());
      Set rightSet = TO_NEXT.apply(nodeContext.contextOfChild(right).addToVisited(right).build());
      if (leftSet.contains(right) && rightSet.contains(left)) {
        return 0;
      } else if (leftSet.contains(right)) {
        return -1;
      } else if (rightSet.contains(left)) {
        return 1;
      }
      return 0;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy