niedziela, 17 kwietnia 2011

Ant Toolbox: Ant Task with Eclipse Api.


W ramach tego krótkiego wpisu postaram się omówić tworzenie własnego zadania, wykorzystywanego w narzędziu Apache ANT. W internecie aż kipi od tutoriali omawiających tworzenie własnych tasków dla narzędzia ANT. Nie mniej jednak większość z nich jest nieaktualna, bądź mało szczegółowa, szczególnie jeśli chodzi o współpracę z API Eclipse.

1. Po co nam własne zadania.

Ant jako narzędzie do budowania definiuje serie tasków które odpowiadają za podstawowe zadania związane z budowaniem i wdrażaniem oprogramowania. Wśród dostępnych zadań możemy znaleźć te typowe, służące do kopiowania zasobów, czy kompilacji, podpisywania jarów itp. (lista taksów dostępna jest pod adresem ) Nie mniej jednak domyślny zasób tasków anta może być niewystarczający do bardziej zaawansowanych rozwiązań, takich jak np budowanie pluginów Eclipse. Z pomocą w tym wypadku przychodzi dodatkowa biblioteka o nazwie Ant contrib, która dostarcza dodatkowe taski, które znacząco ułatwiają tworzenie skryptów (np: Budowanie warunkowe z użyciem if, regexpy itp). Jeśli mimo tego twój skrypt zawiera pewne niedoskonałości lub uproszczenia to należało by rozważyć możliwość stworzenia własnego zadania.


2. Wymagania
Eclipse wraz z pluginem org.apache.ant.
(Testowane na dystrybucji Eclipse PDE 3.6.2)

3. Task formatujący lokalizacje
W tutorialu tworzymy task który sformatuje lokalizacje usuwając znaki przejścia do katalogów wyższych. Standardowo ant posiada taki mechanizm (http://ant.apache.org/manual/Tasks/property.html) jednak jest to dobry przykład wykorzystania tasków w połączeniu z platformą Eclipse.
Kod klasy obsługującej zadanie prezentuje się następująco:

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;


public class NormalizePath extends Task  {

 AntLogAdapter fLogAdapter;
 private String fDir = "";
 private String fSkipPoints="1";
 protected String fResultProperty="";
 
 
 /**
  * String represents dir locations for example C:/temp/data 
  * @param baseDir
  */
 public void setDir(String baseDir) {
  this.fDir = baseDir;
 }
 /**
  * Used to set return property. This property should have url like C:/temp/
  * @param property
  */
 public void setProperty(String property) {
  this.fResultProperty=property;
 }
 /**
  * Number of segments to skip
  * @param fSkipPoints
  */
 public void setSegments(String fSkipPoints) {
  this.fSkipPoints = fSkipPoints;
 }
 
 public void execute() throws BuildException {
  try {
   fLogAdapter=new AntLogAdapter(this);
   validateAtributes();
   generateResult();
   fLogAdapter=new AntLogAdapter(null);
  } catch (Exception e) {
   throw new BuildException(e.getMessage().toString());
  }
 }

 private void validateAtributes() {
  if(fDir.length()==0){
     throw new BuildException("Dir must be specified using the dir attribute");
  } else if(fResultProperty.length()==0){
    throw new BuildException("Property must be specified using the property attribute.");
  }
 }
 
 public void generateResult() {
  IPath path=new Path(fDir);
  Integer segmentsToRemove = Integer.valueOf(fSkipPoints);
  IPath finalPath = path.removeLastSegments(segmentsToRemove);
  if(getProject() !=null){
   getProject().setProperty(fResultProperty, finalPath.toOSString());
  }
 }

 @Override
 public String getTaskType() {
  return "normalize.path";
 }
 
}

Zadanie ma na celu usunięcie części segmentów z adresu URI wprowadzonego jako paramter.
Pola typu string oznaczają parametry wejściowe. Najważniejszym elementem klasy jest funkcja execute, która waliduje oraz usuwa segmenty.
Parametry wejściowe zadania to:

 lokalizacja - reprezentowana poprzez zmienną fDir.
 ilość segmentów do usunięcia - fSkipPoints
 nazwa zmiennej zwracającej wynik  fResultProperty


Aby przetestować klasę musimy utworzyć obiekt pośredni który udostępni nam środowisko testowe przypominające wywołanie tasku poprzez anta:

import org.apache.tools.ant.Project;

import pl.com.astec.mdt.ant.tasks.NormalizePath;

public class NormalizePathMock extends NormalizePath{
 
 private Project tempProject;

 @Override
 public Project getProject() {
  if(tempProject==null)
   tempProject = new Project();
  return tempProject;
 }
 
 public String getResult() {
  if(tempProject!=null && fResultProperty!=null){
   return tempProject.getProperty(fResultProperty);
  }
  return "";
 }
}

Na sam koniec prosta klasa testowa stanowiąca przykład wykorzystania zadania
public class NormalizePathTest {
 
 NormalizePathMock task=new NormalizePathMock();
 
 @Test
 public void testGenerateResult() {
  String baseDir = "C://temp/testdir/removed_folder";
  Path expected=new Path("C://temp/testdir");
  
  task.setDir(baseDir);
  task.setProperty("test");
  task.generateResult();
  
  Path actual=new Path(task.getResult());
  Assert.assertTrue(actual.equals(expected));
 }
}