1 package com.github.javaparser.printer.lexicalpreservation; 2 3 import com.github.javaparser.JavaToken; 4 import com.github.javaparser.TokenRange; 5 import com.github.javaparser.TokenTypes; 6 import com.github.javaparser.ast.Node; 7 import com.github.javaparser.printer.concretesyntaxmodel.CsmToken; 8 9 import java.util.Iterator; 10 import java.util.List; 11 import java.util.Optional; 12 import java.util.function.Function; 13 import java.util.stream.Collectors; 14 import java.util.stream.IntStream; 15 16 /** 17 * This class represents a group of {@link Removed} elements. 18 * The {@link Removed} elements are ideally consecutive for the methods in this class to work correctly. 19 * 20 * This class consists of methods that calculate information to better handle the difference application for the 21 * containing {@link Removed} elements. 22 * 23 * @see Iterable 24 * 25 * @author ThLeu 26 */ 27 final class RemovedGroup implements Iterable<Removed> { 28 29 private final Integer firstElementIndex; 30 private final List<Removed> removedList; 31 32 private boolean isProcessed = false; 33 RemovedGroup(Integer firstElementIndex, List<Removed> removedList)34 private RemovedGroup(Integer firstElementIndex, List<Removed> removedList) { 35 if (firstElementIndex == null) { 36 throw new IllegalArgumentException("firstElementIndex should not be null"); 37 } 38 39 if (removedList == null || removedList.isEmpty()) { 40 throw new IllegalArgumentException("removedList should not be null or empty"); 41 } 42 43 this.firstElementIndex = firstElementIndex; 44 this.removedList = removedList; 45 } 46 47 /** 48 * Factory method to create a RemovedGroup which consists of consecutive Removed elements 49 * 50 * @param firstElementIndex the difference index at which the RemovedGroup starts 51 * @param removedList list of the consecutive Removed elements 52 * @return a RemovedGroup object 53 * @throws IllegalArgumentException if the firstElementIndex is null or the removedList is empty or null 54 */ of(Integer firstElementIndex, List<Removed> removedList)55 public static RemovedGroup of(Integer firstElementIndex, List<Removed> removedList) { 56 return new RemovedGroup(firstElementIndex, removedList); 57 } 58 59 /** 60 * Marks the RemovedGroup as processed which indicates that it should not be processed again 61 */ processed()62 final void processed() { 63 isProcessed = true; 64 } 65 66 /** 67 * Returns whether the RemovedGroup was already processed and should not be processed again 68 * 69 * @return wheter the RemovedGroup was already processed 70 */ isProcessed()71 final boolean isProcessed() { 72 return isProcessed; 73 } 74 getIndicesBeingRemoved()75 private List<Integer> getIndicesBeingRemoved() { 76 return IntStream.range(firstElementIndex, firstElementIndex + removedList.size()) 77 .boxed() 78 .collect(Collectors.toList()); 79 } 80 81 /** 82 * Returns the difference index of the last element being removed with this RemovedGroup 83 * 84 * @return the last difference incex of this RemovedGroup 85 */ getLastElementIndex()86 final Integer getLastElementIndex() { 87 List<Integer> indicesBeingRemoved = getIndicesBeingRemoved(); 88 return indicesBeingRemoved.get(indicesBeingRemoved.size() - 1); 89 } 90 91 /** 92 * Returns the first element of this RemovedGroup 93 * 94 * @return the first element of this RemovedGroup 95 */ getFirstElement()96 final Removed getFirstElement() { 97 return removedList.get(0); 98 } 99 100 /** 101 * Returns the last element of this RemovedGroup 102 * 103 * @return the last element of this RemovedGroup 104 */ getLastElement()105 final Removed getLastElement() { 106 return removedList.get(removedList.size() - 1); 107 } 108 109 /** 110 * Returns true if the RemovedGroup equates to a complete line 111 * This is the case if there are only spaces and tabs left on the line besides the Removed elements. 112 * <br/> 113 * Example: 114 * <pre> 115 * " [Removed] [EOL]" -> this would be a complete line, regardless of spaces or tabs before or after the [Removed] element 116 * " [Removed] void [EOL]" -> this would not be a complete line because of the "void" 117 * " public [Removed] [EOL]" -> this would not be a complete line because of the "public" 118 * </pre> 119 * 120 * @return true if the RemovedGroup equates to a complete line 121 */ isACompleteLine()122 final boolean isACompleteLine() { 123 return hasOnlyWhitespace(getFirstElement(), hasOnlyWhitespaceInFrontFunction) 124 && hasOnlyWhitespace(getLastElement(), hasOnlyWhitespaceBehindFunction); 125 } 126 127 private final Function<JavaToken, Boolean> hasOnlyWhitespaceJavaTokenInFrontFunction = begin -> hasOnlyWhiteSpaceForTokenFunction(begin, token -> token.getPreviousToken()); 128 private final Function<JavaToken, Boolean> hasOnlyWhitespaceJavaTokenBehindFunction = end -> hasOnlyWhiteSpaceForTokenFunction(end, token -> token.getNextToken()); 129 private final Function<TokenRange, Boolean> hasOnlyWhitespaceInFrontFunction = tokenRange -> hasOnlyWhitespaceJavaTokenInFrontFunction.apply(tokenRange.getBegin()); 130 private final Function<TokenRange, Boolean> hasOnlyWhitespaceBehindFunction = tokenRange -> hasOnlyWhitespaceJavaTokenBehindFunction.apply(tokenRange.getEnd()); 131 hasOnlyWhitespace(Removed startElement, Function<TokenRange, Boolean> hasOnlyWhitespaceFunction)132 private boolean hasOnlyWhitespace(Removed startElement, Function<TokenRange, Boolean> hasOnlyWhitespaceFunction) { 133 boolean hasOnlyWhitespace = false; 134 if (startElement.isChild()) { 135 LexicalDifferenceCalculator.CsmChild csmChild = (LexicalDifferenceCalculator.CsmChild) startElement.getElement(); 136 Node child = csmChild.getChild(); 137 138 Optional<TokenRange> tokenRange = child.getTokenRange(); 139 if (tokenRange.isPresent()) { 140 hasOnlyWhitespace = hasOnlyWhitespaceFunction.apply(tokenRange.get()); 141 } 142 } else if (startElement.isToken()) { 143 CsmToken token = (CsmToken) startElement.getElement(); 144 if (TokenTypes.isEndOfLineToken(token.getTokenType())) { 145 hasOnlyWhitespace = true; 146 } 147 } 148 return hasOnlyWhitespace; 149 } 150 hasOnlyWhiteSpaceForTokenFunction(JavaToken token, Function<JavaToken, Optional<JavaToken>> tokenFunction)151 private boolean hasOnlyWhiteSpaceForTokenFunction(JavaToken token, Function<JavaToken, Optional<JavaToken>> tokenFunction) { 152 Optional<JavaToken> tokenResult = tokenFunction.apply(token); 153 154 if (tokenResult.isPresent()) { 155 if (TokenTypes.isSpaceOrTab(tokenResult.get().getKind())) { 156 return hasOnlyWhiteSpaceForTokenFunction(tokenResult.get(), tokenFunction); 157 } else if (TokenTypes.isEndOfLineToken(tokenResult.get().getKind())) { 158 return true; 159 } else { 160 return false; 161 } 162 } 163 164 return true; 165 } 166 167 /** 168 * Returns the indentation in front of this RemovedGroup if possible. 169 * If there is something else than whitespace in front, Optional.empty() is returned. 170 * 171 * @return the indentation in front of this RemovedGroup or Optional.empty() 172 */ getIndentation()173 final Optional<Integer> getIndentation() { 174 Removed firstElement = getFirstElement(); 175 176 int indentation = 0; 177 if (firstElement.isChild()) { 178 LexicalDifferenceCalculator.CsmChild csmChild = (LexicalDifferenceCalculator.CsmChild) firstElement.getElement(); 179 Node child = csmChild.getChild(); 180 181 Optional<TokenRange> tokenRange = child.getTokenRange(); 182 if (tokenRange.isPresent()) { 183 JavaToken begin = tokenRange.get().getBegin(); 184 185 if (hasOnlyWhitespaceJavaTokenInFrontFunction.apply(begin)) { 186 Optional<JavaToken> previousToken = begin.getPreviousToken(); 187 188 while(previousToken.isPresent() && (TokenTypes.isSpaceOrTab(previousToken.get().getKind()))) { 189 indentation++; 190 191 previousToken = previousToken.get().getPreviousToken(); 192 } 193 194 if (previousToken.isPresent()) { 195 if (TokenTypes.isEndOfLineToken(previousToken.get().getKind())) { 196 return Optional.of(Integer.valueOf(indentation)); 197 } else { 198 return Optional.empty(); 199 } 200 } else { 201 return Optional.of(Integer.valueOf(indentation)); 202 } 203 } 204 } 205 } 206 207 return Optional.empty(); 208 } 209 210 @Override iterator()211 public final Iterator<Removed> iterator() { 212 return new Iterator<Removed>() { 213 private int currentIndex = 0; 214 215 @Override 216 public boolean hasNext() { 217 return currentIndex < removedList.size() && removedList.get(currentIndex) != null; 218 } 219 220 @Override 221 public Removed next() { 222 return removedList.get(currentIndex++); 223 } 224 225 }; 226 } 227 } 228