• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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