Table of Contents
A build script uses TaskFactory:s to configure and create
Task:s. Behind the scenes, a task factory uses
a TaskSpecification object for storing task configuration in.
When the build script asks the task factory to create a
Task, the task factory calls the task specification's
createTask
method. If the build script continues
to configure the task factory after it has created the task, the factory makes a copy of
the task specification using the specification's copyProperties
method and proceeds configuring the copy. By doing so, it makes sure that the
created task can read its configuration from the specification without the risk
of anyone modifying it.
Writing a Schmant task involves implementing a Task, a TaskSpecification and a TaskFactory class. There are a number of abstract stub implementations that a Task implementation may inherit. It is highly recommended, but not required, that it inherits AbstractTask or any of its subclasses. AbstractTask implements both Task and TaskSpecification. Task factory classes may inherit AbstractTaskFactory or any of its subclasses.
See the API documentation for details on the abstract Task and TaskFactory implementations.
The fastest way of implementing a new task is probably to get, um, inspiration from an existing task. Schmant's source distribution contains the source code for all of Schmant's own tasks.
Tasks can be developed in any Java IDE. The target Java version should be 6.0.
When a task is run from Schmant, all classes in the Jar files in the
Schmant distribution's lib
directory is
available to it. This includes the Schmant classes and the classes in the
EntityFS core, util, Jar and Zip Jars. Additional Jar files can be packaged in the
task's task package. See the section called “Task package file layout”.
The tools/create_task_package_eclipse_workspace.js
script in the Schmant distribution can be used to create an empty Eclipse
workspace for developing a task and a task package in.
Below is a commented version of the task and task factory implementation for a fictive task that translate the contents of a text file to the Robber's language.
Example 2.1. Implementation of RobbersLanguageTranslatorTask
// package se.rovarspraket; // The translator task is a process task. It takes the file from its source // property, translates it, and puts the result in the location referenced by // the target property. // // The task produces one object (the translated file), which makes it a // Producer. // // Note: This task would have been easier to implement if it had inherited the // AbstractTextInsertionTask. For pedagogic reasons, it does not. public final class RobbersLanguageTranslatorTask extends AbstractProcessTask< RobbersLanguageTranslatorTask> implements Producer<WritableFile> { // This ObjectTransformer is used to cast each argument given to the // addTranslatedCharacters method into a Character object when flattening the // arguments. // // Inherit from CastingTransformer. private static final class CastingToCharacterTransformer extends CastingTransformer<Character> { // Define a singleton instance. private static final CastingToCharacterTransformer INSTANCE = new CastingToCharacterTransformer(); } // The characters that are translated by default private static final Set<Character> DEFAULT_TRANSLATED_CHARACTERS = new HashSet<Character>( Arrays.asList( new Character[] { 'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'z' })); // The vowel that should be inserted. Default is "o". private char m_vowel = 'o'; // A set of characters to translate private final Set<Character> m_translatedCharacters = new HashSet<Character>(DEFAULT_TRANSLATED_CHARACTERS); // The translated file is assigned to this property when the task is run. private final AtomicReference<WritableFile> m_produced = new AtomicReference<WritableFile>(); // Make the constructor package private. We want that only the task factory // should be able to instantiate this class. RobbersLanguageTranslatorTask() { // Nothing } // This is required by the Producer interface. @Override public WritableFile get() { return m_produced.get(); } // Setters called by the task factory void setVowel(char c) { m_vowel = c; } void addTranslatedCharacter(char c) { m_translatedCharacters.add(c); } // Add one or several translated characters. This method will flatten the // argument into a list. void addTranslatedCharacters(Object o) { // This method is inherited from the AbstractArgumentChecker class. It // checks that none of the supplied objects are null check(o); // Flatten the argument by adding it to a FlatteningList. FlatteningList<Character> l = new FlatteningList<Character>(); // Add all arguments. use a custom ObjectTransformer that casts each // argument into a Character. l.add(o, CastingToCharacterTransformer.INSTANCE); // Add the flattened argument to the set. m_translatedCharacters.addAll(l); } void clearTranslatedCharacters() { m_translatedCharacters.clear(); } // Replace the default, not very good, log header. @Override protected String getDefaultLogHeader() { return "Totroranonsoslolatotinongog " + getSource(); } // Create the regular expression pattern that is used to replace the text private Pattern createPattern() { StringBuilder sb = new StringBuilder("["); for (Character c : m_translatedCharacters) { sb.append(c); } // Make the pattern case-insensitive return Pattern.compile( sb.append("]").toString(), Pattern.CASE_INSENSITIVE); } // This method is called from AbstractTask when the task is run. @Override protected void runInternal(Report r) { // Interpret the source and target properties. // Since the strategy will only return one object, we can call get() to get // it. ReadableFile source = ArgumentInterpreter.getInstance().interpret( getSource(), InterpretAsReadableFileStrategy.AS_SINGLE).get(); // The target is a new writable file. If there already is a file at the // target location, the overwrite strategy decides what to do with it. WritableFile target = ArgumentInterpreter.getInstance(). interpret( getTarget(), new InterpretAsNewWritableFileStrategy( getOverwriteStrategy(), ArgumentInterpretationStrategy.ALLOW_ONE_AND_ONLY_ONE_RESULT_OBJECT)). get(); Pattern pat = createPattern(); // Read the source file to a String and create a Matcher on the string String inText = Files.readTextFile(source); Matcher mat = pat.matcher(inText); // The text replacement loop StringBuilder res = new StringBuilder(); int lastMatch = 0; while(mat.find()) { // Copy all characters between this match and the previous match res.append(inText.substring(lastMatch, mat.start())); // The secret substitution... res.append(mat.group()); res.append(m_vowel); res.append(mat.group()); lastMatch = mat.end(); } // Copy all trailing characters res.append(inText.substring(lastMatch, inText.length())); // Write the result to the target file Files.writeText(target, res.toString()); // The target is our produced object m_produced.set(target); } // This method is called when the task (specification) is copied by the task // factory. It copies all properties to the new task. @Override public void copyProperties(RobbersLanguageTranslatorTask spec) { // We must call this super.copyProperties(spec); // The character is immutable, so it can safely be copied to the spec spec.m_vowel = m_vowel; // The list is mutable, so we only copy the list contents, not the list // itself. spec.m_translatedCharacters.addAll(m_translatedCharacters); } } //
The task factory:
Example 2.2. Implementation of RobbersLanguageTranslatorTF
// package se.rovarspraket; public final class RobbersLanguageTranslatorTF extends AbstractProcessTaskFactory< RobbersLanguageTranslatorTF, RobbersLanguageTranslatorTask> { // Setters. All values are set on the task specification, which happens to be // the task object itself. public RobbersLanguageTranslatorTF setVowel(char c) { getSpecification().setVowel(c); return this; } public RobbersLanguageTranslatorTF addTranslatedCharacter(char c) { getSpecification().addTranslatedCharacter(c); return this; } // Add a single translated character or an array or a collection of translated // characters. The argument will be flattened in // RobbersLanguageTranslatorTask. public RobbersLanguageTranslatorTF addTranslatedCharacters(Object o) { getSpecification().addTranslatedCharacters(o); return this; } public RobbersLanguageTranslatorTF clearTranslatedCharacters() { getSpecification().clearTranslatedCharacters(); return this; } // AbstractTaskFactory wants us to implement this. It // is called every time that the task factory needs to create a new // TaskSpecification object (which happens to be the Task itself). @Override protected RobbersLanguageTranslatorTask createSpecification() { return new RobbersLanguageTranslatorTask(); } } //
And this is how the task can be used from a build script:
Example 2.3. Using the Robber's language translator task
enableTaskPackage("se.rovarspraket"); // The source file var source = new CharSequenceReadableFile("Rhododendron"); // Create the target file in a RAM directory var target = new FutureFile( SchmantFileSystems.createRamFileSystem(), "translated.txt"); new dr.RobbersLanguageTranslatorTF(). setSource(source). setTarget(target). run(); // Exercise: What does this print? info(Files.readTextFile(target.getFile()));