Java + Groovy: Creating Immutable List

Java: Mutable List

// class java.util.Arrays$ArrayList
final List mutableList = Arrays.asList(1, 2, 3);

Java: Immutable List

// class java.util.Collections$UnmodifiableRandomAccessList
final List immutableList = Collections.unmodifiableList(Arrays.asList(1, 2, 3));

Java: Immutable List using Guava

// class com.google.common.collect.RegularImmutableList
final ImmutableList guavaImmutableList = ImmutableList.of(1, 2, 3);

Groovy: Immutable List

// class java.util.Collections$UnmodifiableRandomAccessList
final def groovyImmutableList = [1, 2, 3].asImmutable()

IntelliJ: Selectively Disable Line Wrap

PROBLEM

Sometimes, we have very lengthy statements that look like this:-

@Service
public class LengthOfStayHeuristicServiceImpl extends HeuristicService {
    @Override
    public Double compute(HeuristicBean heuristicBean) {
        ...
				
        setValue(map, surgeryLocationMap, LengthOfStayAlgorithm.VariableEnum.SURGERY_LOCATION_LUMBAR_OR_SACRAL_AND_LUMBOSACRAL_MINUS_CERVICAL_AND_NOT_SPECIFIED);
        setValue(map, surgeryLocationMap, LengthOfStayAlgorithm.VariableEnum.SURGERY_LOCATION_LUMBAR_OR_SACRAL_MINUS_LUMBOSACRAL);
        setValue(map, surgeryLocationMap, LengthOfStayAlgorithm.VariableEnum.SURGERY_LOCATION_CERVICAL_MINUS_NOT_SPECIFIED);

        return new LengthOfStayAlgorithm(map).run();
    }
}

When we reformat the code in IntelliJ, it becomes like this:-

@Service
public class LengthOfStayHeuristicServiceImpl extends HeuristicService {
    @Override
    public Double compute(HeuristicBean heuristicBean) {
        ...
				
        setValue(map,
                 surgeryLocationMap,
                 LengthOfStayAlgorithm.VariableEnum.SURGERY_LOCATION_LUMBAR_OR_SACRAL_AND_LUMBOSACRAL_MINUS_CERVICAL_AND_NOT_SPECIFIED);
        setValue(map,
                 surgeryLocationMap,
                 LengthOfStayAlgorithm.VariableEnum.SURGERY_LOCATION_LUMBAR_OR_SACRAL_MINUS_LUMBOSACRAL);
        setValue(map,
                 surgeryLocationMap,
                 LengthOfStayAlgorithm.VariableEnum.SURGERY_LOCATION_CERVICAL_MINUS_NOT_SPECIFIED);

        return new LengthOfStayAlgorithm(map).run();
    }
}

There are times we really don’t want the long statements to wrap around because they look very messy.

SOLUTION

While there is no option to selectively disable just the line wrap in IntelliJ, there is a way to selectively disable code formatting.

First, we need to enable the Formatter Control.

Now, we can annotate our code like this:-

@Service
public class LengthOfStayHeuristicServiceImpl extends HeuristicService {
    @Override
    public Double compute(HeuristicBean heuristicBean) {
        ...
				
        // @formatter:off
        setValue(map, surgeryLocationMap, LengthOfStayAlgorithm.VariableEnum.SURGERY_LOCATION_LUMBAR_OR_SACRAL_AND_LUMBOSACRAL_MINUS_CERVICAL_AND_NOT_SPECIFIED);
        setValue(map, surgeryLocationMap, LengthOfStayAlgorithm.VariableEnum.SURGERY_LOCATION_LUMBAR_OR_SACRAL_MINUS_LUMBOSACRAL);
        setValue(map, surgeryLocationMap, LengthOfStayAlgorithm.VariableEnum.SURGERY_LOCATION_CERVICAL_MINUS_NOT_SPECIFIED);
        // @formatter:on

        return new LengthOfStayAlgorithm(map).run();
    }
}

When we reformat the code, that portion of code will remain unformatted.

Java: Promoting Testability by Having Enum Implementing an Interface

OVERVIEW

This post illustrates how we can easily write a better test case without polluting our production code with non-production code by performing a minor refactoring to the production code.

PROBLEM

Let’s assume we have a simple Data Reader that reads all the lines of a given algorithm data file and returns them:-

public class DataReader {
    public List<String> getDataLines(AlgorithmEnum algorithm) {
        // we have `StS-data.txt`, `CtE-data.txt` and `TtI-data.txt` under `src/main/resources` dir
        String fileName = String.format("%s-data.txt", algorithm.getShortName());
        Scanner scanner = new Scanner(getClass().getClassLoader().getResourceAsStream(fileName));

        List<String> list = new ArrayList<String>();

        while (scanner.hasNextLine()) {
            list.add(scanner.nextLine());
        }

        return list;
    }
}

This API accepts an AlgorithmEnum and it looks something like this:-

public enum AlgorithmEnum {
    SKIN_TO_SKIN("StS"),
    CLOSURE_TO_EXIT("CtE"),
    TIME_TO_INCISION("TtI");

    private String shortName;
    
    AlgorithmEnum(String shortName) {
        this.shortName = shortName;
    }

    public String getShortName() {
        return shortName;
    }
}

Let’s assume each algorithm data file has millions of data lines.

So, how do we test this code?

SOLUTION 1: Asserting Actual Line Count == Expected Line Count

One straightforward way is to:-

  • Pass in one of the Enum constants (AlgorithmEnum.SKIN_TO_SKIN, etc) into DataReader.getDataLines(..)
  • Get the actual line counts
  • Assert the actual line counts against the expected line counts

public class DataReaderTest {
    @Test
    public void testGetDataLines() {
        List<String> lines = new DataReader().getDataLines(AlgorithmEnum.SKIN_TO_SKIN);
        assertThat(lines, hasSize(7500));
    }
}    

This is a pretty weak test because we only check the line counts. Since we are dealing with a lot of data lines, it becomes impossible to verify the correctness of each data line.

SOLUTION 2: Adding a Test Constant to AlgorithmEnum

Another approach is to add a test constant to AlgorithmEnum:-

public enum AlgorithmEnum {
    SKIN_TO_SKIN("StS"),
    CLOSURE_TO_EXIT("CtE"),
    TIME_TO_INCISION("TtI"),
		
    // added a constant for testing purpose
    TEST_ABC("ABC");

    private String shortName;
    
    AlgorithmEnum(String shortName) {
        this.shortName = shortName;
    }

    public String getShortName() {
        return shortName;
    }
}

Now, we can easily test the code with our test data stored at src/test/resources/ABC-data.txt:-

public class DataReaderTest {
    @Test
    public void testGetDataLines() {
        List<String> lines = new DataReader().getDataLines(AlgorithmEnum.TEST_ABC);
        assertThat(lines, is(Arrays.asList("line 1", "line 2", "line 3")));
    }
}

While this approach works, we pretty much polluted our production code with non-production code, which may become a maintenance nightmare as the project grows larger in the future.

SOLUTION 3: AlgorithmEnum Implements an Interface

Instead of writing a mediocre test case or polluting the production code with non-production code, we can perform a minor refactoring to our existing production code.

First, we create a simple interface:-

public interface Algorithm {
    String getShortName();
}

Then, we have AlgorithmEnum to implement Algorithm:-

public enum AlgorithmEnum implements Algorithm {
    SKIN_TO_SKIN("StS"),
    CLOSURE_TO_EXIT("CtE"),
    TIME_TO_INCISION("TtI");

    private String shortName;

    AlgorithmEnum(String shortName) {
        this.shortName = shortName;
    }

    public String getShortName() {
        return shortName;
    }
}

Now, instead of passing AlgorithmEnum into getDataLines(...), we will pass in Algorithm interface.

public class DataReader {
    public List<String> getDataLines(Algorithm algorithm) {
        String fileName = String.format("%s-data.txt", algorithm.getShortName());
        Scanner scanner = new Scanner(getClass().getClassLoader().getResourceAsStream(fileName));
    
        List<String> list = new ArrayList<String>();
    
        while (scanner.hasNextLine()) {
            list.add(scanner.nextLine());
        }

        return list;
    }
}

With these minor changes, we can easily unit test the code with our mock data stored under src/test/resources directory.

public class DataReaderTest {
    @Test
    public void testGetDataLines() {
        List<String> lines = new DataReader().getDataLines(new Algorithm() {
            @Override
            public String getShortName() {
                // we have `ABC-data.txt` under `src/test/resources` dir
                return "ABC";
            }
        });

        assertThat(lines, is(Arrays.asList("line 1", "line 2", "line 3")));
    }
}

Spock: Reading Test Data from CSV File

Following up on my recent post about creating a Spock specification to read the test data from a CSV file without loading all the data into the memory, I created a CSVReader that implements Iterable that allows me to pull this off. You may download the source code here.

With this implementation, I can now write an elegant Spock specification:-

class MySpockSpec extends Specification {
    @Unroll
    def "#firstNum + 1 == #secondNum"() {
        expect:
        Integer.valueOf(firstNum as String) + 1 == Integer.valueOf(secondNum as String)

        where:
        [firstNum, secondNum] << new CSVReader(getClass().getClassLoader().getResourceAsStream("test.csv"))
    }
}

Maven: Plugin Execution Not Covered by Lifecycle Configuration

PROBLEM

When running a Maven project in Eclipse or Spring Tool Suite (STS), we get an exception that is similar to this:-

Plugin execution not covered by lifecycle configuration:
org.jacoco:jacoco-maven-plugin:0.6.4.201312101107:prepare-agent (execution: pre-unit-test, phase: initialize)

SOLUTION

This problem stems from m2eclipse (m2e) plugin, which provides Maven support in Eclipse-based IDEs.

STEP 1: Suppress the Error Message

The simplest solution is to instruct m2e to silently ignore the plugin execution by adding the following configuration in the project root pom.xml:-

IMPORTANT: Please make sure to enter the correct plugin’s groupId, artifactId, versionRange and goals that causes the problem in the first place.

<pluginManagement>
    <plugins>
        <plugin>
            <groupId>org.eclipse.m2e</groupId>
            <artifactId>lifecycle-mapping</artifactId>
            <version>1.0.0</version>
            <configuration>
                <lifecycleMappingMetadata>
                    <pluginExecutions>
                        <pluginExecution>
                            <pluginExecutionFilter>
                                <groupId>org.jacoco</groupId>
                                <artifactId>jacoco-maven-plugin</artifactId>
                                <versionRange>[0.0.1,)</versionRange>
                                <goals>
                                    <goal>prepare-agent</goal>
                                </goals>
                            </pluginExecutionFilter>
                            <action>
                                <ignore />
                            </action>
                        </pluginExecution>
                    </pluginExecutions>
                </lifecycleMappingMetadata>
            </configuration>
        </plugin>
    </plugins>
</pluginManagement>

STEP 2: Learn More About This Problem

Eclipse provides a great documentation regarding this issue. You can learn the “what”, “why” and “how” at http://wiki.eclipse.org/M2E_plugin_execution_not_covered

STEP 3: A More Elegant Fix

The problem with the above fix is we clutter our pristine Maven project configuration with an IDE plugin specific configuration that has nothing to do with the project in the first place.

So, the best solution is ditch Eclipse-based IDEs and switch to IntelliJ IDEA… because you either have idea, or don’t.

IntelliJ: Handling SVN Global Ignore List

PROBLEM

Every time we check out a project from SVN for the first time, we always have to remember to set the SVN ignore list in IntelliJ.

SOLUTION

NOTE: We only need to perform these steps just once per development machine.

To do so, we configure the SVN global ignore list and have IntelliJ to conform to that rule.

  • In Mac, go to IntelliJ IDEA -> Preferences. In Windows, go to File -> Settings.
  • In the preference dialog, go to Version Control -> Subversion.
  • Check Use system default Subversion configuration directory.
  • Note down Subversion configuration directory.
  • Close preference dialog.
  • Open [SVN_CONFIG_DIR]/config in a text editor.
  • Scroll to the [miscellany] section that looks something like this:-

    ...
    [miscellany]
    ### Set global-ignores to a set of whitespace-delimited globs
    ### which Subversion will ignore in its 'status' output, and
    ### while importing or adding files and directories.
    ### '*' matches leading dots, e.g. '*.rej' matches '.foo.rej'.
    # global-ignores = *.o *.lo *.la *.al .libs *.so *.so.[0-9]* *.a *.pyc *.pyo
    #   *.rej *~ #*# .#* .*.swp .DS_Store
    ### Set log-encoding to the default encoding for log messages
    # log-encoding = latin1
    ...
    

  • Uncomment global-ignores statement by removing #:-

    ...
    [miscellany]
    ### Set global-ignores to a set of whitespace-delimited globs
    ### which Subversion will ignore in its 'status' output, and
    ### while importing or adding files and directories.
    ### '*' matches leading dots, e.g. '*.rej' matches '.foo.rej'.
    global-ignores = *.o *.lo *.la *.al .libs *.so *.so.[0-9]* *.a *.pyc *.pyo
       *.rej *~ #*# .#* .*.swp .DS_Store
    ### Set log-encoding to the default encoding for log messages
    # log-encoding = latin1
    ...
    

  • Add the following patterns:-

    ...
    [miscellany]
    ### Set global-ignores to a set of whitespace-delimited globs
    ### which Subversion will ignore in its 'status' output, and
    ### while importing or adding files and directories.
    ### '*' matches leading dots, e.g. '*.rej' matches '.foo.rej'.
    global-ignores = *.o *.lo *.la *.al .libs *.so *.so.[0-9]* *.a *.pyc *.pyo
       *.rej *~ #*# .#* .*.swp .DS_Store
       .idea target .git .classpath .project .settings *.iml *.log *.bak *.class *.jar *.war *.ear
    ### Set log-encoding to the default encoding for log messages
    # log-encoding = latin1
    ...
    

  • Save and close this config file.

Now, all the unnecessary files will not appear under “Changes” section in IntelliJ and they will not be committed into SVN.