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}