001/*
002 * Copyright 2007 The Kuali Foundation
003 * 
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * 
008 * http://www.opensource.org/licenses/ecl2.php
009 * 
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.ole.gl.batch.service.impl;
017
018import java.io.BufferedReader;
019import java.io.File;
020import java.io.FileNotFoundException;
021import java.io.FileReader;
022import java.io.IOException;
023import java.util.Iterator;
024import java.util.NoSuchElementException;
025
026import org.apache.log4j.Logger;
027import org.kuali.ole.gl.businessobject.OriginEntryFull;
028import org.kuali.ole.gl.exception.LoadException;
029
030/**
031 * This class lazy loads the origin entries in a flat file. This implementation uses a limited amount of memory because it does not
032 * pre-load all of the origin entries at once. (Assuming that the Java garbage collector is working well). However, if the code that
033 * uses this iterator stores the contents of this iterator in a big list somewhere, then a lot of memory may be consumed, depending
034 * on the size of the file.
035 */
036public class OriginEntryFileIterator implements Iterator<OriginEntryFull> {
037    private static Logger LOG = Logger.getLogger(OriginEntryFileIterator.class);
038
039    protected OriginEntryFull nextEntry;
040    protected BufferedReader reader;
041    protected int lineNumber;
042    protected boolean autoCloseReader;
043
044    /**
045     * Constructs a OriginEntryFileIterator
046     * 
047     * @param reader a reader representing flat-file origin entries
048     * @param autoCloseReader whether to automatically close the reader when the end of origin entries has been reached (i.e. when
049     *        hasNext() returns false)
050     */
051    public OriginEntryFileIterator(BufferedReader reader) {
052        this(reader, true);
053    }
054
055    /**
056     * Constructs a OriginEntryFileIterator
057     * 
058     * @param reader a reader representing flat-file origin entries
059     * @param autoCloseReader whether to automatically close the reader when the end of origin entries has been reached (i.e. when
060     *        hasNext() returns false)
061     */
062    public OriginEntryFileIterator(BufferedReader reader, boolean autoCloseReader) {
063        if (reader == null) {
064            LOG.error("reader is null in the OriginEntryFileIterator!");
065            throw new IllegalArgumentException("reader is null!");
066        }
067        this.reader = reader;
068        nextEntry = null;
069        lineNumber = 0;
070        this.autoCloseReader = autoCloseReader;
071    }
072
073    /**
074     * Constructs a OriginEntryFileIterator When constructed with this method, the file handle will be automatically closed when the
075     * end of origin entries has been reached (i.e. when hasNext() returns false)
076     * 
077     * @param file the file
078     */
079    public OriginEntryFileIterator(File file) {
080        if (file == null) {
081            LOG.error("reader is null in the OriginEntryFileIterator!");
082            throw new IllegalArgumentException("reader is null!");
083        }
084        try {
085            this.reader = new BufferedReader(new FileReader(file));
086            this.autoCloseReader = true;
087            nextEntry = null;
088            lineNumber = 0;
089        }
090        catch (FileNotFoundException e) {
091            LOG.error("File not found for OriginEntryFileIterator! " + file.getAbsolutePath(), e);
092            throw new RuntimeException("File not found for OriginEntryFileIterator! " + file.getAbsolutePath());
093        }
094    }
095
096    /**
097     * @see java.util.Iterator#hasNext()
098     */
099    public boolean hasNext() {
100        if (nextEntry == null) {
101            fetchNextEntry();
102            return nextEntry != null;
103        }
104        else {
105            // we have the next entry loaded
106            return true;
107        }
108    }
109
110    /**
111     * @see java.util.Iterator#next()
112     */
113    public OriginEntryFull next() {
114        if (nextEntry != null) {
115            // an entry may have been fetched by hasNext()
116            OriginEntryFull temp = nextEntry;
117            nextEntry = null;
118            return temp;
119        }
120        else {
121            // maybe next() is called repeatedly w/o calling hasNext. This is a bad idea, but the
122            // interface allows it
123            fetchNextEntry();
124            if (nextEntry == null) {
125                throw new NoSuchElementException();
126            }
127
128            // clear out the nextEntry to signal that no record has been loaded
129            OriginEntryFull temp = nextEntry;
130            nextEntry = null;
131            return temp;
132        }
133    }
134
135    /**
136     * @see java.util.Iterator#remove()
137     */
138    public void remove() {
139        throw new UnsupportedOperationException("Cannot remove entry from collection");
140    }
141
142    /**
143     * This method returns the next line in origin entry file
144     */
145    protected void fetchNextEntry() {
146        try {
147            lineNumber++;
148            String line = reader.readLine();
149            if (line == null) {
150                nextEntry = null;
151                if (autoCloseReader) {
152                    reader.close();
153                }
154            }
155            else {
156                nextEntry = new OriginEntryFull();
157                try {
158                    nextEntry.setFromTextFileForBatch(line, lineNumber - 1);
159                }
160                catch (LoadException e) {
161                    // wipe out the next entry so that the next call to hasNext or next will force a new row to be fetched
162                    nextEntry = null;
163
164                    // if there's an LoadException, then we'll just let it propagate up the call stack
165                    throw e;
166                }
167            }
168        }
169        catch (IOException e) {
170            LOG.error("error in the CorrectionDocumentServiceImpl iterator", e);
171            nextEntry = null;
172            throw new RuntimeException("error retrieving origin entries");
173        }
174    }
175
176    /**
177     * @see java.lang.Object#finalize()
178     */
179    @Override
180    protected void finalize() throws Throwable {
181        super.finalize();
182        if (autoCloseReader && reader != null) {
183            reader.close();
184        }
185    }
186}