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}