1 | package com.github.javaparser.utils; |
---|---|
2 | |
3 | import com.github.javaparser.JavaToken; |
4 | |
5 | import java.util.Optional; |
6 | |
7 | /** |
8 | * A representation of line endings, that can be used throughout the codebase. |
9 | * <br>This is to replace {@code Utils.EOL} which is not explicit in representing the system's EOL character. |
10 | * <br>It also exposes helper methods for, e.g., detection of the line ending of a given string. |
11 | * |
12 | * @author Roger Howell |
13 | * @see <a href="https://github.com/javaparser/javaparser/issues/2647">https://github.com/javaparser/javaparser/issues/2647</a> |
14 | */ |
15 | public enum LineSeparator { |
16 | /** |
17 | * The CR {@code \r} line ending is the default line separator for classic MacOS |
18 | */ |
19 | CR("\r", "CR (\\r)"), |
20 | /** |
21 | * The LF {@code \n} line ending is the default line separator for Unix and modern MacOS |
22 | */ |
23 | LF("\n", "LF (\\n)"), |
24 | /** |
25 | * The CRLF {@code \r\n} line ending is the default line separator for Windows |
26 | */ |
27 | CRLF("\r\n", "CRLF (\\r\\n)"), |
28 | /** |
29 | * This line ending is set to whatever the host system's line separator is |
30 | */ |
31 | SYSTEM( |
32 | System.getProperty("line.separator"), |
33 | "SYSTEM : (" + System.getProperty("line.separator") |
34 | .replace("\r", "\\r") |
35 | .replace("\n", "\\n") + |
36 | ")" |
37 | ), |
38 | /** |
39 | * The ARBITRARY line ending can be used where we do not care about the line separator, |
40 | * only that we use the same one consistently |
41 | */ |
42 | ARBITRARY("\n", "ARBITRARY (\\n)"), |
43 | /** |
44 | * The MIXED line ending is used where strings appear to have multiple different line separators e.g. {@code "line |
45 | * 1\nline 2\rline 3\r\n"} or {@code "line 1\nline 2\rline 3\nline 4\n"} |
46 | */ |
47 | MIXED("", "MIXED"), |
48 | /** |
49 | * The UNKNOWN line ending can be used in the case where the given string has not yet been analysed to determine its |
50 | * line separator |
51 | */ |
52 | UNKNOWN("", "UNKNOWN"), |
53 | /** |
54 | * The NONE line ending is used where there are precisely zero line endings e.g. a simple one-line string |
55 | */ |
56 | NONE("", "NONE"); |
57 | |
58 | private final String text; |
59 | private final String description; |
60 | |
61 | LineSeparator(String text, String description) { |
62 | this.text = text; |
63 | this.description = description; |
64 | } |
65 | |
66 | /** |
67 | * @return The number of times that the given needle is found within the haystack. |
68 | */ |
69 | private static int count(String haystack, String needle) { |
70 | // Note that if the needle is multiple characters, e.g. \r\n, the difference in string length will be disproportionately affected. |
71 | return (haystack.length() - haystack.replaceAll(needle, "").length()) / needle.length(); |
72 | } |
73 | |
74 | public static LineSeparator detect(String string) { |
75 | int countCr = count(string, "\r"); |
76 | int countLf = count(string, "\n"); |
77 | int countCrLf = count(string, "\r\n"); |
78 | |
79 | return getLineEnding(countCr, countLf, countCrLf); |
80 | } |
81 | |
82 | public static LineSeparator getLineEnding(int countCr, int countLf, int countCrLf) { |
83 | boolean noLineEndings = countCr == 0 && countLf == 0 && countCrLf == 0; |
84 | if (noLineEndings) { |
85 | return NONE; |
86 | } |
87 | |
88 | boolean crOnly = countCr > 0 && countLf == 0 && countCrLf == 0; |
89 | if (crOnly) { |
90 | return CR; |
91 | } |
92 | boolean lfOnly = countCr == 0 && countLf > 0 && countCrLf == 0; |
93 | if (lfOnly) { |
94 | return LF; |
95 | } |
96 | |
97 | // Note that wherever \r\n are found, there will also be an equal number of \r and \n characters found. |
98 | boolean crLfOnly = countCr == countLf && countLf == countCrLf; |
99 | if (crLfOnly) { |
100 | return CRLF; |
101 | } |
102 | |
103 | // Not zero line endings, and not a single line ending, thus is mixed. |
104 | return MIXED; |
105 | } |
106 | |
107 | /** |
108 | * @param ending A string containing ONLY the line separator needle (e.g. {@code \r}, {@code \n}, or {@code \r\n}) |
109 | * @return Where the given ending is a "standard" line separator (i.e. {@code \r}, {@code \n}, or {@code \r\n}), |
110 | * return that. Otherwise an empty optional. |
111 | */ |
112 | public static Optional<LineSeparator> lookup(String ending) { |
113 | if (CR.asRawString().equals(ending)) { |
114 | return Optional.of(CR); |
115 | } else if (LF.asRawString().equals(ending)) { |
116 | return Optional.of(LF); |
117 | } else if (CRLF.asRawString().equals(ending)) { |
118 | return Optional.of(CRLF); |
119 | } else { |
120 | return Optional.empty(); |
121 | } |
122 | } |
123 | |
124 | public static Optional<LineSeparator> lookupEscaped(String ending) { |
125 | if (CR.asEscapedString().equals(ending)) { |
126 | return Optional.of(CR); |
127 | } else if (LF.asEscapedString().equals(ending)) { |
128 | return Optional.of(LF); |
129 | } else if (CRLF.asEscapedString().equals(ending)) { |
130 | return Optional.of(CRLF); |
131 | } else { |
132 | return Optional.empty(); |
133 | } |
134 | } |
135 | |
136 | public String describe() { |
137 | // TODO: Return a generated description rather than one hardcoded via constructor. |
138 | return description; |
139 | } |
140 | |
141 | public boolean equalsString(LineSeparator lineSeparator) { |
142 | return text.equals(lineSeparator.asRawString()); |
143 | } |
144 | |
145 | public boolean isStandardEol() { |
146 | // Compare based on the strings to allow for e.g. LineSeparator.SYSTEM |
147 | return equalsString(LineSeparator.CR) || equalsString(LineSeparator.LF) || equalsString(LineSeparator.CRLF); |
148 | } |
149 | |
150 | public String asEscapedString() { |
151 | String result = text |
152 | .replace("\r", "\\r") |
153 | .replace("\n", "\\n"); |
154 | |
155 | return result; |
156 | } |
157 | |
158 | public String asRawString() { |
159 | return text; |
160 | } |
161 | |
162 | // TODO: Determine if this should be used within TokenTypes.java -- thus leaving this as private for now. |
163 | private Optional<JavaToken.Kind> asJavaTokenKind() { |
164 | if(this == CR) { |
165 | return Optional.of(JavaToken.Kind.OLD_MAC_EOL); |
166 | } else if(this == LF) { |
167 | return Optional.of(JavaToken.Kind.UNIX_EOL); |
168 | } else if(this == CRLF) { |
169 | return Optional.of(JavaToken.Kind.WINDOWS_EOL); |
170 | } |
171 | |
172 | return Optional.empty(); |
173 | } |
174 | |
175 | @Override |
176 | public String toString() { |
177 | return asRawString(); |
178 | } |
179 | |
180 | } |
181 |
Members