001package org.unix4j.line; 002 003/** 004 * A {@link Line} implementation based on two {@link CharSequence}'s, usually 005 * two strings one for the contents and one for the line ending characters. 006 * <p> 007 * As opposed to this implementation, the {@link SingleCharSequenceLine} 008 * consists of a single character sequence for both the content string and the 009 * line ending. 010 */ 011public class SimpleLine implements Line { 012 013 private final CharSequence content; 014 private final CharSequence lineEnding; 015 016 /** 017 * Constructor with contents and default operating system dependent line 018 * ending as defined by {@link Line#LINE_ENDING}. 019 * 020 * @param content 021 * the character sequence containing the {@link #getContent() 022 * content} data of the line 023 * @throws NullPointerException 024 * if {@code content} is null 025 * @see Line#LINE_ENDING 026 */ 027 public SimpleLine(CharSequence content) { 028 this(content, LINE_ENDING); 029 } 030 031 /** 032 * Constructor with contents and lineEnding character sequences. 033 * 034 * @param content 035 * the character sequence containing the {@link #getContent() 036 * content} data of the line 037 * @param lineEnding 038 * a single line ending character 039 * @throws IllegalArgumentException 040 * if {@code lineEnding} contains more than two characters 041 * @throws NullPointerException 042 * if {@code content} or {@code LineEvent} is null 043 */ 044 public SimpleLine(CharSequence content, char lineEnding) { 045 this(content, String.valueOf(lineEnding)); 046 } 047 /** 048 * Constructor with contents and lineEnding character sequences. 049 * 050 * @param content 051 * the character sequence containing the {@link #getContent() 052 * content} data of the line 053 * @param lineEnding 054 * the character sequence containing the {@link #getLineEnding() 055 * line ending} characters, must have length zero, one or two 056 * @throws IllegalArgumentException 057 * if {@code lineEnding} contains more than two characters 058 * @throws NullPointerException 059 * if {@code content} or {@code LineEvent} is null 060 */ 061 public SimpleLine(CharSequence content, CharSequence lineEnding) { 062 if (content == null) { 063 throw new NullPointerException("content cannot be null"); 064 } 065 final int elen = lineEnding.length(); 066 if (elen > 2) { 067 throw new IllegalArgumentException("lineEndingLength must be a string of length two or less, but was found to be " + elen + ": " + lineEnding); 068 } 069 this.content = content; 070 this.lineEnding = lineEnding; 071 } 072 073 @Override 074 public int length() { 075 return content.length() + lineEnding.length(); 076 } 077 078 @Override 079 public char charAt(int index) { 080 return index < content.length() ? content.charAt(index) : lineEnding.charAt(index - content.length()); 081 } 082 083 @Override 084 public CharSequence subSequence(int start, int end) { 085 final int clen = content.length(); 086 final int elen = lineEnding.length(); 087 if (start < clen && end <= clen) { 088 return content.subSequence(start, end); 089 } 090 final int estart = start - clen; 091 final int eend = end - clen; 092 if (estart >= 0 && estart <= elen && eend >= 0 && eend <= elen) { 093 return lineEnding.subSequence(estart, eend); 094 } 095 // overlaps the two strings 096 final StringBuilder sb = new StringBuilder(clen + elen); 097 return sb.append(content).append(lineEnding).subSequence(start, end); 098 } 099 100 @Override 101 public String getContent() { 102 return content.toString(); 103 } 104 105 @Override 106 public int getContentLength() { 107 return content.length(); 108 } 109 110 @Override 111 public String getLineEnding() { 112 return lineEnding.toString(); 113 } 114 115 @Override 116 public int getLineEndingLength() { 117 return lineEnding.length(); 118 } 119 120 @Override 121 public String toString() { 122 final StringBuilder sb = new StringBuilder(content.length() + lineEnding.length()); 123 return sb.append(content).append(lineEnding).toString(); 124 } 125 126 @Override 127 public int hashCode() { 128 return toString().hashCode(); 129 } 130 131 @Override 132 public boolean equals(Object obj) { 133 if (obj == this) 134 return true; 135 if (obj instanceof Line) { 136 return toString().equals(obj.toString()); 137 } 138 return false; 139 } 140 141 /** 142 * Returns a sub-line of the given {@code line}, similar to a substring or 143 * the method {@link #subSequence(int, int)}. The difference is that the 144 * result is again a proper line, with or without line ending depending on 145 * the cutting point and the {@code preserveLineEnding} flag. 146 * 147 * @param line the line to cut 148 * @param start the start index (inclusive), valid from 0 to {@code end} 149 * @param end the end index (exclusive), valid from 0 to 150 * {@code line.length()} if {@code preserveLineEnding=false}, 151 * and from 0 to {@code line.getContentLength()} if 152 * {@code preserveLineEnding=true} 153 * @param preserveLineEnding if true, the line's ending is preserved and 154 * copied to the returned line (only the 155 * content of the line is cut in this case) 156 * 157 * @return a new line representing a sub-line of the input line 158 */ 159 public static Line subLine(Line line, int start, int end, boolean preserveLineEnding) { 160 if (start == 0 && (end == line.length() || (preserveLineEnding && end == line.getContentLength()))) { 161 return line; 162 } 163 if (start < 0) { 164 throw new IllegalArgumentException("start cannot be negative: " + start); 165 } 166 if (end < 0) { 167 throw new IllegalArgumentException("end cannot be negative: " + end); 168 } 169 if (start > end) { 170 throw new IllegalArgumentException("start cannot be after end: " + start + " > " + end); 171 } 172 if (preserveLineEnding) { 173 if (end > line.getContentLength()) { 174 throw new IllegalArgumentException("start cannot be after line content end: " + end + " > " + line.getContentLength()); 175 } 176 return new SimpleLine(line.subSequence(start, end), line.getLineEnding()); 177 } else { 178 final int clen = line.getContentLength(); 179 if (end > line.length()) { 180 throw new IllegalArgumentException("start cannot be after line end: " + end + " > " + line.length()); 181 } 182 if (end <= clen) { 183 return new SimpleLine(line.subSequence(start, end), ""); 184 } else { 185 if (start < clen) { 186 return new SimpleLine(line.subSequence(start, clen), line.subSequence(line.getContentLength(), end)); 187 } else { 188 return new SimpleLine("", line.subSequence(start, end)); 189 } 190 } 191 } 192 } 193}