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

io.resys.wrench.assets.flow.spi.builders.GenericNodeBuilder Maven / Gradle / Ivy

There is a newer version: 3.1.4
Show newest version
package io.resys.wrench.assets.flow.spi.builders;

/*-
 * #%L
 * wrench-assets-flow
 * %%
 * Copyright (C) 2016 - 2019 Copyright 2016 ReSys OÜ
 * %%
 * 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.
 * #L%
 */

import java.io.IOException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.common.collect.Range;

import io.resys.wrench.assets.datatype.spi.util.Assert;
import io.resys.wrench.assets.flow.api.FlowAstFactory.NodeBuilder;
import io.resys.wrench.assets.flow.api.FlowAstFactory.NodeInputType;
import io.resys.wrench.assets.flow.api.model.FlowAst.FlowCommandMessage;
import io.resys.wrench.assets.flow.api.model.FlowAst.FlowCommandMessageType;
import io.resys.wrench.assets.flow.api.model.FlowAst.FlowCommandType;
import io.resys.wrench.assets.flow.api.model.ImmutableFlowCommand;
import io.resys.wrench.assets.flow.api.model.ImmutableFlowCommandMessage;
import io.resys.wrench.assets.flow.spi.exceptions.NodeFlowException;
import io.resys.wrench.assets.flow.spi.model.NodeBean;
import io.resys.wrench.assets.flow.spi.model.NodeFlowBean;
import io.resys.wrench.assets.flow.spi.model.NodeSourceBean;
import io.resys.wrench.assets.flow.spi.support.FlowNodesFactory;

public class GenericNodeBuilder implements NodeBuilder {
  private final static String LINE_SEPARATOR = System.lineSeparator();
  private final ObjectMapper yamlMapper;
  private final Consumer messageConsumer;
  private final NodeFlowBean result;
  private final List sourcesAdded = new ArrayList<>();
  private final List sourcesDeleted = new ArrayList<>();


  public GenericNodeBuilder(ObjectMapper yamlMapper, Collection inputTypes, Consumer messageConsumer) {
    super();
    this.yamlMapper = yamlMapper;
    this.messageConsumer = messageConsumer;
    this.result = new NodeFlowBean(inputTypes);

  }

  @Override
  public GenericNodeBuilder add(int line, String value) {
    sourcesAdded.stream().filter(s -> s.getLine() >= line).forEach(s -> s.setLine(s.getLine() + 1));
    sourcesAdded.add(new NodeSourceBean(line, ImmutableFlowCommand.builder().id(line).value(value).type(FlowCommandType.ADD).build()));
    return this;
  }

  @Override
  public GenericNodeBuilder set(int line, String value) {
    Optional source = sourcesAdded.stream().filter(s -> s.getLine() == line).findFirst();
    Assert.isTrue(source.isPresent(), () -> String.format("Can't change value of non existing line: %s!", line));
    source.get().add(ImmutableFlowCommand.builder().id(line).value(value).type(FlowCommandType.SET).build());
    return this;
  }

  @Override
  public GenericNodeBuilder delete(int line) {
    Iterator sources = sourcesAdded.iterator();
    while(sources.hasNext()) {
      NodeSourceBean source = sources.next();
      if(source.getLine() == line) {
        sources.remove();
        sourcesDeleted.add(source);
      } else if(source.getLine() > line) {
        source.setLine(source.getLine() - 1);
      }
    }
    return this;
  }
  @Override
  public GenericNodeBuilder delete(int from, int to) {
    Range range = Range.closed(from, to);
    int linesToDelete = (to - from) + 1;

    Iterator sources = sourcesAdded.iterator();
    while(sources.hasNext()) {
      NodeSourceBean source = sources.next();

      if(range.contains(source.getLine())) {
        sources.remove();
        sourcesDeleted.add(source);
      } else if(source.getLine() > to) {
        source.setLine(source.getLine() - linesToDelete);
      }
    }

    return this;
  }

  @Override
  public NodeFlowBean build() {
    Collections.sort(sourcesAdded);
    Iterator iterator = sourcesAdded.iterator();
    NodeBean parent = result;
    StringBuilder value = new StringBuilder();

    int previousLineNumber = 0;
    int lineNumber = 0;
    while(iterator.hasNext()) {
      NodeSourceBean src = iterator.next();
      String lineContent = src.getCommands().get(src.getCommands().size() - 1).getValue();
      lineNumber = src.getLine();

      // add to src
      for(int index = previousLineNumber; index < lineNumber -1; index++) {
        value.append(LINE_SEPARATOR);
      }

      if(lineContent != null) {
        value.append(lineContent);
      }
      value.append(LINE_SEPARATOR);
      previousLineNumber = lineNumber;

      if(lineContent == null) {
        continue;
      }

      boolean containsOnlySpaces = lineContent.length() > 0 && "".equals(lineContent.trim());
      boolean endsWithSpace = lineContent.endsWith(" ");
      if(containsOnlySpaces || endsWithSpace) {
        int start = containsOnlySpaces ? 0 : getSpaceStart(lineContent);
        int end = lineContent.length();
        messageConsumer.accept(ImmutableFlowCommandMessage.builder()
            .line(lineNumber)
            .range(FlowNodesFactory.range().build(start, end))
            .value("space has no meaning")
            .type(FlowCommandMessageType.WARNING)
            .build());
      }

      if(containsOnlySpaces || lineContent.length() == 0) {
        continue;
      }

      Map.Entry keywordAndValue = getKeywordAndValue(yamlMapper, lineContent, lineNumber);
      if(keywordAndValue == null) {
        continue;
      }

      int indent = getIndent(lineContent);
      if(indent % 2 != 0) {
        String message = String.format("Incorrect indent: %s, at line: %s!", indent, lineNumber);
        messageConsumer.accept(ImmutableFlowCommandMessage.builder()
            .line(lineNumber)
            .value(message)
            .type(FlowCommandMessageType.ERROR)
            .build());
        return result.setEnd(lineNumber).setValue(buildSource(value));
      }

      int indentToFind = indent - 2;
      while(parent != null) {
        if(parent.getIndent() <= indentToFind) {
          break;
        }
        parent = parent.getParent();
      }

      if(parent == null) {
        String message = String.format("Incorrect indent at line: %s, expecting: %s but was: %s!", lineNumber, indentToFind, indent);
        messageConsumer.accept(ImmutableFlowCommandMessage.builder()
            .line(lineNumber)
            .value(message)
            .type(FlowCommandMessageType.ERROR)
            .build());
        return result.setEnd(lineNumber).setValue(buildSource(value));
      }

      try {
        parent = parent.addChild(src, indent, keywordAndValue.getKey(), keywordAndValue.getValue());
      } catch(NodeFlowException e) {
        messageConsumer.accept(
            ImmutableFlowCommandMessage.builder()
            .line(lineNumber)
            .value(e.getMessage())
            .type(FlowCommandMessageType.ERROR)
            .build());
        return result.setEnd(lineNumber).setValue(value.toString());
      }
    }

    return result.setEnd(lineNumber).setValue(buildSource(value));
  }
  
  private String buildSource(StringBuilder value) {
    String result = value.toString();
    if(result.endsWith(LINE_SEPARATOR)) {
      return result.substring(0, result.length() - LINE_SEPARATOR.length());
    }
    return result;
  }

  private static int getSpaceStart(String lineContent) {
    char[] charArray = lineContent.toCharArray();
    int index = charArray.length;
    do {
      index--;
      if(charArray[index] != ' ') {
        break;
      }
    } while(index > -1);
    return index + 1;
  }

  private Map.Entry getKeywordAndValue(ObjectMapper yamlMapper, String lineContent, int lineNumber) {
    String value;
    String keyword;
    try {
      JsonNode node = yamlMapper.readTree(lineContent);
      if(node == null || node.isNull()) {
        return null;
      }
      node = node.isArray() ? ((ArrayNode) node).iterator().next() : node;
      Iterator iterator = node.fieldNames();
      if(iterator.hasNext()) {
        keyword = iterator.next();
        JsonNode nodeValue = node.get(keyword);
        if (node.isNull()) {
          value = null;
        } else if (nodeValue.isArray()) {
          value = nodeValue.toString();
        } else {
          value = nodeValue.asText();
        }
      } else {
        String message = String.format("Unknown content on line: %d", lineNumber);
        messageConsumer.accept(ImmutableFlowCommandMessage.builder()
            .line(lineNumber)
            .value(message)
            .type(FlowCommandMessageType.ERROR)
            .build());
        return null;
      }
    } catch(IOException e) {
      String message = String.format("Unknown content on line: %d", lineNumber);
      messageConsumer.accept(ImmutableFlowCommandMessage.builder()
          .line(lineNumber)
          .value(message)
          .type(FlowCommandMessageType.ERROR)
          .build());
      return null;
    }
    return new AbstractMap.SimpleImmutableEntry(keyword, value);
  }

  private static int getIndent(String value){
    char[] characters = value.toCharArray();
    for(int index = 0; index < value.length(); index++){
      if(!Character.isWhitespace(characters[index])){
        return index;
      }
    }
    return 0;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy