001/*
002 * Copyright 2006-2014 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 */
016
017package org.springframework.batch.item.file.transform;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.List;
022
023import org.springframework.util.StringUtils;
024
025/**
026 * Abstract class handling common concerns of various {@link LineTokenizer}
027 * implementations such as dealing with names and actual construction of
028 * {@link FieldSet}
029 * 
030 * @author Dave Syer
031 * @author Robert Kasanicky
032 * @author Lucas Ward
033 * @author Michael Minella
034 */
035public abstract class AbstractLineTokenizer implements LineTokenizer {
036
037        protected String[] names = new String[0];
038
039        private boolean strict = true;
040        
041        private String emptyToken = "";
042
043        private FieldSetFactory fieldSetFactory = new DefaultFieldSetFactory();
044
045        /**
046         * Public setter for the strict flag. If true (the default) then number of 
047         * tokens in line must match the number of tokens defined 
048         * (by {@link Range}, columns, etc.) in {@link LineTokenizer}. 
049         * If false then lines with less tokens will be tolerated and padded with 
050         * empty columns, and lines with more tokens will 
051         * simply be truncated.
052         * 
053         * @param strict the strict flag to set
054         */
055        public void setStrict(boolean strict) {
056                this.strict = strict;
057        }
058        
059        /**
060         * Provides access to the strict flag for subclasses if needed.
061         * 
062         * @return the strict flag value
063         */
064        protected boolean isStrict() {
065                return strict;
066        }
067        
068        /**
069         * Factory for {@link FieldSet} instances. Can be injected by clients to
070         * customize the default number and date formats.
071         * 
072         * @param fieldSetFactory the {@link FieldSetFactory} to set
073         */
074        public void setFieldSetFactory(FieldSetFactory fieldSetFactory) {
075                this.fieldSetFactory = fieldSetFactory;
076        }
077
078        /**
079         * Setter for column names. Optional, but if set, then all lines must have
080         * as many or fewer tokens.
081         * 
082         * @param names names of each column
083         */
084        public void setNames(String... names) {
085                if(names == null) {
086                        this.names = null;
087                }
088                else {
089                        boolean valid = false;
090                        for (String name : names) {
091                                if(StringUtils.hasText(name)) {
092                                        valid = true;
093                                        break;
094                                }
095                        }
096
097                        if(valid) {
098                                this.names = Arrays.asList(names).toArray(new String[names.length]);
099                        }
100                }
101        }
102
103        /**
104         * @return <code>true</code> if column names have been specified
105         * @see #setNames(String[])
106         */
107        public boolean hasNames() {
108                if (names != null && names.length > 0) {
109                        return true;
110                }
111                return false;
112        }
113
114        /**
115         * Yields the tokens resulting from the splitting of the supplied
116         * <code>line</code>.
117         * 
118         * @param line the line to be tokenized (can be <code>null</code>)
119         * 
120         * @return the resulting tokens
121         */
122    @Override
123        public FieldSet tokenize(String line) {
124
125                if (line == null) {
126                        line = "";
127                }
128
129                List<String> tokens = new ArrayList<String>(doTokenize(line));
130                
131                // if names are set and strict flag is false
132                if ( ( names.length != 0 ) && ( ! strict ) ) {
133                        adjustTokenCountIfNecessary( tokens );
134                }
135                
136                String[] values = tokens.toArray(new String[tokens.size()]);
137
138                if (names.length == 0) {
139                        return fieldSetFactory.create(values);
140                }
141                else if (values.length != names.length) {
142                        throw new IncorrectTokenCountException(names.length, values.length, line);
143                }
144                return fieldSetFactory.create(values, names);
145        }
146
147        protected abstract List<String> doTokenize(String line);
148        
149        /**
150         * Adds empty tokens or truncates existing token list to match expected 
151         * (configured) number of tokens in {@link LineTokenizer}.
152         * 
153         * @param tokens - list of tokens
154         */
155        private void adjustTokenCountIfNecessary( List<String> tokens ) {
156                
157                int nameLength = names.length;
158                int tokensSize = tokens.size();
159                
160                // if the number of tokens is not what expected
161                if ( nameLength != tokensSize ) {
162                        
163                        if ( nameLength > tokensSize ) {
164
165                                // add empty tokens until the token list size matches
166                                // the expected number of tokens
167                                for ( int i = 0; i < ( nameLength - tokensSize ); i++ ) {
168                                        tokens.add( emptyToken );
169                                }
170
171                        } else {
172                                // truncate token list to match the number of expected tokens
173                                for ( int i = tokensSize - 1; i >= nameLength; i-- ) {
174                                        tokens.remove(i);
175                                }
176                        }
177                                
178                }
179        }
180}