1 /* 2 * Copyright 2016 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15 package com.google.googlejavaformat; 16 17 import com.google.common.base.CharMatcher; 18 import com.google.common.collect.ImmutableSet; 19 import com.google.common.collect.Iterators; 20 import java.util.Iterator; 21 import java.util.NoSuchElementException; 22 23 /** Platform-independent newline handling. */ 24 public class Newlines { 25 26 /** Returns the number of line breaks in the input. */ count(String input)27 public static int count(String input) { 28 return Iterators.size(lineOffsetIterator(input)) - 1; 29 } 30 31 /** Returns the index of the first break in the input, or {@code -1}. */ firstBreak(String input)32 public static int firstBreak(String input) { 33 Iterator<Integer> it = lineOffsetIterator(input); 34 it.next(); 35 return it.hasNext() ? it.next() : -1; 36 } 37 38 private static final ImmutableSet<String> BREAKS = ImmutableSet.of("\r\n", "\n", "\r"); 39 40 /** Returns true if the entire input string is a recognized line break. */ isNewline(String input)41 public static boolean isNewline(String input) { 42 return BREAKS.contains(input); 43 } 44 45 /** Returns the length of the newline sequence at the current offset, or {@code -1}. */ hasNewlineAt(String input, int idx)46 public static int hasNewlineAt(String input, int idx) { 47 for (String b : BREAKS) { 48 if (input.startsWith(b, idx)) { 49 return b.length(); 50 } 51 } 52 return -1; 53 } 54 55 /** 56 * Returns the terminating line break in the input, or {@code null} if the input does not end in a 57 * break. 58 */ getLineEnding(String input)59 public static String getLineEnding(String input) { 60 for (String b : BREAKS) { 61 if (input.endsWith(b)) { 62 return b; 63 } 64 } 65 return null; 66 } 67 68 /** 69 * Returns the first line separator in the text, or {@code "\n"} if the text does not contain a 70 * single line separator. 71 */ guessLineSeparator(String text)72 public static String guessLineSeparator(String text) { 73 for (int i = 0; i < text.length(); i++) { 74 char c = text.charAt(i); 75 switch (c) { 76 case '\r': 77 if (i + 1 < text.length() && text.charAt(i + 1) == '\n') { 78 return "\r\n"; 79 } 80 return "\r"; 81 case '\n': 82 return "\n"; 83 default: 84 break; 85 } 86 } 87 return "\n"; 88 } 89 90 /** Returns true if the input contains any line breaks. */ containsBreaks(String text)91 public static boolean containsBreaks(String text) { 92 return CharMatcher.anyOf("\n\r").matchesAnyOf(text); 93 } 94 95 /** Returns an iterator over the start offsets of lines in the input. */ lineOffsetIterator(String input)96 public static Iterator<Integer> lineOffsetIterator(String input) { 97 return new LineOffsetIterator(input); 98 } 99 100 /** Returns an iterator over lines in the input, including trailing whitespace. */ lineIterator(String input)101 public static Iterator<String> lineIterator(String input) { 102 return new LineIterator(input); 103 } 104 105 private static class LineOffsetIterator implements Iterator<Integer> { 106 107 private int curr = 0; 108 private int idx = 0; 109 private final String input; 110 LineOffsetIterator(String input)111 private LineOffsetIterator(String input) { 112 this.input = input; 113 } 114 115 @Override hasNext()116 public boolean hasNext() { 117 return curr != -1; 118 } 119 120 @Override next()121 public Integer next() { 122 if (curr == -1) { 123 throw new NoSuchElementException(); 124 } 125 int result = curr; 126 advance(); 127 return result; 128 } 129 advance()130 private void advance() { 131 for (; idx < input.length(); idx++) { 132 char c = input.charAt(idx); 133 switch (c) { 134 case '\r': 135 if (idx + 1 < input.length() && input.charAt(idx + 1) == '\n') { 136 idx++; 137 } 138 // falls through 139 case '\n': 140 idx++; 141 curr = idx; 142 return; 143 default: 144 break; 145 } 146 } 147 curr = -1; 148 } 149 150 @Override remove()151 public void remove() { 152 throw new UnsupportedOperationException("remove"); 153 } 154 } 155 156 private static class LineIterator implements Iterator<String> { 157 158 int idx; 159 String curr; 160 161 private final String input; 162 private final Iterator<Integer> indices; 163 LineIterator(String input)164 private LineIterator(String input) { 165 this.input = input; 166 this.indices = lineOffsetIterator(input); 167 idx = indices.next(); // read leading 0 168 } 169 advance()170 private void advance() { 171 int last = idx; 172 if (indices.hasNext()) { 173 idx = indices.next(); 174 } else if (hasNext()) { 175 // no terminal line break 176 idx = input.length(); 177 } else { 178 throw new NoSuchElementException(); 179 } 180 curr = input.substring(last, idx); 181 } 182 183 @Override hasNext()184 public boolean hasNext() { 185 return idx < input.length(); 186 } 187 188 @Override next()189 public String next() { 190 advance(); 191 return curr; 192 } 193 194 @Override remove()195 public void remove() { 196 throw new UnsupportedOperationException("remove"); 197 } 198 } 199 } 200