001package org.unix4j.unix.wc;
002
003import java.util.EnumMap;
004
005import org.unix4j.line.Line;
006import org.unix4j.line.SimpleLine;
007import org.unix4j.processor.LineProcessor;
008import org.unix4j.util.Counter;
009import org.unix4j.util.StringUtil;
010
011/**
012 * Counters for lines, words and characters with {@link Counter} objects in a
013 * map by {@link CounterType}. It depends on the argument options which counters
014 * are actually maintained. The {@link #update(Line)} method updates the
015 * relevant counters based on a line.
016 */
017class Counters {
018
019        public final static int MIN_COUNT_PADDING = 2;
020
021        private final EnumMap<CounterType, Counter> counters = new EnumMap<CounterType, Counter>(CounterType.class);
022        private boolean lastLineWasEmpty;
023
024        /**
025         * Constructor initialising all relevant counters depending on the options
026         * set in {@code args}.
027         * 
028         * @param args
029         *            the arguments with the options indicating which counts are
030         *            desired
031         */
032        public Counters(WcArguments args) {
033                for (final CounterType type : CounterType.values()) {
034                        if (type.isOptionSet(args)) {
035                                counters.put(type, new Counter());
036                        }
037                }
038                if (counters.isEmpty()) {
039                        // no option is set, count everything
040                        for (final CounterType type : CounterType.values()) {
041                                counters.put(type, new Counter());
042                        }
043                }
044        }
045
046        /**
047         * Updates all the relevant counters based on the specified {@code line}.
048         * 
049         * @param line
050         *            the line to incorporate into the counts
051         */
052        public void update(Line line) {
053                for (final CounterType type : counters.keySet()) {
054                        final Counter counter = counters.get(type);
055                        if (counter != null) {
056                                counter.increment(type.count(line));
057                        }
058                }
059                lastLineWasEmpty = line.getContentLength() == 0;
060        }
061
062        /**
063         * Updates the totals counter based on some other counter. Adds the counts
064         * of the specified {@code counters} to the total counts of {@code this}
065         * counter.
066         * 
067         * @param counters
068         *            the counters with per-file counts
069         */
070        public void updateTotal(Counters counters) {
071                for (final CounterType type : this.counters.keySet()) {
072                        final Counter total = this.counters.get(type);
073                        final Counter update = counters.counters.get(type);
074                        if (total != null && update != null) {
075                                total.increment(update.getCount());
076                        }
077                }
078        }
079
080        /**
081         * Resets all counts to zero.
082         */
083        public void reset() {
084                for (final Counter counter : counters.values()) {
085                        counter.reset();
086                }
087        }
088
089        private int getWidestCount() {
090                int max = 0;
091                for (final Counter counter : counters.values()) {
092                        max = Math.max(max, counter.getWidth());
093                }
094                return max;
095        }
096
097    public int getFixedWidthOfColumnsInOutput() {
098        if(counters.size() == 1){
099            return counters.values().iterator().next().getWidth();
100        } else {
101            return getWidestCount() + MIN_COUNT_PADDING;
102        }
103    }
104
105        /**
106         * Writes the counts line to the specified {@code output}.
107         * 
108         * @param output
109         *            the output destination
110         */
111        public void writeCountsLine(LineProcessor output) {
112                writeCountsLineWithFileInfo(output, null);
113        }
114
115
116    /**
117     * Writes the counts line to the specified {@code output} appending the
118     * specified file information.
119     *
120     * @param output
121     *            the output destination
122     * @param fileInfo
123     *            the file information, usually a file name or path
124     */
125    public void writeCountsLineWithFileInfo(LineProcessor output, String fileInfo) {
126        writeCountsLineWithFileInfo(output, fileInfo, getFixedWidthOfColumnsInOutput());
127    }
128
129        /**
130         * Writes the counts line to the specified {@code output} appending the
131         * specified file information.
132         * 
133         * @param output
134         *            the output destination
135         * @param fileInfo
136         *            the file information, usually a file name or path
137         * @param fixedWidthOfColumnsInOutput
138     *        the fixed width of the outputted counts.  Will usually be the width
139     *        of the widest count, plus two characters
140         */
141        public void writeCountsLineWithFileInfo(LineProcessor output, String fileInfo, int fixedWidthOfColumnsInOutput) {
142                dontCountSingleEmptyLine();
143                final CharSequence countString;
144        final StringBuilder sb = new StringBuilder();
145
146        for (final Counter counter : counters.values()) {
147            final String formattedCount = StringUtil.fixSizeString(fixedWidthOfColumnsInOutput, false, ' ', counter.getCount());
148            sb.append(formattedCount);
149        }
150        countString = sb;
151
152                if (fileInfo == null) {
153                        output.processLine(new SimpleLine(countString));
154                } else {
155                        output.processLine(new SimpleLine(countString + " " + fileInfo));
156                }
157        }
158
159        private void dontCountSingleEmptyLine() {
160                if (lastLineWasEmpty) {
161                        final Counter lineCounter = counters.get(CounterType.Lines);
162                        if (lineCounter != null && lineCounter.getCount() == 1) {
163                                lineCounter.reset();
164                        }
165                }
166        }
167}