[jdeb] 01/01: Imported Upstream version 1.3

Tony Mancill tmancill at moszumanska.debian.org
Sun Dec 6 20:11:14 UTC 2015


This is an automated email from the git hooks/post-receive script.

tmancill pushed a commit to annotated tag upstream/1.3
in repository jdeb.

commit 387f080428d57cd97a6d12ea16c0fd6af835af41
Author: tony mancill <tmancill at debian.org>
Date:   Sat Aug 30 22:58:24 2014 -0700

    Imported Upstream version 1.3
---
 .gitignore                                         |   3 +-
 .travis.yml                                        |   2 +-
 HISTORY.md                                         |  45 +-
 README.md                                          |  16 +-
 docs/ant.md                                        |  16 +
 docs/maven.md                                      | 123 ++++-
 pom.xml                                            | 128 +++--
 src/examples/maven/pom.xml                         |   8 +-
 src/it/extension/pom.xml                           |  23 +
 src/it/extension/src/deb/control/control           |   7 +
 .../main/java/org/vafer/jdeb/examples/Main.java    |   7 +
 src/it/extension/verify.groovy                     |   1 +
 src/it/pom-only/pom.xml                            |   1 +
 .../java/org/vafer/jdeb/ChangesFileBuilder.java    | 160 +++---
 src/main/java/org/vafer/jdeb/Compression.java      | 164 +++---
 src/main/java/org/vafer/jdeb/Console.java          |   9 +-
 src/main/java/org/vafer/jdeb/ControlBuilder.java   | 451 ++++++++-------
 src/main/java/org/vafer/jdeb/DataBuilder.java      | 614 ++++++++++-----------
 src/main/java/org/vafer/jdeb/DataConsumer.java     |   4 +-
 src/main/java/org/vafer/jdeb/DataProducer.java     |   2 +-
 src/main/java/org/vafer/jdeb/DebMaker.java         | 349 ++++++++++--
 .../java/org/vafer/jdeb/PackagingException.java    |   2 +-
 src/main/java/org/vafer/jdeb/ant/Data.java         |  12 +-
 src/main/java/org/vafer/jdeb/ant/DebAntTask.java   |   8 +-
 src/main/java/org/vafer/jdeb/ant/Link.java         | 218 ++++----
 src/main/java/org/vafer/jdeb/ant/Mapper.java       |  10 +-
 src/main/java/org/vafer/jdeb/ant/TaskConsole.java  |  17 +-
 .../java/org/vafer/jdeb/changes/ChangeSet.java     |   2 +-
 .../org/vafer/jdeb/changes/ChangesProvider.java    |   2 +-
 .../jdeb/changes/TextfileChangesProvider.java      |   2 +-
 .../jdeb/debian/BinaryPackageControlFile.java      |  16 +-
 .../java/org/vafer/jdeb/debian/ChangesFile.java    |  32 +-
 .../java/org/vafer/jdeb/debian/ControlField.java   | 260 ++++-----
 .../java/org/vafer/jdeb/debian/ControlFile.java    | 102 +++-
 src/main/java/org/vafer/jdeb/mapping/LsMapper.java |   2 +-
 src/main/java/org/vafer/jdeb/mapping/Mapper.java   |   2 +-
 .../java/org/vafer/jdeb/mapping/NullMapper.java    |   2 +-
 .../java/org/vafer/jdeb/mapping/PermMapper.java    |   2 +-
 .../org/vafer/jdeb/maven/AbstractPluginMojo.java   |  47 --
 src/main/java/org/vafer/jdeb/maven/Data.java       |  97 ++--
 src/main/java/org/vafer/jdeb/maven/DebMojo.java    | 434 +++++++++++----
 src/main/java/org/vafer/jdeb/maven/Mapper.java     |  45 +-
 .../vafer/jdeb/maven/MissingSourceBehavior.java    |   3 +-
 .../java/org/vafer/jdeb/maven/MojoConsole.java     |  17 +-
 .../vafer/jdeb/producers/AbstractDataProducer.java |  24 +-
 .../vafer/jdeb/producers/DataProducerArchive.java  |   2 +-
 .../jdeb/producers/DataProducerDirectory.java      |  47 +-
 .../org/vafer/jdeb/producers/DataProducerFile.java |  17 +-
 .../vafer/jdeb/producers/DataProducerFileSet.java  |  10 +-
 .../vafer/jdeb/producers/DataProducerFiles.java    |  58 ++
 .../org/vafer/jdeb/producers/DataProducerLink.java |  11 +-
 .../jdeb/producers/DataProducerPathTemplate.java   |  15 +-
 .../java/org/vafer/jdeb/producers/Producers.java   | 116 ++++
 .../java/org/vafer/jdeb/signing/PGPSigner.java     | 328 ++++++-----
 .../java/org/vafer/jdeb/utils/FilteredFile.java    |  10 +-
 .../vafer/jdeb/utils/InformationInputStream.java   |   2 +-
 .../vafer/jdeb/utils/InformationOutputStream.java  |   2 +-
 .../org/vafer/jdeb/utils/MapVariableResolver.java  |   2 +-
 .../vafer/jdeb/utils/PGPSignatureOutputStream.java |  57 ++
 src/main/java/org/vafer/jdeb/utils/Utils.java      | 263 +++++++--
 .../org/vafer/jdeb/utils/VariableResolver.java     |   2 +-
 src/main/resources/META-INF/plexus/components.xml  |  50 ++
 src/test/java/org/vafer/jdeb/ArchiveVisitor.java   |  62 +--
 src/test/java/org/vafer/jdeb/ArchiveWalker.java    | 209 +++----
 .../java/org/vafer/jdeb/DataBuilderTestCase.java   | 165 +++---
 src/test/java/org/vafer/jdeb/DebMakerTestCase.java |  10 +-
 .../java/org/vafer/jdeb/EmptyDataProducer.java     |  56 +-
 src/test/java/org/vafer/jdeb/NullConsole.java      |  60 +-
 .../org/vafer/jdeb/ant/AntSelectorTestCase.java    |   2 +-
 .../org/vafer/jdeb/ant/DebAntTaskTestCase.java     |  32 +-
 .../changes/TextfileChangesProviderTestCase.java   |   2 +-
 .../org/vafer/jdeb/debian/ChangesFileTestCase.java |   4 +-
 .../vafer/jdeb/debian/ControlFieldTestCase.java    |  88 +--
 .../jdeb/debian/PackageControlFileTestCase.java    |  28 +-
 .../org/vafer/jdeb/mapping/LsMapperTestCase.java   |   2 +-
 .../java/org/vafer/jdeb/maven/DataTestCase.java    |   2 +-
 .../jdeb/producers/DataProducerFilesTestCase.java  |  89 +++
 .../DataProducerPathTemplateTestCase.java          |   2 +-
 .../org/vafer/jdeb/signing/DebMakerTestCase.java   | 113 ++++
 .../org/vafer/jdeb/signing/PGPSignerTestCase.java  |  14 +-
 .../org/vafer/jdeb/utils/FilteredFileTestCase.java |  11 +-
 .../jdeb/utils/InformationInputStreamTestCase.java |   2 +-
 .../java/org/vafer/jdeb/utils/UtilsTestCase.java   |  55 +-
 .../resources/org/vafer/jdeb/deb/control/control   |   1 +
 src/test/resources/testbuild.xml                   |  14 +
 85 files changed, 3574 insertions(+), 1932 deletions(-)

diff --git a/.gitignore b/.gitignore
index 5f74209..97c5ada 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,4 +9,5 @@ dependency-reduced-pom.xml
 *.iws
 *.ipr
 /.idea
-.release
\ No newline at end of file
+.release
+*~
diff --git a/.travis.yml b/.travis.yml
index 1c86c94..6d16074 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
 language: java
 jdk:
+  - oraclejdk8
   - oraclejdk7
-  - oraclejdk6
   - openjdk7
   - openjdk6
diff --git a/HISTORY.md b/HISTORY.md
index bf3d6ae..ce8029e 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,11 +1,44 @@
-## Version 1.1, released ?
+## Version 1.3.1, in progress
 
+## Version 1.3, released 25.07.2014
+
+* [CHG] Switched from maven2 to maven3
+* [FIX] Fixed badly formatted version for "milestone", "m", "a", "b" and "cr" (they where not matched as beta versions)
+* [FIX] Add two spaces in the md5sums file between the checksum and the filename to be compatible with GNU coreutils md5sum
+
+## Version 1.2, released 22.06.2014
+
+* [CHG] Deprecated "submodules" in favour of "skipSubmodules"
+* [FIX] Warned about missing signing settings even when not signing
+* [FIX] Fixed variable replacement issue on line endings
+* [ADD] Support <packaging>deb</packaging> in Maven
+* [ADD] Support for "signMethod" and "signRole"
+* [ADD] Added "skipPOMs"
+
+## Version 1.1.1, released 10.03.2014
+
+* [FIX] renamed SNAPSHOT handling to snapshotExpand & snapshotEnv
+
+## Version 1.1, released 28.02.2014
+
+* [CHG] Renamed configuration option 'disabled' to 'skip'
+* [ADD] Added support for conffiles (Thanks to Lukas Roedl)
+* [ADD] Added "files" data producer (Thanks to Roman Kashitsyn)
+* [FIX] Better token parsing
+* [ADD] Added support "Distribution" field
+* [ADD] Added support of maven-encrypted passphrases
 * [ADD] xz compression support
 * [ADD] Added link support to the Ant task
+* [ADD] Added support for system properties in parsed files (Thanks to David Sauer)
 * [ADD] Support permission in (hard) link setup
+* [ADD] User defined fields in the control file are now supported (Thanks to Marco Soeima)
+* [ADD] Added the ability to disable an execution during Maven build (Thanks to  Jonathan Piron)
 * [FIX] On Windows, parent directories are now created automatically when adding files to the data archive
 * [CHG] Links are now symbolic by default
-
+* [CHG] Alpha, Beta and RC versions are transformed to a package version ordered before the final release (ex: 1.0~RC1)
+* [FIX] Permission mappers now work properly with Ant (Thanks to Christian Egli)
+* [FIX] Symbolic links longer than 100 characters are now supported
+* [FIX] The signed changes files now pass the validation with gpg --verify (Thanks to Max Garmash and Roman Kashitsyn)
 
 ## Version 1.0.1, released 28.02.2013
 
@@ -49,7 +82,7 @@ Some smaller fixes, Support for the 1.8 format, Changes support working.
 
 * [FIX] Default path for changes file with Maven
 * [FIX] Unresolved variables are now treated as null
-* [CHG] "Changes" support version 1.8 
+* [CHG] "Changes" support version 1.8
 * [CHG] Warn if control files have non-unix line endings
 * [CHG] Throw an exception for unknown mappers
 * [REM] InvalidDescriptorException, wasn't really used anyway
@@ -80,9 +113,9 @@ Many improvements on the Ant task.
 Quite a few fixes related to locale settings.
 Support for bzip2 and more descriptor keys.
 
-* [FIX] English locale for date format.	
-* [FIX] Proper installation size to be kbytes instead of bytes.	
-* [FIX] Close streams properly.	
+* [FIX] English locale for date format.
+* [FIX] Proper installation size to be kbytes instead of bytes.
+* [FIX] Close streams properly.
 * [CHG] The Ant task now breaks on errors.
 * [ADD] Support for bzip2 compression in data element of the Ant task.
 * [ADD] Compression attribute to specify data file compression (bzip2, gzip, none).
diff --git a/README.md b/README.md
index 16850e9..1ab576b 100644
--- a/README.md
+++ b/README.md
@@ -5,20 +5,18 @@
 This library provides an Ant task and a Maven plugin to create Debian packages
 from Java builds in a truly cross platform manner. Build your Debian packages
 on any platform that has Java support. Windows, Linux, OS X - it doesn't require
-additional native tools installed. The API underneath is well abstracted and
-can easily be adopted for other areas as well.
+additional native tools installed.
 
 Check the documentation on how to use it with [Maven](http://github.com/tcurdt/jdeb/blob/master/docs/maven.md)
 or [Ant](http://github.com/tcurdt/jdeb/blob/master/docs/ant.md). Especially don't forget to check out the
 [examples](http://github.com/tcurdt/jdeb/blob/master/src/examples/). Current
-[javadocs](http://tcurdt.github.com/jdeb/release/1.0.1/apidocs/) and a source
-[xref](http://tcurdt.github.com/jdeb/release/1.0.1/xref/) is also available.
+[javadocs](http://tcurdt.github.com/jdeb/release/1.3/apidocs/) and a source
+[xref](http://tcurdt.github.com/jdeb/release/1.3/xref/) is also available.
 
 
 ## Where to get it
 
 The jars are available in the [Maven central repository](http://repo1.maven.org/maven2/org/vafer/jdeb/).
-The source releases you can get in the [download section](http://github.com/tcurdt/jdeb/downloads).
 
 If feel adventures or want to help out feel free to get the latest code
 [via git](http://github.com/tcurdt/jdeb/tree/master).
@@ -26,11 +24,15 @@ If feel adventures or want to help out feel free to get the latest code
     git clone git://github.com/tcurdt/jdeb.git
 
 
+## Working towards jdeb 2.0
+
+Over the years jdeb has grown more and more powerful - but also got more complex. In order to simplify the usage and apply what we have learned we are taking a step back. If you want to help to shape the usage of jdeb 2.0 feel free to contribute on the [gist](https://gist.github.com/tcurdt/9275523) where we want to iterate on the maven and ant interfaces.
+
+
 ## Related projects
 
 Some links to other cross platform tools to package Linux applications:
-
+* [apt-repo](https://github.com/theoweiss/apt-repo)
 * [ant-deb-task](http://code.google.com/p/ant-deb-task)
 * [jRPM](http://jrpm.sourceforge.net)
-* [RedLine](http://www.freecompany.org/redline)
 * [Install-Toolkit](http://install-toolkit.sourceforge.net)
diff --git a/docs/ant.md b/docs/ant.md
index a27cb87..b07a69c 100644
--- a/docs/ant.md
+++ b/docs/ant.md
@@ -166,3 +166,19 @@ Then you can sign your changes file with:
 <b>Security Note</b>: Hard coding the passphrase in the `<deb>` task can be a serious
 security hole. Consider using variable substitution and asking the passphrase
 to the user with the `<input>` task, or retrieving it from a secured `.properties` file.
+
+## Conffiles file
+
+If you package up a directory as Debian package you can also add the affected files to
+the conffiles file. You only have to set the `conffile` attribute to `true`.
+
+```xml
+    <target name="package">
+      <taskdef name="deb" classname="org.vafer.jdeb.ant.DebAntTask"/>
+      <deb destfile="jdeb.deb" control="${deb}/control">
+        <data src="src/main/resources/deb/data" type="directory" conffile="true">
+          <exclude name="**/.svn"/>
+        </data>
+      </deb>
+    </target>
+```
diff --git a/docs/maven.md b/docs/maven.md
index 83e825c..87b4f6c 100644
--- a/docs/maven.md
+++ b/docs/maven.md
@@ -9,7 +9,7 @@ the plugin to your POM like this
       <plugin>
         <artifactId>jdeb</artifactId>
         <groupId>org.vafer</groupId>
-        <version>1.0</version>
+	<version>1.3</version>
         <executions>
           <execution>
             <phase>package</phase>
@@ -35,11 +35,48 @@ the plugin to your POM like this
   </build>
 ```
 
+Or if you want to build a custom deb file
+
+```xml
+  <!-- Indicate it's a deb package which will automatically execute jdeb goal -->
+  <packaging>deb</packaging>
+
+  <build>
+    <extensions>
+      <!-- Add support for the "deb" packaging -->
+      <extension>
+        <groupId>org.vafer</groupId>
+        <artifactId>jdeb</artifactId>
+	<version>1.3</version>
+      </extension>
+    </extensions>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <artifactId>jdeb</artifactId>
+          <groupId>org.vafer</groupId>
+          <!-- Customize deb package -->
+          <configuration>
+            <dataSet>
+              <data>
+                <src>${project.build.directory}/preparedfiles</src>
+                <type>directory</type>
+                <mapper>
+                  <type>perm</type>
+                  <prefix>/usr/lib/xwiki/myproject</prefix>
+                </mapper>
+              </data>
+            </dataSet>
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+```
+
 At least the one main control file is required to control the creation of the
 debian package. This required control file should be found in the control
-directory. By default the control file name is also `control` which gives a
-path of `src/deb/control/control` by default. This control file contains the
-metadata about the Debian package. Usually it will look something along the lines of
+directory (inside the data dir). By default the control file name is also `control` which gives a path of `src/deb/control/control` by default. This control file contains the metadata about the Debian package. Usually it will look something along the lines of
 
     Package: [[name]]
     Version: [[version]]
@@ -51,6 +88,7 @@ metadata about the Debian package. Usually it will look something along the line
     Description: jetty java servlet container
     Distribution: development
 
+but check out the [exmample](https://github.com/tcurdt/jdeb/tree/master/src/examples/maven) to get a better overview.
 
 If the environment variables `DEBEMAIL` and `DEBFULLNAME` are both set this
 will overrule the `Maintainer` field set in there. The `Installed-Size` will
@@ -86,13 +124,21 @@ changesIn     | The changes to add
 changesOut    | The changes file generated                                                   | No
 changesSave   | (NYI) The merged changes file                                                | No
 compression   | (NYI) Compression method for the data file (`gzip`, `bzip2`, `xz` or `none`) | No; defaults to `gzip`
-keyring       | (NYI) The file containing the PGP keys                                       | No
-key           | (NYI) The name of the key to be used in the keyring                          | No
-passphrase    | (NYI) The passphrase to use the key                                          | No
+signPackage   | If the debian package should be signed                                       | No
+signMethod    | Which utility is used for verification (`dpkg-sig`, `debsig-verify`)         | No; defaults to `debsig-verify`
+signRole      | Determines the filename of the signature, debsig only verifies `origin`      | No; defaults to `origin`
+signCfgPrefix | Prefix for when reading keyring, key and passphrase from settings.xml        | No; defaults to `jdeb.`
+keyring       | The file containing the PGP keys                                             | No
+key           | The name of the key to be used in the keyring                                | No
+passphrase    | The passphrase to use the key                                                | No
 attach        | Attach artifact to project                                                   | No; defaults to `true`
-submodules    | Execute the goal on all sub-modules                                          | No; defaults to `true`
-timestamped   | Turn SNAPSHOT into timestamps                                                | No; defaults to `false`
+snapshotExpand| Expand SNAPSHOT into the content of an environment variable or timestamp.    | No; defaults to `false`
+snapshotEnv   | Name of the environment variable. If it's empty defaults to a timestamp.     | No; defaults to `SNAPSHOT`
 verbose       | Verbose logging                                                              | No; defaults to `true`, will be `false` in the future
+skip          | Indicates if an execution should be skipped                                  | No; defaults to `false`
+skipSubmodules| Skip goal on all submodules                                                  | No; defaults to `false`
+skipPOMs      | Skip goal on POM artifacts                                                   | No; defaults to `true`
+
 
 If you use the `dataSet` element, you'll need to populate it with a one or
 more `data` elements. A `data` element is used to specify a directory, a
@@ -106,10 +152,12 @@ src              | The directory, tarball, file to include in the package
 dst              | New filename at destination (type must be `file`)                            | No
 linkName         | The path of the link (type must be `link`)                                   | Yes for link
 linkTarget       | The target of the link (type must be `link`)                                 | Yes for link
-type             | Type of the data source. (archive, directory, file, link or template)        | No; but will be Yes in the future
+symlink          | Indicate if the link is a symblolic link (type must be `link`)               | No; defaults to `true`
+type             | Type of the data source. (archive, directory, file, files, link or template) | No; but will be Yes in the future
 missingSrc       | Fail if src file/folder is missing (ignore or fail)                          | No; defaults to `fail`
 includes         | A comma seperated list of files to include from the directory or tarball     | No; defaults to all files
 excludes         | A comma seperated list of files to exclude from the directory or tarball     | No; defaults to no exclutions
+conffile         | A boolean value to define if the files should be included in the conffiles   | No; defaults to `false`
 mapper           | The files to exclude from the directory or tarball                           | No
 paths/(path..)   | One or more string literal paths that will created in the package            | No; Yes for type `template`
 
@@ -123,12 +171,12 @@ uid           | Numerical uid                                         | No; defa
 gid           | Numerical gid                                         | No; defaults to 0
 user          | User name                                             | No; defaults to "root"
 group         | User group                                            | No; defaults to "root"
-filemode      | File permissions as octet                             | No; deftauls to 644
+filemode      | File permissions as octet                             | No; defaults to 644
 dirmode       | Dir permissions as octet                              | No; defaults to 755
 strip         | Strip n path components from the original file        | No; defaults to 0
 
 Below is an example of how you could configure your jdeb maven plugin to
-include a directory, a tarball, and a file in your deb package:
+include a directory, a tarball, and a file in your deb package and then sign it with the key 8306FE21 in /home/user/.gnupg/secring.gpg:
 
 ```xml
   <build>
@@ -136,7 +184,7 @@ include a directory, a tarball, and a file in your deb package:
       <plugin>
         <artifactId>jdeb</artifactId>
         <groupId>org.vafer</groupId>
-        <version>1.0</version>
+	<version>1.3</version>
         <executions>
           <execution>
             <phase>package</phase>
@@ -144,6 +192,12 @@ include a directory, a tarball, and a file in your deb package:
               <goal>jdeb</goal>
             </goals>
             <configuration>
+              <signPackage>true</signPackage>
+              <signMethod>dpkg-sig</signMethod>
+              <signRole>builder</signRole>
+              <keyring>/home/user/.gnupg/secring.gpg</keyring>
+              <key>8306FE21</key>
+              <passphrase>abcdef</passphrase>
 
               <dataSet>
 
@@ -183,6 +237,16 @@ include a directory, a tarball, and a file in your deb package:
                   <missingSrc>ignore</missingSrc>
                 </data>
 
+                <!-- Multiple files example -->
+                <data>
+                  <type>files</type>
+                  <paths>
+                    <path>README.txt</path>
+                    <path>CHANGES.txt</path>
+                  </paths>
+                  <dst>/var/lib/${artifactId}</dst>
+                </data>
+
                 <!-- Template example -->
                 <data>
                   <type>template</type>
@@ -209,6 +273,19 @@ include a directory, a tarball, and a file in your deb package:
                   <linkTarget>/a/sym/link/to/the/scr/file</linkTarget>
                   <symlink>true</symlink>
                 </data>
+
+                <!-- Conffiles example -->
+                <data>
+                  <src>${project.build.directory}/data</src>
+                  <type>directory</type>
+                  <includes/>
+                  <excludes>**/.svn</excludes>
+                  <conffile>true</conffile>
+                  <mapper>
+                    <type>ls</type>
+                    <src>mapping.txt</src>
+                  </mapper>
+                </data>
               </dataSet>
 
             </configuration>
@@ -218,3 +295,23 @@ include a directory, a tarball, and a file in your deb package:
     </plugins>
   </build>
 ```
+If you don't want to store your key information in the POM you can store this is your settings.xml, here's an example settings.xml:
+
+```xml
+  <settings>
+    <profiles>
+      <profile>
+        <id>jdeb-signing</id>
+        <properties>
+          <jdeb.keyring>/home/user/.gnupg/secring.gpg</jdeb.keyring>
+          <jdeb.key>8306FE21</jdeb.key>
+          <jdeb.passphrase>abcdef</jdeb.passphrase>
+        </properties>
+      </profile>
+    </profiles>
+    <activeProfiles>
+      <activeProfile>jdeb-signing</activeProfile>
+    </activeProfiles>
+  </settings>
+```
+keyring, key and passphrase can then be omitted from the POM entirely.
diff --git a/pom.xml b/pom.xml
index d6d4991..1731c8d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
     <artifactId>jdeb</artifactId>
     <packaging>maven-plugin</packaging>
     <name>jdeb</name>
-    <version>1.0.2-SNAPSHOT</version>
+    <version>1.3</version>
     <description>
         This library provides an Ant task and a Maven plugin to create Debian packages from Java builds in a truly cross
         platform manner. Build your Debian packages on any platform that has Java support. Windows, Linux, OS X - it doesn't
@@ -34,16 +34,25 @@
         </developer>
     </developers>
     <contributors>
+        <contributor><name>Alexander Horz</name></contributor>
+        <contributor><name>Ben McCann</name></contributor>
         <contributor><name>Bryan Sant</name></contributor>
+        <contributor><name>Christian Egli</name></contributor>
         <contributor><name>Christian Rigdon</name></contributor>
+        <contributor><name>David Sauer</name></contributor>
         <contributor><name>Elliot West</name></contributor>
         <contributor><name>Jeroen Rosenberg</name></contributor>
+        <contributor><name>Lukas Roedl</name></contributor>
         <contributor><name>Manuel Woelker</name></contributor>
+        <contributor><name>Marco Soeima</name></contributor>
+        <contributor><name>Max Garmash</name></contributor>
+        <contributor><name>Nepomuk Seiler</name></contributor>
         <contributor><name>Patrick Schultz</name></contributor>
         <contributor><name>Petr Kozelka</name></contributor>
-        <contributor><name>Scott Kuehn</name></contributor>
         <contributor><name>Ralph van Etten</name></contributor>
-        <contributor><name>Alexander Horz</name></contributor>
+        <contributor><name>Roman Kashitsyn</name></contributor>
+        <contributor><name>Scott Kuehn</name></contributor>
+        <contributor><name>Sergio Fernández</name></contributor>
     </contributors>
     <licenses>
         <license>
@@ -62,13 +71,35 @@
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <maven.compiler.source>1.6</maven.compiler.source>
         <maven.compiler.target>1.6</maven.compiler.target>
+        <mavenVersion>3.2.2</mavenVersion>
+        <mavenPluginPluginVersion>3.3</mavenPluginPluginVersion>
     </properties>
     <build>
         <pluginManagement>
             <plugins>
                 <plugin>
                     <artifactId>maven-site-plugin</artifactId>
-                    <version>3.1</version>
+                    <version>3.4</version>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-plugin-plugin</artifactId>
+                    <version>${mavenPluginPluginVersion}</version>
+                    <configuration>
+                        <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
+                        <extractors>
+                            <extractor>java-annotations</extractor>
+                        </extractors>
+                    </configuration>
+                    <executions>
+                        <execution>
+                            <id>mojo-descriptor</id>
+                            <phase>process-classes</phase>
+                            <goals>
+                                <goal>descriptor</goal>
+                            </goals>
+                        </execution>
+                    </executions>
                 </plugin>
             </plugins>
         </pluginManagement>
@@ -76,7 +107,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
-                <version>2.5.1</version>
+                <version>3.1</version>
                 <configuration>
                     <source>${maven.compiler.source}</source>
                     <target>${maven.compiler.target}</target>
@@ -86,7 +117,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-shade-plugin</artifactId>
-                <version>1.7.1</version>
+                <version>2.3</version>
                 <executions>
                     <execution>
                         <phase>package</phase>
@@ -96,10 +127,22 @@
                         <configuration>
                             <!-- <dependencyReducedPomLocation>${basedir}/target/dependency-reduced-pom.xml</dependencyReducedPomLocation> -->
                             <minimizeJar>true</minimizeJar>
+                            <filters>
+                                <filter>
+                                    <artifact>*:*</artifact>
+                                    <excludes>
+                                        <exclude>META-INF/*.SF</exclude>
+                                        <exclude>META-INF/*.DSA</exclude>
+                                        <exclude>META-INF/*.RSA</exclude>
+                                    </excludes>
+                                </filter>
+                            </filters>
                             <artifactSet>
                                 <includes>
                                     <include>org.apache.commons:commons-compress</include>
                                     <include>commons-io:commons-io</include>
+                                    <include>org.bouncycastle:bcpg-jdk15on</include>
+                                    <include>org.bouncycastle:bcprov-jdk15on</include>
                                 </includes>
                             </artifactSet>
                             <relocations>
@@ -107,6 +150,10 @@
                                     <pattern>org.apache.commons</pattern>
                                     <shadedPattern>org.vafer.jdeb.shaded.compress</shadedPattern>
                                 </relocation>
+                                <relocation>
+                                    <pattern>org.bouncycastle</pattern>
+                                    <shadedPattern>org.vafer.jdeb.shaded.bc</shadedPattern>
+                                </relocation>
                             </relocations>
                         </configuration>
                     </execution>
@@ -115,7 +162,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <version>2.11</version>
+                <version>2.17</version>
                 <configuration>
                     <forkMode>never</forkMode>
                     <includes>
@@ -131,27 +178,23 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-invoker-plugin</artifactId>
-                <version>1.6</version>
+                <version>1.9</version>
                 <configuration>
-                    <!-- <debug>true</debug> -->
-                    <projectsDirectory>src/it</projectsDirectory>
                     <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
-                    <preBuildHookScript>setup</preBuildHookScript>
-                    <postBuildHookScript>verify</postBuildHookScript>
                     <localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath>
                     <settingsFile>src/it/settings.xml</settingsFile>
-                    <pomIncludes>
-                        <pomInclude>*/pom.xml</pomInclude>
-                    </pomIncludes>
+                    <goals>
+                        <goal>clean</goal>
+                        <goal>package</goal>
+                    </goals>
+                    <debug>true</debug>
                 </configuration>
                 <executions>
                     <execution>
+                        <id>integration-test</id>
                         <goals>
-                          <goal>integration-test</goal>
-                            <!--
-                            <goal>verify</goal>
-                            <goal>install</goal>
-                            -->
+                          <goal>install</goal>
+                          <goal>run</goal>
                         </goals>
                     </execution>
                 </executions>
@@ -162,49 +205,40 @@
         <!-- mvn versions:display-dependency-updates -->
         <!-- mvn versions:display-plugin-updates -->
         <dependency>
-        <groupId>commons-io</groupId>
-        <artifactId>commons-io</artifactId>
-        <version>2.4</version>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.4</version>
         </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-compress</artifactId>
-            <version>1.5</version>
+            <version>1.7</version>
         </dependency>
         <dependency>
             <groupId>org.apache.maven</groupId>
             <artifactId>maven-core</artifactId>
-            <version>2.2.1</version>
+            <version>${mavenVersion}</version>
         </dependency>
         <dependency>
             <groupId>org.apache.maven</groupId>
             <artifactId>maven-plugin-api</artifactId>
-            <version>2.2.1</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.maven</groupId>
-            <artifactId>maven-project</artifactId>
-            <version>2.2.1</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.maven</groupId>
-            <artifactId>maven-artifact</artifactId>
-            <version>2.2.1</version>
+            <version>${mavenVersion}</version>
         </dependency>
         <dependency>
-            <groupId>org.codehaus.plexus</groupId>
-            <artifactId>plexus-utils</artifactId>
-            <version>1.4.1</version>
+            <groupId>org.apache.maven.plugin-tools</groupId>
+            <artifactId>maven-plugin-annotations</artifactId>
+            <version>${mavenPluginPluginVersion}</version>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.ant</groupId>
             <artifactId>ant</artifactId>
-            <version>1.7.1</version>
+            <version>1.9.3</version>
         </dependency>
         <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcpg-jdk15on</artifactId>
-            <version>1.48</version>
+            <version>1.51</version>
         </dependency>
         <dependency>
             <groupId>junit</groupId>
@@ -212,6 +246,12 @@
             <version>3.8.2</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <reporting>
         <excludeDefaults>true</excludeDefaults>
@@ -250,17 +290,17 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-report-plugin</artifactId>
-                <version>2.12</version>
+                <version>2.17</version>
             </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-pmd-plugin</artifactId>
-                <version>2.7.1</version>
+                <version>3.1</version>
             </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jxr-plugin</artifactId>
-                <version>2.3</version>
+                <version>2.4</version>
             </plugin>
             <plugin>
                 <groupId>org.codehaus.mojo</groupId>
diff --git a/src/examples/maven/pom.xml b/src/examples/maven/pom.xml
index 9a61bc5..fc543cf 100644
--- a/src/examples/maven/pom.xml
+++ b/src/examples/maven/pom.xml
@@ -3,7 +3,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.vafer</groupId>
     <artifactId>jdeb-example</artifactId>
-    <version>1.0</version>
+    <version>1.0-SNAPSHOT</version>
     <description>description from pom</description>
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -14,7 +14,7 @@
             <plugin>
                 <artifactId>jdeb</artifactId>
                 <groupId>org.vafer</groupId>
-                <version>1.0</version>
+		<version>1.3</version>
                 <executions>
                     <execution>
                         <phase>package</phase>
@@ -23,6 +23,10 @@
                         </goals>
                         <configuration>
                             <verbose>true</verbose>
+                            <snapshotExpand>true</snapshotExpand>
+                            <!-- expand "SNAPSHOT" to what is in the "USER" env variable -->
+                            <snapshotEnv>USER</snapshotEnv>
+                            <verbose>true</verbose>
                             <controlDir>${basedir}/src/deb/control</controlDir>
                             <dataSet>
 
diff --git a/src/it/extension/pom.xml b/src/it/extension/pom.xml
new file mode 100644
index 0000000..1bbf950
--- /dev/null
+++ b/src/it/extension/pom.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.vafer</groupId>
+  <artifactId>jdeb-it</artifactId>
+  <version>1.0</version>
+  <packaging>deb</packaging>
+  <description>description from pom</description>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+  </properties>
+  <build>
+    <extensions>
+      <!-- Add support for the "deb" packaging -->
+      <extension>
+        <groupId>org.vafer</groupId>
+        <artifactId>jdeb</artifactId>
+        <version>@project.version@</version>
+      </extension>
+    </extensions>
+  </build>
+</project>
diff --git a/src/it/extension/src/deb/control/control b/src/it/extension/src/deb/control/control
new file mode 100644
index 0000000..6cfaa51
--- /dev/null
+++ b/src/it/extension/src/deb/control/control
@@ -0,0 +1,7 @@
+Package: [[name]]
+Version: [[version]]
+Section: misc
+Priority: low
+Architecture: all
+Description: [[description]]
+Maintainer: tcurdt at vafer.org
diff --git a/src/it/extension/src/main/java/org/vafer/jdeb/examples/Main.java b/src/it/extension/src/main/java/org/vafer/jdeb/examples/Main.java
new file mode 100644
index 0000000..62a2434
--- /dev/null
+++ b/src/it/extension/src/main/java/org/vafer/jdeb/examples/Main.java
@@ -0,0 +1,7 @@
+package org.vafer.jdeb.examples;
+
+public class Main {
+    public static void main(String[] args) {
+        System.out.println("jdeb example!");
+    }    
+}
diff --git a/src/it/extension/verify.groovy b/src/it/extension/verify.groovy
new file mode 100644
index 0000000..d2980bc
--- /dev/null
+++ b/src/it/extension/verify.groovy
@@ -0,0 +1 @@
+assert new File( basedir, 'target/jdeb-it_1.0_all.deb' ).exists();
diff --git a/src/it/pom-only/pom.xml b/src/it/pom-only/pom.xml
index 6a0cc16..3be21ed 100644
--- a/src/it/pom-only/pom.xml
+++ b/src/it/pom-only/pom.xml
@@ -24,6 +24,7 @@
                         </goals>
                         <configuration>
                             <verbose>true</verbose>
+                            <skipPOMs>false</skipPOMs>
                             <controlDir>${basedir}/src/deb/control</controlDir>
                         </configuration>
                     </execution>
diff --git a/src/main/java/org/vafer/jdeb/ChangesFileBuilder.java b/src/main/java/org/vafer/jdeb/ChangesFileBuilder.java
index 5da3367..390fae8 100644
--- a/src/main/java/org/vafer/jdeb/ChangesFileBuilder.java
+++ b/src/main/java/org/vafer/jdeb/ChangesFileBuilder.java
@@ -1,80 +1,80 @@
-/*
- * Copyright 2013 The Apache Software Foundation.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.vafer.jdeb;
-
-import java.io.File;
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Date;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.output.NullOutputStream;
-import org.vafer.jdeb.changes.ChangesProvider;
-import org.vafer.jdeb.debian.BinaryPackageControlFile;
-import org.vafer.jdeb.debian.ChangesFile;
-import org.vafer.jdeb.utils.InformationOutputStream;
-
-/**
- * Builds the Debian changes file.
- */
-class ChangesFileBuilder {
-
-    public ChangesFile createChanges(BinaryPackageControlFile packageControlFile, File binaryPackage, ChangesProvider changesProvider) throws IOException, PackagingException {
-
-        ChangesFile changesFile = new ChangesFile();
-        changesFile.setChanges(changesProvider.getChangesSets());
-        changesFile.initialize(packageControlFile);
-
-        changesFile.set("Date", ChangesFile.DATE_FORMAT.format(new Date()));
-        
-        try {
-            // compute the checksums of the binary package
-            InformationOutputStream md5output = new InformationOutputStream(new NullOutputStream(), MessageDigest.getInstance("MD5"));
-            InformationOutputStream sha1output = new InformationOutputStream(md5output, MessageDigest.getInstance("SHA1"));
-            InformationOutputStream sha256output = new InformationOutputStream(sha1output, MessageDigest.getInstance("SHA-256"));
-            
-            FileUtils.copyFile(binaryPackage, sha256output);
-            
-            // Checksums-Sha1:
-            //  56ef4c6249dc3567fd2967f809c42d1f9b61adf7 45964 jdeb.deb
-            changesFile.set("Checksums-Sha1", sha1output.getHexDigest() + " " + binaryPackage.length() + " " + binaryPackage.getName());
-            
-            // Checksums-Sha256:
-            //  38c6fa274eb9299a69b739bcbdbd05c7ffd1d8d6472f4245ed732a25c0e5d616 45964 jdeb.deb
-            changesFile.set("Checksums-Sha256", sha256output.getHexDigest() + " " + binaryPackage.length() + " " + binaryPackage.getName());
-            
-            StringBuilder files = new StringBuilder(md5output.getHexDigest());
-            files.append(' ').append(binaryPackage.length());
-            files.append(' ').append(packageControlFile.get("Section"));
-            files.append(' ').append(packageControlFile.get("Priority"));
-            files.append(' ').append(binaryPackage.getName());
-            changesFile.set("Files", files.toString());
-            
-        } catch (NoSuchAlgorithmException e) {
-            throw new PackagingException("Unable to compute the checksums for " + binaryPackage, e);
-        }
-        
-        if (!changesFile.isValid()) {
-            throw new PackagingException("Changes file fields are invalid " + changesFile.invalidFields() +
-                ". The following fields are mandatory: " + changesFile.getMandatoryFields() +
-                ". Please check your pom.xml/build.xml and your control file.");
-        }
-        
-        return changesFile;
-    }
-}
+/*
+ * Copyright 2014 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.vafer.jdeb;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Date;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.output.NullOutputStream;
+import org.vafer.jdeb.changes.ChangesProvider;
+import org.vafer.jdeb.debian.BinaryPackageControlFile;
+import org.vafer.jdeb.debian.ChangesFile;
+import org.vafer.jdeb.utils.InformationOutputStream;
+
+/**
+ * Builds the Debian changes file.
+ */
+class ChangesFileBuilder {
+
+    public ChangesFile createChanges(BinaryPackageControlFile packageControlFile, File binaryPackage, ChangesProvider changesProvider) throws IOException, PackagingException {
+
+        ChangesFile changesFile = new ChangesFile();
+        changesFile.setChanges(changesProvider.getChangesSets());
+        changesFile.initialize(packageControlFile);
+
+        changesFile.set("Date", ChangesFile.formatDate(new Date()));
+        
+        try {
+            // compute the checksums of the binary package
+            InformationOutputStream md5output = new InformationOutputStream(new NullOutputStream(), MessageDigest.getInstance("MD5"));
+            InformationOutputStream sha1output = new InformationOutputStream(md5output, MessageDigest.getInstance("SHA1"));
+            InformationOutputStream sha256output = new InformationOutputStream(sha1output, MessageDigest.getInstance("SHA-256"));
+            
+            FileUtils.copyFile(binaryPackage, sha256output);
+            
+            // Checksums-Sha1:
+            //  56ef4c6249dc3567fd2967f809c42d1f9b61adf7 45964 jdeb.deb
+            changesFile.set("Checksums-Sha1", sha1output.getHexDigest() + " " + binaryPackage.length() + " " + binaryPackage.getName());
+            
+            // Checksums-Sha256:
+            //  38c6fa274eb9299a69b739bcbdbd05c7ffd1d8d6472f4245ed732a25c0e5d616 45964 jdeb.deb
+            changesFile.set("Checksums-Sha256", sha256output.getHexDigest() + " " + binaryPackage.length() + " " + binaryPackage.getName());
+            
+            StringBuilder files = new StringBuilder(md5output.getHexDigest());
+            files.append(' ').append(binaryPackage.length());
+            files.append(' ').append(packageControlFile.get("Section"));
+            files.append(' ').append(packageControlFile.get("Priority"));
+            files.append(' ').append(binaryPackage.getName());
+            changesFile.set("Files", files.toString());
+            
+        } catch (NoSuchAlgorithmException e) {
+            throw new PackagingException("Unable to compute the checksums for " + binaryPackage, e);
+        }
+        
+        if (!changesFile.isValid()) {
+            throw new PackagingException("Changes file fields are invalid " + changesFile.invalidFields() +
+                ". The following fields are mandatory: " + changesFile.getMandatoryFields() +
+                ". Please check your pom.xml/build.xml and your control file.");
+        }
+        
+        return changesFile;
+    }
+}
diff --git a/src/main/java/org/vafer/jdeb/Compression.java b/src/main/java/org/vafer/jdeb/Compression.java
index 5ab1a6c..1d1c0e3 100644
--- a/src/main/java/org/vafer/jdeb/Compression.java
+++ b/src/main/java/org/vafer/jdeb/Compression.java
@@ -1,82 +1,82 @@
-/*
- * Copyright 2013 The jdeb developers.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.vafer.jdeb;
-
-import java.io.OutputStream;
-
-import org.apache.commons.compress.compressors.CompressorException;
-import org.apache.commons.compress.compressors.CompressorStreamFactory;
-
-/**
- * Compression method used for the data file.
- * 
- * @author Emmanuel Bourg
- */
-public enum Compression {
-
-    NONE(""),
-    GZIP(".gz"),
-    BZIP2(".bz2"),
-    XZ(".xz");
-
-    private String extension;
-
-    private Compression(String extension) {
-        this.extension = extension;
-    }
-
-    /**
-     * Returns the extension of the compression method
-     */
-    public String getExtension() {
-        return extension;
-    }
-
-    public OutputStream toCompressedOutputStream(OutputStream out) throws CompressorException {
-        switch (this) {
-            case GZIP:
-                return new CompressorStreamFactory().createCompressorOutputStream("gz", out);
-            case BZIP2:
-                return new CompressorStreamFactory().createCompressorOutputStream("bzip2", out);
-            case XZ:
-                return new CompressorStreamFactory().createCompressorOutputStream("xz", out);
-            default:
-                return out;
-        }
-    }
-
-    /**
-     * Returns the compression method corresponding to the specified name.
-     * The matching is case insensitive.
-     * 
-     * @param name the name of the compression method
-     * @return the compression method, or null if not recognized
-     */
-    public static Compression toEnum(String name) {
-        if ("gzip".equalsIgnoreCase(name) || "gz".equalsIgnoreCase(name)) {
-            return GZIP;
-        } else if ("bzip2".equalsIgnoreCase(name) || "bz2".equalsIgnoreCase(name)) {
-            return BZIP2;
-        } else if ("xz".equalsIgnoreCase(name)) {
-            return XZ;
-        } else if ("none".equalsIgnoreCase(name)) {
-            return NONE;
-        } else {
-            return null;
-        }
-    }
-}
+/*
+ * Copyright 2014 The jdeb developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.vafer.jdeb;
+
+import java.io.OutputStream;
+
+import org.apache.commons.compress.compressors.CompressorException;
+import org.apache.commons.compress.compressors.CompressorStreamFactory;
+
+/**
+ * Compression method used for the data file.
+ * 
+ * @author Emmanuel Bourg
+ */
+public enum Compression {
+
+    NONE(""),
+    GZIP(".gz"),
+    BZIP2(".bz2"),
+    XZ(".xz");
+
+    private String extension;
+
+    private Compression(String extension) {
+        this.extension = extension;
+    }
+
+    /**
+     * Returns the extension of the compression method
+     */
+    public String getExtension() {
+        return extension;
+    }
+
+    public OutputStream toCompressedOutputStream(OutputStream out) throws CompressorException {
+        switch (this) {
+            case GZIP:
+                return new CompressorStreamFactory().createCompressorOutputStream("gz", out);
+            case BZIP2:
+                return new CompressorStreamFactory().createCompressorOutputStream("bzip2", out);
+            case XZ:
+                return new CompressorStreamFactory().createCompressorOutputStream("xz", out);
+            default:
+                return out;
+        }
+    }
+
+    /**
+     * Returns the compression method corresponding to the specified name.
+     * The matching is case insensitive.
+     * 
+     * @param name the name of the compression method
+     * @return the compression method, or null if not recognized
+     */
+    public static Compression toEnum(String name) {
+        if ("gzip".equalsIgnoreCase(name) || "gz".equalsIgnoreCase(name)) {
+            return GZIP;
+        } else if ("bzip2".equalsIgnoreCase(name) || "bz2".equalsIgnoreCase(name)) {
+            return BZIP2;
+        } else if ("xz".equalsIgnoreCase(name)) {
+            return XZ;
+        } else if ("none".equalsIgnoreCase(name)) {
+            return NONE;
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/org/vafer/jdeb/Console.java b/src/main/java/org/vafer/jdeb/Console.java
index b0b21cf..2e18553 100644
--- a/src/main/java/org/vafer/jdeb/Console.java
+++ b/src/main/java/org/vafer/jdeb/Console.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.vafer.jdeb;
 
 /**
@@ -22,8 +23,10 @@ package org.vafer.jdeb;
  */
 public interface Console {
 
-    void info( String s );
+    void debug( String message );
+
+    void info( String message );
 
-    void warn( String s );
+    void warn( String message );
 
 }
diff --git a/src/main/java/org/vafer/jdeb/ControlBuilder.java b/src/main/java/org/vafer/jdeb/ControlBuilder.java
index fdb4e7e..8fa58ec 100644
--- a/src/main/java/org/vafer/jdeb/ControlBuilder.java
+++ b/src/main/java/org/vafer/jdeb/ControlBuilder.java
@@ -1,208 +1,243 @@
-/*
- * Copyright 2013 The jdeb developers.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.vafer.jdeb;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.math.BigInteger;
-import java.text.ParseException;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.zip.GZIPOutputStream;
-
-import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
-import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.io.output.NullOutputStream;
-import org.apache.tools.ant.DirectoryScanner;
-import org.vafer.jdeb.debian.BinaryPackageControlFile;
-import org.vafer.jdeb.mapping.PermMapper;
-import org.vafer.jdeb.utils.FilteredFile;
-import org.vafer.jdeb.utils.InformationInputStream;
-import org.vafer.jdeb.utils.Utils;
-import org.vafer.jdeb.utils.VariableResolver;
-
-/**
- * Builds the control archive of the Debian package.
- */
-class ControlBuilder {
-    
-    /** The name of the package maintainer scripts */
-    private static final Set<String> MAINTAINER_SCRIPTS = new HashSet<String>(Arrays.asList("preinst", "postinst", "prerm", "postrm", "config"));
-
-    /** The name of the other control files subject to token substitution */
-    private static final Set<String> CONFIGURATION_FILENAMES = new HashSet<String>(Arrays.asList("conffiles", "templates", "triggers"));
-
-    private Console console;
-    private VariableResolver resolver;
-
-    ControlBuilder(Console console, VariableResolver resolver) {
-        this.console = console;
-        this.resolver = resolver;
-    }
-
-    /**
-     * Build control archive of the deb
-     *
-     * @param packageControlFile the package control file
-     * @param controlFiles the other control information files (maintainer scripts, etc)
-     * @param dataSize  the size of the installed package
-     * @param checksums the md5 checksums of the files in the data archive
-     * @param output
-     * @return
-     * @throws java.io.FileNotFoundException
-     * @throws java.io.IOException
-     * @throws java.text.ParseException
-     */
-    void buildControl(BinaryPackageControlFile packageControlFile, File[] controlFiles, StringBuilder checksums, File output) throws IOException, ParseException {
-        final File dir = output.getParentFile();
-        if (dir != null && (!dir.exists() || !dir.isDirectory())) {
-            throw new IOException("Cannot write control file at '" + output.getAbsolutePath() + "'");
-        }
-
-        final TarArchiveOutputStream outputStream = new TarArchiveOutputStream(new GZIPOutputStream(new FileOutputStream(output)));
-        outputStream.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
-        
-        // create the final package control file out of the "control" file, copy all other files, ignore the directories
-        for (File file : controlFiles) {
-            if (file.isDirectory()) {
-                // warn about the misplaced directory, except for directories ignored by default (.svn, cvs, etc)
-                if (!isDefaultExcludes(file)) {
-                    console.info("Found directory '" + file + "' in the control directory. Maybe you are pointing to wrong dir?");
-                }
-                continue;
-            }
-
-            if (CONFIGURATION_FILENAMES.contains(file.getName()) || MAINTAINER_SCRIPTS.contains(file.getName())) {
-                FilteredFile configurationFile = new FilteredFile(new FileInputStream(file), resolver);
-                addControlEntry(file.getName(), configurationFile.toString(), outputStream);
-
-            } else if (!"control".equals(file.getName())) {
-                // initialize the information stream to guess the type of the file
-                InformationInputStream infoStream = new InformationInputStream(new FileInputStream(file));
-                Utils.copy(infoStream, NullOutputStream.NULL_OUTPUT_STREAM);
-                infoStream.close();
-
-                // fix line endings for shell scripts
-                InputStream in = new FileInputStream(file);
-                if (infoStream.isShell() && !infoStream.hasUnixLineEndings()) {
-                    byte[] buf = Utils.toUnixLineEndings(in);
-                    in = new ByteArrayInputStream(buf);
-                }
-                
-                addControlEntry(file.getName(), IOUtils.toString(in), outputStream);
-                
-                in.close();
-            }
-        }
-
-        if (packageControlFile == null) {
-            throw new FileNotFoundException("No 'control' file found in " + Arrays.toString(controlFiles));
-        }
-        
-        addControlEntry("control", packageControlFile.toString(), outputStream);
-        addControlEntry("md5sums", checksums.toString(), outputStream);
-
-        outputStream.close();
-    }
-    
-    
-    /**
-     * Creates a package control file from the specified file and adds the
-     * <tt>Date</tt>, <tt>Distribution</tt> and <tt>Urgency</tt> fields if missing.
-     * The <tt>Installed-Size</tt> field is also initialized to the actual size of
-     * the package. The <tt>Maintainer</tt> field is overridden by the <tt>DEBEMAIL</tt>
-     * and <tt>DEBFULLNAME</tt> environment variables if defined.
-     * 
-     * @param file       the control file
-     * @param pDataSize  the size of the installed package
-     */
-    public BinaryPackageControlFile createPackageControlFile(File file, BigInteger pDataSize) throws IOException, ParseException {
-        FilteredFile controlFile = new FilteredFile(new FileInputStream(file), resolver);
-        BinaryPackageControlFile packageControlFile = new BinaryPackageControlFile(controlFile.toString());
-        
-        if (packageControlFile.get("Distribution") == null) {
-            packageControlFile.set("Distribution", "unknown");
-        }
-
-        if (packageControlFile.get("Urgency") == null) {
-            packageControlFile.set("Urgency", "low");
-        }
-
-        packageControlFile.set("Installed-Size", pDataSize.divide(BigInteger.valueOf(1024)).toString());
-
-        // override the Version if the DEBVERSION environment variable is defined
-        final String debVersion = System.getenv("DEBVERSION");
-        if (debVersion != null) {
-            packageControlFile.set("Version", debVersion);
-            console.info("Using version'" + debVersion + "' from the environment variables.");
-        }
-
-
-        // override the Maintainer field if the DEBFULLNAME and DEBEMAIL environment variables are defined
-        final String debFullName = System.getenv("DEBFULLNAME");
-        final String debEmail = System.getenv("DEBEMAIL");
-
-        if (debFullName != null && debEmail != null) {
-            final String maintainer = debFullName + " <" + debEmail + ">";
-            packageControlFile.set("Maintainer", maintainer);
-            console.info("Using maintainer '" + maintainer + "' from the environment variables.");
-        }
-        
-        return packageControlFile;
-    }
-
-
-    private static void addControlEntry(final String pName, final String pContent, final TarArchiveOutputStream pOutput) throws IOException {
-        final byte[] data = pContent.getBytes("UTF-8");
-
-        final TarArchiveEntry entry = new TarArchiveEntry("./" + pName, true);
-        entry.setSize(data.length);
-        entry.setNames("root", "root");
-        
-        if (MAINTAINER_SCRIPTS.contains(pName)) {
-            entry.setMode(PermMapper.toMode("755"));
-        } else {
-            entry.setMode(PermMapper.toMode("644"));
-        }
-        
-        pOutput.putArchiveEntry(entry);
-        pOutput.write(data);
-        pOutput.closeArchiveEntry();
-    }
-    
-    /**
-     * Tells if the specified directory is ignored by default (.svn, cvs, etc)
-     * 
-     * @param directory
-     */
-    private boolean isDefaultExcludes(File directory) {
-        for (String pattern : DirectoryScanner.getDefaultExcludes()) {
-            if (DirectoryScanner.match(pattern, directory.getAbsolutePath().replace("\\", "/"))) {
-                return true;
-            }
-        }
-        
-        return false;
-    }
-}
+/*
+ * Copyright 2014 The jdeb developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.vafer.jdeb;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.GZIPOutputStream;
+
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.output.NullOutputStream;
+import org.apache.tools.ant.DirectoryScanner;
+import org.vafer.jdeb.debian.BinaryPackageControlFile;
+import org.vafer.jdeb.mapping.PermMapper;
+import org.vafer.jdeb.utils.FilteredFile;
+import org.vafer.jdeb.utils.InformationInputStream;
+import org.vafer.jdeb.utils.Utils;
+import org.vafer.jdeb.utils.VariableResolver;
+
+/**
+ * Builds the control archive of the Debian package.
+ */
+class ControlBuilder {
+    
+    /** The name of the package maintainer scripts */
+    private static final Set<String> MAINTAINER_SCRIPTS = new HashSet<String>(Arrays.asList("preinst", "postinst", "prerm", "postrm", "config"));
+
+    /** The name of the other control files subject to token substitution */
+    private static final Set<String> CONFIGURATION_FILENAMES = new HashSet<String>(Arrays.asList("conffiles", "templates", "triggers"));
+
+    private Console console;
+    private VariableResolver resolver;
+    private final String openReplaceToken;
+    private final String closeReplaceToken;
+
+    ControlBuilder(Console console, VariableResolver resolver, String openReplaceToken, String closeReplaceToken) {
+        this.console = console;
+        this.resolver = resolver;
+        this.openReplaceToken = openReplaceToken;
+        this.closeReplaceToken = closeReplaceToken;
+    }
+
+    /**
+     * Build control archive of the deb
+     *
+     * @param packageControlFile the package control file
+     * @param controlFiles the other control information files (maintainer scripts, etc)
+     * @param dataSize  the size of the installed package
+     * @param checksums the md5 checksums of the files in the data archive
+     * @param output
+     * @return
+     * @throws java.io.FileNotFoundException
+     * @throws java.io.IOException
+     * @throws java.text.ParseException
+     */
+    void buildControl(BinaryPackageControlFile packageControlFile, File[] controlFiles, List<String> conffiles, StringBuilder checksums, File output) throws IOException, ParseException {
+        final File dir = output.getParentFile();
+        if (dir != null && (!dir.exists() || !dir.isDirectory())) {
+            throw new IOException("Cannot write control file at '" + output.getAbsolutePath() + "'");
+        }
+
+        final TarArchiveOutputStream outputStream = new TarArchiveOutputStream(new GZIPOutputStream(new FileOutputStream(output)));
+        outputStream.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
+        
+        boolean foundConffiles = false;
+        
+        // create the final package control file out of the "control" file, copy all other files, ignore the directories
+        for (File file : controlFiles) {
+            if (file.isDirectory()) {
+                // warn about the misplaced directory, except for directories ignored by default (.svn, cvs, etc)
+                if (!isDefaultExcludes(file)) {
+                    console.warn("Found directory '" + file + "' in the control directory. Maybe you are pointing to wrong dir?");
+                }
+                continue;
+            }
+
+            if ("conffiles".equals(file.getName())) {
+                foundConffiles = true;
+            }
+            
+            if (CONFIGURATION_FILENAMES.contains(file.getName()) || MAINTAINER_SCRIPTS.contains(file.getName())) {
+                FilteredFile configurationFile = new FilteredFile(new FileInputStream(file), resolver);
+                configurationFile.setOpenToken(openReplaceToken);
+                configurationFile.setCloseToken(closeReplaceToken);
+                addControlEntry(file.getName(), configurationFile.toString(), outputStream);
+
+            } else if (!"control".equals(file.getName())) {
+                // initialize the information stream to guess the type of the file
+                InformationInputStream infoStream = new InformationInputStream(new FileInputStream(file));
+                Utils.copy(infoStream, NullOutputStream.NULL_OUTPUT_STREAM);
+                infoStream.close();
+
+                // fix line endings for shell scripts
+                InputStream in = new FileInputStream(file);
+                if (infoStream.isShell() && !infoStream.hasUnixLineEndings()) {
+                    byte[] buf = Utils.toUnixLineEndings(in);
+                    in = new ByteArrayInputStream(buf);
+                }
+                
+                addControlEntry(file.getName(), IOUtils.toString(in), outputStream);
+                
+                in.close();
+            }
+        }
+        
+        if ((conffiles != null) && (conffiles.size() > 0)) {
+            if (foundConffiles) {
+                console.info("Found file 'conffiles' in the control directory. Skipping conffiles generation.");
+            } else {
+                addControlEntry("conffiles", createPackageConffilesFile(conffiles), outputStream);
+            }
+        } else if ((conffiles != null) && (conffiles.size() == 0)) {
+            console.info("Skipping 'conffiles' generation. No entries provided.");
+        }
+
+        if (packageControlFile == null) {
+            throw new FileNotFoundException("No 'control' file found in " + controlFiles.toString());
+        }
+        
+        addControlEntry("control", packageControlFile.toString(), outputStream);
+        addControlEntry("md5sums", checksums.toString(), outputStream);
+
+        outputStream.close();
+    }
+    
+    private String createPackageConffilesFile(final List<String> conffiles) {
+        StringBuilder content = new StringBuilder();
+        
+        if (conffiles != null && !conffiles.isEmpty()) {
+            for (String nextFileName : conffiles) {
+                content.append(nextFileName).append("\n");
+            }
+        }
+
+        return content.toString();
+    }
+    
+    
+    /**
+     * Creates a package control file from the specified file and adds the
+     * <tt>Date</tt>, <tt>Distribution</tt> and <tt>Urgency</tt> fields if missing.
+     * The <tt>Installed-Size</tt> field is also initialized to the actual size of
+     * the package. The <tt>Maintainer</tt> field is overridden by the <tt>DEBEMAIL</tt>
+     * and <tt>DEBFULLNAME</tt> environment variables if defined.
+     * 
+     * @param file       the control file
+     * @param pDataSize  the size of the installed package
+     */
+    public BinaryPackageControlFile createPackageControlFile(File file, BigInteger pDataSize) throws IOException, ParseException {
+        FilteredFile controlFile = new FilteredFile(new FileInputStream(file), resolver);
+        BinaryPackageControlFile packageControlFile = new BinaryPackageControlFile(controlFile.toString());
+        
+        if (packageControlFile.get("Distribution") == null) {
+            packageControlFile.set("Distribution", "unknown");
+        }
+
+        if (packageControlFile.get("Urgency") == null) {
+            packageControlFile.set("Urgency", "low");
+        }
+
+        packageControlFile.set("Installed-Size", pDataSize.divide(BigInteger.valueOf(1024)).toString());
+
+        // override the Version if the DEBVERSION environment variable is defined
+        final String debVersion = System.getenv("DEBVERSION");
+        if (debVersion != null) {
+            packageControlFile.set("Version", debVersion);
+            console.debug("Using version'" + debVersion + "' from the environment variables.");
+        }
+
+
+        // override the Maintainer field if the DEBFULLNAME and DEBEMAIL environment variables are defined
+        final String debFullName = System.getenv("DEBFULLNAME");
+        final String debEmail = System.getenv("DEBEMAIL");
+
+        if (debFullName != null && debEmail != null) {
+            final String maintainer = debFullName + " <" + debEmail + ">";
+            packageControlFile.set("Maintainer", maintainer);
+            console.debug("Using maintainer '" + maintainer + "' from the environment variables.");
+        }
+        
+        return packageControlFile;
+    }
+
+
+    private static void addControlEntry(final String pName, final String pContent, final TarArchiveOutputStream pOutput) throws IOException {
+        final byte[] data = pContent.getBytes("UTF-8");
+
+        final TarArchiveEntry entry = new TarArchiveEntry("./" + pName, true);
+        entry.setSize(data.length);
+        entry.setNames("root", "root");
+        
+        if (MAINTAINER_SCRIPTS.contains(pName)) {
+            entry.setMode(PermMapper.toMode("755"));
+        } else {
+            entry.setMode(PermMapper.toMode("644"));
+        }
+        
+        pOutput.putArchiveEntry(entry);
+        pOutput.write(data);
+        pOutput.closeArchiveEntry();
+    }
+    
+    /**
+     * Tells if the specified directory is ignored by default (.svn, cvs, etc)
+     * 
+     * @param directory
+     */
+    private boolean isDefaultExcludes(File directory) {
+        for (String pattern : DirectoryScanner.getDefaultExcludes()) {
+            if (DirectoryScanner.match(pattern, directory.getAbsolutePath().replace("\\", "/"))) {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+}
diff --git a/src/main/java/org/vafer/jdeb/DataBuilder.java b/src/main/java/org/vafer/jdeb/DataBuilder.java
index 4768fe4..0acef78 100644
--- a/src/main/java/org/vafer/jdeb/DataBuilder.java
+++ b/src/main/java/org/vafer/jdeb/DataBuilder.java
@@ -1,307 +1,307 @@
-/*
- * Copyright 2013 The jdeb developers.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.vafer.jdeb;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.math.BigInteger;
-import java.nio.ByteBuffer;
-import java.security.DigestOutputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
-import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
-import org.apache.commons.compress.archivers.tar.TarConstants;
-import org.apache.commons.compress.archivers.zip.ZipEncoding;
-import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
-import org.apache.commons.compress.compressors.CompressorException;
-import org.vafer.jdeb.utils.Utils;
-
-/**
- * Builds the data archive of the Debian package.
- */
-class DataBuilder {
-
-    private Console console;
-    
-    private ZipEncoding encoding;
-    
-    private static final class Total {
-        private BigInteger count = BigInteger.valueOf(0);
-
-        public void add( long size ) {
-            count = count.add(BigInteger.valueOf(size));
-        }
-
-        public String toString() {
-            return "" + count;
-        }
-    }
-
-    DataBuilder(Console console) {
-        this.console = console;
-        this.encoding = ZipEncodingHelper.getZipEncoding(null);
-    }
-
-    private void checkField(String name, int length) throws IOException {
-        if (name != null) {
-            ByteBuffer b = encoding.encode(name);
-            if (b.limit() > length) {
-                throw new IllegalArgumentException("Field '" + name + "' too long, maximum is " + length);
-            }
-        }
-    }
-
-    /**
-     * Build the data archive of the deb from the provided DataProducers
-     *
-     * @param producers
-     * @param output
-     * @param checksums
-     * @param compression the compression method used for the data file
-     * @return
-     * @throws java.security.NoSuchAlgorithmException
-     * @throws java.io.IOException
-     * @throws org.apache.commons.compress.compressors.CompressorException
-     */
-    BigInteger buildData(Collection<DataProducer> producers, File output, final StringBuilder checksums, Compression compression) throws NoSuchAlgorithmException, IOException, CompressorException {
-
-        final File dir = output.getParentFile();
-        if (dir != null && (!dir.exists() || !dir.isDirectory())) {
-            throw new IOException("Cannot write data file at '" + output.getAbsolutePath() + "'");
-        }
-
-        final TarArchiveOutputStream tarOutputStream = new TarArchiveOutputStream(compression.toCompressedOutputStream(new FileOutputStream(output)));
-        tarOutputStream.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
-
-        final MessageDigest digest = MessageDigest.getInstance("MD5");
-
-        final Total dataSize = new Total();
-
-        final List<String> addedDirectories = new ArrayList<String>();
-        final DataConsumer receiver = new DataConsumer() {
-            public void onEachDir( String dirname, String linkname, String user, int uid, String group, int gid, int mode, long size ) throws IOException {
-                // Check link name
-                checkField(linkname, TarConstants.NAMELEN);
-                // Check user name
-                checkField(user, TarConstants.UNAMELEN);
-                // Check group name
-                checkField(group, TarConstants.GNAMELEN);
-
-                dirname = fixPath(dirname);
-
-                createParentDirectories(dirname, user, uid, group, gid);
-
-                // The directory passed in explicitly by the caller also gets the passed-in mode.  (Unlike
-                // the parent directories for now.  See related comments at "int mode =" in
-                // createParentDirectories, including about a possible bug.)
-                createDirectory(dirname, user, uid, group, gid, mode, 0);
-
-                console.info("dir: " + dirname);
-            }
-
-            public void onEachFile( InputStream inputStream, String filename, String linkname, String user, int uid, String group, int gid, int mode, long size ) throws IOException {
-                // Check link name
-                checkField(linkname, TarConstants.NAMELEN);
-                // Check user name
-                checkField(user, TarConstants.UNAMELEN);
-                // Check group name
-                checkField(group, TarConstants.GNAMELEN);
-
-                filename = fixPath(filename);
-
-                createParentDirectories(filename, user, uid, group, gid);
-
-                final TarArchiveEntry entry = new TarArchiveEntry(filename, true);
-
-                entry.setUserName(user);
-                entry.setUserId(uid);
-                entry.setGroupName(group);
-                entry.setGroupId(gid);
-                entry.setMode(mode);
-                entry.setSize(size);
-
-                tarOutputStream.putArchiveEntry(entry);
-
-                dataSize.add(size);
-                digest.reset();
-
-                Utils.copy(inputStream, new DigestOutputStream(tarOutputStream, digest));
-
-                final String md5 = Utils.toHex(digest.digest());
-
-                tarOutputStream.closeArchiveEntry();
-
-                console.info(
-                    "file:" + entry.getName() +
-                        " size:" + entry.getSize() +
-                        " mode:" + entry.getMode() +
-                        " linkname:" + entry.getLinkName() +
-                        " username:" + entry.getUserName() +
-                        " userid:" + entry.getUserId() +
-                        " groupname:" + entry.getGroupName() +
-                        " groupid:" + entry.getGroupId() +
-                        " modtime:" + entry.getModTime() +
-                        " md5: " + md5
-                );
-
-                // append to file md5 list
-                checksums.append(md5).append(" ").append(entry.getName()).append('\n');
-            }
-
-            public void onEachLink(String path, String linkname, boolean symlink, String user, int uid, String group, int gid, int mode) throws IOException {
-                // Check link name
-                checkField(linkname, TarConstants.NAMELEN);
-                // Check user name
-                checkField(user, TarConstants.UNAMELEN);
-                // Check group name
-                checkField(group, TarConstants.GNAMELEN);
-
-                path = fixPath(path);
-
-                createParentDirectories(path, user, uid, group, gid);
-
-                final TarArchiveEntry entry = new TarArchiveEntry(path, symlink ? TarArchiveEntry.LF_SYMLINK : TarArchiveEntry.LF_LINK);
-                entry.setLinkName(linkname);
-
-                entry.setUserName(user);
-                entry.setUserId(uid);
-                entry.setGroupName(group);
-                entry.setGroupId(gid);
-                entry.setMode(mode);
-
-                tarOutputStream.putArchiveEntry(entry);
-                tarOutputStream.closeArchiveEntry();
-
-                console.info(
-                    "link:" + entry.getName() +
-                    " mode:" + entry.getMode() +
-                    " linkname:" + entry.getLinkName() +
-                    " username:" + entry.getUserName() +
-                    " userid:" + entry.getUserId() +
-                    " groupname:" + entry.getGroupName() +
-                    " groupid:" + entry.getGroupId()
-                 );
-            }
-            
-
-            private void createDirectory( String directory, String user, int uid, String group, int gid, int mode, long size ) throws IOException {
-                // All dirs should end with "/" when created, or the test DebAndTaskTestCase.testTarFileSet() thinks its a file
-                // and so thinks it has the wrong permission.
-                // This consistency also helps when checking if a directory already exists in addedDirectories.
-        
-                if (!directory.endsWith("/")) {
-                    directory += "/";
-                }
-        
-                if (!addedDirectories.contains(directory)) {
-                    TarArchiveEntry entry = new TarArchiveEntry(directory, true);
-                    entry.setUserName(user);
-                    entry.setUserId(uid);
-                    entry.setGroupName(group);
-                    entry.setGroupId(gid);
-                    entry.setMode(mode);
-                    entry.setSize(size);
-        
-                    tarOutputStream.putArchiveEntry(entry);
-                    tarOutputStream.closeArchiveEntry();
-                    addedDirectories.add(directory); // so addedDirectories consistently have "/" for finding duplicates.
-                }
-            }
-        
-            private void createParentDirectories( String filename, String user, int uid, String group, int gid ) throws IOException {
-                String dirname = fixPath(new File(filename).getParent());
-                
-                // Debian packages must have parent directories created
-                // before sub-directories or files can be installed.
-                // For example, if an entry of ./usr/lib/foo/bar existed
-                // in a .deb package, but the ./usr/lib/foo directory didn't
-                // exist, the package installation would fail.  The .deb must
-                // then have an entry for ./usr/lib/foo and then ./usr/lib/foo/bar
-        
-                if (dirname == null) {
-                    return;
-                }
-        
-                // The loop below will create entries for all parent directories
-                // to ensure that .deb packages will install correctly.
-                String[] pathParts = dirname.split("/");
-                String parentDir = "./";
-                for (int i = 1; i < pathParts.length; i++) {
-                    parentDir += pathParts[i] + "/";
-                    // Make it so the dirs can be traversed by users.
-                    // We could instead try something more granular, like setting the directory
-                    // permission to 'rx' for each of the 3 user/group/other read permissions
-                    // found on the file being added (ie, only if "other" has read
-                    // permission on the main node, then add o+rx permission on all the containing
-                    // directories, same w/ user & group), and then also we'd have to
-                    // check the parentDirs collection of those already added to
-                    // see if those permissions need to be similarly updated.  (Note, it hasn't
-                    // been demonstrated, but there might be a bug if a user specifically
-                    // requests a directory with certain permissions,
-                    // that has already been auto-created because it was a parent, and if so, go set
-                    // the user-requested mode on that directory instead of this automatic one.)
-                    // But for now, keeping it simple by making every dir a+rx.   Examples are:
-                    // drw-r----- fs/fs   # what you get with setMode(mode)
-                    // drwxr-xr-x fs/fs   # Usable. Too loose?
-                    int mode = TarArchiveEntry.DEFAULT_DIR_MODE;
-        
-                    createDirectory(parentDir, user, uid, group, gid, mode, 0);
-                }
-            }
-        };
-
-        try {
-            for (DataProducer data : producers) {
-                data.produce(receiver);
-            }
-        } finally {
-            tarOutputStream.close();
-        }
-
-        console.info("Total size: " + dataSize);
-
-        return dataSize.count;
-    }
-
-    private String fixPath( String path ) {
-        if (path == null || path.equals(".")) {
-            return path;
-        }
-        
-        // If we're receiving directory names from Windows, then we'll convert to use slash
-        // This does eliminate the ability to use of a backslash in a directory name on *NIX,
-        // but in practice, this is a non-issue
-        if (path.contains("\\")) {
-            path = path.replace('\\', '/');
-        }
-        // ensure the path is like : ./foo/bar
-        if (path.startsWith("/")) {
-            path = "." + path;
-        } else if (!path.startsWith("./")) {
-            path = "./" + path;
-        }
-        return path;
-    }
-
-}
+/*
+ * Copyright 2014 The jdeb developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.vafer.jdeb;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
+import org.apache.commons.compress.archivers.tar.TarConstants;
+import org.apache.commons.compress.archivers.zip.ZipEncoding;
+import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
+import org.apache.commons.compress.compressors.CompressorException;
+import org.vafer.jdeb.utils.Utils;
+
+/**
+ * Builds the data archive of the Debian package.
+ */
+class DataBuilder {
+
+    private Console console;
+    
+    private ZipEncoding encoding;
+    
+    private static final class Total {
+        private BigInteger count = BigInteger.valueOf(0);
+
+        public void add( long size ) {
+            count = count.add(BigInteger.valueOf(size));
+        }
+
+        public String toString() {
+            return "" + count;
+        }
+    }
+
+    DataBuilder(Console console) {
+        this.console = console;
+        this.encoding = ZipEncodingHelper.getZipEncoding(null);
+    }
+
+    private void checkField(String name, int length) throws IOException {
+        if (name != null) {
+            ByteBuffer b = encoding.encode(name);
+            if (b.limit() > length) {
+                throw new IllegalArgumentException("Field '" + name + "' too long, maximum is " + length);
+            }
+        }
+    }
+
+    /**
+     * Build the data archive of the deb from the provided DataProducers
+     *
+     * @param producers
+     * @param output
+     * @param checksums
+     * @param compression the compression method used for the data file
+     * @return
+     * @throws java.security.NoSuchAlgorithmException
+     * @throws java.io.IOException
+     * @throws org.apache.commons.compress.compressors.CompressorException
+     */
+    BigInteger buildData(Collection<DataProducer> producers, File output, final StringBuilder checksums, Compression compression) throws NoSuchAlgorithmException, IOException, CompressorException {
+
+        final File dir = output.getParentFile();
+        if (dir != null && (!dir.exists() || !dir.isDirectory())) {
+            throw new IOException("Cannot write data file at '" + output.getAbsolutePath() + "'");
+        }
+
+        final TarArchiveOutputStream tarOutputStream = new TarArchiveOutputStream(compression.toCompressedOutputStream(new FileOutputStream(output)));
+        tarOutputStream.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
+
+        final MessageDigest digest = MessageDigest.getInstance("MD5");
+
+        final Total dataSize = new Total();
+
+        final List<String> addedDirectories = new ArrayList<String>();
+        final DataConsumer receiver = new DataConsumer() {
+            public void onEachDir( String dirname, String linkname, String user, int uid, String group, int gid, int mode, long size ) throws IOException {
+                // Check link name
+                checkField(linkname, TarConstants.NAMELEN);
+                // Check user name
+                checkField(user, TarConstants.UNAMELEN);
+                // Check group name
+                checkField(group, TarConstants.GNAMELEN);
+
+                dirname = fixPath(dirname);
+
+                createParentDirectories(dirname, user, uid, group, gid);
+
+                // The directory passed in explicitly by the caller also gets the passed-in mode.  (Unlike
+                // the parent directories for now.  See related comments at "int mode =" in
+                // createParentDirectories, including about a possible bug.)
+                createDirectory(dirname, user, uid, group, gid, mode, 0);
+
+                console.debug("dir: " + dirname);
+            }
+            
+            public void onEachFile( InputStream inputStream, String filename, String linkname, String user, int uid, String group, int gid, int mode, long size ) throws IOException {
+                // Check link name
+                checkField(linkname, TarConstants.NAMELEN);
+                // Check user name
+                checkField(user, TarConstants.UNAMELEN);
+                // Check group name
+                checkField(group, TarConstants.GNAMELEN);
+
+                filename = fixPath(filename);
+
+                createParentDirectories(filename, user, uid, group, gid);
+
+                final TarArchiveEntry entry = new TarArchiveEntry(filename, true);
+
+                entry.setUserName(user);
+                entry.setUserId(uid);
+                entry.setGroupName(group);
+                entry.setGroupId(gid);
+                entry.setMode(mode);
+                entry.setSize(size);
+
+                tarOutputStream.putArchiveEntry(entry);
+
+                dataSize.add(size);
+                digest.reset();
+
+                Utils.copy(inputStream, new DigestOutputStream(tarOutputStream, digest));
+
+                final String md5 = Utils.toHex(digest.digest());
+
+                tarOutputStream.closeArchiveEntry();
+
+                console.debug(
+                    "file:" + entry.getName() +
+                        " size:" + entry.getSize() +
+                        " mode:" + entry.getMode() +
+                        " linkname:" + entry.getLinkName() +
+                        " username:" + entry.getUserName() +
+                        " userid:" + entry.getUserId() +
+                        " groupname:" + entry.getGroupName() +
+                        " groupid:" + entry.getGroupId() +
+                        " modtime:" + entry.getModTime() +
+                        " md5: " + md5
+                );
+
+                // append to file md5 list, two spaces to be compatible with GNU coreutils md5sum
+                checksums.append(md5).append("  ").append(entry.getName()).append('\n');
+            }
+
+            public void onEachLink(String path, String linkname, boolean symlink, String user, int uid, String group, int gid, int mode) throws IOException {
+                // Check link name
+                checkField(linkname, TarConstants.NAMELEN);
+                // Check user name
+                checkField(user, TarConstants.UNAMELEN);
+                // Check group name
+                checkField(group, TarConstants.GNAMELEN);
+
+                path = fixPath(path);
+
+                createParentDirectories(path, user, uid, group, gid);
+
+                final TarArchiveEntry entry = new TarArchiveEntry(path, symlink ? TarArchiveEntry.LF_SYMLINK : TarArchiveEntry.LF_LINK);
+                entry.setLinkName(linkname);
+
+                entry.setUserName(user);
+                entry.setUserId(uid);
+                entry.setGroupName(group);
+                entry.setGroupId(gid);
+                entry.setMode(mode);
+
+                tarOutputStream.putArchiveEntry(entry);
+                tarOutputStream.closeArchiveEntry();
+
+                console.debug(
+                    "link:" + entry.getName() +
+                    " mode:" + entry.getMode() +
+                    " linkname:" + entry.getLinkName() +
+                    " username:" + entry.getUserName() +
+                    " userid:" + entry.getUserId() +
+                    " groupname:" + entry.getGroupName() +
+                    " groupid:" + entry.getGroupId()
+                 );
+            }
+            
+
+            private void createDirectory( String directory, String user, int uid, String group, int gid, int mode, long size ) throws IOException {
+                // All dirs should end with "/" when created, or the test DebAndTaskTestCase.testTarFileSet() thinks its a file
+                // and so thinks it has the wrong permission.
+                // This consistency also helps when checking if a directory already exists in addedDirectories.
+        
+                if (!directory.endsWith("/")) {
+                    directory += "/";
+                }
+        
+                if (!addedDirectories.contains(directory)) {
+                    TarArchiveEntry entry = new TarArchiveEntry(directory, true);
+                    entry.setUserName(user);
+                    entry.setUserId(uid);
+                    entry.setGroupName(group);
+                    entry.setGroupId(gid);
+                    entry.setMode(mode);
+                    entry.setSize(size);
+        
+                    tarOutputStream.putArchiveEntry(entry);
+                    tarOutputStream.closeArchiveEntry();
+                    addedDirectories.add(directory); // so addedDirectories consistently have "/" for finding duplicates.
+                }
+            }
+        
+            private void createParentDirectories( String filename, String user, int uid, String group, int gid ) throws IOException {
+                String dirname = fixPath(new File(filename).getParent());
+                
+                // Debian packages must have parent directories created
+                // before sub-directories or files can be installed.
+                // For example, if an entry of ./usr/lib/foo/bar existed
+                // in a .deb package, but the ./usr/lib/foo directory didn't
+                // exist, the package installation would fail.  The .deb must
+                // then have an entry for ./usr/lib/foo and then ./usr/lib/foo/bar
+        
+                if (dirname == null) {
+                    return;
+                }
+        
+                // The loop below will create entries for all parent directories
+                // to ensure that .deb packages will install correctly.
+                String[] pathParts = dirname.split("/");
+                String parentDir = "./";
+                for (int i = 1; i < pathParts.length; i++) {
+                    parentDir += pathParts[i] + "/";
+                    // Make it so the dirs can be traversed by users.
+                    // We could instead try something more granular, like setting the directory
+                    // permission to 'rx' for each of the 3 user/group/other read permissions
+                    // found on the file being added (ie, only if "other" has read
+                    // permission on the main node, then add o+rx permission on all the containing
+                    // directories, same w/ user & group), and then also we'd have to
+                    // check the parentDirs collection of those already added to
+                    // see if those permissions need to be similarly updated.  (Note, it hasn't
+                    // been demonstrated, but there might be a bug if a user specifically
+                    // requests a directory with certain permissions,
+                    // that has already been auto-created because it was a parent, and if so, go set
+                    // the user-requested mode on that directory instead of this automatic one.)
+                    // But for now, keeping it simple by making every dir a+rx.   Examples are:
+                    // drw-r----- fs/fs   # what you get with setMode(mode)
+                    // drwxr-xr-x fs/fs   # Usable. Too loose?
+                    int mode = TarArchiveEntry.DEFAULT_DIR_MODE;
+        
+                    createDirectory(parentDir, user, uid, group, gid, mode, 0);
+                }
+            }
+        };
+
+        try {
+            for (DataProducer data : producers) {
+                data.produce(receiver);
+            }
+        } finally {
+            tarOutputStream.close();
+        }
+
+        console.debug("Total size: " + dataSize);
+
+        return dataSize.count;
+    }
+
+    private String fixPath( String path ) {
+        if (path == null || path.equals(".")) {
+            return path;
+        }
+        
+        // If we're receiving directory names from Windows, then we'll convert to use slash
+        // This does eliminate the ability to use of a backslash in a directory name on *NIX,
+        // but in practice, this is a non-issue
+        if (path.contains("\\")) {
+            path = path.replace('\\', '/');
+        }
+        // ensure the path is like : ./foo/bar
+        if (path.startsWith("/")) {
+            path = "." + path;
+        } else if (!path.startsWith("./")) {
+            path = "./" + path;
+        }
+        return path;
+    }
+
+}
diff --git a/src/main/java/org/vafer/jdeb/DataConsumer.java b/src/main/java/org/vafer/jdeb/DataConsumer.java
index d108888..3bcdfa0 100644
--- a/src/main/java/org/vafer/jdeb/DataConsumer.java
+++ b/src/main/java/org/vafer/jdeb/DataConsumer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@ public interface DataConsumer {
     void onEachDir( String dirname, String linkname, String user, int uid, String group, int gid, int mode, long size ) throws IOException;
 
     void onEachFile( InputStream input, String filename, String linkname, String user, int uid, String group, int gid, int mode, long size ) throws IOException;
-
+    
     void onEachLink( String path, String linkName, boolean symlink, String user, int uid, String group, int gid, int mode) throws IOException;
 
 }
diff --git a/src/main/java/org/vafer/jdeb/DataProducer.java b/src/main/java/org/vafer/jdeb/DataProducer.java
index 4218bf2..de85c5c 100644
--- a/src/main/java/org/vafer/jdeb/DataProducer.java
+++ b/src/main/java/org/vafer/jdeb/DataProducer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/vafer/jdeb/DebMaker.java b/src/main/java/org/vafer/jdeb/DebMaker.java
index 1254dc4..3cd5c23 100644
--- a/src/main/java/org/vafer/jdeb/DebMaker.java
+++ b/src/main/java/org/vafer/jdeb/DebMaker.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,25 +16,41 @@
 
 package org.vafer.jdeb;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Security;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Date;
+import java.util.List;
+import java.util.Locale;
 
 import org.apache.commons.compress.archivers.ar.ArArchiveEntry;
 import org.apache.commons.compress.archivers.ar.ArArchiveOutputStream;
+import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
+import org.bouncycastle.crypto.digests.MD5Digest;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
+import org.bouncycastle.util.encoders.Hex;
 import org.vafer.jdeb.changes.ChangeSet;
 import org.vafer.jdeb.changes.ChangesProvider;
 import org.vafer.jdeb.changes.TextfileChangesProvider;
 import org.vafer.jdeb.debian.BinaryPackageControlFile;
 import org.vafer.jdeb.debian.ChangesFile;
 import org.vafer.jdeb.signing.PGPSigner;
+import org.vafer.jdeb.utils.PGPSignatureOutputStream;
 import org.vafer.jdeb.utils.Utils;
 import org.vafer.jdeb.utils.VariableResolver;
 
@@ -92,14 +108,34 @@ public class DebMaker {
     /** The compression method used for the data file (none, gzip, bzip2 or xz) */
     private String compression = "gzip";
 
+    /** Whether to sign the package that is created */
+    private boolean signPackage;
+
+    /** Defines which utility is used to verify the signed package */
+    private String signMethod;
+
+    /** Defines the role to sign with */
+    private String signRole;
+
     private VariableResolver variableResolver;
+    private String openReplaceToken;
+    private String closeReplaceToken;
 
     private final Collection<DataProducer> dataProducers = new ArrayList<DataProducer>();
 
+    private final Collection<DataProducer> conffilesProducers = new ArrayList<DataProducer>();
+
 
-    public DebMaker(Console console, Collection<DataProducer> dataProducers) {
+    public DebMaker(Console console, Collection<DataProducer> dataProducers, Collection<DataProducer> conffileProducers) {
         this.console = console;
-        this.dataProducers.addAll(dataProducers);
+        if (dataProducers != null) {
+            this.dataProducers.addAll(dataProducers);
+        }
+        if (conffileProducers != null) {
+            this.conffilesProducers.addAll(conffileProducers);
+        }
+
+        Security.addProvider(new BouncyCastleProvider());
     }
 
     public void setDeb(File deb) {
@@ -142,6 +178,18 @@ public class DebMaker {
         this.changesSave = changes;
     }
 
+    public void setSignPackage(boolean signPackage) {
+        this.signPackage = signPackage;
+    }
+
+    public void setSignMethod(String signMethod) {
+        this.signMethod = signMethod;
+    }
+
+    public void setSignRole(String signRole) {
+        this.signRole = signRole;
+    }
+
     public void setKeyring(File keyring) {
         this.keyring = keyring;
     }
@@ -179,7 +227,7 @@ public class DebMaker {
             if (changesIn.exists() && (!changesIn.isFile() || !changesIn.canRead())) {
                 throw new PackagingException("The 'changesIn' setting needs to point to a readable file. " + changesIn + " was not found/readable.");
             }
-            
+
             if (changesOut != null && !isWritableFile(changesOut)) {
                 throw new PackagingException("Cannot write the output for 'changesOut' to " + changesOut);
             }
@@ -201,10 +249,6 @@ public class DebMaker {
         if (deb == null) {
             throw new PackagingException("You need to specify where the deb file is supposed to be created.");
         }
-        
-        if (dataProducers.size() == 0) {
-            throw new PackagingException("You need to provide at least one reference to a tgz or directory with data.");
-        }
     }
 
     public void makeDeb() throws PackagingException {
@@ -212,12 +256,48 @@ public class DebMaker {
         try {
             console.info("Creating debian package: " + deb);
 
-            packageControlFile = createDeb(Compression.toEnum(compression));
+            // If we should sign the package
+            boolean doSign = signPackage;
+
+            if (doSign) {
+
+                if (keyring == null || !keyring.exists()) {
+                    doSign = false;
+                    console.warn("Signing requested, but no keyring supplied");
+                }
+
+                if (key == null) {
+                    doSign = false;
+                    console.warn("Signing requested, but no key supplied");
+                }
+
+                if (passphrase == null) {
+                    doSign = false;
+                    console.warn("Signing requested, but no passphrase supplied");
+                }
+
+                FileInputStream keyRingInput = new FileInputStream(keyring);
+                PGPSigner signer = null;
+                try {
+                    signer = new PGPSigner(new FileInputStream(keyring), key, passphrase);
+                } finally {
+                    keyRingInput.close();
+                }
+
+                int digest = PGPUtil.SHA1;
+
+                PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(signer.getSecretKey().getPublicKey().getAlgorithm(), digest));
+                signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, signer.getPrivateKey());
+
+                packageControlFile = createSignedDeb(Compression.toEnum(compression), signatureGenerator, signer);
+            } else {
+                packageControlFile = createDeb(Compression.toEnum(compression));
+            }
 
         } catch (Exception e) {
             throw new PackagingException("Failed to create debian package " + deb, e);
         }
-        
+
         makeChangesFiles(packageControlFile);
     }
 
@@ -225,15 +305,15 @@ public class DebMaker {
         if (changesOut == null) {
             changesOut = new File(deb.getParentFile(), deb.getName().replace(".deb", ".changes"));
         }
-        
+
         ChangesProvider changesProvider;
         FileOutputStream out = null;
-        
+
         try {
             console.info("Creating changes file: " + changesOut);
-            
+
             out = new FileOutputStream(changesOut);
-            
+
             if (changesIn != null && changesIn.exists()) {
                 // read the changes form a textfile provider
                 changesProvider = new TextfileChangesProvider(new FileInputStream(changesIn), packageControlFile);
@@ -243,16 +323,21 @@ public class DebMaker {
                     @Override
                     public ChangeSet[] getChangesSets() {
                         return new ChangeSet[] {
-                                new ChangeSet(packageControlFile.get("Package"), packageControlFile.get("Version"), new Date(),
-                                        "stable", "low", packageControlFile.get("Maintainer"), new String[0])
+                                new ChangeSet(packageControlFile.get("Package"),
+                                        packageControlFile.get("Version"),
+                                        new Date(),
+                                        packageControlFile.get("Distribution"),
+                                        packageControlFile.get("Urgency"),
+                                        packageControlFile.get("Maintainer"),
+                                        new String[0])
                         };
                     }
                 };
             }
-            
+
             ChangesFileBuilder builder = new ChangesFileBuilder();
             ChangesFile changesFile = builder.createChanges(packageControlFile, deb, changesProvider);
-            
+
             if (keyring != null && key != null && passphrase != null) {
                 console.info("Signing the changes file with the key " + key);
                 PGPSigner signer = new PGPSigner(new FileInputStream(keyring), key, passphrase);
@@ -267,11 +352,11 @@ public class DebMaker {
         } finally {
             IOUtils.closeQuietly(out);
         }
-        
+
         if (changesSave == null || !(changesProvider instanceof TextfileChangesProvider)) {
             return;
         }
-        
+
         try {
             console.info("Saving changes to file: " + changesSave);
 
@@ -281,7 +366,44 @@ public class DebMaker {
             throw new PackagingException("Failed to save debian changes file " + changesSave, e);
         }
     }
-    
+
+    private List<String> populateConffiles(Collection<DataProducer> producers) {
+        final List<String> result = new ArrayList<String>();
+
+        if (producers == null || producers.isEmpty()) {
+            return result;
+        }
+
+        final DataConsumer receiver = new DataConsumer() {
+            public void onEachDir( String dirname, String linkname, String user, int uid, String group, int gid, int mode, long size ) throws IOException {
+                //
+            }
+
+            public void onEachFile( InputStream inputStream, String filename, String linkname, String user, int uid, String group, int gid, int mode, long size ) throws IOException {
+                String tempConffileItem = filename;
+                if (tempConffileItem.startsWith(".")) {
+                    tempConffileItem = tempConffileItem.substring(1);
+                }
+                console.info("Adding conffile: " + tempConffileItem);
+                result.add(tempConffileItem);
+            }
+
+            public void onEachLink(String path, String linkname, boolean symlink, String user, int uid, String group, int gid, int mode) throws IOException {
+                //
+            }
+        };
+
+        try {
+            for (DataProducer data : producers) {
+                data.produce(receiver);
+            }
+        } catch(Exception e) {
+            //
+        }
+
+        return result;
+    }
+
     /**
      * Create the debian archive with from the provided control files and data producers.
      *
@@ -293,6 +415,18 @@ public class DebMaker {
      * @throws PackagingException
      */
     public BinaryPackageControlFile createDeb(Compression compression) throws PackagingException {
+        return createSignedDeb(compression, null, null);
+    }
+    /**
+     * Create the debian archive with from the provided control files and data producers.
+     *
+     * @param compression   the compression method used for the data file (gzip, bzip2 or anything else for no compression)
+     * @param signatureGenerator   the signature generator
+     *
+     * @return PackageDescriptor
+     * @throws PackagingException
+     */
+    public BinaryPackageControlFile createSignedDeb(Compression compression, final PGPSignatureGenerator signatureGenerator, PGPSigner signer ) throws PackagingException {
         File tempData = null;
         File tempControl = null;
 
@@ -300,13 +434,16 @@ public class DebMaker {
             tempData = File.createTempFile("deb", "data");
             tempControl = File.createTempFile("deb", "control");
 
-            console.info("Building data");
+            console.debug("Building data");
             DataBuilder dataBuilder = new DataBuilder(console);
             StringBuilder md5s = new StringBuilder();
             BigInteger size = dataBuilder.buildData(dataProducers, tempData, md5s, compression);
-            
-            console.info("Building control");
-            ControlBuilder controlBuilder = new ControlBuilder(console, variableResolver);
+
+            console.info("Building conffiles");
+            List<String> tempConffiles = populateConffiles(conffilesProducers);
+
+            console.debug("Building control");
+            ControlBuilder controlBuilder = new ControlBuilder(console, variableResolver, openReplaceToken, closeReplaceToken);
             BinaryPackageControlFile packageControlFile = controlBuilder.createPackageControlFile(new File(control, "control"), size);
             if (packageControlFile.get("Package") == null) {
                 packageControlFile.set("Package", packageName);
@@ -323,26 +460,67 @@ public class DebMaker {
             if (packageControlFile.get("Homepage") == null) {
                 packageControlFile.set("Homepage", homepage);
             }
-            
-            controlBuilder.buildControl(packageControlFile, control.listFiles(), md5s, tempControl);
-            
+
+            controlBuilder.buildControl(packageControlFile, control.listFiles(), tempConffiles , md5s, tempControl);
+
             if (!packageControlFile.isValid()) {
                 throw new PackagingException("Control file fields are invalid " + packageControlFile.invalidFields() +
-                    ". The following fields are mandatory: " + packageControlFile.getMandatoryFields() +
-                    ". Please check your pom.xml/build.xml and your control file.");
+                        ". The following fields are mandatory: " + packageControlFile.getMandatoryFields() +
+                        ". Please check your pom.xml/build.xml and your control file.");
             }
 
             deb.getParentFile().mkdirs();
-            
-            
+
             ArArchiveOutputStream ar = new ArArchiveOutputStream(new FileOutputStream(deb));
-            
-            addTo(ar, "debian-binary", "2.0\n");
-            addTo(ar, "control.tar.gz", tempControl);
-            addTo(ar, "data.tar" + compression.getExtension(), tempData);
+
+            String binaryName = "debian-binary";
+            String binaryContent = "2.0\n";
+            String controlName = "control.tar.gz";
+            String dataName = "data.tar" + compression.getExtension();
+
+            addTo(ar, binaryName, binaryContent);
+            addTo(ar, controlName, tempControl);
+            addTo(ar, dataName, tempData);
+
+            if (signatureGenerator != null) {
+                console.info("Signing package with key " + key);
+
+                if(signRole == null) {
+                    signRole = "origin";
+                }
+
+                // Use debsig-verify as default
+                if(signMethod == null || !"dpkg-sig".equals(signMethod)) {
+                    // Sign file to verify with debsig-verify
+                    PGPSignatureOutputStream sigStream = new PGPSignatureOutputStream(signatureGenerator);
+
+                    addTo(sigStream, binaryContent);
+                    addTo(sigStream, tempControl);
+                    addTo(sigStream, tempData);
+                    addTo(ar, "_gpg" + signRole, sigStream.generateASCIISignature());
+
+                } else {
+
+                    // Sign file to verify with dpkg-sig --verify
+                    final String outputStr =
+                            "Version: 4\n" +
+                                    "Signer: \n" +
+                                    "Date: " + new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy", Locale.ENGLISH).format(new Date()) + "\n" +
+                                    "Role: " + signRole +"\n" +
+                                    "Files: \n" +
+                                    addFile(binaryName, binaryContent) +
+                                    addFile(controlName, tempControl) +
+                                    addFile(dataName, tempData);
+
+                    ByteArrayOutputStream message = new ByteArrayOutputStream();
+                    signer.clearSign(outputStr, message);
+
+                    addTo(ar, "_gpg" + signRole, message.toString());
+                }
+            }
 
             ar.close();
-            
+
             return packageControlFile;
 
         } catch (Exception e) {
@@ -361,6 +539,81 @@ public class DebMaker {
         }
     }
 
+    private String addFile(String name, String input){
+        return addLine(md5Hash(input), sha1Hash(input), input.length(), name);
+    }
+
+    private String addFile(String name, File input){
+        return addLine(md5Hash(input), sha1Hash(input), input.length(), name);
+    }
+
+    private String addLine(String md5, String sha1, long size, String name){
+        return "\t" + md5 + " " + sha1 + " " + size + " " + name + "\n";
+    }
+
+    private String md5Hash(String input){
+        return md5Hash(input.getBytes());
+    }
+
+    private String md5Hash(File input){
+        try {
+            return md5Hash(FileUtils.readFileToByteArray(input));
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    private String md5Hash(byte input[]){
+        //update the input of MD5
+        MD5Digest md5 = new MD5Digest();
+        md5.update(input, 0, input.length);
+
+        //get the output/ digest size and hash it
+        byte[] digest = new byte[md5.getDigestSize()];
+        md5.doFinal(digest, 0);
+
+        return new String(Hex.encode(digest));
+    }
+
+    private String sha1Hash(String input){
+        return sha1Hash(input.getBytes());
+    }
+
+    private String sha1Hash(File input){
+        try {
+            return sha1Hash(FileUtils.readFileToByteArray(input));
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    private String sha1Hash(byte input[]){
+        try
+        {
+            //prepare the input
+            MessageDigest hash = MessageDigest.getInstance("SHA1");
+            hash.update(input);
+
+            //proceed ....
+            byte[] digest = hash.digest();
+
+            return new String(Hex.encode(digest));
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            System.err.println("No such algorithm");
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
     private void addTo(ArArchiveOutputStream pOutput, String pName, String pContent) throws IOException {
         final byte[] content = pContent.getBytes();
         pOutput.putArchiveEntry(new ArArchiveEntry(pName, content.length));
@@ -380,4 +633,26 @@ public class DebMaker {
 
         pOutput.closeArchiveEntry();
     }
+
+    private void addTo(final PGPSignatureOutputStream pOutput, final String pContent) throws IOException {
+        final byte[] content = pContent.getBytes();
+        pOutput.write(content);
+    }
+
+    private void addTo(final PGPSignatureOutputStream pOutput, final File pContent) throws IOException {
+        final InputStream input = new FileInputStream(pContent);
+        try {
+            Utils.copy(input, pOutput);
+        } finally {
+            input.close();
+        }
+    }
+
+    public void setOpenReplaceToken(String openReplaceToken) {
+        this.openReplaceToken = openReplaceToken;
+    }
+
+    public void setCloseReplaceToken(String closeReplaceToken) {
+        this.closeReplaceToken = closeReplaceToken;
+    }
 }
diff --git a/src/main/java/org/vafer/jdeb/PackagingException.java b/src/main/java/org/vafer/jdeb/PackagingException.java
index 5fddfd7..c2eca64 100644
--- a/src/main/java/org/vafer/jdeb/PackagingException.java
+++ b/src/main/java/org/vafer/jdeb/PackagingException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/vafer/jdeb/ant/Data.java b/src/main/java/org/vafer/jdeb/ant/Data.java
index b32872f..f8e5dd9 100644
--- a/src/main/java/org/vafer/jdeb/ant/Data.java
+++ b/src/main/java/org/vafer/jdeb/ant/Data.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -44,6 +44,8 @@ public final class Data extends PatternSet implements DataProducer {
 
     private String type;
 
+    private Boolean conffile;
+
     private String destinationName;
 
     public void setSrc(File src) {
@@ -58,6 +60,14 @@ public final class Data extends PatternSet implements DataProducer {
         this.type = type;
     }
 
+    public void setConffile(Boolean conffile) {
+        this.conffile = conffile;
+    }
+    
+    public Boolean getConffile() {
+        return this.conffile;
+    }
+
     public void setDst(String destinationName) {
         this.destinationName = destinationName;
     }
diff --git a/src/main/java/org/vafer/jdeb/ant/DebAntTask.java b/src/main/java/org/vafer/jdeb/ant/DebAntTask.java
index bf8ea43..7b7a5eb 100644
--- a/src/main/java/org/vafer/jdeb/ant/DebAntTask.java
+++ b/src/main/java/org/vafer/jdeb/ant/DebAntTask.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -72,6 +72,7 @@ public class DebAntTask extends MatchingTask {
     private Collection<Link> links = new ArrayList<Link>();
 
     private Collection<DataProducer> dataProducers = new ArrayList<DataProducer>();
+    private Collection<DataProducer> conffilesProducers = new ArrayList<DataProducer>();
 
 
     public void setDestfile( File deb ) {
@@ -145,12 +146,15 @@ public class DebAntTask extends MatchingTask {
                 } else if (!Arrays.asList("file", "directory", "archive").contains(data.getType().toLowerCase())) {
                     throw new BuildException("The type '" + data.getType() + "' of the data element is unknown (expected 'file', 'directory' or 'archive')");
                 }
+                if (data.getConffile() != null && data.getConffile()) {
+                    conffilesProducers.add(dataProducer);
+                }
             }
         }
         
         Console console = new TaskConsole(this, verbose);
         
-        DebMaker debMaker = new DebMaker(console, dataProducers);
+        DebMaker debMaker = new DebMaker(console, dataProducers, conffilesProducers);
         debMaker.setDeb(deb);
         debMaker.setControl(control);
         debMaker.setChangesIn(changesIn);
diff --git a/src/main/java/org/vafer/jdeb/ant/Link.java b/src/main/java/org/vafer/jdeb/ant/Link.java
index 7a7a928..3cad190 100644
--- a/src/main/java/org/vafer/jdeb/ant/Link.java
+++ b/src/main/java/org/vafer/jdeb/ant/Link.java
@@ -1,109 +1,109 @@
-/*
- * Copyright 2013 The jdeb developers.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.vafer.jdeb.ant;
-
-import org.apache.commons.compress.archivers.zip.UnixStat;
-import org.vafer.jdeb.DataProducer;
-import org.vafer.jdeb.mapping.PermMapper;
-import org.vafer.jdeb.producers.DataProducerLink;
-
-/**
- * Defines a symbolic or hard link.
- * 
- * @author Emmanuel Bourg
- * @version $Revision$, $Date$
- */
-public final class Link {
-
-    private String name;
-    private String target;
-    private boolean symbolic = true;
-    private String username = "root";
-    private String group = "root";
-    private int uid = 0;
-    private int gid = 0;
-    private int mode = UnixStat.LINK_FLAG | UnixStat.DEFAULT_LINK_PERM;
-
-    DataProducer toDataProducer() {
-        org.vafer.jdeb.mapping.Mapper mapper = new PermMapper(uid, gid, username, group, mode, mode, 0, null);
-        return new DataProducerLink(name, target, symbolic, null, null, new org.vafer.jdeb.mapping.Mapper[]{mapper});
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public String getTarget() {
-        return target;
-    }
-
-    public void setTarget(String target) {
-        this.target = target;
-    }
-
-    public boolean isSymbolic() {
-        return symbolic;
-    }
-
-    public void setSymbolic(boolean symbolic) {
-        this.symbolic = symbolic;
-    }
-
-    public String getUsername() {
-        return username;
-    }
-
-    public void setUsername(String username) {
-        this.username = username;
-    }
-
-    public String getGroup() {
-        return group;
-    }
-
-    public void setGroup(String group) {
-        this.group = group;
-    }
-
-    public int getUid() {
-        return uid;
-    }
-
-    public void setUid(int uid) {
-        this.uid = uid;
-    }
-
-    public int getGid() {
-        return gid;
-    }
-
-    public void setGid(int gid) {
-        this.gid = gid;
-    }
-
-    public int getMode() {
-        return mode;
-    }
-
-    public void setMode(String mode) {
-        this.mode = UnixStat.LINK_FLAG | Integer.parseInt(mode, 8);
-    }
-}
+/*
+ * Copyright 2014 The jdeb developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.vafer.jdeb.ant;
+
+import org.apache.commons.compress.archivers.zip.UnixStat;
+import org.vafer.jdeb.DataProducer;
+import org.vafer.jdeb.mapping.PermMapper;
+import org.vafer.jdeb.producers.DataProducerLink;
+
+/**
+ * Defines a symbolic or hard link.
+ * 
+ * @author Emmanuel Bourg
+ * @version $Revision$, $Date$
+ */
+public final class Link {
+
+    private String name;
+    private String target;
+    private boolean symbolic = true;
+    private String username = "root";
+    private String group = "root";
+    private int uid = 0;
+    private int gid = 0;
+    private int mode = UnixStat.LINK_FLAG | UnixStat.DEFAULT_LINK_PERM;
+
+    DataProducer toDataProducer() {
+        org.vafer.jdeb.mapping.Mapper mapper = new PermMapper(uid, gid, username, group, mode, mode, 0, null);
+        return new DataProducerLink(name, target, symbolic, null, null, new org.vafer.jdeb.mapping.Mapper[]{mapper});
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getTarget() {
+        return target;
+    }
+
+    public void setTarget(String target) {
+        this.target = target;
+    }
+
+    public boolean isSymbolic() {
+        return symbolic;
+    }
+
+    public void setSymbolic(boolean symbolic) {
+        this.symbolic = symbolic;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public void setGroup(String group) {
+        this.group = group;
+    }
+
+    public int getUid() {
+        return uid;
+    }
+
+    public void setUid(int uid) {
+        this.uid = uid;
+    }
+
+    public int getGid() {
+        return gid;
+    }
+
+    public void setGid(int gid) {
+        this.gid = gid;
+    }
+
+    public int getMode() {
+        return mode;
+    }
+
+    public void setMode(String mode) {
+        this.mode = UnixStat.LINK_FLAG | Integer.parseInt(mode, 8);
+    }
+}
diff --git a/src/main/java/org/vafer/jdeb/ant/Mapper.java b/src/main/java/org/vafer/jdeb/ant/Mapper.java
index e99dd1b..7c562f5 100644
--- a/src/main/java/org/vafer/jdeb/ant/Mapper.java
+++ b/src/main/java/org/vafer/jdeb/ant/Mapper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -39,8 +39,8 @@ public final class Mapper {
     private int gid = -1;
     private String user;
     private String group;
-    private int fileMode = -1;
-    private int dirMode = -1;
+    private String fileMode;
+    private String dirMode;
 
     public void setType( final String pType ) {
         mapperType = pType;
@@ -76,11 +76,11 @@ public final class Mapper {
         group = pGroup;
     }
 
-    public void setFileMode( final int pFileMode ) {
+    public void setFileMode( final String pFileMode ) {
         fileMode = pFileMode;
     }
 
-    public void setDirMode( int pDirMode ) {
+    public void setDirMode( final String pDirMode ) {
         dirMode = pDirMode;
     }
 
diff --git a/src/main/java/org/vafer/jdeb/ant/TaskConsole.java b/src/main/java/org/vafer/jdeb/ant/TaskConsole.java
index 066455f..46b643d 100644
--- a/src/main/java/org/vafer/jdeb/ant/TaskConsole.java
+++ b/src/main/java/org/vafer/jdeb/ant/TaskConsole.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,11 +16,13 @@
 
 package org.vafer.jdeb.ant;
 
+import org.apache.tools.ant.Project;
 import org.apache.tools.ant.Task;
 import org.vafer.jdeb.Console;
 
 /**
- * Console implementation for Ant tasks.
+ * Console implementation for Ant tasks. debug messages are only displayed
+ * when the <tt>verbose</tt> parameter is true.
  */
 class TaskConsole implements Console {
 
@@ -32,14 +34,21 @@ class TaskConsole implements Console {
         this.verbose = verbose;
     }
 
-    public void info(String message) {
+    @Override
+    public void debug(String message) {
         if (verbose) {
             task.log(message);
         }
     }
 
-    public void warn(String message) {
+    @Override
+    public void info(String message) {
         task.log(message);
     }
 
+    @Override
+    public void warn(String message) {
+        task.log(message, Project.MSG_WARN);
+    }
+
 }
diff --git a/src/main/java/org/vafer/jdeb/changes/ChangeSet.java b/src/main/java/org/vafer/jdeb/changes/ChangeSet.java
index 4ebc3c5..e6adb2b 100644
--- a/src/main/java/org/vafer/jdeb/changes/ChangeSet.java
+++ b/src/main/java/org/vafer/jdeb/changes/ChangeSet.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/vafer/jdeb/changes/ChangesProvider.java b/src/main/java/org/vafer/jdeb/changes/ChangesProvider.java
index 235ded8..9282747 100644
--- a/src/main/java/org/vafer/jdeb/changes/ChangesProvider.java
+++ b/src/main/java/org/vafer/jdeb/changes/ChangesProvider.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/vafer/jdeb/changes/TextfileChangesProvider.java b/src/main/java/org/vafer/jdeb/changes/TextfileChangesProvider.java
index ed97531..900166e 100644
--- a/src/main/java/org/vafer/jdeb/changes/TextfileChangesProvider.java
+++ b/src/main/java/org/vafer/jdeb/changes/TextfileChangesProvider.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/vafer/jdeb/debian/BinaryPackageControlFile.java b/src/main/java/org/vafer/jdeb/debian/BinaryPackageControlFile.java
index 274b53d..9931969 100644
--- a/src/main/java/org/vafer/jdeb/debian/BinaryPackageControlFile.java
+++ b/src/main/java/org/vafer/jdeb/debian/BinaryPackageControlFile.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -65,6 +65,11 @@ public final class BinaryPackageControlFile extends ControlFile {
     }
 
     @Override
+    public void set(final String field, final String value) {
+        super.set(field, value);
+    }
+
+    @Override
     protected ControlField[] getFields() {
         return FIELDS;
     }
@@ -72,14 +77,19 @@ public final class BinaryPackageControlFile extends ControlFile {
     /**
      * Returns the short description of the package. The short description
      * consists in the first line of the Description field.
-     * 
+     *
      * @return
      */
     public String getShortDescription() {
         if (get("Description") == null) {
             return null;
         }
-        
+
         return get("Description").split("\n")[0];
     }
+
+    @Override
+    protected char getUserDefinedFieldLetter() {
+        return 'B';
+    }
 }
diff --git a/src/main/java/org/vafer/jdeb/debian/ChangesFile.java b/src/main/java/org/vafer/jdeb/debian/ChangesFile.java
index 49bc6b9..e3c46c7 100644
--- a/src/main/java/org/vafer/jdeb/debian/ChangesFile.java
+++ b/src/main/java/org/vafer/jdeb/debian/ChangesFile.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,11 +16,13 @@
 
 package org.vafer.jdeb.debian;
 
+import org.vafer.jdeb.changes.ChangeSet;
+
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.Date;
 import java.util.Locale;
-
-import org.vafer.jdeb.changes.ChangeSet;
+import java.util.Map.Entry;
 
 /**
  * Reflecting a changes file
@@ -30,8 +32,6 @@ import org.vafer.jdeb.changes.ChangeSet;
  */
 public final class ChangesFile extends ControlFile {
 
-    public static final DateFormat DATE_FORMAT = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH); // RFC 2822 format
-
     private static final ControlField[] FIELDS = {
             new ControlField("Format", true),
             new ControlField("Date", true),
@@ -53,13 +53,14 @@ public final class ChangesFile extends ControlFile {
 
     public ChangesFile() {
         set("Format", "1.8");
+        set("Urgency", "low");
         set("Distribution", "stable");
     }
 
     /**
      * Initializes the fields on the changes file with the values of the specified
      * binary package control file.
-     * 
+     *
      * @param packageControlFile
      */
     public void initialize(BinaryPackageControlFile packageControlFile) {
@@ -69,7 +70,12 @@ public final class ChangesFile extends ControlFile {
         set("Version",      packageControlFile.get("Version"));
         set("Maintainer",   packageControlFile.get("Maintainer"));
         set("Changed-By",   packageControlFile.get("Maintainer"));
-        
+        set("Distribution", packageControlFile.get("Distribution"));
+
+        for (Entry<String, String> entry : packageControlFile.getUserDefinedFields().entrySet()) {
+            set(entry.getKey(), entry.getValue());
+        }
+
         StringBuilder description = new StringBuilder();
         description.append(packageControlFile.get("Package"));
         if (packageControlFile.get("Description") != null) {
@@ -86,7 +92,7 @@ public final class ChangesFile extends ControlFile {
             final ChangeSet mostRecentChangeSet = changeSets[0];
             set("Urgency", mostRecentChangeSet.getUrgency());
             set("Changed-By", mostRecentChangeSet.getChangedBy());
-            
+
             for (ChangeSet changeSet : changeSets) {
                 sb.append(changeSet.toString());
             }
@@ -99,4 +105,14 @@ public final class ChangesFile extends ControlFile {
     protected ControlField[] getFields() {
         return FIELDS;
     }
+
+    @Override
+    protected char getUserDefinedFieldLetter() {
+        return 'C';
+    }
+
+    public static String formatDate(Date date) {
+        final DateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH); // RFC 2822 format
+        return format.format(date);
+    }
 }
diff --git a/src/main/java/org/vafer/jdeb/debian/ControlField.java b/src/main/java/org/vafer/jdeb/debian/ControlField.java
index 73e37ba..c08e73b 100644
--- a/src/main/java/org/vafer/jdeb/debian/ControlField.java
+++ b/src/main/java/org/vafer/jdeb/debian/ControlField.java
@@ -1,130 +1,130 @@
-/*
- * Copyright 2013 The jdeb developers.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.vafer.jdeb.debian;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
-
-/**
- * A field of a control file. This class is immutable.
- *
- * @author Emmanuel Bourg
- */
-public class ControlField {
-
-    /**
-     * The format of a field.
-     */
-    public enum Type {
-        /** Value on a single line */
-        SIMPLE,
-        /** Value on multiple lines, space characters are ignored */
-        FOLDED,
-        /** Value on multiple lines, space characters are preserved */
-        MULTILINE
-    }
-
-    /** The name of the field */
-    private String name;
-
-    /** Tells if the field is mandatory */
-    private boolean mandatory;
-
-    /** The type of the field */
-    private Type type = Type.SIMPLE;
-
-    /** Tells is the first line of the field must be empty (for MULTILINE values only) */
-    private boolean firstLineEmpty;
-
-
-    public ControlField(String name) {
-        this(name, false);
-    }
-
-    public ControlField(String name, boolean mandatory) {
-        this(name, mandatory, Type.SIMPLE);
-    }
-
-    public ControlField(String name, boolean mandatory, Type type) {
-        this(name, mandatory, type, false);
-    }
-
-    public ControlField(String name, boolean mandatory, Type type, boolean firstLineEmpty) {
-        this.name = name;
-        this.mandatory = mandatory;
-        this.type = type;
-        this.firstLineEmpty = firstLineEmpty;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public boolean isMandatory() {
-        return mandatory;
-    }
-
-    public Type getType() {
-        return type;
-    }
-
-    public boolean isFirstLineEmpty() {
-        return firstLineEmpty;
-    }
-
-    /**
-     * Returns the field with the specified value properly formatted. Multiline
-     * values are automatically indented, and dots are added on the empty lines.
-     * 
-     * <pre>
-     * Field-Name: value
-     * </pre>
-     */
-    public String format(String value) {
-        StringBuilder s = new StringBuilder();
-        
-        if (value != null && value.trim().length() > 0) {
-            boolean continuationLine = false;
-            
-            s.append(getName()).append(":");
-            if (isFirstLineEmpty()) {
-                s.append("\n");
-                continuationLine = true;
-            }
-            
-            try {
-                BufferedReader reader = new BufferedReader(new StringReader(value));
-                String line;
-                while ((line = reader.readLine()) != null) {
-                    if (continuationLine && line.trim().length() == 0) {
-                        // put a dot on the empty continuation lines
-                        s.append(" .\n");
-                    } else {
-                        s.append(" ").append(line).append("\n");
-                    }
-
-                    continuationLine = true;
-                }
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-        }
-        
-        return s.toString();
-    }
-}
+/*
+ * Copyright 2014 The jdeb developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.vafer.jdeb.debian;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+
+/**
+ * A field of a control file. This class is immutable.
+ *
+ * @author Emmanuel Bourg
+ */
+public class ControlField {
+
+    /**
+     * The format of a field.
+     */
+    public enum Type {
+        /** Value on a single line */
+        SIMPLE,
+        /** Value on multiple lines, space characters are ignored */
+        FOLDED,
+        /** Value on multiple lines, space characters are preserved */
+        MULTILINE
+    }
+
+    /** The name of the field */
+    private String name;
+
+    /** Tells if the field is mandatory */
+    private boolean mandatory;
+
+    /** The type of the field */
+    private Type type = Type.SIMPLE;
+
+    /** Tells is the first line of the field must be empty (for MULTILINE values only) */
+    private boolean firstLineEmpty;
+
+
+    public ControlField(String name) {
+        this(name, false);
+    }
+
+    public ControlField(String name, boolean mandatory) {
+        this(name, mandatory, Type.SIMPLE);
+    }
+
+    public ControlField(String name, boolean mandatory, Type type) {
+        this(name, mandatory, type, false);
+    }
+
+    public ControlField(String name, boolean mandatory, Type type, boolean firstLineEmpty) {
+        this.name = name;
+        this.mandatory = mandatory;
+        this.type = type;
+        this.firstLineEmpty = firstLineEmpty;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public boolean isMandatory() {
+        return mandatory;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    public boolean isFirstLineEmpty() {
+        return firstLineEmpty;
+    }
+
+    /**
+     * Returns the field with the specified value properly formatted. Multiline
+     * values are automatically indented, and dots are added on the empty lines.
+     * 
+     * <pre>
+     * Field-Name: value
+     * </pre>
+     */
+    public String format(String value) {
+        StringBuilder s = new StringBuilder();
+        
+        if (value != null && value.trim().length() > 0) {
+            boolean continuationLine = false;
+            
+            s.append(getName()).append(":");
+            if (isFirstLineEmpty()) {
+                s.append("\n");
+                continuationLine = true;
+            }
+            
+            try {
+                BufferedReader reader = new BufferedReader(new StringReader(value));
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    if (continuationLine && line.trim().length() == 0) {
+                        // put a dot on the empty continuation lines
+                        s.append(" .\n");
+                    } else {
+                        s.append(" ").append(line).append("\n");
+                    }
+
+                    continuationLine = true;
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        
+        return s.toString();
+    }
+}
diff --git a/src/main/java/org/vafer/jdeb/debian/ControlFile.java b/src/main/java/org/vafer/jdeb/debian/ControlFile.java
index 00c8b10..e014683 100644
--- a/src/main/java/org/vafer/jdeb/debian/ControlFile.java
+++ b/src/main/java/org/vafer/jdeb/debian/ControlFile.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.text.ParseException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -37,6 +38,8 @@ import java.util.Set;
 public abstract class ControlFile {
 
     protected final Map<String, String> values = new LinkedHashMap<String, String>();
+    protected final Map<String, String> userDefinedFields = new LinkedHashMap<String, String>();
+    protected final Set<ControlField> userDefinedFieldNames = new HashSet<ControlField>();
 
     public void parse(String input) throws IOException, ParseException {
         parse(new ByteArrayInputStream(input.getBytes("UTF-8")));
@@ -51,10 +54,8 @@ public abstract class ControlFile {
             final String line = reader.readLine();
 
             if (line == null) {
-                if (buffer.length() > 0) {
-                    // flush value of the previous field
-                    set(field, buffer.toString());
-                }
+                // flush value of the previous field
+                set(field, buffer.toString());
                 break;
             }
 
@@ -69,11 +70,9 @@ public abstract class ControlFile {
 
                 // new field
 
-                if (buffer.length() > 0) {
-                    // flush value of the previous field
-                    set(field, buffer.toString());
-                    buffer = new StringBuilder();
-                }
+                // flush value of the previous field
+                set(field, buffer.toString());
+                buffer = new StringBuilder();
 
 
                 final int i = line.indexOf(':');
@@ -98,8 +97,19 @@ public abstract class ControlFile {
 
     }
 
-    public void set(final String field, final String value) {
-        if (!"".equals(value)) {
+    public void set(String field, final String value) {
+        if (field != null && isUserDefinedField(field)) {
+            userDefinedFields.put(field, value);
+            String fieldName = getUserDefinedFieldName(field);
+
+            if (fieldName != null) {
+                userDefinedFieldNames.add(new ControlField(fieldName));
+            }
+
+            field = fieldName;
+        }
+
+        if (field != null && !"".equals(field)) {
             values.put(field, value);
         }
     }
@@ -110,6 +120,14 @@ public abstract class ControlFile {
 
     protected abstract ControlField[] getFields();
 
+    protected Map<String, String> getUserDefinedFields() {
+        return userDefinedFields;
+    }
+
+    protected Set<ControlField> getUserDefinedFieldNames() {
+        return userDefinedFieldNames;
+    }
+
     public List<String> getMandatoryFields() {
         List<String> fields = new ArrayList<String>();
 
@@ -118,7 +136,7 @@ public abstract class ControlFile {
                 fields.add(field.getName());
             }
         }
-        
+
         return fields;
     }
 
@@ -128,7 +146,7 @@ public abstract class ControlFile {
 
     public Set<String> invalidFields() {
         Set<String> invalid = new HashSet<String>();
-        
+
         for (ControlField field : getFields()) {
             if (field.isMandatory() && get(field.getName()) == null) {
                 invalid.add(field.getName());
@@ -148,6 +166,60 @@ public abstract class ControlFile {
     }
 
     public String toString() {
-        return toString(getFields());
+        List<ControlField> fields = new ArrayList<ControlField>();
+        fields.addAll(Arrays.asList(getFields()));
+        fields.addAll(getUserDefinedFieldNames());
+        return toString(fields.toArray(new ControlField[fields.size()]));
+    }
+
+    /**
+     * Returns the letter expected in the prefix of a user defined field
+     * in order to include the field in this control file.
+     *
+     * @return The letter returned is:
+     * <ul>
+     *   <li>B: for a binary package</li>
+     *   <li>S: for a source package</li>
+     *   <li>C: for a changes file</li>
+     * </ul>
+     * 
+     * @since 1.1
+     * @see <a href="http://www.debian.org/doc/debian-policy/ch-controlfields.html#s5.7">Debian Policy - User-defined fields</a>
+     */
+    protected abstract char getUserDefinedFieldLetter();
+
+    /**
+     * Tells if the specified field name is a user defined field.
+     * User-defined fields must begin with an 'X', followed by one or more
+     * letters that specify the output file and a hyphen.
+     * 
+     * @param field the name of the field
+     *
+     * @since 1.1
+     * @see <a href="http://www.debian.org/doc/debian-policy/ch-controlfields.html#s5.7">Debian Policy - User-defined fields</a>
+     */
+    protected boolean isUserDefinedField(String field) {
+        return field.startsWith("X") && field.indexOf("-") > 0;
+    }
+
+    /**
+     * Returns the user defined field without its prefix.
+     * 
+     * @param field the name of the user defined field
+     * @return the user defined field without the prefix, or null if the fields
+     *         doesn't apply to this control file.
+     * @since 1.1
+     */
+    protected String getUserDefinedFieldName(String field) {
+        int index = field.indexOf('-');
+        char letter = getUserDefinedFieldLetter();
+
+        for (int i = 0; i < index; ++i) {
+            if (field.charAt(i) == letter) {
+                return field.substring(index + 1);
+            }
+        }
+
+        return null;
     }
 }
diff --git a/src/main/java/org/vafer/jdeb/mapping/LsMapper.java b/src/main/java/org/vafer/jdeb/mapping/LsMapper.java
index fdf19c5..1998c72 100644
--- a/src/main/java/org/vafer/jdeb/mapping/LsMapper.java
+++ b/src/main/java/org/vafer/jdeb/mapping/LsMapper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/vafer/jdeb/mapping/Mapper.java b/src/main/java/org/vafer/jdeb/mapping/Mapper.java
index 1229fda..2f30727 100644
--- a/src/main/java/org/vafer/jdeb/mapping/Mapper.java
+++ b/src/main/java/org/vafer/jdeb/mapping/Mapper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/vafer/jdeb/mapping/NullMapper.java b/src/main/java/org/vafer/jdeb/mapping/NullMapper.java
index de5b115..1c8a8d3 100644
--- a/src/main/java/org/vafer/jdeb/mapping/NullMapper.java
+++ b/src/main/java/org/vafer/jdeb/mapping/NullMapper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/vafer/jdeb/mapping/PermMapper.java b/src/main/java/org/vafer/jdeb/mapping/PermMapper.java
index f3c6701..66b8583 100644
--- a/src/main/java/org/vafer/jdeb/mapping/PermMapper.java
+++ b/src/main/java/org/vafer/jdeb/mapping/PermMapper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/vafer/jdeb/maven/AbstractPluginMojo.java b/src/main/java/org/vafer/jdeb/maven/AbstractPluginMojo.java
deleted file mode 100644
index de3cf65..0000000
--- a/src/main/java/org/vafer/jdeb/maven/AbstractPluginMojo.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2013 The jdeb developers.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.vafer.jdeb.maven;
-
-import java.io.File;
-
-import org.apache.maven.plugin.AbstractMojo;
-import org.apache.maven.project.MavenProject;
-
-public abstract class AbstractPluginMojo extends AbstractMojo {
-
-    /**
-     * @parameter expression="${project}"
-     * @required
-     * @readonly
-     */
-    private MavenProject project;
-
-    /**
-     * @parameter expression="${project.build.directory}"
-     * @required
-     * @readonly
-     */
-    protected File buildDirectory;
-
-    protected MavenProject getProject() {
-        if (project.getExecutionProject() != null) {
-            return project.getExecutionProject();
-        }
-
-        return project;
-    }
-
-}
diff --git a/src/main/java/org/vafer/jdeb/maven/Data.java b/src/main/java/org/vafer/jdeb/maven/Data.java
index 0f1dd41..e4309c4 100644
--- a/src/main/java/org/vafer/jdeb/maven/Data.java
+++ b/src/main/java/org/vafer/jdeb/maven/Data.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,10 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.vafer.jdeb.maven;
 
-import static org.vafer.jdeb.maven.MissingSourceBehavior.FAIL;
-import static org.vafer.jdeb.maven.MissingSourceBehavior.IGNORE;
+package org.vafer.jdeb.maven;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -25,14 +23,18 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.StringTokenizer;
 
+import org.apache.maven.plugins.annotations.Parameter;
 import org.vafer.jdeb.DataConsumer;
 import org.vafer.jdeb.DataProducer;
 import org.vafer.jdeb.producers.DataProducerArchive;
 import org.vafer.jdeb.producers.DataProducerDirectory;
 import org.vafer.jdeb.producers.DataProducerFile;
+import org.vafer.jdeb.producers.DataProducerFiles;
 import org.vafer.jdeb.producers.DataProducerLink;
 import org.vafer.jdeb.producers.DataProducerPathTemplate;
 
+import static org.vafer.jdeb.maven.MissingSourceBehavior.*;
+
 /**
  * Maven "data" element acting as a factory for DataProducers. So far Archive and
  * Directory producers are supported. Both support the usual ant pattern set
@@ -42,38 +44,30 @@ import org.vafer.jdeb.producers.DataProducerPathTemplate;
  */
 public final class Data implements DataProducer {
 
+    @Parameter
     private File src;
 
-    /**
-     * @parameter expression="${src}"
-     */
     public void setSrc( File src ) {
         this.src = src;
     }
 
+    @Parameter
     private String dst;
 
-    /**
-     * @parameter expression="${dst}"
-     */
     public void setDst( String dst ) {
         this.dst = dst;
     }
 
+    @Parameter
     private String type;
 
-    /**
-     * @parameter expression="${type}"
-     */
     public void setType( String type ) {
         this.type = type;
     }
 
+    @Parameter
     private MissingSourceBehavior missingSrc = FAIL;
 
-    /**
-     * @parameter expression="${missingSrc}"
-     */
     public void setMissingSrc( String missingSrc ) {
         MissingSourceBehavior value = MissingSourceBehavior.valueOf(missingSrc.trim().toUpperCase());
         if (value == null) {
@@ -82,59 +76,58 @@ public final class Data implements DataProducer {
         this.missingSrc = value;
     }
 
+    @Parameter
     private String linkName;
 
-    /**
-     * @parameter expression="${linkName}"
-     */
     public void setLinkName(String linkName) {
         this.linkName = linkName;
     }
 
+    @Parameter
     private String linkTarget;
 
-    /**
-     * @parameter expression="${linkTarget}"
-     */
     public void setLinkTarget(String linkTarget) {
         this.linkTarget = linkTarget;
     }
 
+    @Parameter
     private boolean symlink = true;
 
-    /**
-     * @parameter expression="${symlink}"
-     */
     public void setSymlink(boolean symlink) {
         this.symlink = symlink;
     }
-
-    private String[] includePatterns;
+    
+    private boolean conffile = false;
 
     /**
-     * @parameter expression="${includes}" alias="includes"
+     * @parameter expression="${conffile}"
      */
+    public void setConffile(boolean conffile) {
+        this.conffile = conffile;
+    }
+    
+    public boolean getConffile() {
+        return this.conffile;
+    }
+
+    @Parameter(alias = "includes")
+    private String[] includePatterns;
+
     public void setIncludes( String includes ) {
         includePatterns = splitPatterns(includes);
     }
 
+    @Parameter(alias = "excludes")
     private String[] excludePatterns;
 
-    /**
-     * @parameter expression="${excludes}" alias="excludes"
-     */
     public void setExcludes( String excludes ) {
         excludePatterns = splitPatterns(excludes);
     }
 
-    /**
-     * @parameter expression="${mapper}"
-     */
+    @Parameter
     private Mapper mapper;
 
-    /**
-     * @parameter expression="${paths}"
-     */
+    @Parameter
     private String[] paths;
 
     /* For testing only */
@@ -163,7 +156,7 @@ public final class Data implements DataProducer {
 
         // link type
 
-        if ("link".equalsIgnoreCase(type)) {
+        if (typeIs("link")) {
             if (linkName == null) {
                 throw new RuntimeException("linkName is not set");
             }
@@ -177,15 +170,18 @@ public final class Data implements DataProducer {
 
         // template type
 
-        if ("template".equalsIgnoreCase(type)) {
-            if (paths == null || paths.length == 0) {
-                throw new RuntimeException("paths is not set");
-            }
-
+        if (typeIs("template")) {
+            checkPaths();
             new DataProducerPathTemplate(paths, includePatterns, excludePatterns, mappers).produce(pReceiver);
             return;
         }
 
+        if (typeIs("files")) {
+            checkPaths();
+            new DataProducerFiles(paths, dst, mappers).produce(pReceiver);
+            return;
+        }
+
         // Types that require src to exist
 
         if (src == null || !src.exists()) {
@@ -196,17 +192,17 @@ public final class Data implements DataProducer {
             }
         }
 
-        if ("file".equalsIgnoreCase(type)) {
+        if (typeIs("file")) {
             new DataProducerFile(src, dst, includePatterns, excludePatterns, mappers).produce(pReceiver);
             return;
         }
 
-        if ("archive".equalsIgnoreCase(type)) {
+        if (typeIs("archive")) {
             new DataProducerArchive(src, includePatterns, excludePatterns, mappers).produce(pReceiver);
             return;
         }
 
-        if ("directory".equalsIgnoreCase(type)) {
+        if (typeIs("directory")) {
             new DataProducerDirectory(src, includePatterns, excludePatterns, mappers).produce(pReceiver);
             return;
         }
@@ -214,4 +210,13 @@ public final class Data implements DataProducer {
         throw new IOException("Unknown type '" + type + "' (file|directory|archive|template|link) for " + src);
     }
 
+    private boolean typeIs( final String type ) {
+        return type.equalsIgnoreCase(this.type);
+    }
+
+    private void checkPaths() {
+        if (paths == null || paths.length == 0) {
+            throw new RuntimeException("paths parameter is not set");
+        }
+    }
 }
diff --git a/src/main/java/org/vafer/jdeb/maven/DebMojo.java b/src/main/java/org/vafer/jdeb/maven/DebMojo.java
index 9d67332..d2b47d9 100644
--- a/src/main/java/org/vafer/jdeb/maven/DebMojo.java
+++ b/src/main/java/org/vafer/jdeb/maven/DebMojo.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.vafer.jdeb.maven;
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -29,92 +30,91 @@ import java.util.Set;
 
 import org.apache.maven.artifact.Artifact;
 import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.project.MavenProjectHelper;
+import org.apache.maven.settings.Profile;
+import org.apache.maven.settings.Settings;
 import org.apache.tools.tar.TarEntry;
+import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
+import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException;
 import org.vafer.jdeb.Console;
 import org.vafer.jdeb.DataConsumer;
 import org.vafer.jdeb.DataProducer;
 import org.vafer.jdeb.DebMaker;
 import org.vafer.jdeb.PackagingException;
-import org.vafer.jdeb.utils.FilteredFile;
 import org.vafer.jdeb.utils.MapVariableResolver;
 import org.vafer.jdeb.utils.Utils;
 import org.vafer.jdeb.utils.VariableResolver;
 
+import static org.vafer.jdeb.utils.Utils.lookupIfEmpty;
+
 /**
- * Creates deb archive
- *
- * @goal jdeb
- * @phase package
+ * Creates Debian package
  */
-public class DebMojo extends AbstractPluginMojo {
+ at Mojo(name = "jdeb", defaultPhase = LifecyclePhase.PACKAGE)
+public class DebMojo extends AbstractMojo {
 
-    /**
-     * @component
-     */
+    @Component
     private MavenProjectHelper projectHelper;
-    
+
+    @Component(hint = "jdeb-sec")
+    private SecDispatcher secDispatcher;
+
     /**
      * Defines the name of deb package.
-     *
-     * @parameter
      */
+    @Parameter
     private String name;
 
     /**
      * Defines the pattern of the name of final artifacts. Possible
      * substitutions are [[baseDir]] [[buildDir]] [[artifactId]] [[version]]
      * [[extension]] and [[groupId]].
-     *
-     * @parameter default-value="[[buildDir]]/[[artifactId]]_[[version]]_all.[[extension]]"
      */
+    @Parameter(defaultValue = "[[buildDir]]/[[artifactId]]_[[version]]_all.[[extension]]")
     private String deb;
 
     /**
      * Explicitly defines the path to the control directory. At least the
      * control file is mandatory.
-     *
-     * @parameter default-value="[[baseDir]]/src/deb/control"
      */
+    @Parameter(defaultValue = "[[baseDir]]/src/deb/control")
     private String controlDir;
 
     /**
      * Explicitly define the file to read the changes from.
-     *
-     * @parameter default-value="[[baseDir]]/CHANGES.txt"
      */
+    @Parameter(defaultValue = "[[baseDir]]/CHANGES.txt")
     private String changesIn;
 
     /**
      * Explicitly define the file where to write the changes to.
-     *
-     * @parameter default-value="[[buildDir]]/[[artifactId]]_[[version]]_all.changes"
      */
+    @Parameter(defaultValue = "[[buildDir]]/[[artifactId]]_[[version]]_all.changes")
     private String changesOut;
 
     /**
-     * Explicitly define the file where to write the changes of the changes
-     * input to.
-     *
-     * @parameter default-value="[[baseDir]]/CHANGES.txt"
+     * Explicitly define the file where to write the changes of the changes input to.
      */
+    @Parameter(defaultValue = "[[baseDir]]/CHANGES.txt")
     private String changesSave;
 
     /**
      * The compression method used for the data file (none, gzip, bzip2 or xz)
-     *
-     * @parameter default-value="gzip"
      */
+    @Parameter(defaultValue = "gzip")
     private String compression;
 
-
     /**
      * Boolean option whether to attach the artifact to the project
-     *
-     * @parameter default-value="true"
      */
+    @Parameter(defaultValue = "true")
     private String attach;
 
     /**
@@ -122,51 +122,44 @@ public class DebMojo extends AbstractPluginMojo {
      * packages are installed in /opt (see the FHS here:
      * http://www.pathname.com/
      * fhs/pub/fhs-2.3.html#OPTADDONAPPLICATIONSOFTWAREPACKAGES)
-     *
-     * @parameter default-value="/opt/[[artifactId]]"
      */
+    @Parameter(defaultValue = "/opt/[[artifactId]]")
     private String installDir;
 
-
     /**
      * The type of attached artifact
-     *
-     * @parameter default-value="deb"
      */
+    @Parameter(defaultValue = "deb")
     private String type;
 
     /**
      * The project base directory
-     *
-     * @parameter default-value="${basedir}"
-     * @required
-     * @readonly
      */
+    @Parameter(defaultValue = "${basedir}", required = true, readonly = true)
     private File baseDir;
 
     /**
-     * Run the plugin on all sub-modules.
-     * If set to false, the plugin will be run in the same folder where the
-     * mvn command was invoked
-     *
-     * @parameter expression="${submodules}" default-value="true"
+     * The Maven Session Object
      */
-    private boolean submodules;
+    @Parameter( defaultValue = "${session}", readonly = true )
+    private MavenSession session;
+    
+    /**
+     * The Maven Project Object
+     */
+    @Parameter( defaultValue = "${project}", readonly = true )
+    private MavenProject project;
 
     /**
-     * The Maven Session Object
-     *
-     * @parameter expression="${session}"
-     * @required
-     * @readonly
+     * The build directory
      */
-    private MavenSession session;
+    @Parameter(property = "project.build.directory", required = true, readonly = true)
+    private File buildDirectory;
 
     /**
      * The classifier of attached artifact
-     *
-     * @parameter
      */
+    @Parameter
     private String classifier;
 
     /**
@@ -174,7 +167,8 @@ public class DebMojo extends AbstractPluginMojo {
      * The "data" entries may specify a tarball (tar.gz, tar.bz2, tgz), a
      * directory, or a normal file. An entry would look something like this in
      * your pom.xml:
-     *
+     * 
+     * 
      * <pre>
      *   <build>
      *     <plugins>
@@ -221,57 +215,151 @@ public class DebMojo extends AbstractPluginMojo {
      *     </plugins>
      *   </build>
      * </pre>
-     *
-     * @parameter expression="${dataSet}"
+     * 
      */
+    @Parameter
     private Data[] dataSet;
 
     /**
-     * When SNAPSHOT version replace <code>SNAPSHOT</code> with current date
-     * and time to make sure each build is unique.
-     *
-     * @parameter expression="${timestamped}" default-value="false"
-     */
+     * @deprecated
+    @Parameter(defaultValue = "false")
     private boolean timestamped;
+     */
 
     /**
-     * If verbose is true info messages also get logged.
-     * Will be changed to "false" in future versions.
-     * Left to "true" for the transition.
-     *
-     * @parameter expression="${verbose}" default-value="true"
+     * When enabled SNAPSHOT inside the version gets replaced with current timestamp or
+     * if set a value of a environment variable.
+     */
+    @Parameter(defaultValue = "false")
+    private boolean snapshotExpand;
+
+    /**
+     * Which environment variable to check for the SNAPSHOT value.
+     * If the variable is not set/empty it will default to use the timestamp.
      */
+    @Parameter(defaultValue = "SNAPSHOT")
+    private String snapshotEnv;
+
+    /**
+     * If verbose is true more build messages are logged.
+     */
+    @Parameter(defaultValue = "false")
     private boolean verbose;
 
+    /**
+     * Indicates if the execution should be disabled. If <code>true</code>, nothing will occur during execution.
+     * 
+     * @since 1.1
+     */
+    @Parameter(defaultValue = "false")
+    private boolean skip;
+
+    @Parameter(defaultValue = "true")
+    private boolean skipPOMs;
+
+    @Parameter(defaultValue = "false")
+    private boolean skipSubmodules;
+
+    /**
+     * @deprecated
+     */
+    @Parameter(defaultValue = "true")
+    private boolean submodules;
+
+
+    /**
+     * If signPackage is true then a origin signature will be placed
+     * in the generated package.
+     */
+    @Parameter(defaultValue = "false")
+    private boolean signPackage;
+    
+    /**
+     * Defines which utility is used to verify the signed package
+     */
+    @Parameter(defaultValue = "debsig-verify")
+    private String signMethod;
+
+    /**
+     * Defines the role to sign with
+     */
+    @Parameter(defaultValue = "origin")
+    private String signRole;
+    
+    /**
+     * The keyring to use for signing operations.
+     */
+    @Parameter
+    private String keyring;
+
+    /**
+     * The key to use for signing operations.
+     */
+    @Parameter
+    private String key;
+
+    /**
+     * The passphrase to use for signing operations.
+     */
+    @Parameter
+    private String passphrase; 
+
+    /**
+     * The prefix to use when reading signing variables
+     * from settings.
+     */
+    @Parameter(defaultValue = "jdeb.")
+    private String signCfgPrefix;
+
+    /**
+     * The settings.
+     */
+    @Parameter(defaultValue = "${settings}")
+    private Settings settings;
+
     /* end of parameters */
+    
+    private static final String KEY = "key";
+    private static final String KEYRING = "keyring";
+    private static final String PASSPHRASE = "passphrase";
 
     private String openReplaceToken = "[[";
     private String closeReplaceToken = "]]";
+    private Console console;
     private Collection<DataProducer> dataProducers = new ArrayList<DataProducer>();
+    private Collection<DataProducer> conffileProducers = new ArrayList<DataProducer>();
 
     public void setOpenReplaceToken( String openReplaceToken ) {
         this.openReplaceToken = openReplaceToken;
-        // FIXME yuck!
-        FilteredFile.setOpenToken(openReplaceToken);
     }
 
     public void setCloseReplaceToken( String closeReplaceToken ) {
         this.closeReplaceToken = closeReplaceToken;
-        // FIXME yuck!
-        FilteredFile.setCloseToken(closeReplaceToken);
     }
 
     protected void setData( Data[] dataSet ) {
         this.dataSet = dataSet;
         dataProducers.clear();
+        conffileProducers.clear();
         if (dataSet != null) {
             Collections.addAll(dataProducers, dataSet);
+            
+            for (Data item : dataSet) {
+                if (item.getConffile()) {
+                    conffileProducers.add(item);
+                }
+            }
         }
     }
 
     protected VariableResolver initializeVariableResolver( Map<String, String> variables ) {
-        ((Map) variables).putAll(getProject().getProperties());
-        ((Map) variables).putAll(System.getProperties());
+        @SuppressWarnings("unchecked")
+        final Map<String, String> projectProperties = Map.class.cast(getProject().getProperties());
+        @SuppressWarnings("unchecked")
+        final Map<String, String> systemProperties = Map.class.cast(System.getProperties());
+
+        variables.putAll(projectProperties);
+        variables.putAll(systemProperties);
         variables.put("name", name != null ? name : getProject().getName());
         variables.put("artifactId", getProject().getArtifactId());
         variables.put("groupId", getProject().getGroupId());
@@ -296,7 +384,22 @@ public class DebMojo extends AbstractPluginMojo {
      * @return the Maven project version
      */
     private String getProjectVersion() {
-        return Utils.convertToDebianVersion(getProject().getVersion(), this.timestamped ? session.getStartTime() : null);
+        return Utils.convertToDebianVersion(getProject().getVersion(), this.snapshotExpand, this.snapshotEnv, session.getStartTime());
+    }
+
+    /**
+     * @return whether the artifact is a POM or not
+     */
+    private boolean isPOM() {
+        String type = getProject().getArtifact().getType();
+        return "pom".equalsIgnoreCase(type);
+    }
+
+    /**
+     * @return whether the artifact is of configured type (i.e. the package to generate is the main artifact)
+     */
+    private boolean isType() {
+        return type.equals(getProject().getArtifact().getType());
     }
 
     /**
@@ -321,19 +424,31 @@ public class DebMojo extends AbstractPluginMojo {
      *
      * @throws MojoExecutionException on error
      */
-    @Override
     public void execute() throws MojoExecutionException {
 
         final MavenProject project = getProject();
 
-        if (isSubmodule() && !submodules) {
-            getLog().info("skipping sub module: jdeb executing at top-level only");
+        if (skip) {
+            getLog().info("skipping as configured (skip)");
             return;
         }
 
+        if (skipPOMs && isPOM()) {
+            getLog().info("skipping because artifact is a pom (skipPOMs)");
+            return;
+        }
+
+        if (skipSubmodules && isSubmodule()) {
+            getLog().info("skipping submodule (skipSubmodules)");
+            return;
+        }
+
+
         setData(dataSet);
 
-        Console console = new MojoConsole(getLog(), verbose);
+        console = new MojoConsole(getLog(), verbose);
+
+        initializeSignProperties();
 
         final VariableResolver resolver = initializeVariableResolver(new HashMap<String, String>());
 
@@ -343,32 +458,27 @@ public class DebMojo extends AbstractPluginMojo {
         final File changesInFile = new File(Utils.replaceVariables(resolver, changesIn, openReplaceToken, closeReplaceToken));
         final File changesOutFile = new File(Utils.replaceVariables(resolver, changesOut, openReplaceToken, closeReplaceToken));
         final File changesSaveFile = new File(Utils.replaceVariables(resolver, changesSave, openReplaceToken, closeReplaceToken));
+        final File keyringFile = keyring == null ? null : new File(Utils.replaceVariables(resolver, keyring, openReplaceToken, closeReplaceToken));
 
         // if there are no producers defined we try to use the artifacts
         if (dataProducers.isEmpty()) {
 
-            if (!hasMainArtifact()) {
-
-                final String packaging = project.getPackaging();
-                if ("pom".equalsIgnoreCase(packaging)) {
-                    getLog().warn("Creating empty debian package.");
-                } else {
-                    throw new MojoExecutionException(
-                        "Nothing to include into the debian package. " +
-                            "Did you maybe forget to add a <data> tag or call the plugin directly?");
-                }
-
-            } else {
-
+            if (hasMainArtifact()) {
                 Set<Artifact> artifacts = new HashSet<Artifact>();
 
                 artifacts.add(project.getArtifact());
 
-                for (Artifact artifact : (Set<Artifact>) project.getArtifacts()) {
+                @SuppressWarnings("unchecked")
+                final Set<Artifact> projectArtifacts = project.getArtifacts();
+
+                for (Artifact artifact : projectArtifacts) {
                     artifacts.add(artifact);
                 }
 
-                for (Artifact artifact : (List<Artifact>) project.getAttachedArtifacts()) {
+                @SuppressWarnings("unchecked")
+                final List<Artifact> attachedArtifacts = project.getAttachedArtifacts();
+
+                for (Artifact artifact : attachedArtifacts) {
                     artifacts.add(artifact);
                 }
 
@@ -399,7 +509,7 @@ public class DebMojo extends AbstractPluginMojo {
         }
 
         try {
-            DebMaker debMaker = new DebMaker(console, dataProducers);
+            DebMaker debMaker = new DebMaker(console, dataProducers, conffileProducers);
             debMaker.setDeb(debFile);
             debMaker.setControl(controlDirFile);
             debMaker.setPackage(getProject().getArtifactId());
@@ -409,14 +519,26 @@ public class DebMojo extends AbstractPluginMojo {
             debMaker.setChangesOut(changesOutFile);
             debMaker.setChangesSave(changesSaveFile);
             debMaker.setCompression(compression);
+            debMaker.setKeyring(keyringFile);
+            debMaker.setKey(key);
+            debMaker.setPassphrase(passphrase);
+            debMaker.setSignPackage(signPackage);
+            debMaker.setSignMethod(signMethod);
+            debMaker.setSignRole(signRole);
             debMaker.setResolver(resolver);
+            debMaker.setOpenReplaceToken(openReplaceToken);
+            debMaker.setCloseReplaceToken(closeReplaceToken);
             debMaker.validate();
             debMaker.makeDeb();
 
             // Always attach unless explicitly set to false
             if ("true".equalsIgnoreCase(attach)) {
-                getLog().info("Attaching created debian archive " + debFile);
-                projectHelper.attachArtifact(project, type, classifier, debFile);
+                console.info("Attaching created debian package " + debFile);
+                if (!isType()) {
+                    projectHelper.attachArtifact(project, type, classifier, debFile);
+                } else {
+                    project.getArtifact().setFile(debFile);
+                }
             }
 
         } catch (PackagingException e) {
@@ -424,4 +546,126 @@ public class DebMojo extends AbstractPluginMojo {
             throw new MojoExecutionException("Failed to create debian package " + debFile, e);
         }
     }
+
+    /**
+     * Initializes unspecified sign properties using available defaults
+     * and global settings.
+     */
+    private void initializeSignProperties() {
+        if (!signPackage) {
+            return;
+        }
+
+        if (key != null && keyring != null && passphrase != null) {
+            return;
+        }
+
+        Map<String, String> properties =
+                readPropertiesFromActiveProfiles(signCfgPrefix, KEY, KEYRING, PASSPHRASE);
+
+        key = lookupIfEmpty(key, properties, KEY);
+        keyring = lookupIfEmpty(keyring, properties, KEYRING);
+        passphrase = decrypt(lookupIfEmpty(passphrase, properties, PASSPHRASE));
+
+        if (keyring == null) {
+            try {
+                keyring = Utils.guessKeyRingFile().getAbsolutePath();
+                console.info("Located keyring at " + keyring);
+            } catch (FileNotFoundException e) {
+                console.warn(e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Decrypts given passphrase if needed using maven security dispatcher.
+     * See http://maven.apache.org/guides/mini/guide-encryption.html for details.
+     *
+     * @param maybeEncryptedPassphrase possibly encrypted passphrase
+     * @return decrypted passphrase
+     */
+    private String decrypt( final String maybeEncryptedPassphrase ) {
+        if (maybeEncryptedPassphrase == null) {
+            return null;
+        }
+
+        try {
+            final String decrypted = secDispatcher.decrypt(maybeEncryptedPassphrase);
+            if (maybeEncryptedPassphrase.equals(decrypted)) {
+                console.info("Passphrase was not encrypted");
+            } else {
+                console.info("Passphrase was successfully decrypted");
+            }
+            return decrypted;
+        } catch (SecDispatcherException e) {
+            console.warn("Unable to decrypt passphrase: " + e.getMessage());
+        }
+
+        return maybeEncryptedPassphrase;
+    }
+    
+    /**
+     * 
+     * @return the maven project used by this mojo
+     */
+    private MavenProject getProject() {
+        if (project.getExecutionProject() != null) {
+            return project.getExecutionProject();
+        }
+
+        return project;
+    }
+
+
+
+    /**
+     * Read properties from the active profiles.
+     *
+     * Goes through all active profiles (in the order the
+     * profiles are defined in settings.xml) and extracts
+     * the desired properties (if present). The prefix is
+     * used when looking up properties in the profile but
+     * not in the returned map.
+     *
+     * @param prefix The prefix to use or null if no prefix should be used
+     * @param properties The properties to read
+     *
+     * @return A map containing the values for the properties that were found
+     */
+    public Map<String, String> readPropertiesFromActiveProfiles( final String prefix,
+                                                                 final String... properties ) {
+        if (settings == null) {
+            console.debug("No maven setting injected");
+            return Collections.emptyMap();
+        }
+
+        final List<String> activeProfilesList = settings.getActiveProfiles();
+        if (activeProfilesList.isEmpty()) {
+            console.debug("No active profiles found");
+            return Collections.emptyMap();
+        }
+
+        final Map<String, String> map = new HashMap<String, String>();
+        final Set<String> activeProfiles = new HashSet<String>(activeProfilesList);
+
+        // Iterate over all active profiles in order
+        for (final Profile profile : settings.getProfiles()) {
+            // Check if the profile is active
+            final String profileId = profile.getId();
+            if (activeProfiles.contains(profileId)) {
+                console.debug("Trying active profile " + profileId);
+                for (final String property : properties) {
+                    final String propKey = prefix != null ? prefix + property : property;
+                    final String value = profile.getProperties().getProperty(propKey);
+                    if (value != null) {
+                        console.debug("Found property " + property + " in profile " + profileId);
+                        map.put(property, value);
+                    }
+                }
+            }
+        }
+
+        return map;
+    }
+
 }
diff --git a/src/main/java/org/vafer/jdeb/maven/Mapper.java b/src/main/java/org/vafer/jdeb/maven/Mapper.java
index d44a3d2..0e4cebe 100644
--- a/src/main/java/org/vafer/jdeb/maven/Mapper.java
+++ b/src/main/java/org/vafer/jdeb/maven/Mapper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,12 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.vafer.jdeb.maven;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 
+import org.apache.maven.plugins.annotations.Parameter;
 import org.vafer.jdeb.mapping.LsMapper;
 import org.vafer.jdeb.mapping.NullMapper;
 import org.vafer.jdeb.mapping.PermMapper;
@@ -31,55 +33,34 @@ import org.vafer.jdeb.mapping.PermMapper;
  */
 public final class Mapper {
 
-    /**
-     * @parameter
-     * @required
-     */
+    @Parameter(required = true)
     private String type;
 
-    /**
-     * @parameter
-     */
+    @Parameter
     private int uid = -1;
 
-    /**
-     * @parameter
-     */
+    @Parameter
     private int gid = -1;
 
-    /**
-     * @parameter
-     */
+    @Parameter
     private String user;
 
-    /**
-     * @parameter
-     */
+    @Parameter
     private String group;
 
-    /**
-     * @parameter
-     */
+    @Parameter
     private String filemode;
 
-    /**
-     * @parameter
-     */
+    @Parameter
     private String dirmode;
 
-    /**
-     * @parameter
-     */
+    @Parameter
     private String prefix;
 
-    /**
-     * @parameter
-     */
+    @Parameter
     private int strip;
 
-    /**
-     * @parameter
-     */
+    @Parameter
     private File src;
 
 
diff --git a/src/main/java/org/vafer/jdeb/maven/MissingSourceBehavior.java b/src/main/java/org/vafer/jdeb/maven/MissingSourceBehavior.java
index 684e871..c988807 100644
--- a/src/main/java/org/vafer/jdeb/maven/MissingSourceBehavior.java
+++ b/src/main/java/org/vafer/jdeb/maven/MissingSourceBehavior.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.vafer.jdeb.maven;
 
 public enum MissingSourceBehavior {
diff --git a/src/main/java/org/vafer/jdeb/maven/MojoConsole.java b/src/main/java/org/vafer/jdeb/maven/MojoConsole.java
index da78148..ca3b0f2 100644
--- a/src/main/java/org/vafer/jdeb/maven/MojoConsole.java
+++ b/src/main/java/org/vafer/jdeb/maven/MojoConsole.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,7 +20,8 @@ import org.apache.maven.plugin.logging.Log;
 import org.vafer.jdeb.Console;
 
 /**
- * Console implementation for Maven plugins.
+ * Console implementation for Maven plugins. debug messages are only displayed
+ * when the <tt>verbose</tt> parameter is true.
  */
 class MojoConsole implements Console {
 
@@ -32,13 +33,17 @@ class MojoConsole implements Console {
         this.verbose = verbose;
     }
 
-    public void info(String s) {
+    public void debug(String message) {
         if (verbose) {
-            log.info(s);
+            log.info(message);
         }
     }
 
-    public void warn(String s) {
-        log.warn(s);
+    public void info(String message) {
+        log.info(message);
+    }
+
+    public void warn(String message) {
+        log.warn(message);
     }
 }
diff --git a/src/main/java/org/vafer/jdeb/producers/AbstractDataProducer.java b/src/main/java/org/vafer/jdeb/producers/AbstractDataProducer.java
index 47d8764..5396ec6 100644
--- a/src/main/java/org/vafer/jdeb/producers/AbstractDataProducer.java
+++ b/src/main/java/org/vafer/jdeb/producers/AbstractDataProducer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,9 +17,14 @@ package org.vafer.jdeb.producers;
 
 import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
 import org.apache.tools.ant.types.selectors.SelectorUtils;
+import org.vafer.jdeb.DataConsumer;
 import org.vafer.jdeb.DataProducer;
 import org.vafer.jdeb.mapping.Mapper;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
 /**
  * Base Producer class providing including/excluding.
  *
@@ -67,6 +72,23 @@ public abstract class AbstractDataProducer implements DataProducer {
         return false;
     }
 
+    public void produceDir( final DataConsumer consumer,
+                            final String dirname ) throws IOException {
+        TarArchiveEntry entry = Producers.defaultDirEntryWithName(dirname);
+        entry = map(entry);
+        entry.setSize(0);
+        Producers.produceDirEntry(consumer, entry);
+    }
+
+    public void produceFile( final DataConsumer consumer,
+                             final File file,
+                             final String entryName ) throws IOException {
+        TarArchiveEntry entry = Producers.defaultFileEntryWithName(entryName);
+        entry.setSize(file.length());
+        entry = map(entry);
+        Producers.produceInputStreamWithEntry(consumer, new FileInputStream(file), entry);
+    }
+
     public TarArchiveEntry map( final TarArchiveEntry pEntry ) {
 
         TarArchiveEntry entry = pEntry;
diff --git a/src/main/java/org/vafer/jdeb/producers/DataProducerArchive.java b/src/main/java/org/vafer/jdeb/producers/DataProducerArchive.java
index 8160759..ed3d8cb 100644
--- a/src/main/java/org/vafer/jdeb/producers/DataProducerArchive.java
+++ b/src/main/java/org/vafer/jdeb/producers/DataProducerArchive.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/vafer/jdeb/producers/DataProducerDirectory.java b/src/main/java/org/vafer/jdeb/producers/DataProducerDirectory.java
index 70eff71..8bece12 100644
--- a/src/main/java/org/vafer/jdeb/producers/DataProducerDirectory.java
+++ b/src/main/java/org/vafer/jdeb/producers/DataProducerDirectory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -60,30 +60,19 @@ public final class DataProducerDirectory extends AbstractDataProducer implements
                 continue;
             }
 
-            if (!isIncluded(dirname)) {
-                continue;
-            }
-
             if ('/' != File.separatorChar) {
                 dirname = dirname.replace(File.separatorChar, '/');
             }
 
+            if (!isIncluded(dirname)) {
+                continue;
+            }
+
             if (!dirname.endsWith("/")) {
                 dirname += "/";
             }
 
-            TarArchiveEntry entry = new TarArchiveEntry(dirname, true);
-            entry.setUserId(0);
-            entry.setUserName("root");
-            entry.setGroupId(0);
-            entry.setGroupName("root");
-            entry.setMode(TarArchiveEntry.DEFAULT_DIR_MODE);
-
-            entry = map(entry);
-
-            entry.setSize(0);
-
-            pReceiver.onEachDir(entry.getName(), entry.getLinkName(), entry.getUserName(), entry.getUserId(), entry.getGroupName(), entry.getGroupId(), entry.getMode(), entry.getSize());
+            produceDir(pReceiver, dirname);
         }
 
 
@@ -91,31 +80,15 @@ public final class DataProducerDirectory extends AbstractDataProducer implements
             final File file = new File(baseDir, f);
             String filename = getFilename(baseDir, file);
 
-            if (!isIncluded(filename)) {
-                continue;
-            }
-
             if ('/' != File.separatorChar) {
                 filename = filename.replace(File.separatorChar, '/');
             }
 
-            TarArchiveEntry entry = new TarArchiveEntry(filename, true);
-            entry.setUserId(0);
-            entry.setUserName("root");
-            entry.setGroupId(0);
-            entry.setGroupName("root");
-            entry.setMode(TarArchiveEntry.DEFAULT_FILE_MODE);
-
-            entry = map(entry);
-
-            entry.setSize(file.length());
-
-            final InputStream inputStream = new FileInputStream(file);
-            try {
-                pReceiver.onEachFile(inputStream, entry.getName(), entry.getLinkName(), entry.getUserName(), entry.getUserId(), entry.getGroupName(), entry.getGroupId(), entry.getMode(), entry.getSize());
-            } finally {
-                inputStream.close();
+            if (!isIncluded(filename)) {
+                continue;
             }
+
+            produceFile(pReceiver, file, filename);
         }
     }
 
diff --git a/src/main/java/org/vafer/jdeb/producers/DataProducerFile.java b/src/main/java/org/vafer/jdeb/producers/DataProducerFile.java
index 17fb3bd..7bea326 100644
--- a/src/main/java/org/vafer/jdeb/producers/DataProducerFile.java
+++ b/src/main/java/org/vafer/jdeb/producers/DataProducerFile.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -51,24 +51,13 @@ public final class DataProducerFile extends AbstractDataProducer implements Data
             fileName = file.getName();
         }
 
-        TarArchiveEntry entry = new TarArchiveEntry(fileName, true);
-        entry.setUserId(0);
-        entry.setUserName("root");
-        entry.setGroupId(0);
-        entry.setGroupName("root");
-        entry.setMode(TarArchiveEntry.DEFAULT_FILE_MODE);
+        TarArchiveEntry entry = Producers.defaultFileEntryWithName(fileName);
 
         entry = map(entry);
 
         entry.setSize(file.length());
 
-        final InputStream inputStream = new FileInputStream(file);
-        try {
-            pReceiver.onEachFile(inputStream, entry.getName(), entry.getLinkName(), entry.getUserName(), entry.getUserId(), entry.getGroupName(), entry.getGroupId(), entry.getMode(), entry.getSize());
-        } finally {
-            inputStream.close();
-        }
-
+        Producers.produceInputStreamWithEntry(pReceiver, new FileInputStream(file), entry);
     }
 
 }
diff --git a/src/main/java/org/vafer/jdeb/producers/DataProducerFileSet.java b/src/main/java/org/vafer/jdeb/producers/DataProducerFileSet.java
index b4eaf4d..cb921c9 100644
--- a/src/main/java/org/vafer/jdeb/producers/DataProducerFileSet.java
+++ b/src/main/java/org/vafer/jdeb/producers/DataProducerFileSet.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -42,10 +42,10 @@ public final class DataProducerFileSet implements DataProducer {
     }
 
     public void produce( final DataConsumer pReceiver ) throws IOException {
-        String user = "root";
-        int uid = 0;
-        String group = "root";
-        int gid = 0;
+        String user = Producers.ROOT_NAME;
+        int uid = Producers.ROOT_UID;
+        String group = Producers.ROOT_NAME;
+        int gid = Producers.ROOT_UID;
         int filemode = TarEntry.DEFAULT_FILE_MODE;
         int dirmode = TarEntry.DEFAULT_DIR_MODE;
         String prefix = "";
diff --git a/src/main/java/org/vafer/jdeb/producers/DataProducerFiles.java b/src/main/java/org/vafer/jdeb/producers/DataProducerFiles.java
new file mode 100644
index 0000000..cd89c7b
--- /dev/null
+++ b/src/main/java/org/vafer/jdeb/producers/DataProducerFiles.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2014 The jdeb developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.vafer.jdeb.producers;
+
+import org.vafer.jdeb.DataConsumer;
+import org.vafer.jdeb.mapping.Mapper;
+import org.vafer.jdeb.utils.Utils;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Data producer that places multiple files into a single
+ * destination directory.
+ *
+ * @author Roman Kashitsyn
+ */
+public class DataProducerFiles extends AbstractDataProducer {
+
+    private final String[] files;
+    private final String destDir;
+
+    public DataProducerFiles( final String[] files,
+                              final String destDir,
+                              final Mapper[] mappers ) {
+        super(null, null, mappers);
+        this.files = files;
+        this.destDir = destDir;
+    }
+
+    @Override
+    public void produce( DataConsumer receiver ) throws IOException {
+        boolean hasDestDir = !Utils.isNullOrEmpty(destDir);
+
+        for (String fileName : files) {
+            File f = new File(fileName);
+
+            if (hasDestDir) {
+                fileName = Utils.movePath(fileName, destDir);
+            }
+
+            produceFile(receiver, f, fileName);
+        }
+    }
+}
diff --git a/src/main/java/org/vafer/jdeb/producers/DataProducerLink.java b/src/main/java/org/vafer/jdeb/producers/DataProducerLink.java
index 69c851f..c5f5180 100644
--- a/src/main/java/org/vafer/jdeb/producers/DataProducerLink.java
+++ b/src/main/java/org/vafer/jdeb/producers/DataProducerLink.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -41,15 +41,14 @@ public final class DataProducerLink extends AbstractDataProducer implements Data
         this.linkName = linkName;
     }
 
-    @Override
     public void produce( final DataConsumer pReceiver ) throws IOException {
         TarArchiveEntry entry = new TarArchiveEntry(path, symlink ? TarArchiveEntry.LF_SYMLINK : TarArchiveEntry.LF_LINK);
         entry.setLinkName(linkName);
 
-        entry.setUserId(0);
-        entry.setUserName("root");
-        entry.setGroupId(0);
-        entry.setGroupName("root");
+        entry.setUserId(Producers.ROOT_UID);
+        entry.setUserName(Producers.ROOT_NAME);
+        entry.setGroupId(Producers.ROOT_UID);
+        entry.setGroupName(Producers.ROOT_NAME);
         entry.setMode(TarArchiveEntry.DEFAULT_FILE_MODE);
 
         entry = map(entry);
diff --git a/src/main/java/org/vafer/jdeb/producers/DataProducerPathTemplate.java b/src/main/java/org/vafer/jdeb/producers/DataProducerPathTemplate.java
index bd6cfd0..b38343b 100644
--- a/src/main/java/org/vafer/jdeb/producers/DataProducerPathTemplate.java
+++ b/src/main/java/org/vafer/jdeb/producers/DataProducerPathTemplate.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -33,18 +33,7 @@ public class DataProducerPathTemplate extends AbstractDataProducer implements Da
 
     public void produce( DataConsumer pReceiver ) throws IOException {
         for (String literalPath : literalPaths) {
-            TarArchiveEntry entry = new TarArchiveEntry(literalPath, true);
-            entry.setUserId(0);
-            entry.setUserName("root");
-            entry.setGroupId(0);
-            entry.setGroupName("root");
-            entry.setMode(TarArchiveEntry.DEFAULT_DIR_MODE);
-
-            entry = map(entry);
-
-            entry.setSize(0);
-
-            pReceiver.onEachDir(entry.getName(), entry.getLinkName(), entry.getUserName(), entry.getUserId(), entry.getGroupName(), entry.getGroupId(), entry.getMode(), entry.getSize());
+            produceDir(pReceiver, literalPath);
         }
     }
 
diff --git a/src/main/java/org/vafer/jdeb/producers/Producers.java b/src/main/java/org/vafer/jdeb/producers/Producers.java
new file mode 100644
index 0000000..bc1d4f0
--- /dev/null
+++ b/src/main/java/org/vafer/jdeb/producers/Producers.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2014 The jdeb developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.vafer.jdeb.producers;
+
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+
+import org.apache.commons.io.IOUtils;
+import org.vafer.jdeb.DataConsumer;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Package-private utility class with common producers functionality.
+ *
+ * @author Roman Kashitsyn <roman.kashitsyn at gmail.com>
+ */
+class Producers {
+
+    final static int ROOT_UID = 0;
+    final static String ROOT_NAME = "root";
+
+    private Producers() {}
+
+
+    /**
+     * Creates a tar file entry with defaults parameters.
+     * @param entryName the entry name
+     * @return file entry with reasonable defaults
+     */
+    static TarArchiveEntry defaultFileEntryWithName( final String entryName ) {
+        TarArchiveEntry entry = new TarArchiveEntry(entryName, true);
+        entry.setUserId(ROOT_UID);
+        entry.setUserName(ROOT_NAME);
+        entry.setGroupId(ROOT_UID);
+        entry.setGroupName(ROOT_NAME);
+        entry.setMode(TarArchiveEntry.DEFAULT_FILE_MODE);
+        return entry;
+    }
+
+    /**
+     * Creates a tar directory entry with defaults parameters.
+     * @param dirName the directory name
+     * @return dir entry with reasonable defaults
+     */
+    static TarArchiveEntry defaultDirEntryWithName( final String dirName ) {
+        TarArchiveEntry entry = new TarArchiveEntry(dirName, true);
+        entry.setUserId(ROOT_UID);
+        entry.setUserName(ROOT_NAME);
+        entry.setGroupId(ROOT_UID);
+        entry.setGroupName(ROOT_NAME);
+        entry.setMode(TarArchiveEntry.DEFAULT_DIR_MODE);
+        return entry;
+    }
+
+    /**
+     * Forwards tar archive entry entry to a consumer.
+     * @param consumer the consumer
+     * @param entry the entry to pass
+     * @throws IOException
+     */
+    static void produceDirEntry( final DataConsumer consumer,
+                                 final TarArchiveEntry entry ) throws IOException {
+        consumer.onEachDir(
+                entry.getName(),
+                entry.getLinkName(),
+                entry.getUserName(),
+                entry.getUserId(),
+                entry.getGroupName(),
+                entry.getGroupId(),
+                entry.getMode(),
+                entry.getSize()
+        );
+    }
+
+
+    /**
+     * Feeds input stream to data consumer using metadata from tar entry.
+     * @param consumer the consumer
+     * @param inputStream the stream to feed
+     * @param entry the entry to use for metadata
+     * @throws IOException on consume error
+     */
+    static void produceInputStreamWithEntry( final DataConsumer consumer,
+                                             final InputStream inputStream,
+                                             final TarArchiveEntry entry ) throws IOException {
+        try {
+            consumer.onEachFile(inputStream,
+                    entry.getName(),
+                    entry.getLinkName(),
+                    entry.getUserName(),
+                    entry.getUserId(),
+                    entry.getGroupName(),
+                    entry.getGroupId(),
+                    entry.getMode(),
+                    entry.getSize()
+            );
+        } finally {
+            IOUtils.closeQuietly(inputStream);
+        }
+    }
+
+}
diff --git a/src/main/java/org/vafer/jdeb/signing/PGPSigner.java b/src/main/java/org/vafer/jdeb/signing/PGPSigner.java
index b2af9e5..83a6aed 100644
--- a/src/main/java/org/vafer/jdeb/signing/PGPSigner.java
+++ b/src/main/java/org/vafer/jdeb/signing/PGPSigner.java
@@ -1,152 +1,176 @@
-/*
- * Copyright 2013 The jdeb developers.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.vafer.jdeb.signing;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.nio.charset.Charset;
-import java.security.GeneralSecurityException;
-import java.util.Iterator;
-
-import org.bouncycastle.bcpg.ArmoredOutputStream;
-import org.bouncycastle.bcpg.BCPGOutputStream;
-import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPPrivateKey;
-import org.bouncycastle.openpgp.PGPSecretKey;
-import org.bouncycastle.openpgp.PGPSecretKeyRing;
-import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
-import org.bouncycastle.openpgp.PGPSignature;
-import org.bouncycastle.openpgp.PGPSignatureGenerator;
-import org.bouncycastle.openpgp.PGPUtil;
-import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
-import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
-import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
-
-/**
- * Signing with OpenPGP.
- * 
- * @author Torsten Curdt
- * @author Emmanuel Bourg
- */
-public class PGPSigner {
-
-    private static final byte[] EOL = "\r\n".getBytes(Charset.forName("UTF-8"));
-
-    private PGPPrivateKey privateKey;
-
-    public PGPSigner(InputStream keyring, String keyId, String passphrase) throws IOException, PGPException {
-        PGPSecretKey secretKey = getSecretKey(keyring, keyId);
-        privateKey = secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passphrase.toCharArray()));
-    }
-
-    /**
-     * Creates a clear sign signature over the input data. (Not detached)
-     *
-     * @param input      the content to be signed
-     * @param output     the output destination of the signature
-     */
-    public void clearSign(String input, OutputStream output) throws IOException, PGPException, GeneralSecurityException {
-        clearSign(new ByteArrayInputStream(input.getBytes("UTF-8")), output);
-    }
-
-    /**
-     * Creates a clear sign signature over the input data. (Not detached)
-     *
-     * @param input      the content to be signed
-     * @param output     the output destination of the signature
-     */
-    public void clearSign(InputStream input, OutputStream output) throws IOException, PGPException, GeneralSecurityException {
-        int digest = PGPUtil.SHA1;
-        
-        PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(privateKey.getPublicKeyPacket().getAlgorithm(), digest));
-        signatureGenerator.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, privateKey);
-        
-        ArmoredOutputStream armoredOutput = new ArmoredOutputStream(output);
-        armoredOutput.beginClearText(digest);
-        
-        BufferedReader reader = new BufferedReader(new InputStreamReader(input));
-        
-        String line;
-        while ((line = reader.readLine()) != null) {
-            // trailing spaces must be removed for signature calculation (see http://tools.ietf.org/html/rfc4880#section-7.1)
-            byte[] data = trim(line).getBytes("UTF-8");
-            
-            armoredOutput.write(data);
-            armoredOutput.write(EOL);
-            
-            signatureGenerator.update(data);
-            signatureGenerator.update(EOL);
-        }
-
-        armoredOutput.endClearText();
-        
-        PGPSignature signature = signatureGenerator.generate();
-        signature.encode(new BCPGOutputStream(armoredOutput));
-        
-        armoredOutput.close();
-    }
-    
-    /**
-     * Returns the secret key matching the specified identifier.
-     * 
-     * @param input the input stream containing the keyring collection
-     * @param keyId the 4 bytes identifier of the key
-     */
-    private PGPSecretKey getSecretKey(InputStream input, String keyId) throws IOException, PGPException {
-        PGPSecretKeyRingCollection keyrings = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(input));
-        
-        Iterator rIt = keyrings.getKeyRings();
-
-        while (rIt.hasNext()) {
-            PGPSecretKeyRing kRing = (PGPSecretKeyRing) rIt.next();
-            Iterator kIt = kRing.getSecretKeys();
-
-            while (kIt.hasNext()) {
-                PGPSecretKey key = (PGPSecretKey) kIt.next();
-                
-                if (key.isSigningKey() && Long.toHexString(key.getKeyID() & 0xFFFFFFFFL).equals(keyId.toLowerCase())) {
-                    return key;
-                }
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Trim the trailing spaces.
-     * 
-     * @param line
-     */
-    private String trim(String line) {
-        char[] chars = line.toCharArray();
-        int len = chars.length;
-
-        while (len > 0) {
-            if (!Character.isWhitespace(chars[len - 1])) {
-                break;
-            }
-            len--;
-        }
-        
-        return line.substring(0, len);
-    }
-}
+/*
+ * Copyright 2014 The jdeb developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.vafer.jdeb.signing;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.security.GeneralSecurityException;
+import java.util.Iterator;
+
+import org.apache.commons.io.LineIterator;
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
+
+/**
+ * Signing with OpenPGP.
+ * 
+ * @author Torsten Curdt
+ * @author Emmanuel Bourg
+ */
+public class PGPSigner {
+
+    private static final byte[] EOL = "\n".getBytes(Charset.forName("UTF-8"));
+
+    private PGPSecretKey secretKey;
+    private PGPPrivateKey privateKey;
+
+    public PGPSigner(InputStream keyring, String keyId, String passphrase) throws IOException, PGPException {
+        secretKey = getSecretKey(keyring, keyId);
+        if(secretKey == null)
+        {
+            throw new PGPException(String.format("Specified key %s does not exist in key ring %s", keyId, keyring));
+        }
+        privateKey = secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passphrase.toCharArray()));
+    }
+
+    /**
+     * Creates a clear sign signature over the input data. (Not detached)
+     *
+     * @param input      the content to be signed
+     * @param output     the output destination of the signature
+     */
+    public void clearSign(String input, OutputStream output) throws IOException, PGPException, GeneralSecurityException {
+        clearSign(new ByteArrayInputStream(input.getBytes("UTF-8")), output);
+    }
+
+    /**
+     * Creates a clear sign signature over the input data. (Not detached)
+     *
+     * @param input      the content to be signed
+     * @param output     the output destination of the signature
+     */
+    public void clearSign(InputStream input, OutputStream output) throws IOException, PGPException, GeneralSecurityException {
+        int digest = PGPUtil.SHA1;
+        
+        PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(privateKey.getPublicKeyPacket().getAlgorithm(), digest));
+        signatureGenerator.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, privateKey);
+        
+        ArmoredOutputStream armoredOutput = new ArmoredOutputStream(output);
+        armoredOutput.beginClearText(digest);
+        
+        LineIterator iterator = new LineIterator(new InputStreamReader(input));
+        
+        while (iterator.hasNext()) {
+            String line = iterator.nextLine();
+            
+            // trailing spaces must be removed for signature calculation (see http://tools.ietf.org/html/rfc4880#section-7.1)
+            byte[] data = trim(line).getBytes("UTF-8");
+            
+            armoredOutput.write(data);
+            armoredOutput.write(EOL);
+            
+            signatureGenerator.update(data);
+            if (iterator.hasNext()) {
+                signatureGenerator.update(EOL);
+            }
+        }
+
+        armoredOutput.endClearText();
+        
+        PGPSignature signature = signatureGenerator.generate();
+        signature.encode(new BCPGOutputStream(armoredOutput));
+        
+        armoredOutput.close();
+    }
+    
+    /**
+     * Returns the secret key.
+     */
+    public PGPSecretKey getSecretKey()
+    {
+        return secretKey;
+    }
+
+    /**
+     * Returns the private key.
+     */
+    public PGPPrivateKey getPrivateKey()
+    {
+        return privateKey;
+    }
+
+    /**
+     * Returns the secret key matching the specified identifier.
+     * 
+     * @param input the input stream containing the keyring collection
+     * @param keyId the 4 bytes identifier of the key
+     */
+    private PGPSecretKey getSecretKey(InputStream input, String keyId) throws IOException, PGPException {
+        PGPSecretKeyRingCollection keyrings = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(input));
+        
+        Iterator rIt = keyrings.getKeyRings();
+
+        while (rIt.hasNext()) {
+            PGPSecretKeyRing kRing = (PGPSecretKeyRing) rIt.next();
+            Iterator kIt = kRing.getSecretKeys();
+
+            while (kIt.hasNext()) {
+                PGPSecretKey key = (PGPSecretKey) kIt.next();
+                
+                if (key.isSigningKey() && Long.toHexString(key.getKeyID() & 0xFFFFFFFFL).equals(keyId.toLowerCase())) {
+                    return key;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Trim the trailing spaces.
+     * 
+     * @param line
+     */
+    private String trim(String line) {
+        char[] chars = line.toCharArray();
+        int len = chars.length;
+
+        while (len > 0) {
+            if (!Character.isWhitespace(chars[len - 1])) {
+                break;
+            }
+            len--;
+        }
+        
+        return line.substring(0, len);
+    }
+}
diff --git a/src/main/java/org/vafer/jdeb/utils/FilteredFile.java b/src/main/java/org/vafer/jdeb/utils/FilteredFile.java
index c6f2beb..665e546 100644
--- a/src/main/java/org/vafer/jdeb/utils/FilteredFile.java
+++ b/src/main/java/org/vafer/jdeb/utils/FilteredFile.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -25,19 +25,19 @@ import java.util.List;
 
 public class FilteredFile {
 
-    private static String openToken = "[[";
-    private static String closeToken = "]]";
+    private String openToken = "[[";
+    private String closeToken = "]]";
     private List<String> lines = new ArrayList<String>();
 
     public FilteredFile(InputStream in, VariableResolver resolver) throws IOException {
         parse(in, resolver);
     }
 
-    public static void setOpenToken(String token) {
+    public void setOpenToken(String token) {
         openToken = token;
     }
 
-    public static void setCloseToken(String token) {
+    public void setCloseToken(String token) {
         closeToken = token;
     }
 
diff --git a/src/main/java/org/vafer/jdeb/utils/InformationInputStream.java b/src/main/java/org/vafer/jdeb/utils/InformationInputStream.java
index 7907176..ac653ac 100644
--- a/src/main/java/org/vafer/jdeb/utils/InformationInputStream.java
+++ b/src/main/java/org/vafer/jdeb/utils/InformationInputStream.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/vafer/jdeb/utils/InformationOutputStream.java b/src/main/java/org/vafer/jdeb/utils/InformationOutputStream.java
index 63f11c4..1db72ee 100644
--- a/src/main/java/org/vafer/jdeb/utils/InformationOutputStream.java
+++ b/src/main/java/org/vafer/jdeb/utils/InformationOutputStream.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/vafer/jdeb/utils/MapVariableResolver.java b/src/main/java/org/vafer/jdeb/utils/MapVariableResolver.java
index de50343..deb6d72 100644
--- a/src/main/java/org/vafer/jdeb/utils/MapVariableResolver.java
+++ b/src/main/java/org/vafer/jdeb/utils/MapVariableResolver.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/vafer/jdeb/utils/PGPSignatureOutputStream.java b/src/main/java/org/vafer/jdeb/utils/PGPSignatureOutputStream.java
new file mode 100644
index 0000000..854408d
--- /dev/null
+++ b/src/main/java/org/vafer/jdeb/utils/PGPSignatureOutputStream.java
@@ -0,0 +1,57 @@
+package org.vafer.jdeb.utils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+
+/**
+ * An output stream that calculates the signature of the input data as it
+ * is written
+ * 
+ * @author mpoindexter
+ *
+ */
+public class PGPSignatureOutputStream extends OutputStream {
+    private final PGPSignatureGenerator signatureGenerator;
+
+    public PGPSignatureOutputStream( PGPSignatureGenerator signatureGenerator ) {
+        super();
+        this.signatureGenerator = signatureGenerator;
+    }
+
+    public void write( int b ) throws IOException {
+        signatureGenerator.update(new byte[] { (byte)b });
+    }
+
+    public void write( byte[] b ) throws IOException {
+        signatureGenerator.update(b);
+    }
+
+    public void write( byte[] b, int off, int len ) throws IOException {
+        signatureGenerator.update(b, off, len);
+    }
+    
+    public PGPSignature generateSignature() throws PGPException {
+        return signatureGenerator.generate();
+    }
+    
+    public String generateASCIISignature() throws PGPException {
+        try {
+            PGPSignature signature = generateSignature();
+            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+            ArmoredOutputStream armorStream = new ArmoredOutputStream(buffer);
+            signature.encode(armorStream);
+            armorStream.close();
+            return new String(buffer.toByteArray());
+        } catch(IOException e) {
+            //Should never happen since we are just using a memory buffer
+            throw new RuntimeException(e);
+        }
+    }
+
+} 
\ No newline at end of file
diff --git a/src/main/java/org/vafer/jdeb/utils/Utils.java b/src/main/java/org/vafer/jdeb/utils/Utils.java
index b078f22..987d5fa 100644
--- a/src/main/java/org/vafer/jdeb/utils/Utils.java
+++ b/src/main/java/org/vafer/jdeb/utils/Utils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,12 +16,20 @@
 package org.vafer.jdeb.utils;
 
 import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
-import java.util.Date;
 import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.apache.tools.ant.filters.FixCrLfFilter;
 import org.apache.tools.ant.util.ReaderInputStream;
@@ -34,6 +42,9 @@ import org.apache.tools.ant.util.ReaderInputStream;
  * @author Torsten Curdt <tcurdt at vafer.org>
  */
 public final class Utils {
+    private static final Pattern BETA_PATTERN = Pattern.compile("(.*?)([\\.\\-_]?)(alpha|a|beta|b|milestone|m|cr|rc)(.*)", Pattern.CASE_INSENSITIVE);
+
+    private static final Pattern SNAPSHOT_PATTERN = Pattern.compile("(.*)[\\-\\+]SNAPSHOT");
 
     public static int copy( final InputStream pInput, final OutputStream pOutput ) throws IOException {
         final byte[] buffer = new byte[2048];
@@ -87,7 +98,6 @@ public final class Utils {
         return s;
     }
 
-
     /**
      * Substitute the variables in the given expression with the
      * values from the resolver
@@ -101,49 +111,71 @@ public final class Utils {
         
         final StringBuilder out = new StringBuilder();
         StringBuilder sb = new StringBuilder();
-        char[] watch = open;
-        int w = 0;
+        char[] last = null;
+        int wo = 0;
+        int wc = 0;
+        int level = 0;
         for (char c : pExpression.toCharArray()) {
-            if (c == watch[w]) {
-                w++;
-                if (watch.length == w) {
-                    // found the full token to watch for
-
-                    if (watch == open) {
-                        // found open
-                        out.append(sb);
-                        sb = new StringBuilder();
-                        // search for close
-                        watch = close;
-                    } else {
-                        // found close
+            if (c == open[wo]) {
+                wo++;
+                if (open.length == wo) {
+                    // found open
+                    if (last == open) {
+                        out.append(open);
+                    }
+                    level++;
+                    out.append(sb);
+                    sb = new StringBuilder();
+                    wo = 0;
+                    last = open;
+                }
+            } else if (c == close[wc]) {
+                wc++;
+                if (close.length == wc) {
+                    // found close
+                    if (last == open) {
                         final String variable = pResolver.get(sb.toString());
                         if (variable != null) {
                             out.append(variable);
                         } else {
-                            out.append(pOpen);
+                            out.append(open);
                             out.append(sb);
-                            out.append(pClose);
+                            out.append(close);
                         }
-                        sb = new StringBuilder();
-                        // search for open
-                        watch = open;
+                    } else {
+                        out.append(sb);
+                        out.append(close);
                     }
-                    w = 0;
+                    sb = new StringBuilder();
+                    level--;
+                    wc = 0;
+                    last = close;
                 }
             } else {
 
-                if (w > 0) {
-                    sb.append(watch, 0, w);
+                if (wo > 0) {
+                    sb.append(open, 0, wo);
+                }
+
+                if (wc > 0) {
+                    sb.append(close, 0, wc);
                 }
 
                 sb.append(c);
 
-                w = 0;
+                wo = wc = 0;
             }
         }
 
-        if (watch == close) {
+        if (wo > 0) {
+            sb.append(open, 0, wo);
+        }
+
+        if (wc > 0) {
+            sb.append(close, 0, wc);
+        }
+
+        if (level > 0) {
             out.append(open);
         }
         out.append(sb);
@@ -168,20 +200,179 @@ public final class Utils {
     }
 
     /**
-     * convert to debian version format
+     * Convert the project version to a version suitable for a Debian package.
+     * -SNAPSHOT suffixes are replaced with a timestamp (~yyyyMMddHHmmss).
+     * The separator before a rc, alpha or beta version is replaced with '~'
+     * such that the version is always ordered before the final or GA release.
+     * 
+     * @param version the project version to convert to a Debian package version
+     * @param timestamp the date used as the timestamp to replace the SNAPSHOT suffix
      */
-    public static String convertToDebianVersion( String version, Date timestamp ) {
-        version = version.replace('-', '+');
-        if (version.endsWith("+SNAPSHOT")) {
-            version = version.substring(0, version.length() - "+SNAPSHOT".length());
-            version += "~";
+    public static String convertToDebianVersion( String version, boolean apply, String envName, Date timestamp ) {
+        Matcher matcher = SNAPSHOT_PATTERN.matcher(version);
+        if (matcher.matches()) {
+            version = matcher.group(1) + "~";
 
-            if (timestamp != null) {
-                version += new SimpleDateFormat("yyyyMMddHHmmss").format(timestamp);
+            if (apply) {
+                final String envValue = System.getenv(envName);
+                final String snapshot = (envValue != null && envValue.length() > 0)
+                        ? envValue
+                        : new SimpleDateFormat("yyyyMMddHHmmss").format(timestamp);
+                version += snapshot;
             } else {
                 version += "SNAPSHOT";
             }
+        } else {
+            matcher = BETA_PATTERN.matcher(version);
+            if (matcher.matches()) {
+                version = matcher.group(1) + "~" + matcher.group(3) + matcher.group(4);
+            }
         }
+        
+        version = version.replace('-', '+');
+        
         return version;
     }
+
+    /**
+     * Construct new path by replacing file directory part. No
+     * files are actually modified.
+     * @param file path to move
+     * @param target new path directory
+     */
+    public static String movePath( final String file,
+                                   final String target ) {
+        final String name = new File(file).getName();
+        return target.endsWith("/") ? target + name : target + '/' + name;
+    }
+
+    /**
+     * Extracts value from map if given value is null.
+     * @param value current value
+     * @param props properties to extract value from
+     * @param key property name to extract
+     * @return initial value or value extracted from map
+     */
+    public static String lookupIfEmpty( final String value,
+                                        final Map<String, String> props,
+                                        final String key ) {
+        return value != null ? value : props.get(key);
+    }
+    
+    /**
+     * Get the known locations where the secure keyring can be located.
+     * Looks through known locations of the GNU PG secure keyring.
+     * 
+     * @return The location of the PGP secure keyring if it was found,
+     *         null otherwise
+     */
+    public static Collection<String> getKnownPGPSecureRingLocations() {
+        final LinkedHashSet<String> locations = new LinkedHashSet<String>();
+
+        final String os = System.getProperty("os.name");
+        final boolean runOnWindows = os == null || os.toLowerCase().contains("win");
+
+        if (runOnWindows) {
+            // The user's roaming profile on Windows, via environment
+            final String windowsRoaming = System.getenv("APPDATA");
+            if (windowsRoaming != null) {
+                locations.add(joinPaths(windowsRoaming, "gnupg", "secring.gpg"));
+            }
+
+            // The user's local profile on Windows, via environment
+            final String windowsLocal = System.getenv("LOCALAPPDATA");
+            if (windowsLocal != null) {
+                locations.add(joinPaths(windowsLocal, "gnupg", "secring.gpg"));
+            }
+
+            // The Windows installation directory
+            final String windir = System.getProperty("WINDIR");
+            if (windir != null) {
+                // Local Profile on Windows 98 and ME
+                locations.add(joinPaths(windir, "Application Data", "gnupg", "secring.gpg"));
+            }
+        }
+
+        final String home = System.getProperty("user.home");
+
+        if (home != null && runOnWindows) {
+            // These are for various flavours of Windows
+            // if the environment variables above have failed
+
+            // Roaming profile on Vista and later
+            locations.add(joinPaths(home, "AppData", "Roaming", "gnupg", "secring.gpg"));
+            // Local profile on Vista and later
+            locations.add(joinPaths(home, "AppData", "Local", "gnupg", "secring.gpg"));
+            // Roaming profile on 2000 and XP
+            locations.add(joinPaths(home, "Application Data", "gnupg", "secring.gpg"));
+            // Local profile on 2000 and XP
+            locations.add(joinPaths(home, "Local Settings", "Application Data", "gnupg", "secring.gpg"));
+        }
+
+        // *nix, including OS X
+        if (home != null) {
+            locations.add(joinPaths(home, ".gnupg", "secring.gpg"));
+        }
+
+        return locations;
+    }
+
+    /**
+     * Tries to guess location of the user secure keyring using various
+     * heuristics.
+     *
+     * @return path to the keyring file
+     * @throws FileNotFoundException if no keyring file found
+     */
+    public static File guessKeyRingFile() throws FileNotFoundException {
+        final Collection<String> possibleLocations = getKnownPGPSecureRingLocations();
+        for (final String location : possibleLocations) {
+            final File candidate = new File(location);
+            if (candidate.exists()) {
+                return candidate;
+            }
+        }
+        final StringBuilder message = new StringBuilder("Could not locate secure keyring, locations tried: ");
+        final Iterator<String> it = possibleLocations.iterator();
+        while (it.hasNext()) {
+            message.append(it.next());
+            if (it.hasNext()) {
+                message.append(", ");
+            }
+        }
+        throw new FileNotFoundException(message.toString());
+    }
+
+    /**
+     * Join together path elements with File.separator. Filters out null
+     * elements.
+     * 
+     * @param elements The path elements to join
+     * @return elements concatenated together with File.separator
+     */
+    public static String joinPaths(String... elements) {
+        StringBuilder builder = new StringBuilder();
+        boolean first = true;
+        for (String element : elements) {
+            // Skip null elements
+            if (element == null) {
+                // This won't change the value of first if we skip elements
+                // in the beginning of the array
+                continue;
+            }
+            if (!first) {
+                builder.append(File.separatorChar);
+            }
+            builder.append(element);
+            first = false;
+        }
+        return builder.toString();
+    }
+
+    /**
+     * Returns true if string is null or empty.
+     */
+    public static boolean isNullOrEmpty(final String str) {
+        return str == null || str.length() == 0;
+    }
 }
diff --git a/src/main/java/org/vafer/jdeb/utils/VariableResolver.java b/src/main/java/org/vafer/jdeb/utils/VariableResolver.java
index 9e71ce8..4c05e66 100644
--- a/src/main/java/org/vafer/jdeb/utils/VariableResolver.java
+++ b/src/main/java/org/vafer/jdeb/utils/VariableResolver.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/main/resources/META-INF/plexus/components.xml b/src/main/resources/META-INF/plexus/components.xml
new file mode 100644
index 0000000..7222b9c
--- /dev/null
+++ b/src/main/resources/META-INF/plexus/components.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+
+<component-set>
+  <components>
+    <component>
+      <role>org.sonatype.plexus.components.sec.dispatcher.SecDispatcher</role>
+      <role-hint>jdeb-sec</role-hint>
+      <implementation>org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher</implementation>
+      <requirements>
+        <requirement>
+          <role>org.sonatype.plexus.components.cipher.PlexusCipher</role>
+          <field-name>_cipher</field-name>
+        </requirement>
+      </requirements>
+      <configuration>
+        <_configuration-file>~/.m2/settings-security.xml</_configuration-file>
+      </configuration>
+    </component>
+    <component>
+      <role>org.sonatype.plexus.components.cipher.PlexusCipher</role>
+      <role-hint>jdeb-sec</role-hint>
+      <implementation>org.sonatype.plexus.components.cipher.DefaultPlexusCipher</implementation>
+    </component>
+
+    <!-- deb packaging extension -->
+    <component>
+      <role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role>
+      <role-hint>deb</role-hint>
+      <implementation>org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping</implementation>
+      <configuration>
+        <phases>
+          <process-resources>org.apache.maven.plugins:maven-resources-plugin:resources</process-resources>
+          <package>org.vafer:jdeb:jdeb</package>
+          <install>org.apache.maven.plugins:maven-install-plugin:install</install>
+          <deploy>org.apache.maven.plugins:maven-deploy-plugin:deploy</deploy>
+        </phases>
+      </configuration>
+    </component>
+    <component>
+      <role>org.apache.maven.artifact.handler.ArtifactHandler</role>
+      <role-hint>deb</role-hint>
+      <implementation>org.apache.maven.artifact.handler.DefaultArtifactHandler</implementation>
+      <configuration>
+        <type>deb</type>
+        <extension>deb</extension>
+        <packaging>deb</packaging>
+      </configuration>
+    </component>
+  </components>
+</component-set>
\ No newline at end of file
diff --git a/src/test/java/org/vafer/jdeb/ArchiveVisitor.java b/src/test/java/org/vafer/jdeb/ArchiveVisitor.java
index 5c3ebc1..3eb7414 100644
--- a/src/test/java/org/vafer/jdeb/ArchiveVisitor.java
+++ b/src/test/java/org/vafer/jdeb/ArchiveVisitor.java
@@ -1,31 +1,31 @@
-/*
- * Copyright 2013 Emmanuel Bourg
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.vafer.jdeb;
-
-import java.io.IOException;
-
-import org.apache.commons.compress.archivers.ArchiveEntry;
-
-/**
- * Callback used for inspecting an archive.
- * 
- * @author Emmanuel Bourg
- */
-public interface ArchiveVisitor<E extends ArchiveEntry> {
-    
-    void visit(E entry, byte[] content) throws IOException;
-}
+/*
+ * Copyright 2014 The jdeb developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.vafer.jdeb;
+
+import java.io.IOException;
+
+import org.apache.commons.compress.archivers.ArchiveEntry;
+
+/**
+ * Callback used for inspecting an archive.
+ * 
+ * @author Emmanuel Bourg
+ */
+public interface ArchiveVisitor<E extends ArchiveEntry> {
+    
+    void visit(E entry, byte[] content) throws IOException;
+}
diff --git a/src/test/java/org/vafer/jdeb/ArchiveWalker.java b/src/test/java/org/vafer/jdeb/ArchiveWalker.java
index 4fa02ee..2feb423 100644
--- a/src/test/java/org/vafer/jdeb/ArchiveWalker.java
+++ b/src/test/java/org/vafer/jdeb/ArchiveWalker.java
@@ -1,96 +1,113 @@
-/*
- * Copyright 2013 Emmanuel Bourg
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.vafer.jdeb;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.zip.GZIPInputStream;
-
-import org.apache.commons.compress.archivers.ArchiveEntry;
-import org.apache.commons.compress.archivers.ArchiveInputStream;
-import org.apache.commons.compress.archivers.ar.ArArchiveEntry;
-import org.apache.commons.compress.archivers.ar.ArArchiveInputStream;
-import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
-import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
-import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
-import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
-
-/**
- * Support class for inspecting the content of an archive.
- * 
- * @author Emmanuel Bourg
- */
-public class ArchiveWalker {
-    
-    public static void walk(ArchiveInputStream in, ArchiveVisitor visitor) throws IOException {
-        try {
-            ArchiveEntry entry;
-            while ((entry = in.getNextEntry()) != null) {
-                byte[] content = new byte[(int) entry.getSize()];
-                if (entry.getSize() > 0) {
-                    int length = in.read(content);
-                    if (length != entry.getSize()) {
-                        throw new IOException("Couldn't read entry " + entry.getName() + " : read " + length + ", expected " + entry.getSize());
-                    }
-                }
-                
-                visitor.visit(entry, content);
-            }
-        } finally {
-            in.close();
-        }
-    }
-
-    public static boolean walkControl(File deb, final ArchiveVisitor<TarArchiveEntry> visitor) throws IOException {
-        return walkEmbedded(deb, "control.tar", visitor, Compression.GZIP);
-    }
-
-    public static boolean walkData(File deb, final ArchiveVisitor<TarArchiveEntry> visitor, final Compression compression) throws IOException {
-        return walkEmbedded(deb, "data.tar", visitor, compression);
-    }
-
-    public static boolean walkEmbedded(File deb, final String name, final ArchiveVisitor<TarArchiveEntry> visitor, final Compression compression) throws IOException {
-        final AtomicBoolean found = new AtomicBoolean(false);
-        ArArchiveInputStream in = new ArArchiveInputStream(new FileInputStream(deb));
-        ArchiveWalker.walk(in, new ArchiveVisitor<ArArchiveEntry>() {
-            public void visit(ArArchiveEntry entry, byte[] content) throws IOException {
-                if (entry.getName().equals(name + compression.getExtension())) {
-                    InputStream in = new ByteArrayInputStream(content);
-                    if (compression == Compression.GZIP) {
-                        in = new GZIPInputStream(in);
-                    } else if (compression == Compression.XZ) {
-                        in = new XZCompressorInputStream(in);
-                    } else if (compression == Compression.BZIP2) {
-                        in = new BZip2CompressorInputStream(in);
-                    }
-                    
-                    ArchiveWalker.walk(new TarArchiveInputStream(in), new ArchiveVisitor<TarArchiveEntry>() {
-                        public void visit(TarArchiveEntry entry, byte[] content) throws IOException {
-                            found.set(true);
-                            visitor.visit(entry, content);
-                        }
-                    });
-                }
-            }
-        });
-        return found.get();
-    }
-}
+/*
+ * Copyright 2014 The jdeb developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.vafer.jdeb;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.zip.GZIPInputStream;
+
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.ArchiveInputStream;
+import org.apache.commons.compress.archivers.ar.ArArchiveEntry;
+import org.apache.commons.compress.archivers.ar.ArArchiveInputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
+import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
+
+/**
+ * Support class for inspecting the content of an archive.
+ * 
+ * @author Emmanuel Bourg
+ */
+public class ArchiveWalker {
+    
+    public static void walk(ArchiveInputStream in, ArchiveVisitor visitor) throws IOException {
+        try {
+            ArchiveEntry entry;
+            while ((entry = in.getNextEntry()) != null) {
+                byte[] content = new byte[(int) entry.getSize()];
+                if (entry.getSize() > 0) {
+                    int length = in.read(content);
+                    if (length != entry.getSize()) {
+                        throw new IOException("Couldn't read entry " + entry.getName() + " : read " + length + ", expected " + entry.getSize());
+                    }
+                }
+                
+                visitor.visit(entry, content);
+            }
+        } finally {
+            in.close();
+        }
+    }
+
+    public static boolean walkControl(File deb, final ArchiveVisitor<TarArchiveEntry> visitor) throws IOException {
+        return walkEmbedded(deb, "control.tar", visitor, Compression.GZIP);
+    }
+
+    public static boolean walkData(File deb, final ArchiveVisitor<TarArchiveEntry> visitor, final Compression compression) throws IOException {
+        return walkEmbedded(deb, "data.tar", visitor, compression);
+    }
+
+    public static boolean walkEmbedded(File deb, final String name, final ArchiveVisitor<TarArchiveEntry> visitor, final Compression compression) throws IOException {
+        final AtomicBoolean found = new AtomicBoolean(false);
+        ArArchiveInputStream in = new ArArchiveInputStream(new FileInputStream(deb));
+        ArchiveWalker.walk(in, new ArchiveVisitor<ArArchiveEntry>() {
+            public void visit(ArArchiveEntry entry, byte[] content) throws IOException {
+                if (entry.getName().equals(name + compression.getExtension())) {
+                    InputStream in = new ByteArrayInputStream(content);
+                    if (compression == Compression.GZIP) {
+                        in = new GZIPInputStream(in);
+                    } else if (compression == Compression.XZ) {
+                        in = new XZCompressorInputStream(in);
+                    } else if (compression == Compression.BZIP2) {
+                        in = new BZip2CompressorInputStream(in);
+                    }
+                    
+                    ArchiveWalker.walk(new TarArchiveInputStream(in), new ArchiveVisitor<TarArchiveEntry>() {
+                        public void visit(TarArchiveEntry entry, byte[] content) throws IOException {
+                            found.set(true);
+                            visitor.visit(entry, content);
+                        }
+                    });
+                }
+            }
+        });
+        return found.get();
+    }
+    
+    public static boolean arArchiveContains(File archive, String filename) throws IOException {
+        ArchiveEntry entry;
+        ArArchiveInputStream tin;
+        
+        tin = new ArArchiveInputStream(new FileInputStream (archive));
+        
+        while ((entry = tin.getNextEntry()) != null) {
+            if(entry.getName().equals(filename)){
+            	tin.close();
+            	return true;
+            }
+        }
+        
+    	tin.close();
+        return false;
+    } 
+}
diff --git a/src/test/java/org/vafer/jdeb/DataBuilderTestCase.java b/src/test/java/org/vafer/jdeb/DataBuilderTestCase.java
index 5a0bc17..e3ac66a 100644
--- a/src/test/java/org/vafer/jdeb/DataBuilderTestCase.java
+++ b/src/test/java/org/vafer/jdeb/DataBuilderTestCase.java
@@ -1,82 +1,83 @@
-/*
- * Copyright 2013 The jdeb developers.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.vafer.jdeb;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.util.Arrays;
-
-import junit.framework.TestCase;
-import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
-import org.apache.tools.ant.Project;
-import org.apache.tools.ant.types.FileSet;
-import org.vafer.jdeb.producers.DataProducerFile;
-import org.vafer.jdeb.producers.DataProducerFileSet;
-
-public class DataBuilderTestCase extends TestCase {
-    
-    /**
-     * Checks if the file paths in the md5sums file use only unix file separators
-     * (this test can only fail on Windows)
-     */
-    public void testBuildDataWithFileSet() throws Exception {
-        DataBuilder builder = new DataBuilder(new NullConsole());
-
-        Project project = new Project();
-        project.setCoreLoader(getClass().getClassLoader());
-        project.init();
-
-        FileSet fileset = new FileSet();
-        fileset.setDir(new File(getClass().getResource("deb/data").toURI()));
-        fileset.setIncludes("**/*");
-        fileset.setProject(project);
-
-        StringBuilder md5s = new StringBuilder();
-        builder.buildData(Arrays.asList((DataProducer) new DataProducerFileSet(fileset)), new File("target/data.tar"), md5s, Compression.GZIP);
-
-        assertTrue("empty md5 file", md5s.length() > 0);
-        assertFalse("windows path separator found", md5s.indexOf("\\") != -1);
-    }
-    
-    public void testCreateParentDirectories() throws Exception {
-        File archive = new File("target/data.tar");
-        if (archive.exists()) {
-            archive.delete();
-        }
-        
-        DataBuilder builder = new DataBuilder(new NullConsole());
-        
-        DataProducer producer = new DataProducerFile(new File("pom.xml"), "/usr/share/myapp/pom.xml", null, null, null); 
-        
-        builder.buildData(Arrays.asList(producer), archive, new StringBuilder(), Compression.NONE);
-        
-        int count = 0;
-        TarArchiveInputStream in = null;
-        try {
-            in = new TarArchiveInputStream(new FileInputStream(archive));
-            while (in.getNextTarEntry() != null) {
-                count++;
-            }
-        } finally {
-            if (in != null) {
-                in.close();
-            }
-        }
-        
-        assertEquals("entries", 4, count);
-    }
-}
+/*
+ * Copyright 2014 The jdeb developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.vafer.jdeb;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.types.FileSet;
+import org.vafer.jdeb.producers.DataProducerFile;
+import org.vafer.jdeb.producers.DataProducerFileSet;
+
+public class DataBuilderTestCase extends TestCase {
+    
+    /**
+     * Checks if the file paths in the md5sums file use only unix file separators
+     * (this test can only fail on Windows)
+     */
+    public void testBuildDataWithFileSet() throws Exception {
+        DataBuilder builder = new DataBuilder(new NullConsole());
+
+        Project project = new Project();
+        project.setCoreLoader(getClass().getClassLoader());
+        project.init();
+
+        FileSet fileset = new FileSet();
+        fileset.setDir(new File(getClass().getResource("deb/data").toURI()));
+        fileset.setIncludes("**/*");
+        fileset.setProject(project);
+
+        StringBuilder md5s = new StringBuilder();
+        builder.buildData(Arrays.asList((DataProducer) new DataProducerFileSet(fileset)), new File("target/data.tar"), md5s, Compression.GZIP);
+
+        assertTrue("empty md5 file", md5s.length() > 0);
+        assertFalse("windows path separator found", md5s.indexOf("\\") != -1);
+        assertTrue("two spaces between md5 and file", md5s.toString().equals("8bc944dbd052ef51652e70a5104492e3  ./test/testfile\n"));
+    }
+    
+    public void testCreateParentDirectories() throws Exception {
+        File archive = new File("target/data.tar");
+        if (archive.exists()) {
+            archive.delete();
+        }
+        
+        DataBuilder builder = new DataBuilder(new NullConsole());
+        
+        DataProducer producer = new DataProducerFile(new File("pom.xml"), "/usr/share/myapp/pom.xml", null, null, null); 
+        
+        builder.buildData(Arrays.asList(producer), archive, new StringBuilder(), Compression.NONE);
+        
+        int count = 0;
+        TarArchiveInputStream in = null;
+        try {
+            in = new TarArchiveInputStream(new FileInputStream(archive));
+            while (in.getNextTarEntry() != null) {
+                count++;
+            }
+        } finally {
+            if (in != null) {
+                in.close();
+            }
+        }
+        
+        assertEquals("entries", 4, count);
+    }
+}
diff --git a/src/test/java/org/vafer/jdeb/DebMakerTestCase.java b/src/test/java/org/vafer/jdeb/DebMakerTestCase.java
index 7eaf17c..021c9e6 100644
--- a/src/test/java/org/vafer/jdeb/DebMakerTestCase.java
+++ b/src/test/java/org/vafer/jdeb/DebMakerTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@ public class DebMakerTestCase extends TestCase {
 
         File deb = File.createTempFile("jdeb", ".deb");
 
-        DebMaker maker = new DebMaker(new NullConsole(), Arrays.asList(data));
+        DebMaker maker = new DebMaker(new NullConsole(), Arrays.asList(data), null);
         maker.setControl(new File(getClass().getResource("deb/control").toURI()));
         maker.setDeb(deb);
         
@@ -88,7 +88,8 @@ public class DebMakerTestCase extends TestCase {
         }
         
         Collection<DataProducer> producers = Arrays.asList(new DataProducer[] {new EmptyDataProducer()});
-        DebMaker maker = new DebMaker(new NullConsole(), producers);
+        Collection<DataProducer> conffileProducers = Arrays.asList(new DataProducer[] {new EmptyDataProducer()});
+        DebMaker maker = new DebMaker(new NullConsole(), producers, conffileProducers);
         maker.setDeb(deb);
         maker.setControl(new File("target/test-classes/org/vafer/jdeb/deb/control"));
         
@@ -132,7 +133,8 @@ public class DebMakerTestCase extends TestCase {
         variables.put("version", "1.0");
         
         Collection<DataProducer> producers = Arrays.asList(new DataProducer[] {new EmptyDataProducer()});
-        DebMaker maker = new DebMaker(new NullConsole(), producers);
+        Collection<DataProducer> conffileProducers = Arrays.asList(new DataProducer[] {new EmptyDataProducer()});
+        DebMaker maker = new DebMaker(new NullConsole(), producers, conffileProducers);
         maker.setDeb(deb);
         maker.setControl(new File("target/test-classes/org/vafer/jdeb/deb/control"));
         maker.setResolver(new MapVariableResolver(variables));
diff --git a/src/test/java/org/vafer/jdeb/EmptyDataProducer.java b/src/test/java/org/vafer/jdeb/EmptyDataProducer.java
index 9b38ca6..c699572 100644
--- a/src/test/java/org/vafer/jdeb/EmptyDataProducer.java
+++ b/src/test/java/org/vafer/jdeb/EmptyDataProducer.java
@@ -1,28 +1,28 @@
-/*
- * Copyright 2013 The jdeb developers.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.vafer.jdeb;
-
-import java.io.IOException;
-
-/**
- * @author Emmanuel Bourg
- */
-public class EmptyDataProducer implements DataProducer {
-
-    public void produce(DataConsumer receiver) throws IOException {
-    }
-}
+/*
+ * Copyright 2014 The jdeb developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.vafer.jdeb;
+
+import java.io.IOException;
+
+/**
+ * @author Emmanuel Bourg
+ */
+public class EmptyDataProducer implements DataProducer {
+
+    public void produce(DataConsumer receiver) throws IOException {
+    }
+}
diff --git a/src/test/java/org/vafer/jdeb/NullConsole.java b/src/test/java/org/vafer/jdeb/NullConsole.java
index 97a8f85..22a1a77 100644
--- a/src/test/java/org/vafer/jdeb/NullConsole.java
+++ b/src/test/java/org/vafer/jdeb/NullConsole.java
@@ -1,28 +1,32 @@
-/*
- * Copyright 2013 The jdeb developers.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.vafer.jdeb;
-
-public class NullConsole implements Console {
-
-    @Override
-    public void info(String s) {
-    }
-
-    @Override
-    public void warn(String s) {
-    }
-}
+/*
+ * Copyright 2014 The jdeb developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.vafer.jdeb;
+
+public class NullConsole implements Console {
+
+    @Override
+    public void debug(String message) {
+    }
+
+    @Override
+    public void info(String message) {
+    }
+
+    @Override
+    public void warn(String message) {
+    }
+}
diff --git a/src/test/java/org/vafer/jdeb/ant/AntSelectorTestCase.java b/src/test/java/org/vafer/jdeb/ant/AntSelectorTestCase.java
index 05beec9..6a34f58 100644
--- a/src/test/java/org/vafer/jdeb/ant/AntSelectorTestCase.java
+++ b/src/test/java/org/vafer/jdeb/ant/AntSelectorTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/test/java/org/vafer/jdeb/ant/DebAntTaskTestCase.java b/src/test/java/org/vafer/jdeb/ant/DebAntTaskTestCase.java
index ee553ca..df6289e 100644
--- a/src/test/java/org/vafer/jdeb/ant/DebAntTaskTestCase.java
+++ b/src/test/java/org/vafer/jdeb/ant/DebAntTaskTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -93,12 +93,9 @@ public final class DebAntTaskTestCase extends TestCase {
     }
 
     public void testEmptyPackage() {
-        try {
-            project.executeTarget("empty-package");
-            fail("No exception thrown");
-        } catch (BuildException e) {
-            // expected
-        }
+        project.executeTarget("empty-package");
+
+        assertTrue("package not build", new File("target/test-classes/test.deb").exists());
     }
 
     public void testPackageWithArchive() {
@@ -225,6 +222,21 @@ public final class DebAntTaskTestCase extends TestCase {
         assertTrue("Link not found", linkFound.get());
     }
 
+    public void testMapper() throws Exception {
+        project.executeTarget("perm-mapper");
+
+        File deb = new File("target/test-classes/test.deb");
+        assertTrue("package not build", deb.exists());
+
+        ArchiveWalker.walkData(deb, new ArchiveVisitor<TarArchiveEntry>() {
+            public void visit(TarArchiveEntry entry, byte[] content) throws IOException {
+                if (entry.isFile()) {
+                    assertEquals("file mode (" + entry.getName() + ")", 0700, entry.getMode());
+                }
+            }
+        }, Compression.GZIP);
+    }
+
     public void testUnkownCompression() throws Exception {
         try {
             project.executeTarget("unknown-compression");
@@ -305,4 +317,10 @@ public final class DebAntTaskTestCase extends TestCase {
 
         assertTrue("tar file not found", found);
     }
+
+    public void testPackageConffiles() {
+        project.executeTarget("conffiles");
+
+        assertTrue("package not build", new File("target/test-classes/test.deb").exists());
+    }
 }
diff --git a/src/test/java/org/vafer/jdeb/changes/TextfileChangesProviderTestCase.java b/src/test/java/org/vafer/jdeb/changes/TextfileChangesProviderTestCase.java
index e36a3d9..f5a7a17 100644
--- a/src/test/java/org/vafer/jdeb/changes/TextfileChangesProviderTestCase.java
+++ b/src/test/java/org/vafer/jdeb/changes/TextfileChangesProviderTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/test/java/org/vafer/jdeb/debian/ChangesFileTestCase.java b/src/test/java/org/vafer/jdeb/debian/ChangesFileTestCase.java
index c8e68aa..60a7131 100644
--- a/src/test/java/org/vafer/jdeb/debian/ChangesFileTestCase.java
+++ b/src/test/java/org/vafer/jdeb/debian/ChangesFileTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,11 +26,13 @@ public final class ChangesFileTestCase extends TestCase {
         packageControlFile.set("Package", "test-package");
         packageControlFile.set("Description", "This is\na description\non several lines");
         packageControlFile.set("Version", "1.0");
+        packageControlFile.set("XC-UserDefinedField", "This is a user defined field.");
         
         ChangesFile changes = new ChangesFile();
         changes.setChanges(new ChangeSet[0]);
         changes.initialize(packageControlFile);
         
         assertEquals("1.0", changes.get("Version"));
+        assertEquals("This is a user defined field.", changes.get("UserDefinedField"));
     }
 }
diff --git a/src/test/java/org/vafer/jdeb/debian/ControlFieldTestCase.java b/src/test/java/org/vafer/jdeb/debian/ControlFieldTestCase.java
index 35ce579..45291b5 100644
--- a/src/test/java/org/vafer/jdeb/debian/ControlFieldTestCase.java
+++ b/src/test/java/org/vafer/jdeb/debian/ControlFieldTestCase.java
@@ -1,44 +1,44 @@
-/*
- * Copyright 2013 The jdeb developers.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.vafer.jdeb.debian;
-
-import junit.framework.TestCase;
-
-/**
- * @author Emmanuel Bourg
- * @version $Revision$, $Date$
- */
-public class ControlFieldTestCase extends TestCase {
-
-    public void testFormatSimpleValue() {
-        ControlField field = new ControlField("Field-Name");
-        
-        assertEquals("Field-Name: value\n", field.format("value"));
-    }
-    
-    public void testFormatMultilineValue1() {
-        ControlField field = new ControlField("Field-Name", false, ControlField.Type.MULTILINE);
-        
-        assertEquals("Field-Name: value1\n value2\n .\n value3\n", field.format("value1\nvalue2\n\nvalue3"));
-    }
-    
-    public void testFormatMultilineValue2() {
-        ControlField field = new ControlField("Field-Name", false, ControlField.Type.MULTILINE, true);
-        
-        assertEquals("Field-Name:\n value1\n value2\n .\n value3\n", field.format("value1\nvalue2\n\nvalue3"));
-    }
-}
+/*
+ * Copyright 2014 The jdeb developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.vafer.jdeb.debian;
+
+import junit.framework.TestCase;
+
+/**
+ * @author Emmanuel Bourg
+ * @version $Revision$, $Date$
+ */
+public class ControlFieldTestCase extends TestCase {
+
+    public void testFormatSimpleValue() {
+        ControlField field = new ControlField("Field-Name");
+        
+        assertEquals("Field-Name: value\n", field.format("value"));
+    }
+    
+    public void testFormatMultilineValue1() {
+        ControlField field = new ControlField("Field-Name", false, ControlField.Type.MULTILINE);
+        
+        assertEquals("Field-Name: value1\n value2\n .\n value3\n", field.format("value1\nvalue2\n\nvalue3"));
+    }
+    
+    public void testFormatMultilineValue2() {
+        ControlField field = new ControlField("Field-Name", false, ControlField.Type.MULTILINE, true);
+        
+        assertEquals("Field-Name:\n value1\n value2\n .\n value3\n", field.format("value1\nvalue2\n\nvalue3"));
+    }
+}
diff --git a/src/test/java/org/vafer/jdeb/debian/PackageControlFileTestCase.java b/src/test/java/org/vafer/jdeb/debian/PackageControlFileTestCase.java
index 7c18587..77be912 100644
--- a/src/test/java/org/vafer/jdeb/debian/PackageControlFileTestCase.java
+++ b/src/test/java/org/vafer/jdeb/debian/PackageControlFileTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@ public final class PackageControlFileTestCase extends TestCase {
                 " Value2.1\n" +
                 " Value2.2\n" +
                 "Key3: Value3\n";
-        
+
         BinaryPackageControlFile d = new BinaryPackageControlFile(input);
         assertFalse(d.isValid());
 
@@ -44,9 +44,9 @@ public final class PackageControlFileTestCase extends TestCase {
         packageControlFile.set("Package", "test-package");
         packageControlFile.set("Description", "This is\na description\non several lines");
         packageControlFile.set("Version", "1.0");
-        
+
         String s = packageControlFile.toString();
-        
+
         BinaryPackageControlFile packageControlFile2 = new BinaryPackageControlFile(s);
         assertEquals("Package", packageControlFile.get("Package"), packageControlFile2.get("Package"));
         assertEquals("Description", packageControlFile.get("Description"), packageControlFile2.get("Description"));
@@ -64,28 +64,34 @@ public final class PackageControlFileTestCase extends TestCase {
         } catch (ParseException e) {
         }
     }
-    
+
     public void testGetShortDescription() {
         BinaryPackageControlFile packageControlFile = new BinaryPackageControlFile();
-        
+
         assertNull(packageControlFile.getShortDescription());
-        
+
         packageControlFile.set("Description", "This is the short description\nThis is the loooooong description");
-        
+
         assertEquals("short description", "This is the short description", packageControlFile.getShortDescription());
-        
+
         packageControlFile.set("Description", "\nThere is no short description");
-        
+
         assertEquals("short description", "", packageControlFile.getShortDescription());
     }
 
     public void testGetDescription() throws Exception {
         BinaryPackageControlFile packageControlFile = new BinaryPackageControlFile();
         packageControlFile.parse(new FileInputStream("target/test-classes/org/vafer/jdeb/deb/control/control"));
-        
+
         assertEquals("Description", "revision @REVISION@, test package\n" +
                 "This is a sample package control file.\n\n" +
                 "Use for testing purposes only.",
                 packageControlFile.get("Description"));
     }
+
+    public void testGetUserDefinedFields() throws Exception {
+        BinaryPackageControlFile packageControlFile = new BinaryPackageControlFile();
+        packageControlFile.parse(new FileInputStream("target/test-classes/org/vafer/jdeb/deb/control/control"));
+        assertEquals("UserDefinedField", "This is a user defined field.",  packageControlFile.get("UserDefinedField"));
+    }
 }
diff --git a/src/test/java/org/vafer/jdeb/mapping/LsMapperTestCase.java b/src/test/java/org/vafer/jdeb/mapping/LsMapperTestCase.java
index 309759b..e08ea9d 100644
--- a/src/test/java/org/vafer/jdeb/mapping/LsMapperTestCase.java
+++ b/src/test/java/org/vafer/jdeb/mapping/LsMapperTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/test/java/org/vafer/jdeb/maven/DataTestCase.java b/src/test/java/org/vafer/jdeb/maven/DataTestCase.java
index fb89491..497144e 100644
--- a/src/test/java/org/vafer/jdeb/maven/DataTestCase.java
+++ b/src/test/java/org/vafer/jdeb/maven/DataTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/test/java/org/vafer/jdeb/producers/DataProducerFilesTestCase.java b/src/test/java/org/vafer/jdeb/producers/DataProducerFilesTestCase.java
new file mode 100644
index 0000000..b55b007
--- /dev/null
+++ b/src/test/java/org/vafer/jdeb/producers/DataProducerFilesTestCase.java
@@ -0,0 +1,89 @@
+package org.vafer.jdeb.producers;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.vafer.jdeb.DataConsumer;
+
+/**
+ * Tests for {@link org.vafer.jdeb.producers.DataProducerFiles}.
+ *
+ * @author Roman Kashitsyn
+ */
+public class DataProducerFilesTestCase extends TestCase {
+    File file1;
+    File file2;
+
+    public void setUp() throws Exception {
+        file1 = File.createTempFile(getClass().getSimpleName() + ".1", "txt");
+        file2 = File.createTempFile(getClass().getSimpleName() + ".2", "txt");
+    }
+
+    public void tearDown() throws Exception {
+        file1.delete();
+        file2.delete();
+    }
+
+    public void testProducesMultiplePaths() throws IOException {
+        DataConsumer consumer = mock(DataConsumer.class);
+        new DataProducerFiles(
+                new String[]{
+                        file1.getAbsolutePath(),
+                        file2.getAbsolutePath()
+                },
+                "/usr/include",
+                null
+        ).produce(consumer);
+
+        for (File f : Arrays.asList(file1, file2)) {
+            verify(consumer).onEachFile(
+                    any(FileInputStream.class),
+                    eq("/usr/include/" + f.getName()),
+                    any(String.class),
+                    eq("root"),
+                    eq(0),
+                    eq("root"),
+                    eq(0),
+                    anyInt(),
+                    eq(f.length())
+            );
+        }
+    }
+
+    public void testProducesMultiplePathsNoDestination() throws IOException {
+        DataConsumer consumer = mock(DataConsumer.class);
+        new DataProducerFiles(
+                new String[]{
+                        file1.getAbsolutePath(),
+                        file2.getAbsolutePath()
+                },
+                null,
+                null
+        ).produce(consumer);
+
+        for (File f : Arrays.asList(file1, file2)) {
+            verify(consumer).onEachFile(
+                    any(FileInputStream.class),
+                    eq(new TarArchiveEntry(f.getAbsolutePath(), true).getName()),
+                    any(String.class),
+                    eq("root"),
+                    eq(0),
+                    eq("root"),
+                    eq(0),
+                    anyInt(),
+                    eq(f.length())
+            );
+        }
+    }
+}
diff --git a/src/test/java/org/vafer/jdeb/producers/DataProducerPathTemplateTestCase.java b/src/test/java/org/vafer/jdeb/producers/DataProducerPathTemplateTestCase.java
index 60ac425..05f96d3 100644
--- a/src/test/java/org/vafer/jdeb/producers/DataProducerPathTemplateTestCase.java
+++ b/src/test/java/org/vafer/jdeb/producers/DataProducerPathTemplateTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/test/java/org/vafer/jdeb/signing/DebMakerTestCase.java b/src/test/java/org/vafer/jdeb/signing/DebMakerTestCase.java
new file mode 100644
index 0000000..330447d
--- /dev/null
+++ b/src/test/java/org/vafer/jdeb/signing/DebMakerTestCase.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2014 The jdeb developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.vafer.jdeb.signing;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.io.FileUtils;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
+import org.vafer.jdeb.ArchiveVisitor;
+import org.vafer.jdeb.ArchiveWalker;
+import org.vafer.jdeb.Compression;
+import org.vafer.jdeb.DataProducer;
+import org.vafer.jdeb.DebMaker;
+import org.vafer.jdeb.NullConsole;
+import org.vafer.jdeb.debian.BinaryPackageControlFile;
+import org.vafer.jdeb.producers.DataProducerArchive;
+import org.vafer.jdeb.producers.DataProducerDirectory;
+import org.vafer.jdeb.producers.DataProducerLink;
+
+public class DebMakerTestCase extends TestCase {
+
+    public void testCreation() throws Exception {
+
+        File control = new File(getClass().getResource("../deb/control/control").toURI());
+        File archive1 = new File(getClass().getResource("../deb/data.tgz").toURI());
+        File archive2 = new File(getClass().getResource("../deb/data.tar.bz2").toURI());
+        File archive3 = new File(getClass().getResource("../deb/data.zip").toURI());
+        File directory = new File(getClass().getResource("../deb/data").toURI());
+        
+        final InputStream ring = getClass().getClassLoader().getResourceAsStream("org/vafer/gpg/secring.gpg");
+
+        DataProducer[] data = new DataProducer[] {
+            new DataProducerArchive(archive1, null, null, null),
+            new DataProducerArchive(archive2, null, null, null),
+            new DataProducerArchive(archive3, null, null, null),
+            new DataProducerDirectory(directory, null, new String[] { "**/.svn/**" }, null),
+            new DataProducerLink("/link/path-element.ext", "/link/target-element.ext", true, null, null, null)
+        };
+
+        int digest = PGPUtil.SHA1;
+        PGPSigner signer = new PGPSigner(ring, "2E074D8F", "test");
+        PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(signer.getSecretKey().getPublicKey().getAlgorithm(), digest));
+        signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, signer.getPrivateKey());
+        
+        for(int i = 0; i <=1; i++){
+	        File deb = File.createTempFile("jdeb", ".deb");
+	
+	        DebMaker maker = new DebMaker(new NullConsole(), Arrays.asList(data), null);
+	        maker.setControl(new File(getClass().getResource("../deb/control").toURI()));
+	        maker.setDeb(deb);
+	        
+	        if(i==0)
+	        	maker.setSignMethod("debsig-verify");
+	        else
+	        	maker.setSignMethod("dpkg-sig");
+	        
+	        BinaryPackageControlFile packageControlFile = maker.createSignedDeb(Compression.GZIP, signatureGenerator, signer);
+	        
+	        assertTrue(packageControlFile.isValid());
+	
+	        final Map<String, TarArchiveEntry> filesInDeb = new HashMap<String, TarArchiveEntry>();
+	        
+	        ArchiveWalker.walkData(deb, new ArchiveVisitor<TarArchiveEntry>() {
+	            public void visit(TarArchiveEntry entry, byte[] content) throws IOException {
+	                filesInDeb.put(entry.getName(), entry);
+	            }
+	        }, Compression.GZIP);
+	        
+	        assertTrue("_gpgorigin wasn't found in the package", ArchiveWalker.arArchiveContains(deb, "_gpgorigin"));
+	        assertTrue("debian-binary wasn't found in the package", ArchiveWalker.arArchiveContains(deb, "debian-binary"));
+	        assertTrue("control.tar.gz wasn't found in the package", ArchiveWalker.arArchiveContains(deb, "control.tar.gz"));
+	        assertTrue("testfile wasn't found in the package", filesInDeb.containsKey("./test/testfile"));
+	        assertTrue("testfile2 wasn't found in the package", filesInDeb.containsKey("./test/testfile2"));
+	        assertTrue("testfile3 wasn't found in the package", filesInDeb.containsKey("./test/testfile3"));
+	        assertTrue("testfile4 wasn't found in the package", filesInDeb.containsKey("./test/testfile4"));
+	        assertTrue("/link/path-element.ext wasn't found in the package", filesInDeb.containsKey("./link/path-element.ext"));
+	        assertEquals("/link/path-element.ext has wrong link target", "/link/target-element.ext", filesInDeb.get("./link/path-element.ext").getLinkName());
+	        
+	        if(i==0){
+	        	FileUtils.copyFile(deb, new File("./target/test_debsig-verify.deb"));
+	        }else{
+	        	FileUtils.copyFile(deb, new File("./target/test_dpkg-sig.deb"));
+	        }
+	        
+	        assertTrue("Cannot delete the file " + deb, deb.delete());
+        }
+    }
+}
diff --git a/src/test/java/org/vafer/jdeb/signing/PGPSignerTestCase.java b/src/test/java/org/vafer/jdeb/signing/PGPSignerTestCase.java
index 0e1c087..0494104 100644
--- a/src/test/java/org/vafer/jdeb/signing/PGPSignerTestCase.java
+++ b/src/test/java/org/vafer/jdeb/signing/PGPSignerTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
 
 package org.vafer.jdeb.signing;
 
-import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
 import java.util.Arrays;
@@ -31,7 +30,7 @@ public final class PGPSignerTestCase extends TestCase {
 
         assertNotNull(ring);
 
-        String input = "TEST1 \n-TEST2 \n  \nTEST3 \n";
+        String input = "TEST1\n-TEST2 \n  \nTEST3\n";
 
         final String expectedOutputStr =
             "-----BEGIN PGP SIGNED MESSAGE-----\n" +
@@ -42,7 +41,7 @@ public final class PGPSignerTestCase extends TestCase {
                 "\n" +
                 "TEST3\n" +
                 "-----BEGIN PGP SIGNATURE-----\n" +
-                "Version: BCPG v1.48\n" +
+                "Version: BCPG v1.51\n" +
                 "\n" +
                 "iEYEARECABAFAkax1rgJEHM9pIAuB02PAABIJgCghFmoCJCZ0CGiqgVLGGPd/Yh5\n" +
                 "FQQAnRVqvI2ij45JQSHYJBblZ0Vv2meN\n" +
@@ -52,17 +51,16 @@ public final class PGPSignerTestCase extends TestCase {
         final byte[] expectedOutput = expectedOutputStr.getBytes("UTF-8");
 
         final ByteArrayOutputStream os = new ByteArrayOutputStream();
-        
+
         PGPSigner signer = new PGPSigner(ring, "2E074D8F", "test");
         signer.clearSign(input, os);
-        
-        final byte[] output = fixCRLF(os.toByteArray());
 
+        final byte[] output = fixCRLF(os.toByteArray());
         final int from = expectedOutputStr.indexOf("iEYEAREC");
         final int until = expectedOutputStr.indexOf("=aAAT") + 5;
         Arrays.fill(output, from, until, (byte) '?');
         Arrays.fill(expectedOutput, from, until, (byte) '?');
-
+        
         assertEquals(new String(expectedOutput), new String(output));
     }
 
diff --git a/src/test/java/org/vafer/jdeb/utils/FilteredFileTestCase.java b/src/test/java/org/vafer/jdeb/utils/FilteredFileTestCase.java
index e2d6eb8..b2be1b0 100644
--- a/src/test/java/org/vafer/jdeb/utils/FilteredFileTestCase.java
+++ b/src/test/java/org/vafer/jdeb/utils/FilteredFileTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -46,6 +46,15 @@ public class FilteredFileTestCase extends TestCase {
         assertEquals("#!/bin/sh\ncat jdebcustom1 \necho 'custom2'\n", actual);
     }
 
+    public void testTokenSubstitutionWithinOpenCloseTokens() throws Exception {
+        InputStream in = new ReaderInputStream(new StringReader("#!/bin/bash\nif [[ -z \"$(grep [[artifactId]] /etc/passwd )\" ]] ; then\n"));
+
+        FilteredFile placeHolder = new FilteredFile(in, variableResolver);
+
+        String actual = placeHolder.toString();
+        assertEquals("", "#!/bin/bash\nif [[ -z \"$(grep jdeb /etc/passwd )\" ]] ; then\n", actual);
+    }
+
     public void testVariableSubstitution() throws Exception {
         Map<String, String> map = new HashMap<String, String>();
         map.put("VERSION", "1.2");
diff --git a/src/test/java/org/vafer/jdeb/utils/InformationInputStreamTestCase.java b/src/test/java/org/vafer/jdeb/utils/InformationInputStreamTestCase.java
index 478e5a6..c830df3 100644
--- a/src/test/java/org/vafer/jdeb/utils/InformationInputStreamTestCase.java
+++ b/src/test/java/org/vafer/jdeb/utils/InformationInputStreamTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/test/java/org/vafer/jdeb/utils/UtilsTestCase.java b/src/test/java/org/vafer/jdeb/utils/UtilsTestCase.java
index e7b4777..ca0b374 100644
--- a/src/test/java/org/vafer/jdeb/utils/UtilsTestCase.java
+++ b/src/test/java/org/vafer/jdeb/utils/UtilsTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 The jdeb developers.
+ * Copyright 2014 The jdeb developers.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -83,15 +83,60 @@ public class UtilsTestCase extends TestCase {
         result = Utils.replaceVariables(resolver, "if [[ \"${HOST_TYPE}\" -eq \"admin\" ]] ; then", "[[", "]]");
         assertEquals("if [[ \"${HOST_TYPE}\" -eq \"admin\" ]] ; then", result);
 
+        // end of line https://github.com/tcurdt/jdeb/issues/154
+        String input = "if [ -e some_file ]";
+        result = Utils.replaceVariables(resolver, input, "[[", "]]");
+        assertEquals(input, result);
+
         // mixed valid and unknown variables
         result = Utils.replaceVariables(resolver, "[[name]] [[test]]", "[[", "]]");
         assertEquals("jdeb [[test]]", result);
+
+        // nested vars
+        result = Utils.replaceVariables(new VariableResolver() {
+            public String get(String pKey) {
+                return "VAR";
+            }
+        }, "[[var]] [[ [[var]] [[ [[var]] ]] [[var]] ]]", "[[", "]]");
+
+        assertEquals("VAR [[ VAR [[ VAR ]] VAR ]]", result);
+    }
+
+    public void testReplaceVariablesWithinOpenCloseTokens() throws Exception {
+        Map<String, String> variables = new HashMap<String, String>();
+        variables.put("artifactId", "jdeb");
+
+        VariableResolver resolver = new MapVariableResolver(variables);
+
+        String result = Utils.replaceVariables(resolver, "if [[ -z \"$(grep [[artifactId]] /etc/passwd )\" ]] ; then", "[[", "]]");
+
+        assertEquals("", "if [[ -z \"$(grep jdeb /etc/passwd )\" ]] ; then", result);
+
     }
 
     public void testVersionConversion() {
-        Calendar cal = new GregorianCalendar(2013, 02-1, 17);
-        assertEquals("should match", "1.0", Utils.convertToDebianVersion("1.0", null));
-        assertEquals("should match", "1.0~SNAPSHOT", Utils.convertToDebianVersion("1.0+SNAPSHOT", null));
-        assertEquals("should match", "1.0~20130217000000", Utils.convertToDebianVersion("1.0+SNAPSHOT", cal.getTime()));
+        Calendar cal = new GregorianCalendar(2013, Calendar.FEBRUARY, 17);
+        assertEquals("should match", "1.0", Utils.convertToDebianVersion("1.0", false, "SNAPSHOT", cal.getTime()));
+        assertEquals("should match", "1.0~SNAPSHOT", Utils.convertToDebianVersion("1.0+SNAPSHOT", false, "SNAPSHOT", cal.getTime()));
+        assertEquals("should match", "1.0~SNAPSHOT", Utils.convertToDebianVersion("1.0-SNAPSHOT", false, "SNAPSHOT", cal.getTime()));
+        assertEquals("should match", "1.0~20130217000000", Utils.convertToDebianVersion("1.0+SNAPSHOT", true, "SNAPSHOT", cal.getTime()));
+        assertEquals("should match", "1.0~RC2", Utils.convertToDebianVersion("1.0-RC2", true, "SNAPSHOT", cal.getTime()));
+        assertEquals("should match", "1.0~alpha3", Utils.convertToDebianVersion("1.0-alpha3", true, "SNAPSHOT", cal.getTime()));
+        assertEquals("should match", "1.0~Beta+4", Utils.convertToDebianVersion("1.0.Beta-4", true, "SNAPSHOT", cal.getTime()));
+        assertEquals("should match", "1.0~milestone+4", Utils.convertToDebianVersion("1.0-milestone-4", true, "SNAPSHOT", cal.getTime()));
+        assertEquals("should match", "1.0~a+4", Utils.convertToDebianVersion("1.0-a-4", true, "SNAPSHOT", cal.getTime()));
+        assertEquals("should match", "1.0~b+4", Utils.convertToDebianVersion("1.0-b-4", true, "SNAPSHOT", cal.getTime()));
+        assertEquals("should match", "1.0~rc7", Utils.convertToDebianVersion("1.0rc7", true, "SNAPSHOT", cal.getTime()));
+        assertEquals("should match", "1.0~M1", Utils.convertToDebianVersion("1.0.M1", true, "SNAPSHOT", cal.getTime()));
+        assertEquals("should match", "1.0~M2", Utils.convertToDebianVersion("1.0-M2", true, "SNAPSHOT", cal.getTime()));
+        assertEquals("should match", "1.0~M3", Utils.convertToDebianVersion("1.0M3", true, "SNAPSHOT", cal.getTime()));
+    }
+
+    public void testMovePath() {
+        assertEquals("/usr/share/file.txt", Utils.movePath("file.txt", "/usr/share"));
+        assertEquals("/usr/share/file.txt", Utils.movePath("file.txt", "/usr/share/"));
+        assertEquals("/usr/share/noext", Utils.movePath("noext", "/usr/share/"));
+        assertEquals("/usr/share/file.txt", Utils.movePath("/home/user/file.txt", "/usr/share"));
+        assertEquals("/usr/share/file.txt", Utils.movePath("../relative/file.txt", "/usr/share/"));
     }
 }
diff --git a/src/test/resources/org/vafer/jdeb/deb/control/control b/src/test/resources/org/vafer/jdeb/deb/control/control
index 226eca2..673ecbe 100644
--- a/src/test/resources/org/vafer/jdeb/deb/control/control
+++ b/src/test/resources/org/vafer/jdeb/deb/control/control
@@ -10,3 +10,4 @@ Description: revision @REVISION@, test package
  This is a sample package control file.
  .
  Use for testing purposes only.
+XB-UserDefinedField: This is a user defined field.
diff --git a/src/test/resources/testbuild.xml b/src/test/resources/testbuild.xml
index 56acc50..2252814 100644
--- a/src/test/resources/testbuild.xml
+++ b/src/test/resources/testbuild.xml
@@ -87,6 +87,14 @@
     </deb>
   </target>
 
+  <target name="perm-mapper">
+    <deb destfile="test.deb" control="org/vafer/jdeb/deb/control">
+      <data src="org/vafer/jdeb/deb/data" type="directory">
+        <mapper type="perm" filemode="700" prefix="/usr/share/foo"/>
+      </data>
+    </deb>
+  </target>
+
   <target name="unknown-compression">
     <deb destfile="test.deb" control="org/vafer/jdeb/deb/control" compression="rar">
       <fileset dir="org/vafer/jdeb/deb/data"/>
@@ -111,4 +119,10 @@
     </deb>
   </target>
 
+  <target name="conffiles">
+    <deb destfile="test123.deb" control="org/vafer/jdeb/deb/control">
+      <data src="org/vafer/jdeb/deb/data" type="directory" conffile="true" />
+    </deb>
+  </target>
+
 </project>

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/jdeb.git



More information about the pkg-java-commits mailing list