001/* 002 * Copyright 2006-2007 the original author or authors. 003 * 004 * Licensed under the Apache 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 * https://www.apache.org/licenses/LICENSE-2.0 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.springframework.batch.item.file; 017 018import java.io.BufferedReader; 019import java.io.IOException; 020import java.io.InputStreamReader; 021import java.io.Reader; 022import java.io.UnsupportedEncodingException; 023 024import org.springframework.core.io.Resource; 025 026/** 027 * A {@link BufferedReaderFactory} useful for reading simple binary (or text) 028 * files with no line endings, such as those produced by mainframe copy books. 029 * The reader splits a stream up across fixed line endings (rather than the 030 * usual convention based on plain text). The line endings are discarded, just 031 * as with the default plain text implementation. 032 * 033 * @author Dave Syer 034 * 035 * @since 2.1 036 */ 037public class SimpleBinaryBufferedReaderFactory implements BufferedReaderFactory { 038 039 /** 040 * The default line ending value. 041 */ 042 private static final String DEFAULT_LINE_ENDING = "\n"; 043 044 private String lineEnding = DEFAULT_LINE_ENDING; 045 046 /** 047 * @param lineEnding {@link String} indicating what defines the end of a "line". 048 */ 049 public void setLineEnding(String lineEnding) { 050 this.lineEnding = lineEnding; 051 } 052 053 @Override 054 public BufferedReader create(Resource resource, String encoding) throws UnsupportedEncodingException, IOException { 055 return new BinaryBufferedReader(new InputStreamReader(resource.getInputStream(), encoding), lineEnding); 056 } 057 058 /** 059 * BufferedReader extension that splits lines based on a line ending, rather 060 * than the usual plain text conventions. 061 * 062 * @author Dave Syer 063 * 064 */ 065 private final class BinaryBufferedReader extends BufferedReader { 066 067 private final String ending; 068 069 /** 070 * @param in 071 */ 072 private BinaryBufferedReader(Reader in, String ending) { 073 super(in); 074 this.ending = ending; 075 } 076 077 @Override 078 public String readLine() throws IOException { 079 080 StringBuilder buffer = null; 081 082 synchronized (lock) { 083 084 int next = read(); 085 if (next == -1) { 086 return null; 087 } 088 089 buffer = new StringBuilder(); 090 StringBuilder candidateEnding = new StringBuilder(); 091 092 while (!isEndOfLine(buffer, candidateEnding, next)) { 093 next = read(); 094 } 095 buffer.append(candidateEnding); 096 097 } 098 099 if (buffer != null && buffer.length() > 0) { 100 return buffer.toString(); 101 } 102 return null; 103 104 } 105 106 /** 107 * Check for end of line and accumulate a buffer for next time. 108 * 109 * @param buffer the current line excluding the candidate ending 110 * @param candidate a buffer containing accumulated state 111 * @param next the next character (or -1 for end of file) 112 * @return true if the values together signify the end of a file 113 */ 114 private boolean isEndOfLine(StringBuilder buffer, StringBuilder candidate, int next) { 115 116 if (next == -1) { 117 return true; 118 } 119 120 char c = (char) next; 121 if (ending.charAt(0) == c || candidate.length() > 0) { 122 candidate.append(c); 123 } 124 125 if (candidate.length() == 0) { 126 buffer.append(c); 127 return false; 128 } 129 130 boolean end = ending.equals(candidate.toString()); 131 if (end) { 132 candidate.delete(0, candidate.length()); 133 } 134 else if (candidate.length() >= ending.length()) { 135 buffer.append(candidate); 136 candidate.delete(0, candidate.length()); 137 } 138 139 return end; 140 141 } 142 } 143 144}