001package ball.io;
002/*-
003 * ##########################################################################
004 * Utilities
005 * %%
006 * Copyright (C) 2008 - 2022 Allen D. Ball
007 * %%
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *      http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 * ##########################################################################
020 */
021import java.io.IOException;
022import java.io.LineNumberReader;
023import java.io.Reader;
024import java.util.Objects;
025import lombok.ToString;
026
027/**
028 * {@link CharSequence} {@link Reader} implementation.
029 *
030 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
031 */
032@ToString
033public class CharSequenceReader extends LineNumberReader {
034
035    /**
036     * Sole constructor.
037     *
038     * @param   sequence        The {@link CharSequence}.
039     */
040    public CharSequenceReader(CharSequence sequence) {
041        super(new ReaderImpl(sequence), 1024);
042    }
043
044    @ToString
045    private static class ReaderImpl extends Reader {
046        private CharSequence sequence = null;
047        private volatile int length = 0;
048        private volatile int pos = 0;
049        private volatile int mark = 0;
050
051        public ReaderImpl(CharSequence sequence) {
052            super(new Object());
053
054            this.sequence = Objects.requireNonNull(sequence);
055            this.length = sequence.length();
056        }
057
058        @Override
059        public int read() throws IOException {
060            int character = -1;
061
062            synchronized (lock) {
063                if (pos < length) {
064                    character = sequence.charAt(pos++);
065                }
066            }
067
068            return character;
069        }
070
071        @Override
072        public int read(char chars[], int off, int len) throws IOException {
073            int count = -1;
074
075            synchronized (lock) {
076                if (pos < length) {
077                    count = Math.min(length - pos, len);
078
079                    for (int i = 0; i < count; i += 1) {
080                        chars[off + i] = sequence.charAt(pos + i);
081                    }
082
083                    pos += count;
084                }
085            }
086
087            return count;
088        }
089
090        @Override
091        public long skip(long count) throws IOException {
092            if (count < 0) {
093                throw new IllegalArgumentException("skip value is negative");
094            }
095
096            synchronized (lock) {
097                count = Math.min(count, length - pos);
098                pos += count;
099            }
100
101            return count;
102        }
103
104        @Override
105        public boolean ready() throws IOException { return true; }
106
107        @Override
108        public boolean markSupported() { return true; }
109
110        @Override
111        public void mark(int readAheadLimit) throws IOException {
112            if (readAheadLimit < 0){
113                throw new IllegalArgumentException("Read-ahead limit < 0");
114            }
115
116            synchronized (lock) {
117                mark = pos;
118            }
119        }
120
121        @Override
122        public void reset() throws IOException {
123            synchronized (lock) {
124                pos = mark;
125            }
126        }
127
128        @Override
129        public void close() throws IOException { sequence = null; }
130    }
131}