1 /*
2 * Copyright 2006-2007 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.springframework.batch.item.file.transform;
18
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.List;
22
23 /**
24 * Abstract class handling common concerns of various {@link LineTokenizer}
25 * implementations such as dealing with names and actual construction of
26 * {@link FieldSet}
27 *
28 * @author Dave Syer
29 * @author Robert Kasanicky
30 * @author Lucas Ward
31 */
32 public abstract class AbstractLineTokenizer implements LineTokenizer {
33
34 protected String[] names = new String[0];
35
36 private boolean strict = true;
37
38 private String emptyToken = "";
39
40 private FieldSetFactory fieldSetFactory = new DefaultFieldSetFactory();
41
42 /**
43 * Public setter for the strict flag. If true (the default) then number of
44 * tokens in line must match the number of tokens defined
45 * (by {@link Range}, columns, etc.) in {@link LineTokenizer}.
46 * If false then lines with less tokens will be tolerated and padded with
47 * empty columns, and lines with more tokens will
48 * simply be truncated.
49 *
50 * @param strict the strict flag to set
51 */
52 public void setStrict(boolean strict) {
53 this.strict = strict;
54 }
55
56 /**
57 * Provides access to the strict flag for subclasses if needed.
58 *
59 * @return the strict flag value
60 */
61 protected boolean isStrict() {
62 return strict;
63 }
64
65 /**
66 * Factory for {@link FieldSet} instances. Can be injected by clients to
67 * customize the default number and date formats.
68 *
69 * @param fieldSetFactory the {@link FieldSetFactory} to set
70 */
71 public void setFieldSetFactory(FieldSetFactory fieldSetFactory) {
72 this.fieldSetFactory = fieldSetFactory;
73 }
74
75 /**
76 * Setter for column names. Optional, but if set, then all lines must have
77 * as many or fewer tokens.
78 *
79 * @param names
80 */
81 public void setNames(String[] names) {
82 this.names = names==null ? null : Arrays.asList(names).toArray(new String[names.length]);
83 }
84
85 /**
86 * @return <code>true</code> if column names have been specified
87 * @see #setNames(String[])
88 */
89 public boolean hasNames() {
90 if (names != null && names.length > 0) {
91 return true;
92 }
93 return false;
94 }
95
96 /**
97 * Yields the tokens resulting from the splitting of the supplied
98 * <code>line</code>.
99 *
100 * @param line the line to be tokenised (can be <code>null</code>)
101 *
102 * @return the resulting tokens
103 */
104 public FieldSet tokenize(String line) {
105
106 if (line == null) {
107 line = "";
108 }
109
110 List<String> tokens = new ArrayList<String>(doTokenize(line));
111
112 // if names are set and strict flag is false
113 if ( ( names.length != 0 ) && ( ! strict ) ) {
114 adjustTokenCountIfNecessary( tokens );
115 }
116
117 String[] values = (String[]) tokens.toArray(new String[tokens.size()]);
118
119 if (names.length == 0) {
120 return fieldSetFactory.create(values);
121 }
122 else if (values.length != names.length) {
123 throw new IncorrectTokenCountException(names.length, values.length);
124 }
125 return fieldSetFactory.create(values, names);
126 }
127
128 protected abstract List<String> doTokenize(String line);
129
130 /**
131 * Adds empty tokens or truncates existing token list to match expected
132 * (configured) number of tokens in {@link LineTokenizer}.
133 *
134 * @param tokens - list of tokens
135 */
136 private void adjustTokenCountIfNecessary( List<String> tokens ) {
137
138 int nameLength = names.length;
139 int tokensSize = tokens.size();
140
141 // if the number of tokens is not what expected
142 if ( nameLength != tokensSize ) {
143
144 if ( nameLength > tokensSize ) {
145
146 // add empty tokens until the token list size matches
147 // the expected number of tokens
148 for ( int i = 0; i < ( nameLength - tokensSize ); i++ ) {
149 tokens.add( emptyToken );
150 }
151
152 } else {
153 // truncate token list to match the number of expected tokens
154 for ( int i = tokensSize - 1; i >= nameLength; i-- ) {
155 tokens.remove(i);
156 }
157 }
158
159 }
160 }
161 }