Skip to content

Commit edfd975

Browse files
authored
Merge pull request #471 from metafacture/filemapColumns
Optionally set key and/or value column.
2 parents 2fa964a + cbaf9d3 commit edfd975

File tree

5 files changed

+612
-10
lines changed

5 files changed

+612
-10
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ indent_size = 2
3434
[metafacture-io/src/test/resources/org/metafacture/io/compressed.txt]
3535
insert_final_newline = false
3636

37-
[metamorph/src/test/resources/org/metafacture/metamorph/maps/file-map-test.txt]
37+
[metamorph/src/test/resources/org/metafacture/metamorph/maps/file-map-test*.txt]
3838
trim_trailing_whitespace = false
3939

4040
[metafacture-runner/src/main/dist/config/java-options.conf]

metamorph/src/main/java/org/metafacture/metamorph/maps/FileMap.java

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,19 @@
4343
*
4444
* The default {@link #setEncoding encoding} is UTF-8.
4545
* The default {@link #setSeparator separator} is {@code \t}.
46+
* The default {@link #setKeyColumn keyColumn} is {@code 0}.
47+
* The default {@link #setValueColumn valueColumn} is {@code 1}.
4648
*
47-
* By setting {@link #allowEmptyValues} to {@code true} the values in the
48-
* {@link Map} can be empty thus enabling e.g.
49+
* <p>By setting {@link #setAllowEmptyValues allowEmptyValues} to {@code true},
50+
* the values in the {@link Map} can be empty; thus enabling e.g.
4951
* {@link org.metafacture.metamorph.functions.SetReplace} to remove matching
5052
* keys.
5153
*
52-
* <strong>Important:</strong> All other lines that are not split in two parts
53-
* by the separator are ignored!
54+
* <p>By setting {@link #setExpectedColumns expectedColumns} to
55+
* {@code -1}, the number of columns is not checked.
56+
*
57+
* <p><strong>Important:</strong> Otherwise, all lines that are not split into
58+
* the expected number of parts by the separator are ignored!
5459
*
5560
* @author Markus Michael Geipel
5661
*/
@@ -59,10 +64,13 @@ public final class FileMap extends AbstractReadOnlyMap<String, String> {
5964
private final FileOpener fileOpener = new FileOpener();
6065
private final Map<String, String> map = new HashMap<>();
6166

67+
private ArrayList<String> filenames = new ArrayList<>();
6268
private Pattern split = Pattern.compile("\t", Pattern.LITERAL);
6369
private boolean allowEmptyValues;
6470
private boolean isUninitialized = true;
65-
private ArrayList<String> filenames = new ArrayList<>();
71+
private int expectedColumns;
72+
private int keyColumn;
73+
private int valueColumn = 1;
6674

6775
/**
6876
* Creates an instance of {@link FileMap}.
@@ -79,14 +87,28 @@ private void init() {
7987
* Sets whether to allow empty values in the {@link Map} or ignore these
8088
* entries.
8189
*
82-
* <strong>Default value: false </strong>
90+
* <strong>Default value: false</strong>
8391
*
8492
* @param allowEmptyValues true if empty values in the Map are allowed
8593
*/
8694
public void setAllowEmptyValues(final boolean allowEmptyValues) {
8795
this.allowEmptyValues = allowEmptyValues;
8896
}
8997

98+
/**
99+
* Sets number of expected columns; lines with different number of columns
100+
* are ignored. Set to {@code -1} to disable the check and allow arbitrary
101+
* number of columns.
102+
*
103+
* <strong>Default value: calculated from {@link #setKeyColumn key} and
104+
* {@link #setValueColumn value} columns</strong>
105+
*
106+
* @param expectedColumns number of expected columns
107+
*/
108+
public void setExpectedColumns(final int expectedColumns) {
109+
this.expectedColumns = expectedColumns;
110+
}
111+
90112
/**
91113
* Sets a comma separated list of files which provides the {@link Map}.
92114
*
@@ -141,14 +163,22 @@ private void loadFile(final String file) {
141163
Reader reader = fileOpener.open(stream);
142164
BufferedReader br = new BufferedReader(reader)
143165
) {
166+
final int minColumns = Math.max(keyColumn, valueColumn) + 1;
167+
final int expColumns = expectedColumns != 0 ? expectedColumns : minColumns;
168+
144169
String line;
145170
while ((line = br.readLine()) != null) {
146171
if (line.isEmpty()) {
147172
continue;
148173
}
174+
149175
final String[] parts = allowEmptyValues ? split.split(line, -1) : split.split(line);
150-
if (parts.length == 2) {
151-
map.put(parts[0], parts[1]);
176+
if (parts.length < minColumns) {
177+
continue;
178+
}
179+
180+
if (expColumns < 0 || parts.length == expColumns) {
181+
map.put(parts[keyColumn], parts[valueColumn]);
152182
}
153183
}
154184
}
@@ -205,6 +235,28 @@ public void setSeparator(final String delimiter) {
205235
split = Pattern.compile(delimiter, Pattern.LITERAL);
206236
}
207237

238+
/**
239+
* Sets the key column (0-based).
240+
*
241+
* <strong>Default value: {@code 0}</strong>
242+
*
243+
* @param keyColumn the key column
244+
*/
245+
public void setKeyColumn(final int keyColumn) {
246+
this.keyColumn = keyColumn;
247+
}
248+
249+
/**
250+
* Sets the value column (0-based).
251+
*
252+
* <strong>Default value: {@code 1}</strong>
253+
*
254+
* @param valueColumn the value column
255+
*/
256+
public void setValueColumn(final int valueColumn) {
257+
this.valueColumn = valueColumn;
258+
}
259+
208260
@Override
209261
public String get(final Object key) {
210262
if (isUninitialized) {

metamorph/src/main/resources/schemata/metamorph.xsd

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,37 @@
629629
The default separator is the tabulator. </documentation>
630630
</annotation>
631631
</attribute>
632+
<attribute name="keyColumn" use="optional" default="0">
633+
<annotation>
634+
<documentation>Sets the key column (0-based).</documentation>
635+
</annotation>
636+
<simpleType>
637+
<restriction base="int">
638+
<minInclusive value="0" />
639+
</restriction>
640+
</simpleType>
641+
</attribute>
642+
<attribute name="valueColumn" use="optional" default="1">
643+
<annotation>
644+
<documentation>Sets the value column (0-based).</documentation>
645+
</annotation>
646+
<simpleType>
647+
<restriction base="int">
648+
<minInclusive value="0" />
649+
</restriction>
650+
</simpleType>
651+
</attribute>
652+
<attribute name="expectedColumns" use="optional" default="0">
653+
<annotation>
654+
<documentation>Sets number of expected columns; set to -1 to disable
655+
column check.</documentation>
656+
</annotation>
657+
<simpleType>
658+
<restriction base="int">
659+
<minInclusive value="-1" />
660+
</restriction>
661+
</simpleType>
662+
</attribute>
632663
<attribute ref="xml:base" />
633664
</complexType>
634665
</element>

metamorph/src/test/java/org/metafacture/metamorph/maps/FileMapTest.java

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@
1818

1919
import static org.metafacture.metamorph.TestHelpers.assertMorph;
2020

21+
import org.junit.Assert;
2122
import org.junit.Rule;
2223
import org.junit.Test;
2324
import org.metafacture.framework.StreamReceiver;
2425
import org.mockito.Mock;
2526
import org.mockito.junit.MockitoJUnit;
2627
import org.mockito.junit.MockitoRule;
2728

29+
import java.util.function.Consumer;
30+
2831
/**
2932
* Tests for class {@link FileMap}.
3033
*
@@ -39,14 +42,16 @@ public final class FileMapTest {
3942
@Mock
4043
private StreamReceiver receiver;
4144

45+
private static String MAPS = "org/metafacture/metamorph/maps/";
46+
4247
private static String MORPH =
4348
"<rules>" +
4449
" <data source='1'>" +
4550
" <%s='map1' />" +
4651
" </data>" +
4752
"</rules>" +
4853
"<maps>" +
49-
" <filemap name='map1' files='org/metafacture/metamorph/maps/%s' %s/>" +
54+
" <filemap name='map1' files='" + MAPS + "%s' %s/>" +
5055
"</maps>";
5156

5257
@Test
@@ -67,6 +72,24 @@ public void shouldLookupValuesInFileBasedMap() {
6772
);
6873
}
6974

75+
@Test
76+
public void shouldLookupValuesInFileBasedMapWithColumnOptions() {
77+
assertMorph(receiver, buildMorph("lookup in", "keyColumn=\"1\" valueColumn=\"0\" expectedColumns=\"2\""),
78+
i -> {
79+
i.startRecord("1");
80+
i.literal("1", "Germany");
81+
i.literal("1", "Fiji");
82+
i.endRecord();
83+
},
84+
o -> {
85+
o.get().startRecord("1");
86+
o.get().literal("1", "gw");
87+
o.get().literal("1", "fj");
88+
o.get().endRecord();
89+
}
90+
);
91+
}
92+
7093
@Test
7194
public void shouldWhitelistValuesInFileBasedMap() {
7295
assertMorph(receiver, buildMorph("whitelist map", ""),
@@ -206,6 +229,121 @@ public void shouldLookupValuesInBlockedGzipFileMap() {
206229
);
207230
}
208231

232+
@Test
233+
public void shouldLoadFile() {
234+
assertMap(379, i -> {
235+
Assert.assertEquals("Puerto Rico", i.get("pr"));
236+
Assert.assertNull(i.get("zz"));
237+
});
238+
}
239+
240+
@Test
241+
public void shouldLoadFileWithEmptyValues() {
242+
assertMap(380, i -> {
243+
i.setAllowEmptyValues(true);
244+
245+
Assert.assertEquals("Puerto Rico", i.get("pr"));
246+
Assert.assertEquals("", i.get("zz"));
247+
});
248+
}
249+
250+
@Test
251+
public void shouldLoadFileWithSeparator() {
252+
assertMap(99, i -> {
253+
i.setSeparator(" ");
254+
255+
Assert.assertNull(i.get("pp\tPapua"));
256+
Assert.assertEquals("Rico", i.get("pr\tPuerto"));
257+
});
258+
}
259+
260+
@Test
261+
public void shouldLoadFileWithKeyColumn() {
262+
assertMap(21, i -> {
263+
i.setSeparator(" ");
264+
i.setKeyColumn(2);
265+
266+
Assert.assertEquals("New", i.get("Guinea"));
267+
});
268+
}
269+
270+
@Test
271+
public void shouldLoadFileWithValueColumn() {
272+
assertMap(24, i -> {
273+
i.setSeparator(" ");
274+
i.setValueColumn(2);
275+
276+
Assert.assertEquals("Guinea", i.get("pp\tPapua"));
277+
});
278+
}
279+
280+
@Test
281+
public void shouldLoadFileWithKeyAndValueColumn() {
282+
assertMap(66, i -> {
283+
i.setSeparator(" ");
284+
i.setKeyColumn(1);
285+
i.setValueColumn(0);
286+
287+
Assert.assertEquals("pr\tPuerto", i.get("Rico"));
288+
});
289+
}
290+
291+
@Test
292+
public void shouldLoadFileWithExpectedColumns() {
293+
assertMap(24, i -> {
294+
i.setSeparator(" ");
295+
i.setExpectedColumns(3);
296+
297+
Assert.assertEquals("New", i.get("pp\tPapua"));
298+
});
299+
}
300+
301+
@Test
302+
public void shouldLoadFileWithArbitraryExpectedColumns() {
303+
assertMap(149, i -> {
304+
i.setSeparator(" ");
305+
i.setExpectedColumns(-1);
306+
307+
Assert.assertEquals("New", i.get("pp\tPapua"));
308+
});
309+
}
310+
311+
@Test
312+
public void shouldNotLoadFileWithOutOfRangeKeyColumn() {
313+
assertMap(0, i -> {
314+
i.setKeyColumn(2);
315+
});
316+
}
317+
318+
@Test
319+
public void shouldNotLoadFileWithOutOfRangeValueColumn() {
320+
assertMap(0, i -> {
321+
i.setValueColumn(2);
322+
});
323+
}
324+
325+
@Test
326+
public void shouldNotLoadFileWithTooFewExpectedColumns() {
327+
assertMap(0, i -> {
328+
i.setExpectedColumns(1);
329+
});
330+
}
331+
332+
@Test
333+
public void shouldNotLoadFileWithTooManyExpectedColumns() {
334+
assertMap(0, i -> {
335+
i.setExpectedColumns(99);
336+
});
337+
}
338+
339+
private void assertMap(final int size, final Consumer<FileMap> consumer) {
340+
final FileMap fileMap = new FileMap();
341+
fileMap.setFile(MAPS + "file-map-test-columns.txt");
342+
343+
consumer.accept(fileMap);
344+
Assert.assertEquals(size, fileMap.keySet().size());
345+
}
346+
209347
private String buildMorph(final String data, final String options) {
210348
return buildMorph(data, "file-map-test.txt", options);
211349
}

0 commit comments

Comments
 (0)