[med-svn] [artemis] 01/01: New upstream version 16.0.17+dfsg

Sascha Steinbiss satta at debian.org
Wed Dec 7 23:45:41 UTC 2016


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

satta pushed a commit to branch upstream
in repository artemis.

commit 30a81b955840926a2ad45a554692687d1f74b13b
Author: Sascha Steinbiss <satta at debian.org>
Date:   Mon Dec 5 09:11:15 2016 +0000

    New upstream version 16.0.17+dfsg
---
 .classpath                                         |    2 +
 .travis.yml                                        |   26 +
 AUTHORS                                            |    3 +
 ChangeLog                                          |    2 +-
 INSTALL                                            |   48 +-
 LICENSE                                            |  680 ++++++++++
 Makefile                                           |  213 +--
 README => README.md                                |   12 +-
 act                                                |    2 +
 art                                                |    3 +
 docs/act_manual.sgml                               |    2 +-
 docs/act_start_chapter.sgml                        |    2 +-
 docs/feature_edit.gif                              |  Bin 30763 -> 34452 bytes
 docs/jvm_options.sgml                              |   23 +
 docs/main_window_chapter.sgml                      |    1 +
 docs/manual.sgml                                   |    3 +-
 docs/menus.sgml                                    |   56 +-
 docs/qualifier_list.gif                            |  Bin 0 -> 12357 bytes
 docs/qualifier_list2.gif                           |  Bin 0 -> 15166 bytes
 docs/start_chapter.sgml                            |    2 +-
 docs/tick.png                                      |  Bin 0 -> 394 bytes
 docs/unix_args.sgml                                |    8 -
 docs/validation_report.png                         |  Bin 0 -> 55887 bytes
 docs/validator.sgml                                |  181 +++
 etc/feature_keys_gff                               |    6 +-
 etc/key_mapping                                    |    7 +-
 etc/options                                        |   14 +-
 etc/project.properties                             |    6 +-
 etc/qualifier_types                                |    4 +-
 etc/results_to_netscape                            |   15 +-
 etc/versions                                       |    6 +-
 etc/writedb_entry                                  |    9 +-
 gff2embl                                           |  106 ++
 install_dependencies.sh                            |   57 +
 test/data/MAL_8h.bam                               |  Bin 0 -> 97476 bytes
 test/data/MAL_8h.bam.bai                           |  Bin 0 -> 264 bytes
 .../components/alignment/MappedReadsTest.java      |  143 ++
 .../artemis/io/GFF3AttributeBuilderTest.java       |  295 ++++
 test/uk/ac/sanger/artemis/io/GFF3EncoderTest.java  |  102 ++
 uk/ac/sanger/artemis/AlignMatchVector.java         |   16 +-
 uk/ac/sanger/artemis/FeatureVector.java            |    2 +-
 uk/ac/sanger/artemis/chado/ArtemisUtils.java       |    3 +-
 .../artemis/chado/ChadoTransactionManager.java     |   14 +-
 uk/ac/sanger/artemis/components/EditMenu.java      |   17 +
 uk/ac/sanger/artemis/components/EntryEdit.java     |    2 +-
 uk/ac/sanger/artemis/components/FeatureEdit.java   |   21 +-
 .../sanger/artemis/components/RunBlastAtNCBI.java  |    4 +-
 .../artemis/components/RunPfamSearchThread.java    |    4 +-
 uk/ac/sanger/artemis/components/SelectMenu.java    |   68 +-
 uk/ac/sanger/artemis/components/Splash.java        |    2 +-
 .../components/alignment/AbstractGraphPanel.java   |   11 +-
 .../artemis/components/alignment/BamUtils.java     |  208 ++-
 .../artemis/components/alignment/BamView.java      |   68 +-
 .../alignment/CRAMReferenceSequenceFile.java       |    7 +-
 .../components/alignment/CoveragePanel.java        |   86 +-
 .../artemis/components/alignment/MappedReads.java  |  359 ++---
 .../artemis/components/alignment/ReadCount.java    |   40 +
 .../components/alignment/ReadCountDialog.java      |    7 +-
 .../components/genebuilder/GeneEditorPanel.java    |   44 +-
 .../artemis/components/genebuilder/GeneUtils.java  |    5 +-
 .../genebuilder/gff/PropertiesPanel.java           |   24 +-
 uk/ac/sanger/artemis/io/DocumentEntryFactory.java  |   15 +-
 .../sanger/artemis/io/GFF3AttributeAggregator.java |   38 +
 uk/ac/sanger/artemis/io/GFF3AttributeBuilder.java  |  214 +++
 uk/ac/sanger/artemis/io/GFF3Encoder.java           |   38 +
 uk/ac/sanger/artemis/io/GFFStreamFeature.java      | 1411 +++++++++-----------
 .../sanger/artemis/io/GenbankTblOutputStream.java  |   96 +-
 uk/ac/sanger/artemis/io/GffToEMBL.java             |    2 +-
 uk/ac/sanger/artemis/io/KeyVector.java             |   26 +-
 uk/ac/sanger/artemis/io/PublicDBDocumentEntry.java |   24 +-
 .../sanger/artemis/io/SimpleEntryInformation.java  |    8 +-
 uk/ac/sanger/artemis/io/StreamQualifier.java       |   16 +-
 uk/ac/sanger/artemis/plot/Algorithm.java           |    2 +-
 uk/ac/sanger/artemis/plot/UserDataAlgorithm.java   |   34 +-
 uk/ac/sanger/artemis/util/DatabaseDocument.java    |   91 +-
 uk/ac/sanger/artemis/util/FastVector.java          |   26 +-
 uk/ac/sanger/artemis/util/StringVector.java        |   34 +-
 77 files changed, 3545 insertions(+), 1581 deletions(-)

diff --git a/.classpath b/.classpath
index 3d63d38..200d980 100644
--- a/.classpath
+++ b/.classpath
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
 	<classpathentry kind="src" path="corba"/>
+	<classpathentry kind="src" path="lib"/>
 	<classpathentry including="nsdb/|seqdb/|type/" kind="src" path="ant-build/src/main"/>
 	<classpathentry excluding="ant-build/src/main/|uk/ac/sanger/artemis/ExternalProgramUtils.java" including="nsdb/|org/|seqdb/|type/|uk/" kind="src" path=""/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
@@ -23,5 +24,6 @@
 	<classpathentry kind="lib" path="lib/batik/batik-svggen.jar"/>
 	<classpathentry kind="lib" path="lib/batik/batik-util.jar"/>
 	<classpathentry kind="lib" path="lib/batik/batik-xml.jar"/>
+	<classpathentry kind="lib" path="lib/commons-lang-2.6.jar"/>
 	<classpathentry kind="output" path="ant-build/classes/main"/>
 </classpath>
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..065180d
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,26 @@
+language: java
+jdk:
+  - openjdk7
+  - openjdk6
+  - oraclejdk7
+sudo: false
+env:
+  global:
+    - BUILD_CMD='make'
+matrix:
+  include:
+    - jdk: openjdk7
+      env: BUILD_CMD='ant -f build.xml'
+cache:
+  directories:
+  - "${HOME}/dependencies"
+before_install:
+  - "export CASHER_TIME_OUT=300"
+  - "export DISPLAY=:99.0"
+  - "sh -e /etc/init.d/xvfb start"
+  - "source install_dependencies.sh"
+install:
+  - "$BUILD_CMD"
+  - "cd test"
+  - "ln -s $(pwd)/../etc/log4j.properties ."
+script: ant -f build-test.xml test -DEMBOSS_ROOT=$EMBOSS_ROOT | tee test.log && [ -z "$(grep 'Failures:\s[^0]\|Errors:\s[^0]' test.log)" ]
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..f3ff3b3
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,3 @@
+Tim Carver (path-help at sanger.ac.uk)
+Sascha Steinbiss (path-help at sanger.ac.uk)
+Kim Rutherford (path-help at sanger.ac.uk)
diff --git a/ChangeLog b/ChangeLog
index ac4dd04..612a9ba 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,4 @@
-Version XX
+Version 16
 	Add 'Features Within Selection' option to the 'Select' menu to select
 	features that are contained by a selected base range.
 
diff --git a/INSTALL b/INSTALL
index 0240478..a3e2c3c 100644
--- a/INSTALL
+++ b/INSTALL
@@ -1,11 +1,10 @@
 
 Installation instructions for UNIX
+==================================
 
    1. change to /some/directory
-
    2. uncompress and untar the artemis_compiled.tar.gz file
-      On UNIX the command is:   gzip -d < artemis_compiled.tar.gz | tar xf -
-
+      On UNIX the command is:   tar xzvf artemis_compiled.tar.gz 
 
 This will create a "artemis" directory containing all the java classes.  On
 Unix the easiest way to run the program is to run the script called art in the
@@ -18,7 +17,8 @@ menus.  From the File menu you can open a flat file containing an
 entry.  If it reads the entry successfully a new window will open,
 which shows the sequence and features for the entry.
 
-====================================================================
+Download and Install from GitHub
+================================
 
 Installation instructions for GitHub download
 
@@ -30,4 +30,42 @@ Installation instructions for GitHub download
    3. Run Artemis:
       ./art
       or ACT:
-      ./act
\ No newline at end of file
+      ./act
+      
+Makefile targets
+================
+
+make		- compile code  
+make jar	- create Artemis (jar_build/artemis.jar) and ACT 
+			  (jar_build/act.jar) jar files.
+make dist	- create a tar ball of the Artemis/ACT distribution
+
+Creating signed jar files for Artemis/ACT Java Web Start launch
+===============================================================
+
+Use 'make jar' to create jar_build/artemis.jar. See the following pages 
+for a description of deploying and signing jar files:
+
+http://java.sun.com/docs/books/tutorial/deployment/webstart/deploying.html
+http://java.sun.com/docs/books/tutorial/deployment/jar/signing.html
+
+Use the Java keytool to generate a keystore (e.g. artemisstore below) file:
+
+keytool -genkey -alias signFiles -dname "CN=$NAME, \
+        OU=$ORGU, O=$ORG, L=$LOC, S=$STATE, C=$CODE" \
+        -keypass $KEYPASS -storepass $STOREPASS -keystore artemisstore  -validity $VALID
+
+The following can be used to view the key store and list entry details:
+
+keytool -list -v -keystore artemisstore
+
+Then use the following to create a signed jar to use in the JNLP file:
+
+jarsigner -keystore artemisstore -storepass $STOREPASS -keypass $KEYPASS \
+           -signedjar sartemis.jar artemis.jar signFiles
+
+Running the test suite
+======================
+
+cd test
+ant -f build-test.xml
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..3f03f8b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,680 @@
+This software is Copyright (c) 2013 by Wellcome Trust Sanger Institute.
+
+This is free software, licensed under:
+
+  The GNU General Public License, Version 3, June 2007
+
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/Makefile b/Makefile
index 8dc99b0..9b33c29 100644
--- a/Makefile
+++ b/Makefile
@@ -1,210 +1,12 @@
 # This is a GNU Makefile for Artemis
 
-# $Header: //tmp/pathsoft/artemis/Makefile,v 1.47 2009-09-21 15:32:03 tjc Exp $
-
 SHELL=/bin/sh
 
 #OPT_FLAGS = -g -deprecation
 
 JAVAC := javac -source 1.5 -target 1.5 $(OPT_FLAGS) $(EXTRA_FLAGS)
 
-REAL_CLASSPATH := CLASSPATH=lib/biojava.jar:lib/jemAlign.jar:lib/j2ssh/j2ssh-core.jar:lib/ibatis/ibatis-2.3.4.726.jar:lib/ibatis/log4j-1.2.14.jar:lib/postgresql-8.4-701.jdbc3.jar:lib/picard/picard.jar:lib/picard/sam.jar:lib/commons-net-2.2.jar:lib/batik/batik-awt-util.jar:lib/batik/batik-dom.jar:lib/batik/batik-ext.jar:lib/batik/batik-svggen.jar:lib/batik/batik-util.jar:lib/batik/batik-xml.jar:.
-
-# NAMES:= \
-# 	uk/ac/sanger/artemis/OptionChangeListener \
-# 	uk/ac/sanger/artemis/OptionChangeEvent \
-# 	uk/ac/sanger/artemis/Options \
-# 	uk/ac/sanger/artemis/Selection \
-# 	uk/ac/sanger/artemis/components/ArtemisMain \
-#   uk/ac/sanger/artemis/components/ActMain \
-# 	uk/ac/sanger/artemis/components/Splash \
-# 	uk/ac/sanger/artemis/components/ListDialog \
-# 	uk/ac/sanger/artemis/components/FeatureListFrame \
-# 	uk/ac/sanger/artemis/components/FeatureList \
-# 	uk/ac/sanger/artemis/components/FeatureDisplay \
-# 	uk/ac/sanger/artemis/components/CanvasPanel \
-# 	uk/ac/sanger/artemis/components/EntryGroupDisplay \
-# 	uk/ac/sanger/artemis/components/EntryHeaderEdit \
-# 	uk/ac/sanger/artemis/components/BasePlotGroup \
-# 	uk/ac/sanger/artemis/components/EntryEditVector \
-# 	uk/ac/sanger/artemis/components/EntryEdit \
-# 	uk/ac/sanger/artemis/components/WritableEMBLCorbaEntrySource \
-# 	uk/ac/sanger/artemis/components/DbfetchEntrySource \
-# 	uk/ac/sanger/artemis/components/EMBLCorbaEntrySource \
-# 	uk/ac/sanger/artemis/components/CorbaEntrySource \
-# 	uk/ac/sanger/artemis/components/BioJavaEntrySource \
-# 	uk/ac/sanger/artemis/components/FileDialogEntrySource \
-# 	uk/ac/sanger/artemis/components/EntryActionListener \
-# 	uk/ac/sanger/artemis/components/DisplayComponent \
-# 	uk/ac/sanger/artemis/components/EntryFileDialog \
-# 	uk/ac/sanger/artemis/components/StickyFileChooser \
-# 	uk/ac/sanger/artemis/components/LogReadListener \
-# 	uk/ac/sanger/artemis/components/Selector \
-# 	uk/ac/sanger/artemis/components/Navigator \
-# 	uk/ac/sanger/artemis/components/EntryGroupPanel \
-# 	uk/ac/sanger/artemis/components/FeaturePopup \
-# 	uk/ac/sanger/artemis/components/WriteMenu \
-# 	uk/ac/sanger/artemis/components/SelectMenu \
-# 	uk/ac/sanger/artemis/components/GraphMenu \
-# 	uk/ac/sanger/artemis/components/RunMenu \
-# 	uk/ac/sanger/artemis/components/AddMenu \
-# 	uk/ac/sanger/artemis/components/ViewMenu \
-# 	uk/ac/sanger/artemis/components/GotoMenu \
-# 	uk/ac/sanger/artemis/components/EditMenu \
-# 	uk/ac/sanger/artemis/components/SelectionMenu \
-# 	uk/ac/sanger/artemis/components/SelectionViewer \
-# 	uk/ac/sanger/artemis/components/FeaturePlotGroup \
-# 	uk/ac/sanger/artemis/components/FeaturePlot \
-# 	uk/ac/sanger/artemis/components/FeatureEdit \
-# 	uk/ac/sanger/artemis/components/FeatureViewer \
-# 	uk/ac/sanger/artemis/components/SearchResultViewer \
-# 	uk/ac/sanger/artemis/components/LogViewer \
-# 	uk/ac/sanger/artemis/components/FileViewer \
-# 	uk/ac/sanger/artemis/components/MessageFrame \
-# 	uk/ac/sanger/artemis/components/MessageDialog \
-# 	uk/ac/sanger/artemis/components/YesNoDialog \
-# 	uk/ac/sanger/artemis/components/KeyChooser \
-# 	uk/ac/sanger/artemis/components/KeyChoice \
-# 	uk/ac/sanger/artemis/components/QualifierEditor \
-# 	uk/ac/sanger/artemis/components/QualifierTextArea \
-# 	uk/ac/sanger/artemis/components/QualifierChoice \
-# 	uk/ac/sanger/artemis/components/ChoiceFrame \
-# 	uk/ac/sanger/artemis/components/SelectionInfoDisplay \
-# 	uk/ac/sanger/artemis/components/EntryGroupMenu \
-# 	uk/ac/sanger/artemis/components/EntryGroupInfoDisplay \
-# 	uk/ac/sanger/artemis/components/Utilities \
-# 	uk/ac/sanger/artemis/components/MarkerRangeRequester \
-# 	uk/ac/sanger/artemis/components/MarkerRangeRequesterListener \
-# 	uk/ac/sanger/artemis/components/MarkerRangeRequesterEvent \
-# 	uk/ac/sanger/artemis/components/TextRequester \
-# 	uk/ac/sanger/artemis/components/TextRequesterListener \
-# 	uk/ac/sanger/artemis/components/TextRequesterEvent \
-# 	uk/ac/sanger/artemis/components/TextDialog \
-# 	uk/ac/sanger/artemis/components/BasePlot \
-# 	uk/ac/sanger/artemis/components/ProcessWatcher \
-# 	uk/ac/sanger/artemis/components/ProcessWatcherEvent \
-# 	uk/ac/sanger/artemis/components/ProcessWatcherListener \
-# 	uk/ac/sanger/artemis/components/ExternalProgramOptions \
-# 	uk/ac/sanger/artemis/components/Plot \
-# 	uk/ac/sanger/artemis/components/PlotMouseListener \
-# 	uk/ac/sanger/artemis/components/FeatureAminoAcidViewer \
-# 	uk/ac/sanger/artemis/components/FeatureBaseViewer \
-# 	uk/ac/sanger/artemis/components/SequenceViewer \
-# 	uk/ac/sanger/artemis/components/FeatureInfo \
-# 	uk/ac/sanger/artemis/components/DisplayAdjustmentListener \
-# 	uk/ac/sanger/artemis/components/DisplayAdjustmentEvent \
-# 	uk/ac/sanger/artemis/components/ScoreChanger \
-# 	uk/ac/sanger/artemis/components/ScoreScrollbar \
-# 	uk/ac/sanger/artemis/components/ScoreChangeListener \
-# 	uk/ac/sanger/artemis/components/ScoreChangeEvent \
-# 	uk/ac/sanger/artemis/components/InputStreamProgressDialog \
-# 	uk/ac/sanger/artemis/plot/KarlinSigAlgorithm \
-# 	uk/ac/sanger/artemis/plot/UserDataAlgorithm \
-# 	uk/ac/sanger/artemis/plot/Codon12CorrelationAlgorithm \
-# 	uk/ac/sanger/artemis/plot/ATDeviationAlgorithm \
-# 	uk/ac/sanger/artemis/plot/GCDeviationAlgorithm \
-# 	uk/ac/sanger/artemis/plot/GCFrameAlgorithm \
-# 	uk/ac/sanger/artemis/plot/CodonUsageAlgorithm \
-# 	uk/ac/sanger/artemis/plot/CodonUsageFormatException \
-# 	uk/ac/sanger/artemis/plot/CodonUsageWeight \
-# 	uk/ac/sanger/artemis/plot/CodonWeight \
-# 	uk/ac/sanger/artemis/plot/AGWindowAlgorithm \
-# 	uk/ac/sanger/artemis/plot/GCSDWindowAlgorithm \
-# 	uk/ac/sanger/artemis/plot/GCWindowAlgorithm \
-# 	uk/ac/sanger/artemis/plot/HydrophilicityAlgorithm \
-# 	uk/ac/sanger/artemis/plot/HydroAlgorithm \
-# 	uk/ac/sanger/artemis/plot/HydrophobicityAlgorithm \
-# 	uk/ac/sanger/artemis/plot/CoilFeatureAlgorithm \
-# 	uk/ac/sanger/artemis/plot/FeatureAlgorithm \
-# 	uk/ac/sanger/artemis/plot/BaseAlgorithm \
-# 	uk/ac/sanger/artemis/plot/Algorithm \
-# 	uk/ac/sanger/artemis/Logger \
-# 	uk/ac/sanger/artemis/ExternalProgramListener \
-# 	uk/ac/sanger/artemis/ExternalProgramException \
-# 	uk/ac/sanger/artemis/ExternalProgramVector \
-# 	uk/ac/sanger/artemis/SimpleExternalProgramMonitor \
-# 	uk/ac/sanger/artemis/ProcessMonitor \
-# 	uk/ac/sanger/artemis/ProcessMonitor \
-# 	uk/ac/sanger/artemis/ExternalProgramMonitor \
-# 	uk/ac/sanger/artemis/ExternalProgram \
-# 	uk/ac/sanger/artemis/EntryGroupChangeListener \
-# 	uk/ac/sanger/artemis/EntryGroupChangeEvent \
-# 	uk/ac/sanger/artemis/EntryChangeListener \
-# 	uk/ac/sanger/artemis/EntryChangeEvent \
-# 	uk/ac/sanger/artemis/FilteredEntryGroup \
-# 	uk/ac/sanger/artemis/SimpleEntryGroup \
-# 	uk/ac/sanger/artemis/EntryGroup \
-# 	uk/ac/sanger/artemis/EntryVector \
-# 	uk/ac/sanger/artemis/EntrySourceVector \
-# 	uk/ac/sanger/artemis/EntrySource \
-# 	uk/ac/sanger/artemis/Entry \
-# 	uk/ac/sanger/artemis/LastSegmentException \
-# 	uk/ac/sanger/artemis/FeatureFromVectorPredicate \
-# 	uk/ac/sanger/artemis/FeatureKeyQualifierPredicate \
-# 	uk/ac/sanger/artemis/FeatureKeyPredicate \
-# 	uk/ac/sanger/artemis/FeaturePatternPredicate \
-# 	uk/ac/sanger/artemis/FeaturePredicateConjunction \
-# 	uk/ac/sanger/artemis/FeaturePredicate \
-# 	uk/ac/sanger/artemis/FeaturePredicateVector \
-# 	uk/ac/sanger/artemis/FeatureEnumeration \
-# 	uk/ac/sanger/artemis/FeatureSegmentVector \
-# 	uk/ac/sanger/artemis/FeatureChangeListener \
-# 	uk/ac/sanger/artemis/FeatureChangeEvent \
-# 	uk/ac/sanger/artemis/FeatureVector \
-# 	uk/ac/sanger/artemis/Feature \
-# 	uk/ac/sanger/artemis/FeatureSegment \
-# 	uk/ac/sanger/artemis/ActionController \
-# 	uk/ac/sanger/artemis/ActionVector \
-# 	uk/ac/sanger/artemis/Action \
-# 	uk/ac/sanger/artemis/ChangeListener \
-# 	uk/ac/sanger/artemis/ChangeEventVector \
-# 	uk/ac/sanger/artemis/ChangeEvent \
-# 	uk/ac/sanger/artemis/GotoListener \
-# 	uk/ac/sanger/artemis/GotoEvent \
-# 	uk/ac/sanger/artemis/GotoEventSource \
-# 	uk/ac/sanger/artemis/SimpleGotoEventSource \
-# 	uk/ac/sanger/artemis/SelectionChangeListener \
-# 	uk/ac/sanger/artemis/SelectionChangeEvent \
-# 	uk/ac/sanger/artemis/Selectable \
-# 	uk/ac/sanger/artemis/sequence/NoSequenceException \
-# 	uk/ac/sanger/artemis/sequence/MarkerRangeVector \
-# 	uk/ac/sanger/artemis/sequence/MarkerRange \
-# 	uk/ac/sanger/artemis/sequence/MarkerChangeListener \
-# 	uk/ac/sanger/artemis/sequence/MarkerChangeEvent \
-# 	uk/ac/sanger/artemis/sequence/Marker \
-# 	uk/ac/sanger/artemis/sequence/Strand \
-# 	uk/ac/sanger/artemis/sequence/BasePatternFormatException \
-# 	uk/ac/sanger/artemis/sequence/BasePattern \
-# 	uk/ac/sanger/artemis/sequence/Bases \
-# 	uk/ac/sanger/artemis/sequence/AminoAcidSequence \
-# 	uk/ac/sanger/artemis/sequence/SequenceChangeListener \
-# 	uk/ac/sanger/artemis/sequence/SequenceChangeEvent \
-# 	uk/ac/sanger/artemis/io/BioJavaFeature \
-# 	uk/ac/sanger/artemis/io/BioJavaSequence \
-# 	uk/ac/sanger/artemis/io/BioJavaEntry \
-# 	uk/ac/sanger/artemis/io/GenbankStreamSequence \
-# 	uk/ac/sanger/artemis/io/RWCorbaEntry \
-# 	uk/ac/sanger/artemis/io/CorbaEntry \
-# 	uk/ac/sanger/artemis/io/DocumentEntryFactory \
-# 	uk/ac/sanger/artemis/io/BlastDocumentEntry \
-# 	uk/ac/sanger/artemis/io/MSPcrunchDocumentEntry \
-# 	uk/ac/sanger/artemis/io/GFFDocumentEntry \
-# 	uk/ac/sanger/artemis/io/GenbankDocumentEntry \
-# 	uk/ac/sanger/artemis/io/EmblDocumentEntry \
-# 	uk/ac/sanger/artemis/io/PublicDBDocumentEntry \
-# 	uk/ac/sanger/artemis/io/SimpleDocumentEntry \
-# 	uk/ac/sanger/artemis/io/DocumentEntry \
-# 	uk/ac/sanger/artemis/io/Entry \
-# 	uk/ac/sanger/artemis/io/ReadOnlyEntry \
-# 	uk/ac/sanger/artemis/io/QualifierInfoException \
-# 	uk/ac/sanger/artemis/io/QualifierInfo \
-# 	uk/ac/sanger/artemis/io/QualifierInfoVector \
-# 	uk/ac/sanger/artemis/io/QualifierInfoHash \
-# 	uk/ac/sanger/artemis/chado/DbSqlConfig \
-# 	uk/ac/sanger/artemis/circular/DNADraw \
-# 	uk/ac/sanger/artemis/circular/digest/Utils \
-# 	uk/ac/sanger/artemis/circular/digest/CircularGenomeController \
-# 	uk/ac/sanger/artemis/io/GffToEMBL
+REAL_CLASSPATH := CLASSPATH=lib/commons-lang-2.6.jar:lib/biojava.jar:lib/jemAlign.jar:lib/j2ssh/j2ssh-core.jar:lib/ibatis/ibatis-2.3.4.726.jar:lib/ibatis/log4j-1.2.14.jar:lib/postgresql-8.4-701.jdbc3.jar:lib/picard/picard.jar:lib/picard/sam.jar:lib/commons-net-2.2.jar:lib/batik/batik-awt-util.jar:lib/batik/batik-dom.jar:lib/batik/batik-ext.jar:lib/batik/batik-svggen.jar:lib/batik/batik-util.jar:lib/batik/batik-xml.jar:.
 
 ARTEMIS_DIRS = uk/ac/sanger/artemis \
 uk/ac/sanger/artemis/chado \
@@ -226,7 +28,6 @@ uk/ac/sanger/artemis/plot \
 uk/ac/sanger/artemis/sequence \
 uk/ac/sanger/artemis/util
 
-#CLASSES := $(NAMES:%=%.class)
 SOURCES := $(foreach DIR,$(ARTEMIS_DIRS),$(wildcard $(DIR)/*.java))
 CLASSES := $(SOURCES:%.java=%.class)
 
@@ -271,7 +72,7 @@ manual :
 
 CLASS_FILES := `find org uk nsdb type seqdb -name '*.class' -print`
 
-OTHER_FILES := `find images/PSUlogo.gif images/icon.gif COPYING README`
+OTHER_FILES := `find images/PSUlogo.gif images/icon.gif COPYING README.md`
 
 dist :
 	rm -rf artemis_compiled.tar.gz tar_build
@@ -311,24 +112,24 @@ artemis.jar : $(CLASSES)
           done; \
         fi; \
 	cp -R ../lib/LICENSE.Apache ../uk ../org ../nsdb ../type ../seqdb ../etc ../images ../lib/j2ssh/j2ssh.properties \
-	      ../images/PSUlogo.gif ../images/icon.gif ../README ../artemis_sqlmap .
+	      ../images/PSUlogo.gif ../images/icon.gif ../README.md ../artemis_sqlmap .
 	find jar_build -name '*.java' -print | xargs rm -f
 	find jar_build -name '.svn' -print | xargs rm -rf
 	cd jar_build; \
 	rm -rf META-INF/MANIFEST.MF; \
 	echo "Main-Class: uk.ac.sanger.artemis.components.ArtemisMain\nPermissions: all-permissions" > manifest-art; \
-	jar cmf manifest-art artemis.jar META-INF/services images/PSUlogo.gif images/icon.gif README etc \
+	jar cmf manifest-art artemis.jar META-INF/services images/PSUlogo.gif images/icon.gif README.md etc \
 	                     artemis_sqlmap org uk com net nsdb type seqdb LICENSE.Apache j2ssh.properties; \
         echo "Main-Class: uk.ac.sanger.artemis.circular.DNADraw\nPermissions: all-permissions" > manifest-circular; \
-        jar cmf manifest-circular DNAPlotter.jar images/PSUlogo.gif README etc \
+        jar cmf manifest-circular DNAPlotter.jar images/PSUlogo.gif README.md etc \
                              uk org/gmod org/w3c org/apache org/biojava/bio/ com/ibatis/common/jdbc/ net/sf/samtools/ LICENSE.Apache j2ssh.properties; \
 	echo "Main-Class: uk.ac.sanger.artemis.components.alignment.BamView\nPermissions: all-permissions" > manifest-bamview; \
 	jar cmf manifest-bamview BamView.jar META-INF/services etc uk org/apache org/biojava org/biojavax org/gmod org/w3c net/sf com/ibatis; \
 	echo "Main-Class: uk.ac.sanger.artemis.components.ActMain\nPermissions: all-permissions" > manifest-act; \
-	jar cmf manifest-act act.jar META-INF/services images/PSUlogo.gif images/icon.gif README etc \
+	jar cmf manifest-act act.jar META-INF/services images/PSUlogo.gif images/icon.gif README.md etc \
 	                     artemis_sqlmap org uk com net nsdb type seqdb LICENSE.Apache j2ssh.properties; \
 	rm -f etc/log4j.properties; \
-	jar cmf manifest-art artemis_mac.jar images/PSUlogo.gif images/icon.gif README \
+	jar cmf manifest-art artemis_mac.jar images/PSUlogo.gif images/icon.gif README.md \
 	        uk org/gmod nsdb type seqdb LICENSE.Apache artemis_sqlmap
 
 clean :
diff --git a/README b/README.md
similarity index 90%
rename from README
rename to README.md
index 5109688..5c4d9bb 100644
--- a/README
+++ b/README.md
@@ -1,4 +1,6 @@
-INTRODUCTION
+# INTRODUCTION
+
+[![Build Status](https://travis-ci.org/sanger-pathogens/Artemis.svg?branch=master)](https://travis-ci.org/sanger-pathogens/Artemis)
 
 Artemis is a free genome browser and annotation tool that allows visualisation 
 of sequence features, next generation data and the results of analyses within 
@@ -13,7 +15,7 @@ difference between genomes and to explore conservation of synteny, in the contex
 of the entire sequences and their annotation.
 
 
-DOCUMENTATION
+# DOCUMENTATION
 
 The Artemis user manual is at:
   http://www.sanger.ac.uk/resources/software/artemis/
@@ -21,12 +23,12 @@ The Artemis user manual is at:
 The ACT user manual is at:
   http://www.sanger.ac.uk/resources/software/act/
 
-INSTALLATION
+# INSTALLATION
 
 The installation instructions are included in the user manual.
 
 
-DISTRIBUTION
+# DISTRIBUTION
 
 Artemis may be freely distributed under the terms of the GNU Public License,
 and should run on any system with a recent version of Java.  
@@ -35,7 +37,7 @@ For information on how to get Artemis see this web page:
   http://www.sanger.ac.uk/resources/software/artemis/
 
 
-COPYRIGHT
+# COPYRIGHT
 
 Copyright (C) 1998-2013  Genome Research Limited
 
diff --git a/act b/act
index 4200521..365fe5e 100755
--- a/act
+++ b/act
@@ -43,6 +43,8 @@ CLASSPATH=$CLASSPATH:$ACT_HOME/lib/ibatis/ibatis-2.3.4.726.jar:$ACT_HOME/lib/iba
 # picard jars
 CLASSPATH=$ACT_HOME/lib/picard/sam.jar:$ACT_HOME/lib/picard/picard.jar:$CLASSPATH
 
+CLASSPATH="$ACT_HOME/lib/commons-lang-2.6.jar:$CLASSPATH"
+
 export CLASSPATH
 
 ACT_PROPERTIES="-Dartemis.environment=UNIX"
diff --git a/art b/art
index 180d773..f8fe4fe 100755
--- a/art
+++ b/art
@@ -40,6 +40,9 @@ export CLASSPATH
 
 # picard jars
 CLASSPATH=$ARTEMIS_HOME/lib/picard/sam.jar:$ARTEMIS_HOME/lib/picard/picard.jar:$ARTEMIS_HOME/lib/commons-net-2.2.jar:$CLASSPATH
+
+CLASSPATH="$ARTEMIS_HOME/lib/commons-lang-2.6.jar:$CLASSPATH"
+
 export CLASSPATH
 
 
diff --git a/docs/act_manual.sgml b/docs/act_manual.sgml
index 9dbe2b9..44a75f3 100644
--- a/docs/act_manual.sgml
+++ b/docs/act_manual.sgml
@@ -44,7 +44,7 @@
     </PRODUCTNAME>
     <PUBDATE>16 May 2000</PUBDATE>
     <COPYRIGHT>
-      <YEAR>2000-2014</YEAR>
+      <YEAR>2000-2016</YEAR>
       <HOLDER>Genome Research Limited</HOLDER>
     </COPYRIGHT>
     <LEGALNOTICE>
diff --git a/docs/act_start_chapter.sgml b/docs/act_start_chapter.sgml
index 45b24c1..b41b456 100644
--- a/docs/act_start_chapter.sgml
+++ b/docs/act_start_chapter.sgml
@@ -170,7 +170,7 @@ cd ~/act
 
     <PARA>
       <COMMAND>
-java -classpath lib/biojava.jar:lib/jemAlign.jar:lib/j2ssh/j2ssh-core.jar:lib/ibatis/ibatis-2.3.4.726.jar:lib/ibatis/log4j-1.2.14.jar:lib/chado-14-interface.jar:lib/postgresql-8.4-701.jdbc3.jar:lib/picard.jar:lib/picard/sam.jar:lib/batik/batik-awt-util.jar:lib/batik/batik-codec.jar:lib/batik/batik-dom.jar:lib/batik/batik-ext.jar:lib/batik/batik-svggen.jar:lib/batik/batik-util.jar:lib/batik/batik-xml.jar:. -Dartemis.environment=UNIX uk.ac.sanger.artemis.components.ActMain
+java -mx500m -ms100m -classpath lib/biojava.jar:lib/jemAlign.jar:lib/j2ssh/j2ssh-core.jar:lib/ibatis/ibatis-2.3.4.726.jar:lib/ibatis/log4j-1.2.14.jar:lib/ibatis/cglib-nodep-2.2.jar:lib/postgresql-8.4-701.jdbc3.jar:lib/picard.jar:lib/picard/sam.jar:lib/batik/batik-awt-util.jar:lib/batik/batik-codec.jar:lib/batik/batik-dom.jar:lib/batik/batik-ext.jar:lib/batik/batik-svggen.jar:lib/batik/batik-util.jar:lib/batik/batik-xml.jar:lib/commons-lang-2.6.jar:lib/commons-net-2.2.jar:. uk.ac.sanger.a [...]
       </COMMAND>
     </PARA>
   </SECT1>
diff --git a/docs/feature_edit.gif b/docs/feature_edit.gif
index a31014b..3b46dab 100644
Binary files a/docs/feature_edit.gif and b/docs/feature_edit.gif differ
diff --git a/docs/jvm_options.sgml b/docs/jvm_options.sgml
index 242ae1f..65dde42 100644
--- a/docs/jvm_options.sgml
+++ b/docs/jvm_options.sgml
@@ -99,6 +99,29 @@ first (top) sequence in ACT:
     </SECT2>
 ]]>
 
+    <SECT2 ID="JVM-SHOW-SNPS">
+       <TITLE><LITERAL>-Dshow_snps</LITERAL></TITLE>
+      <PARA>
+Show SNP marks in BAM panels.
+</PARA>
+    </SECT2>
+
+    <SECT2 ID="JVM-SHOW-SNPS-PLOT">
+       <TITLE><LITERAL>-Dshow_snp_plot</LITERAL></TITLE>
+      <PARA>
+Automatically open SNP plots for BAM files that are opened.
+</PARA>
+    </SECT2>
+
+
+    <SECT2 ID="JVM-SHOW-COV-PLOT">
+       <TITLE><LITERAL>-Dshow_cov_plot</LITERAL></TITLE>
+      <PARA>
+Automatically open coverage plots for BAM files that are opened.
+</PARA>
+    </SECT2>
+
+
     <SECT2 ID="JVM-CHADO">
        <TITLE><LITERAL>-Dchado="hostname:port/database?username"</LITERAL></TITLE>
       <PARA>
diff --git a/docs/main_window_chapter.sgml b/docs/main_window_chapter.sgml
index b670839..7dc2bee 100644
--- a/docs/main_window_chapter.sgml
+++ b/docs/main_window_chapter.sgml
@@ -265,5 +265,6 @@ menu items which allow the maximum window size to be set.
     </PARA>
   </SECT1>
 
+&validator;
 &mousebuttons;
 </CHAPTER>
diff --git a/docs/manual.sgml b/docs/manual.sgml
index 3ce768f..e356eb7 100644
--- a/docs/manual.sgml
+++ b/docs/manual.sgml
@@ -21,6 +21,7 @@
 <!ENTITY views-directedit SYSTEM "views_directedit.sgml">
 <!ENTITY featurelist SYSTEM "feature_list.sgml">
 <!ENTITY mousebuttons SYSTEM "mousebuttons.sgml">
+<!ENTITY validator SYSTEM "validator.sgml">
 <!ENTITY gettingjava SYSTEM "getting_java.sgml">
 <!ENTITY unixargs SYSTEM "unix_args.sgml">
 <!ENTITY jvmopts SYSTEM "jvm_options.sgml">
@@ -49,7 +50,7 @@
     </PRODUCTNAME>
     <PUBDATE>22 February 1999</PUBDATE>
     <COPYRIGHT>
-      <YEAR>1999-2014</YEAR>
+      <YEAR>1999-2016</YEAR>
       <HOLDER>Genome Research Limited</HOLDER>
     </COPYRIGHT>
     <LEGALNOTICE>
diff --git a/docs/menus.sgml b/docs/menus.sgml
index 204540f..21782ec 100644
--- a/docs/menus.sgml
+++ b/docs/menus.sgml
@@ -165,11 +165,22 @@ similar effect to double clicking the middle button on a base or residue (see
     <TITLE>Features Overlapping Selection</TITLE>
     <PARA>
 Select those (and only those) features that overlap the currently selected
-range of bases or any of the currently selected features.  The current
+range of bases or any of the currently selected features. The current
 selection will be discarded.
     </PARA>
   </SECT2>
 
+
+  <SECT2 ID="SELECTMENU-WITHIN-SELECTION">
+    <TITLE>Features Within Selection</TITLE>
+    <PARA>
+Select those (and only those) features that are fully contained by the currently 
+selected range of bases or any of the currently selected features. The current
+selection will be discarded.
+    </PARA>
+  </SECT2>
+
+
   <SECT2 ID="SELECTMENU-BASE-RANGE">
     <TITLE>Base Range ...</TITLE>
     <PARA>
@@ -535,6 +546,16 @@ LINKEND="GRAPHS">.  Configuration options for graphs are described in <XREF
 LINKEND="OPTIONS-PLOTS">.
     </PARA>
   </SECT2>
+
+<![ %act-only; [
+  <SECT2 ID="VIEWMENU-ADJUST-HEIGHTS">
+    <TITLE>Adjust panel heights ...</TITLE>
+    <PARA>
+Option in ACT for adjusting the panel heights (BAM, VCF, plots, comparisons) by giving
+them different weights in order to distribute the space between each component.
+    </PARA>
+  </SECT2>
+]]>
 </SECT1>
 
 <SECT1 ID="GOTOMENU">
@@ -775,9 +796,6 @@ The feature edit window
         <IMAGEOBJECT>
           <IMAGEDATA FORMAT="gif" FILEREF="feature_edit.gif">
         </IMAGEOBJECT>
-        <IMAGEOBJECT>
-          <IMAGEDATA FORMAT="eps" FILEREF="feature_edit.eps">
-        </IMAGEOBJECT>
       </MEDIAOBJECT>
     </SCREENSHOT>
     <PARA>
@@ -839,9 +857,35 @@ LINKEND="GOTOMENU-GOTO-SELECTION-START"> for more).
             </LISTITEM>
             <LISTITEM>
               <PARA>
-The <LITERAL>Select Feature</LITERAL> button selects this feature (in the same
-way as clicking on the feature in one of the views).
+The <LITERAL>User Qualifiers</LITERAL> button opens a tool for maintaining 
+user defined lists of qualifiers (i.e. qualifiers in the form tag = value pairs 
+on separate lines) and the option to read qualifiers from <ULINK 
+URL="http://www.geneontology.org/GO.format.shtml" TYPE="external">OBO</ULINK> formatted files or 
+URLs. In the intial screen (see below) you are invited to import your qualifier list from the import 
+options in the "File" menu. These lists can be optionally saved between sessions in the file 
+'.artemis.qualifiers' in the home directory. 
               </PARA>
+      <MEDIAOBJECT>
+        <IMAGEOBJECT>
+          <IMAGEDATA FORMAT="gif" FILEREF="qualifier_list.gif">
+        </IMAGEOBJECT>
+      </MEDIAOBJECT>
+
+              <PARA>
+When a qualifier list or OBO file has been added then it is possible to search for
+keywords within a list. The qualifer selected in the drop down list (under the SEARCH button) 
+can then be added to the current feature annotation or added to selected features in &prog;.
+If the keywords text field is left blank then all qualifiers are available from the drop
+down list of qualifier values.
+              </PARA>
+
+      <MEDIAOBJECT>
+        <IMAGEOBJECT>
+          <IMAGEDATA FORMAT="gif" FILEREF="qualifier_list2.gif">
+        </IMAGEOBJECT>
+      </MEDIAOBJECT>
+
+
             </LISTITEM>
           </ITEMIZEDLIST>
         </LISTITEM>
diff --git a/docs/qualifier_list.gif b/docs/qualifier_list.gif
new file mode 100644
index 0000000..3bcd2f0
Binary files /dev/null and b/docs/qualifier_list.gif differ
diff --git a/docs/qualifier_list2.gif b/docs/qualifier_list2.gif
new file mode 100644
index 0000000..c963b15
Binary files /dev/null and b/docs/qualifier_list2.gif differ
diff --git a/docs/start_chapter.sgml b/docs/start_chapter.sgml
index 42d645d..ffb0367 100644
--- a/docs/start_chapter.sgml
+++ b/docs/start_chapter.sgml
@@ -66,7 +66,7 @@ cd ~/artemis
 
     <PARA>
       <COMMAND>
-java -classpath lib/biojava.jar:lib/jemAlign.jar:lib/j2ssh/j2ssh-core.jar:lib/ibatis/ibatis-2.3.4.726.jar:lib/ibatis/log4j-1.2.14.jar:lib/chado-14-interface.jar:lib/postgresql-8.4-701.jdbc3.jar:lib/picard.jar:lib/picard/sam.jar:lib/batik/batik-awt-util.jar:lib/batik/batik-codec.jar:lib/batik/batik-dom.jar:lib/batik/batik-ext.jar:lib/batik/batik-svggen.jar:lib/batik/batik-util.jar:lib/batik/batik-xml.jar:. -Dartemis.environment=UNIX uk.ac.sanger.artemis.components.ArtemisMain
+java  -mx500m -ms100m -classpath lib/biojava.jar:lib/jemAlign.jar:lib/j2ssh/j2ssh-core.jar:lib/ibatis/ibatis-2.3.4.726.jar:lib/ibatis/log4j-1.2.14.jar:lib/ibatis/cglib-nodep-2.2.jar:lib/postgresql-8.4-701.jdbc3.jar:lib/picard.jar:lib/picard/sam.jar:lib/batik/batik-awt-util.jar:lib/batik/batik-codec.jar:lib/batik/batik-dom.jar:lib/batik/batik-ext.jar:lib/batik/batik-svggen.jar:lib/batik/batik-util.jar:lib/batik/batik-xml.jar:lib/commons-lang-2.6.jar:lib/commons-net-2.2.jar:. -Dartemis.env [...]
       </COMMAND>
     </PARA>
   </SECT1>
diff --git a/docs/tick.png b/docs/tick.png
new file mode 100644
index 0000000..9bf53b9
Binary files /dev/null and b/docs/tick.png differ
diff --git a/docs/unix_args.sgml b/docs/unix_args.sgml
index 4b996df..68c37c2 100644
--- a/docs/unix_args.sgml
+++ b/docs/unix_args.sgml
@@ -5,14 +5,6 @@ As well as the listing file names on the command line, the following switches
 are available to UNIX users:
     </PARA>
 
-    <SECT2 ID="UNIXARGS-QUIET">
-      <TITLE><LITERAL>-quiet</LITERAL></TITLE>
-      <PARA>
-This option tells &prog; to suppress normal informational messages while
-running.
-      </PARA>
-    </SECT2>
-
     <SECT2 ID="UNIXARGS-OPTIONS">
       <TITLE><LITERAL>-options</LITERAL></TITLE>
       <PARA>
diff --git a/docs/validation_report.png b/docs/validation_report.png
new file mode 100644
index 0000000..ab37907
Binary files /dev/null and b/docs/validation_report.png differ
diff --git a/docs/validator.sgml b/docs/validator.sgml
new file mode 100644
index 0000000..f2a8ff1
--- /dev/null
+++ b/docs/validator.sgml
@@ -0,0 +1,181 @@
+  <SECT1 ID="VALIDATOR">
+    <TITLE>Annotation Validation</TITLE>
+
+<SECT2 ID="VALIDATOR-RUN">
+    <TITLE>How to Run Validation Checks</TITLE>
+    <PARA>
+&prog; can carry out validations checks to try and minimise annotation errors. 
+These checks can be carried out in the following ways:
+    </PARA>
+<ORDEREDLIST ID="VALID-CHECK">
+    <LISTITEM ID="VALID-CHECK-1">
+      <PARA>
+Click on the tick button
+
+<inlinemediaobject>
+	<imageobject>
+		<imagedata fileref="tick.png">
+	</imageobject>
+</inlinemediaobject>
+
+found in the top right hand side of Artemis to validate all features. When complete it will
+open a report window highlighting any features which have failed the checks.
+
+      </PARA>
+    </LISTITEM>
+    <LISTITEM ID="VALID-CHECK-2">
+      <PARA>
+Select the features to be checked in &prog; and open the popup menu by right clicking on the feature display 
+and selecting the 'Validation report ...' option. 
+      </PARA>
+    </LISTITEM>
+
+    <LISTITEM ID="VALID-CHECK-3">
+      <PARA>
+From the View menu, select the 'Feature Filters' menu item and the 'Validation checks...' option. This opens
+a feature list window for each of the type of check it carries out and these contain the features that have
+failed the check.
+      </PARA>
+    </LISTITEM>
+    <LISTITEM ID="VALID-CHECK-4">
+      <PARA>
+For organisms in a chado database the vaidator can be run from the 'Database and File Manager' window from the 
+'File' menu by selecting the 'Validate Selected Sequence / Organism' option.
+      </PARA>
+    </LISTITEM>
+</ORDEREDLIST>
+</SECT2>
+
+<SECT2 ID="VALIDATOR-CHECKS-ALL">
+    <TITLE>Validation Checks For All File Types</TITLE>
+    <PARA>
+The following checks are made on all file types (e.g. EMBL, GFF3):
+    </PARA>
+
+<ITEMIZEDLIST SPACING="compact">
+   <LISTITEM>
+   <PARA>
+CDS have no internal stop codon 
+   </PARA>
+   </LISTITEM>
+   <LISTITEM>
+   <PARA>
+CDS have a valid stop codon 
+   </PARA>
+   </LISTITEM>
+</ITEMIZEDLIST>
+
+    <PARA>
+Additionally &prog; checks GO annotation for: 
+    </PARA>
+
+<ITEMIZEDLIST SPACING="compact">
+   <LISTITEM>
+   <PARA>
+unexpected white space in with/from and dbxref columns 
+   </PARA>
+   </LISTITEM>
+   <LISTITEM>
+   <PARA>
+the WITH/FROM field must be empty when using IDA, NAS, ND, TAS or EXP evidence code 
+   </PARA>
+   </LISTITEM>
+   <LISTITEM>
+   <PARA>
+GO:0005515 can only have IPI evidence code 
+   </PARA>
+   </LISTITEM>
+   <LISTITEM>
+   <PARA>
+IEP is not allowed for molecular_function and cellular_component terms 
+   </PARA>
+   </LISTITEM>
+   <LISTITEM>
+   <PARA>
+the WITH/FROM field must be filled when using ISS, ISA, ISO and ISM codes
+   </PARA>
+   </LISTITEM>
+</ITEMIZEDLIST>
+</SECT2>
+
+<SECT2 ID="VALIDATOR-CHECKS-GFF">
+    <TITLE>Validation Checks For GFF3</TITLE>
+
+    <PARA>
+The following are checks for GFF3 and Chado entries only: 
+    </PARA>
+
+<ITEMIZEDLIST SPACING="compact">
+   <LISTITEM>
+   <PARA>
+check that the gene model comprises of at least a gene and a transcript feature
+   </PARA>
+   </LISTITEM>
+   <LISTITEM>
+   <PARA>
+check that the boundaries of the features making up a gene model are consistent
+   </PARA>
+   </LISTITEM>
+   <LISTITEM>
+   <PARA>
+check that all the features in a gene model are on the same strand 
+   </PARA>
+   </LISTITEM>
+   <LISTITEM>
+   <PARA>
+check that CDS features have a phase 
+   </PARA>
+   </LISTITEM>
+   <LISTITEM>
+   <PARA>
+check the attribute column to ensure that qualifiers have a value (not empty) and
+that only reserved tags start with an uppercase character
+   </PARA>
+   </LISTITEM>
+   <LISTITEM>
+   <PARA>
+check that partial qualifiers are consistent within a gene model 
+   </PARA>
+   </LISTITEM>
+   <LISTITEM>
+   <PARA>
+check that the gene name prefix is consistent within a gene model 
+   </PARA>
+   </LISTITEM>
+</ITEMIZEDLIST>
+
+</SECT2>
+
+<SECT2 ID="VALIDATOR-REPORT">
+    <TITLE>Validation Report</TITLE>
+
+   <PARA>
+The validation report window displays a summary for the features that have failed one or
+more of the annotation checks above. The title bar of the window displays the number of 
+features that have passed and the number that have failed the validation checks. The 
+problems identified are highlighted in red.
+   </PARA>
+
+   <PARA>
+<MEDIAOBJECT>
+        <IMAGEOBJECT>
+           <IMAGEDATA FORMAT="png" FILEREF="validation_report.png"></IMAGEOBJECT>
+        </MEDIAOBJECT>
+   </PARA>
+
+   <PARA>
+Some of the errors can be fixed automatically. The 'Auto-Fix' button opens a window
+with the fixes enabled that are available for the entry type that is loaded in &prog;. For example, it will
+attempt to fix CDS features that have been found not to end in stop codons. If the last codon is not a 
+stop codon, but the very next codon is a stop codon, then the end of the feature is 
+extended by three bases.
+   </PARA>
+   <PARA>
+For GFF3 and chado entries &prog; will also attempt to fix problems it finds with gene
+boundaries and if a phase is absent then a default phase of 0 is given. Once these are
+fixed the results window will automatically update and remove the problems it has
+managed to resolved.
+   </PARA>
+</SECT2>
+
+  </SECT1>
diff --git a/etc/feature_keys_gff b/etc/feature_keys_gff
index 9f0b36a..afb9f56 100644
--- a/etc/feature_keys_gff
+++ b/etc/feature_keys_gff
@@ -10,12 +10,12 @@ region              ID Name Alias Parent Note Target Gap Derives_from Dbxref Ont
 contig              ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score comment
 #supercontig         ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score 
 #chromosome          ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified note comment description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score 
-gene                ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score private
+gene                ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score private comment
 #CDS                 ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified codon_start description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score  results note comment job
 polypeptide         ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified description locus stable_id EC_number gene_symbol Ontology_term Dbxref product molecule_type size organism_name strain topology localization gff_source gff_seqname score mass isoelectric charge source private signal_peptide membrane_structure cytoplasmic_polypeptide_region non_cytoplasmic_polypeptide_region transmembrane_polypeptide_regio [...]
 pseudogene          ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score private
-pseudogenic_exon    ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score private
-pseudogenic_transcript ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score product
+pseudogenic_exon    ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score private
+pseudogenic_transcript ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score product
 intron              ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score 
 exon                ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified codon_start description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score 
 three_prime_UTR     ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified systematic_id note comment private curation
diff --git a/etc/key_mapping b/etc/key_mapping
index 31afa98..3a7f179 100644
--- a/etc/key_mapping
+++ b/etc/key_mapping
@@ -21,6 +21,9 @@ direct_repeat                              repeat_region   rpt_type=direct
 polypeptide_domain                         misc_feature
 centromere                                 misc_feature    note=centromere
 modified_amino_acid_feature                misc_feature    note=modified_amino_acid_feature
-snRNA                                      ncRNA           ncRNA_class="snRNA"
-snoRNA                                     ncRNA           ncRNA_class="snoRNA"
+snRNA                                      ncRNA           ncRNA_class=snRNA
+snoRNA                                     ncRNA           ncRNA_class=snoRNA
 nucleotide_match                           misc_feature    note=nucleotide_match
+uORF                                       misc_feature    note=uORF
+sORF                                       misc_feature    note=sORF
+tnaORF                                     misc_feature    note=tnaORF
diff --git a/etc/options b/etc/options
index 1987d3f..b1d8988 100644
--- a/etc/options
+++ b/etc/options
@@ -148,7 +148,7 @@ start_codons_4 = atg
 translation_table_5 = \
       agas aggs atam tgaw
 
-start_codons_5 = atg ata att
+start_codons_5 = ttg att atc ata atg gtg
 #start_codons_5_apis = atg ata atc att
 #start_codons_5_polyplacophora = atg ata gtg
 
@@ -423,7 +423,7 @@ extra_keys = \
     TMM signalP
 
 # this list is added to the keys from the feature_keys_gff file
-extra_keys_gff = CDS spliced_leader_RNA sequence_variant fasta_record
+extra_keys_gff = CDS uORF sORF tnaORF spliced_leader_RNA sequence_variant fasta_record
 
 # Names of qualifiers to search when attempting to find the primary or display
 # name of a gene.  These qualifiers names are searched in order when looking
@@ -439,6 +439,7 @@ systematic_name_qualifiers = systematic_id temporary_systematic_id \
 
 # this list is added to the qualifiers from the qualifier_types file
 extra_qualifiers = \
+    alias "text" \
     CHROMO_LINK text \
     C_processing "text" \
     C_processing_BigPi "text" \
@@ -569,6 +570,7 @@ extra_qualifiers = \
 
 # this list is added to the qualifiers from the qualifier_types_gff file
 extra_qualifiers_gff = \
+    alias "text" \
     blast_score text \
     blast_file "text" \
     blastn_file "text" \
@@ -830,7 +832,8 @@ common_keys = \
     BLASTN_HIT BLASTCDS 3'UTR 5'UTR
 
 # SRS
-srs_url = http://srs.ebi.ac.uk/srsbin/cgi-bin/
+#srs_url = http://srs.ebi.ac.uk/srsbin/cgi-bin/
+srs_url = http://www.bioinformatics.nl/srsbin/cgi-bin/
 
 # hyperlinked databases in feature editor
 hyperlinks = \
@@ -854,13 +857,14 @@ hyperlinks = \
   AID http://pubchem.ncbi.nlm.nih.gov/assay/assay.cgi?aid= \
   GeneID http://www.ncbi.nlm.nih.gov/sites/entrez?db=gene&cmd=Retrieve&dopt=full_report&list_uids= \
   Rfam http://rfam.sanger.ac.uk/family/ \
-  GI http://www.ncbi.nlm.nih.gov/entrez/sutils/girevhist.cgi?val=
+  GI http://www.ncbi.nlm.nih.gov/entrez/sutils/girevhist.cgi?val= \
+  TCDB http://www.tcdb.org/search/result.php?tc=
 
 # BamView
 # No. threads used to read from multiple BAM files
 bam_read_thread = 2
 # Max read coverage to display
-bam_max_coverage = 10000
+bam_max_coverage = 1000000
 
 #
 # CHADO DATABASE OPTIONS 
diff --git a/etc/project.properties b/etc/project.properties
index df33aa8..164f202 100644
--- a/etc/project.properties
+++ b/etc/project.properties
@@ -1,10 +1,10 @@
 
-project.Styphi.sequence=ftp://ftp.sanger.ac.uk/pub/pathogens/Salmonella/typhi/St.dna
-project.Styphi.annotation=ftp://ftp.sanger.ac.uk/pub/pathogens/Salmonella/typhi/St.art
+project.Styphi.sequence=ftp://ftp.sanger.ac.uk/pub/project/pathogens/Salmonella/typhi/St.dna
+project.Styphi.annotation=ftp://ftp.sanger.ac.uk/pub/project/pathogens/Salmonella/typhi/St.art
 project.Styphi.title=Styphi
 
 #
-project.PF3D7.sequence = ftp://ftp.sanger.ac.uk/pub4/pathogens/Plasmodium/falciparum/3D7/3D7.latest_version/September_2011/Pf3D7_01.embl.gz
+project.PF3D7.sequence = ftp://ftp.sanger.ac.uk/pub/project/pathogens/Plasmodium/falciparum/3D7/3D7.latest_version/September_2011/Pf3D7_01.embl.gz
 project.PF3D7.bam = \
 	http://www.genedb.org/artemis/NAR/Malaria_RNASeq/version2.1.4/MAL_0h.bam \
 	http://www.genedb.org/artemis/NAR/Malaria_RNASeq/version2.1.4/MAL_8h.bam \
diff --git a/etc/qualifier_types b/etc/qualifier_types
index 7dfd32a..763de70 100644
--- a/etc/qualifier_types
+++ b/etc/qualifier_types
@@ -73,8 +73,8 @@ map                      no        "text"
 mating_type              yes       "text"
 mobile_element_type      no        "text"
 mod_base                 yes        list    OTHER ac4c chm5u cm cmnm5s2u cmnm5u d fm gal q gm i i6a m1a m1f m1g m1i m22g m2a m2g m3c m5c m6a m7g mam5s2u mam5u man q mcm5s2u mcm5u mo5u ms2i6a ms2t6a mt6a mv o5u osyw p q s2c s2t s2u s4u t t6a tm um x yw
-mol_type                 yes       "list"    "unassigned DNA" "unassigned RNA" "genomic DNA" "genomic RNA" "mRNA" "tRNA" "rRNA" "transcribed RNA" "other RNA" "other DNA" "protein" "viral cRNA"
-ncRNA_class              yes        list    "antisense_RNA" "autocatalytically_spliced_intron" "hammerhead_ribozyme" "RNase_P_RNA" "RNase_MRP_RNA" "telomerase_RNA" "guide_RNA" "rasiRNA" "scRNA" "siRNA" "miRNA" "piRNA" "snoRNA" "snRNA" "SRP_RNA" "vault_RNA" "Y_RNA" "other" "ribozyme"
+mol_type                 yes       "list"   "unassigned DNA" "unassigned RNA" "genomic DNA" "genomic RNA" "mRNA" "tRNA" "rRNA" "transcribed RNA" "other RNA" "other DNA" "protein" "viral cRNA"
+ncRNA_class              yes       "list"   "antisense_RNA" "autocatalytically_spliced_intron" "hammerhead_ribozyme" "RNase_P_RNA" "RNase_MRP_RNA" "telomerase_RNA" "guide_RNA" "rasiRNA" "scRNA" "siRNA" "miRNA" "piRNA" "snoRNA" "snRNA" "SRP_RNA" "vault_RNA" "Y_RNA" "other" "ribozyme"
 note                     no        "text"
 number                   yes        number  1 99999999
 old_locus_tag            no        "text"
diff --git a/etc/results_to_netscape b/etc/results_to_netscape
index f313d38..1b97f7e 100755
--- a/etc/results_to_netscape
+++ b/etc/results_to_netscape
@@ -26,6 +26,9 @@ else
   done
 fi
 
+if [ ! -f "$NETSCAPE" ]; then
+    NETSCAPE=`which firefox`
+fi
 
 if [ -f "$DIANA_ENVIRONMENT_FILE" ]
 then
@@ -93,9 +96,10 @@ BEGIN {
   $DNA_DATABASES = "embl";
 
   # change this to point to the wgetz script of your SRS server
-  $SRS_SERVER = "www.sanger.ac.uk/srs6bin/cgi-bin/wgetz?-e+";
-  $SRS_SERVER = "srs.ebi.ac.uk/srsbin/cgi-bin/wgetz?-e+";
-
+  #$SRS_SERVER = "www.sanger.ac.uk/srs6bin/cgi-bin/wgetz?-e+";
+  #$SRS_SERVER = "srs.ebi.ac.uk/srsbin/cgi-bin/wgetz?-e+";
+  $SRS_SERVER = "www.bioinformatics.nl/srsbin/cgi-bin/wgetz?-e+";
+  
   $PROTEIN_DATABASES =~ s/ /%20/g;
   $DNA_DATABASES =~ s/ /%20/g;
 
@@ -140,8 +144,9 @@ sub hyperlink_id
     }
 
     # Text Entry
-    $r = qq#<a href="http://$SRS_SERVER-id+1+\[\{$PROTEIN_DATABASES\}-ID:$id*]|[\{$PROTEIN_DATABASES\}-AccNumber:$id*]+-vn+2">$id</a>#;
-
+    $r = qq#<a href="http://www.uniprot.org/uniprot/$id">$id</a>#;
+    
+    #$r = qq#<a href="http://$SRS_SERVER-id+1+\[\{$PROTEIN_DATABASES\}-ID:$id*]|[\{$PROTEIN_DATABASES\}-AccNumber:$id*]+-vn+2">$id</a>#;
     #$r = qq#<a href="http://$SRS_SERVER-id+1+\[\{$PROTEIN_DATABASES\}-ID:$id*]|[\{$PROTEIN_DATABASES\}-AccNumber:$id*]">$id</a>#;
   }
   return $r
diff --git a/etc/versions b/etc/versions
index 8ed02d7..10e531f 100644
--- a/etc/versions
+++ b/etc/versions
@@ -1,4 +1,4 @@
-Artemis Release 16.0.0
-ACT Release 13.0.0
+Artemis Release 16.0.17
+ACT Release 13.0.16
 DNAPlotter Release 1.11
-BamView 1.2.10
\ No newline at end of file
+BamView 1.2.13
diff --git a/etc/writedb_entry b/etc/writedb_entry
index d747fc4..6fbfcb6 100755
--- a/etc/writedb_entry
+++ b/etc/writedb_entry
@@ -1,6 +1,6 @@
 #!/bin/sh -
-# 
-# This script reads and writes entries from a database. 
+#
+# This script reads and writes entries from a database.
 # Examples:
 # writedb_entry -help
 # writedb_entry -s Pf3D7_01 Pf3D7_05 Pf3D7_07
@@ -28,6 +28,7 @@ done
 ARTEMIS_HOME=`dirname "$PRG"`/..
 
 CLASSPATH="$ARTEMIS_HOME:$ARTEMIS_HOME/lib/biojava.jar:$ARTEMIS_HOME/lib/jemAlign.jar:$ARTEMIS_HOME/lib/jakarta-regexp-1.2.jar:$ARTEMIS_HOME/lib/macos.jar:$ARTEMIS_HOME/lib/postgresql-8.4-701.jdbc3.jar:$ARTEMIS_HOME/lib/chado-14-interface.jar:$CLASSPATH"
+CLASSPATH="$ARTEMIS_HOME/lib/commons-lang-2.6.jar:$CLASSPATH"
 
 # iBatis jars
 CLASSPATH="$CLASSPATH:$ARTEMIS_HOME/lib/ibatis/ibatis-2.3.4.726.jar:$ARTEMIS_HOME/lib/ibatis/:$ARTEMIS_HOME/lib/ibatis/log4j-1.2.14.jar:$ARTEMIS_HOME/lib/ibatis/cglib-nodep-2.2.jar:$ARTEMIS_HOME/lib/retrotranslator-runtime-1.1.0.jar"
@@ -42,7 +43,7 @@ while test $# != 0
     case $1 in
     -Dchado*)
        DEFAULT_CONNECTION="$1" ;;
-    -D*) 
+    -D*)
        FLAGS="$FLAGS $1" ;;
     *) break ;;
     esac
@@ -55,7 +56,7 @@ for arg in "$@"
 do
 	if [ '-c' == "${arg}" ]; then
 		let "nextID = $idx + 1";
-		DEFAULT_CONNECTION="-Dchado=${!nextID}"		
+		DEFAULT_CONNECTION="-Dchado=${!nextID}"
 	fi
 	if [ '-l' == "${arg}" ]; then
 		let "nextID = $idx + 2";
diff --git a/gff2embl b/gff2embl
new file mode 100755
index 0000000..6052ebd
--- /dev/null
+++ b/gff2embl
@@ -0,0 +1,106 @@
+#!/bin/sh -
+
+# Command line utility for coverting GFF to EMBL
+PRG=$0
+progname=`basename $0`
+
+ARTEMIS_HOME=`dirname "$PRG"`/.
+
+CLASSPATH=$ARTEMIS_HOME:$ARTEMIS_HOME/lib/biojava.jar:$ARTEMIS_HOME/lib/jemAlign.jar:$ARTEMIS_HOME/lib/jakarta-regexp-1.2.jar:$ARTEMIS_HOME/lib/macos.jar:$ARTEMIS_HOME/lib/postgresql-8.4-701.jdbc3.jar:$ARTEMIS_HOME/lib/chado-14-interface.jar:$CLASSPATH
+
+# j2ssh jars
+CLASSPATH=$CLASSPATH:$ARTEMIS_HOME/lib/j2ssh/commons-logging.jar:$ARTEMIS_HOME/lib/j2ssh/j2ssh-core.jar:$ARTEMIS_HOME/lib/j2ssh/
+
+# iBatis jars
+CLASSPATH=$CLASSPATH:$ARTEMIS_HOME/lib/ibatis/ibatis-2.3.4.726.jar:$ARTEMIS_HOME/lib/ibatis/:$ARTEMIS_HOME/lib/ibatis/log4j-1.2.14.jar:$ARTEMIS_HOME/lib/ibatis/cglib-nodep-2.2.jar:$ARTEMIS_HOME/lib/retrotranslator-runtime-1.1.0.jar
+export CLASSPATH
+
+# batik jars
+CLASSPATH=$CLASSPATH:$ARTEMIS_HOME/lib/batik/batik-awt-util.jar:$ARTEMIS_HOME/lib/batik/batik-dom.jar:$ARTEMIS_HOME/lib/batik/batik-ext.jar:$ARTEMIS_HOME/lib/batik/batik-svggen.jar:$ARTEMIS_HOME/lib/batik/batik-util.jar:$ARTEMIS_HOME/lib/batik/batik-xml.jar
+
+# picard jars
+CLASSPATH=$ARTEMIS_HOME/lib/picard/sam.jar:$ARTEMIS_HOME/lib/picard/picard.jar:$ARTEMIS_HOME/lib/commons-net-2.2.jar:$CLASSPATH
+CLASSPATH="$ARTEMIS_HOME/lib/commons-lang-2.6.jar:$CLASSPATH"
+export CLASSPATH
+
+if [ $# = 0 ]
+then
+    :
+else
+    if [ "$1" = "-h" -o "$1" = "--help" -o "$1" = "-help" ]
+    then
+        cat <<EOF
+
+SYNOPSIS
+        gff2embl
+USAGE
+        $0 [options] <SEQUENCE_FILE>
+OPTIONS
+        SEQUENCE_FILE                  A GFF3 file
+
+        -s FILE                  space separated list of GFF files to read and write out
+        -o OUTPUT_DIR            output directory
+        -f [y/n]                 flatten the gene model, default is y
+        -z                       gzip output, default is y
+        -a [y|n]                 for EMBL submission format change to n, default is y
+EXAMPLES
+        % -s /Users/tjc/test.gff -o /Users/tjc
+HOMEPAGE
+        http://www.sanger.ac.uk/resources/software/artemis/
+EOF
+        exit 0
+    fi
+fi
+
+ARTEMIS_PROPERTIES="-Dartemis.environment=UNIX"
+
+# Allow URLs to work from behind firewalls
+if [ "$http_proxy" = "" ]
+then
+  http_proxy=$HTTP_PROXY
+fi
+
+if [ "$http_proxy" = "" ]
+then
+  http_proxy=$HTTP_proxy
+fi
+
+if [ "$http_proxy" != "" ]
+then
+  ARTEMIS_PROPERTIES="$ARTEMIS_PROPERTIES -DproxySet=true "`echo $http_proxy | sed 's/http:\/\/\(.*\):\(.*\)/ -Dhttp.proxyHost=\1 -Dhttp.proxyPort=\2/'`
+fi
+
+
+# "-mx500m" sets the maximum amount of memory that Artemis can use.  This may
+# need to be increased when dealing with large files
+MEM="-mx500m -ms20m"
+
+if [ "$JVM_FLAGS" = "" ]
+then
+    FLAGS="$MEM -noverify"
+else
+    FLAGS="$MEM -noverify $JVM_FLAGS"
+fi
+
+
+FLAGS=$FAST_FLAG$FLAGS
+
+if [ "$JAVA_VM" = "" ]
+then
+    if [ "$DEBUG" = yes ]
+    then
+        JAVA=java_g
+    else
+        JAVA=java
+    fi
+else
+    JAVA=$JAVA_VM
+fi
+
+PLATTMP=`uname`
+if [ "$PLATTMP" = "Darwin" ]
+then
+  FLAGS="$FLAGS -Dapple.laf.useScreenMenuBar=true -Dcom.apple.mrj.application.apple.menu.about.name=Circular Plot"
+fi
+
+$JAVA $MEM uk.ac.sanger.artemis.io.GffToEMBL $*
diff --git a/install_dependencies.sh b/install_dependencies.sh
new file mode 100644
index 0000000..44a15c4
--- /dev/null
+++ b/install_dependencies.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+
+set -x
+set -e
+
+start_dir=$(pwd)
+
+EMBOSS_VERSION="6.5.7"
+
+EMBOSS_DOWNLOAD_URL="ftp://emboss.open-bio.org/pub/EMBOSS/old/6.5.0/EMBOSS-${EMBOSS_VERSION}.tar.gz"
+
+# Make an install location
+if [ ! -d "${HOME}/dependencies" ]; then
+  mkdir ${HOME}/dependencies
+fi
+cd ${HOME}/dependencies
+build_dir=$(pwd)
+
+# DOWNLOAD ALL THE THINGS
+download () {
+  url=$1
+  download_location=$2
+
+  if [ -e $download_location ]; then
+    echo "Skipping download of $url, $download_location already exists"
+  else
+    echo "Downloading $url to $download_location"
+    wget $url -O $download_location
+  fi
+}
+
+download $EMBOSS_DOWNLOAD_URL "${build_dir}/emboss-${EMBOSS_VERSION}.tgz"
+
+# Build all the things
+cd $build_dir
+
+## Emboss
+emboss_dir=$(pwd)/EMBOSS-${EMBOSS_VERSION}
+if [ ! -d $emboss_dir ]; then
+  tar xzf emboss-${EMBOSS_VERSION}.tgz
+fi
+cd $emboss_dir
+if [ -e "${emboss_dir}/build/bin/restrict" ]; then
+  echo "Already built Emboss; skipping build"
+else
+  mkdir build
+  ./configure --prefix ${emboss_dir}/build
+  make
+  make install
+fi
+
+export EMBOSS_ROOT=${emboss_dir}/build
+
+cd $start_dir
+
+set +x
+set +e
diff --git a/test/data/MAL_8h.bam b/test/data/MAL_8h.bam
new file mode 100644
index 0000000..71e8e51
Binary files /dev/null and b/test/data/MAL_8h.bam differ
diff --git a/test/data/MAL_8h.bam.bai b/test/data/MAL_8h.bam.bai
new file mode 100644
index 0000000..146643c
Binary files /dev/null and b/test/data/MAL_8h.bam.bai differ
diff --git a/test/uk/ac/sanger/artemis/components/alignment/MappedReadsTest.java b/test/uk/ac/sanger/artemis/components/alignment/MappedReadsTest.java
new file mode 100644
index 0000000..0c12be4
--- /dev/null
+++ b/test/uk/ac/sanger/artemis/components/alignment/MappedReadsTest.java
@@ -0,0 +1,143 @@
+/* 
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2014  Genome Research Limited
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.alignment;
+
+import static org.junit.Assert.assertTrue;
+
+import java.awt.GraphicsEnvironment;
+import java.net.URL;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.swing.JFrame;
+
+import junit.framework.Assert;
+import uk.ac.sanger.artemis.io.Utils;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.components.EntryEdit;
+import uk.ac.sanger.artemis.components.alignment.BamUtils;
+import uk.ac.sanger.artemis.components.alignment.BamView;
+import uk.ac.sanger.artemis.components.alignment.MappedReads;
+import uk.ac.sanger.artemis.components.alignment.ReadCount;
+
+public class MappedReadsTest 
+{
+  private static BamView bv;
+  private static FeatureVector fv;
+  
+  @BeforeClass
+  public static void setUp() 
+  {
+    // ignore if in headless mode with no x11
+    if(GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadless())
+      return;
+    URL entryFile = MappedReadsTest.class.getResource("/data/MAL_8h.bam");
+    System.setProperty("bam", entryFile.getFile());
+    final EntryGroup egrp = Utils.getEntryGroup("/data/MAL1.embl.gz");
+    final EntryEdit ee = new EntryEdit(egrp);
+    ee.setVisible(true);
+
+    while( (bv = ee.getJamView()) == null) 
+    {
+      // wait for BamView to be constructed
+      try {
+        Thread.sleep(100);
+      } catch(Exception e){};
+    }
+    
+    // get a gene feature
+    fv = new FeatureVector();
+    final FeatureVector features = egrp.getAllFeatures();
+    for(int i=0; i<features.size(); i++) 
+    {
+      Feature f = features.elementAt(i);
+      if(f.getSystematicName().equals("PFA0110w"))
+        fv.add(f);
+    }
+  }
+  
+  @Test
+  /**
+   * Test the read count for a gene
+   */
+  public void readCounts()
+  {
+    // ignore if in headless mode with no x11
+    if(GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadless())
+      return;
+
+    final Hashtable<String, List<ReadCount>> featureReadCount =
+        BamUtils.calculateMappedReads(bv, fv, false, true, false, null, null);
+    final List<ReadCount> cnts = featureReadCount.get("PFA0110w");
+    
+    ReadCount c = cnts.get(0);
+    assertTrue(1495.f == c.senseCnt);
+    assertTrue(998.f == c.antiCnt);
+  }
+  
+  @Test
+  /**
+   * Read count for a gene excluding the intron
+   */
+  public void readCountsExcludeIntron()
+  {
+    // ignore if in headless mode with no x11
+    if(GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadless())
+      return;
+
+    final Hashtable<String, List<ReadCount>> featureReadCount =
+        BamUtils.calculateMappedReads(bv, fv, false, false, false, null, null);
+    final List<ReadCount> cnts = featureReadCount.get("PFA0110w");
+    
+    ReadCount c = cnts.get(0);
+    assertTrue(1494.f == c.senseCnt);
+    assertTrue(997.f == c.antiCnt);
+  }
+  
+  @Test
+  /**
+   * Tes the read count for a gene not including those in the intron
+   */
+  public void rpkm()
+  {
+    // ignore if in headless mode with no x11
+    if(GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadless())
+      return;
+
+    String refName = (String) bv.getCombo().getSelectedItem();
+    int thisLength = bv.getSeqLengths().get(refName);
+    int mappedReads[] = BamUtils.calc(bv, refName, thisLength, 
+        false, null);
+
+    Hashtable<String, List<ReadCount>> featureReadCount =
+        BamUtils.calculateMappedReads(bv, fv, false, true, false, mappedReads, null);
+    final List<ReadCount> cnts = featureReadCount.get("PFA0110w");
+    
+    ReadCount c = cnts.get(0);
+    assertTrue(183768.719f == c.senseCnt);
+    assertTrue(122676.375f == c.antiCnt);
+  }
+}
diff --git a/test/uk/ac/sanger/artemis/io/GFF3AttributeBuilderTest.java b/test/uk/ac/sanger/artemis/io/GFF3AttributeBuilderTest.java
new file mode 100644
index 0000000..e65c526
--- /dev/null
+++ b/test/uk/ac/sanger/artemis/io/GFF3AttributeBuilderTest.java
@@ -0,0 +1,295 @@
+/* 
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2014  Genome Research Limited
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.io;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
+import junit.framework.Assert;
+
+import org.junit.Test;
+import org.junit.Before;
+
+import uk.ac.sanger.artemis.io.GFF3AttributeAggregator;
+import uk.ac.sanger.artemis.io.GFF3AttributeBuilder;
+import uk.ac.sanger.artemis.io.GFF3Encoder;
+import uk.ac.sanger.artemis.util.StringVector;
+
+public class GFF3AttributeBuilderTest {
+  private GFF3Encoder                    enc;
+  private String[]                       invals  = { "foo", "bar" };
+  private String[]                       invalsC = { "foo,bar", "baz,quux" };
+  private static GFF3AttributeAggregator testProc;
+
+  @Before
+  public void setUp() {
+    enc = new GFF3Encoder();
+    testProc = new GFF3AttributeAggregator() {
+      @Override
+      public String process(StringVector values) {
+        StringBuilder buffer = new StringBuilder();
+        if (values != null && values.size() > 0) {
+          for (int value_index = 0; value_index < values.size(); ++value_index) {
+            buffer.append(">>"
+                + GFF3Encoder.encode(values.elementAt(value_index)) + "<<");
+            if (value_index < (values.size()) - 1)
+              buffer.append("|");
+          }
+        }
+        return buffer.toString();
+      }
+    };
+  }
+
+  @Test
+  /**
+   * Tests adding one attribute.
+   */
+  public void testAdd1() {
+    GFF3AttributeBuilder ab = new GFF3AttributeBuilder();
+    StringVector in = new StringVector(invals);
+
+    ab.add("attr1", in);
+    assertEquals(ab.toString(), "attr1=foo,bar");
+  }
+
+  @Test
+  /**
+   * Tests adding two different attributes.
+   */
+  public void testAdd2() {
+    GFF3AttributeBuilder ab = new GFF3AttributeBuilder();
+    StringVector in = new StringVector(invals);
+
+    ab.add("attr1", in);
+    ab.add("attr2", in);
+    assertEquals(ab.toString(), "attr1=foo,bar;attr2=foo,bar");
+  }
+
+  @Test
+  /**
+   * Tests adding attributes with custom aggregators.
+   */
+  public void testAddWithAggs() {
+    GFF3AttributeBuilder ab = new GFF3AttributeBuilder();
+    StringVector in = new StringVector(invals);
+    ab.setAggregator("attr1", testProc);
+    ab.add("attr1", in);
+    assertEquals(ab.toString(), "attr1=>>foo<<|>>bar<<");
+    ab.add("attr1", in);
+    assertEquals(ab.toString(), "attr1=>>foo<<|>>bar<< >>foo<<|>>bar<<");
+  }
+
+  @Test
+  /**
+   * Tests adding attributes (encoded values) with custom aggregators.
+   */
+  public void testAddWithAggsCommas() {
+    GFF3AttributeBuilder ab = new GFF3AttributeBuilder();
+    StringVector in = new StringVector(invalsC);
+    ab.add("attr1", in);
+    assertEquals(ab.toString(), "attr1=foo%2Cbar,baz%2Cquux");
+    ab.add("attr1", in);
+    assertEquals(ab.toString(),
+        "attr1=foo%2Cbar,baz%2Cquux foo%2Cbar,baz%2Cquux");
+    ab = new GFF3AttributeBuilder();
+    ab.setAggregator("attr1", testProc);
+    ab.add("attr1", in);
+    assertEquals(ab.toString(), "attr1=>>foo%2Cbar<<|>>baz%2Cquux<<");
+    ab.add("attr1", in);
+    assertEquals(ab.toString(),
+        "attr1=>>foo%2Cbar<<|>>baz%2Cquux<< >>foo%2Cbar<<|>>baz%2Cquux<<");
+  }
+
+  @Test
+  /**
+   * Tests the ignoring of attribute fields in the output.
+   */
+  public void testIgnore() {
+    GFF3AttributeBuilder ab = new GFF3AttributeBuilder();
+    StringVector in = new StringVector(invals);
+
+    ab.ignore("attr1");
+    ab.add("attr1", in);
+    ab.add("attr2", in);
+    assertEquals(ab.toString(), "attr2=foo,bar");
+    ab.unignore("attr1");
+    assertEquals(ab.toString(), "attr1=foo,bar;attr2=foo,bar");
+  }
+
+  @Test
+  /**
+   * Tests the handling of duplicate attributes.
+   */
+  public void testAddMultiAttr() {
+    GFF3AttributeBuilder ab = new GFF3AttributeBuilder();
+    StringVector in = new StringVector(invals);
+
+    ab.add("attr1", in);
+    ab.add("attr1", in);
+    assertEquals(ab.toString(), "attr1=foo,bar foo,bar");
+  }
+
+  @Test
+  /**
+   * Tests the handling of duplicate attributes, with delimiter.
+   */
+  public void testAddMultiAttrWithGlue() {
+    GFF3AttributeBuilder ab = new GFF3AttributeBuilder();
+    StringVector in = new StringVector(invals);
+
+    ab.setGlue("attr1", "X");
+    ab.add("attr1", in);
+    ab.add("attr1", in);
+    ab.add("attr1", in);
+    assertEquals(ab.toString(), "attr1=foo,barXfoo,barXfoo,bar");
+  }
+
+  @Test
+  /**
+   * Tests cloning of attributes to separate keys with default aggregator.
+   */
+  public void testClone() {
+    GFF3AttributeBuilder ab = new GFF3AttributeBuilder();
+    StringVector in = new StringVector(invals);
+
+    ab.setClone("attr1", "brand_new");
+    ab.add("attr1", in);
+    assertEquals(ab.toString(), "attr1=foo,bar;brand_new=foo,bar");
+    ab.add("brand_new", in);
+    assertEquals(ab.toString(), "attr1=foo,bar;brand_new=foo,bar foo,bar");
+  }
+
+  @Test
+  /**
+   * Tests cloning of attributes to separate keys with different aggregators.
+   */
+  public void testCloneWithAggs() {
+    GFF3AttributeBuilder ab = new GFF3AttributeBuilder();
+    StringVector in = new StringVector(invals);
+    ab.setClone("attr1", "brand_new");
+    ab.setAggregator("brand_new", testProc);
+    ab.add("attr1", in);
+    assertEquals(ab.toString(), "attr1=foo,bar;brand_new=>>foo<<|>>bar<<");
+    ab.add("attr1", in);
+    assertEquals(ab.toString(),
+        "attr1=foo,bar foo,bar;brand_new=>>foo<<|>>bar<< >>foo<<|>>bar<<");
+  }
+
+  @Test
+  /**
+   * Tests mapping/cloning of attributes to separate keys with different aggregators.
+   */
+  public void testMappingAndCloneWithAggs1() {
+    GFF3AttributeBuilder ab = new GFF3AttributeBuilder();
+    StringVector in = new StringVector(invals);
+
+    ab.setMapping("attr1", "aaaa");
+    ab.setClone("aaaa", "brand_new");
+    ab.setAggregator("brand_new", testProc);
+    ab.add("attr1", in);
+    assertEquals(ab.toString(), "aaaa=foo,bar;brand_new=>>foo<<|>>bar<<");
+    ab.add("attr1", in);
+    assertEquals(ab.toString(),
+        "aaaa=foo,bar foo,bar;brand_new=>>foo<<|>>bar<< >>foo<<|>>bar<<");
+  }
+
+  @Test
+  /**
+   * Tests mapping/cloning of attributes to separate keys with different aggregators.
+   */
+  public void testMappingAndCloneWithAggs2() {
+    GFF3AttributeBuilder ab = new GFF3AttributeBuilder();
+    StringVector in = new StringVector(invals);
+
+    ab.setMapping("attr1", "aaaa");
+    ab.setClone("attr1", "brand_new");
+    ab.setAggregator("brand_new", testProc);
+    ab.add("attr1", in);
+    assertEquals(ab.toString(), "aaaa=foo,bar;brand_new=>>foo<<|>>bar<<");
+    ab.add("attr1", in);
+    assertEquals(ab.toString(),
+        "aaaa=foo,bar foo,bar;brand_new=>>foo<<|>>bar<< >>foo<<|>>bar<<");
+  }
+
+  @Test
+  /**
+   * Tests mapping one attribute to a new name.
+   */
+  public void testMapping() {
+    GFF3AttributeBuilder ab = new GFF3AttributeBuilder();
+    StringVector in = new StringVector(invals);
+
+    ab.setMapping("attr1", "brand_new");
+    ab.add("attr1", in);
+    ab.add("attr2", in);
+    assertEquals(ab.toString(), "attr2=foo,bar;brand_new=foo,bar");
+  }
+
+  @Test
+  /**
+   * Tests mapping one attribute to a new name with custom target aggregator.
+   */
+  public void testMappingWithAggs() {
+    GFF3AttributeBuilder ab = new GFF3AttributeBuilder();
+    StringVector in = new StringVector(invals);
+    ab.setMapping("attr1", "brand_new");
+    ab.setAggregator("brand_new", testProc);
+    ab.add("attr1", in);
+    ab.add("attr2", in);
+    assertEquals(ab.toString(), "attr2=foo,bar;brand_new=>>foo<<|>>bar<<");
+  }
+
+  @Test
+  /**
+   * Tests mapping one attribute to a new name with custom target aggregator.
+   */
+  public void testMappingCollision() {
+    GFF3AttributeBuilder ab = new GFF3AttributeBuilder();
+    StringVector in = new StringVector(invals);
+
+    ab.setMapping("attr1", "attr2");
+    ab.add("attr1", in);
+    ab.add("attr2", in);
+    assertEquals(ab.toString(), "attr2=foo,bar foo,bar");
+  }
+
+  @Test
+  /**
+   * Tests mapping one attribute to a new name with custom target aggregator.
+   */
+  public void testMappingCollisionWithAggs() {
+    GFF3AttributeBuilder ab = new GFF3AttributeBuilder();
+    StringVector in = new StringVector(invals);
+
+    ab.setMapping("attr1", "attr2");
+    ab.setAggregator("attr2", testProc);
+    ab.add("attr1", in);
+    ab.add("attr2", in);
+    assertEquals(ab.toString(), "attr2=>>foo<<|>>bar<< >>foo<<|>>bar<<");
+    ab = new GFF3AttributeBuilder();
+    ab.setMapping("attr1", "attr2");
+    ab.setAggregator("attr1", testProc);
+    ab.add("attr1", in);
+    ab.add("attr2", in);
+    assertEquals(ab.toString(), "attr2=>>foo<<|>>bar<< foo,bar");
+  }
+}
diff --git a/test/uk/ac/sanger/artemis/io/GFF3EncoderTest.java b/test/uk/ac/sanger/artemis/io/GFF3EncoderTest.java
new file mode 100644
index 0000000..56f8a5a
--- /dev/null
+++ b/test/uk/ac/sanger/artemis/io/GFF3EncoderTest.java
@@ -0,0 +1,102 @@
+/* 
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2014  Genome Research Limited
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.io;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
+import junit.framework.Assert;
+
+import org.junit.Test;
+import org.junit.Before;
+
+import uk.ac.sanger.artemis.io.GFF3Encoder;
+
+public class GFF3EncoderTest {
+  private GFF3Encoder enc;
+
+  @Before
+  public void setUp() {
+    enc = new GFF3Encoder();
+  }
+
+  @Test
+  /**
+   * Tests whether there are missing map characters.
+   */
+  public void testMapEqualLength() {
+    assertEquals(GFF3Encoder.mapChars.length, GFF3Encoder.mappedEscapes.length);
+  }
+
+  @Test
+  /**
+   * Tests whether the mapping is correct according to GFF3 spec.
+   */
+  public void testMapChars() {
+    assertEquals(enc.encode("%"), "%25");
+    assertEquals(enc.decode("%25"), "%");
+    assertEquals(enc.encode("&"), "%26");
+    assertEquals(enc.decode("%26"), "&");
+    assertEquals(enc.encode(","), "%2C");
+    assertEquals(enc.decode("%2C"), ",");
+    assertEquals(enc.encode(";"), "%3B");
+    assertEquals(enc.decode("%3B"), ";");
+    assertEquals(enc.encode("="), "%3D");
+    assertEquals(enc.decode("%3D"), "=");
+    assertEquals(enc.encode("\t"), "%09");
+    assertEquals(enc.decode("%09"), "\t");
+    assertEquals(enc.encode("\n"), "%0A");
+    assertEquals(enc.decode("%0A"), "\n");
+    assertEquals(enc.encode("\r"), "%0D");
+    assertEquals(enc.decode("%0D"), "\r");
+  }
+
+  @Test
+  /**
+   * Tests the decoding of escaped characters in GFF files.
+   */
+  public void testDecode() {
+    for (int i=0; i < GFF3Encoder.mappedEscapes.length; i++) {
+      assertEquals(enc.decode("test"+GFF3Encoder.mappedEscapes[i]+"foo"),
+                   "test"+GFF3Encoder.mapChars[i]+"foo");
+      assertEquals(enc.decode("test%"+GFF3Encoder.mappedEscapes[i]+"foo"),
+                   "test%"+GFF3Encoder.mapChars[i]+"foo");
+      assertEquals(enc.decode("test1"+GFF3Encoder.mappedEscapes[i]+"foo," +
+                              "test2"+GFF3Encoder.mappedEscapes[i]),
+                   "test1"+GFF3Encoder.mapChars[i]+"foo," + 
+                   "test2"+GFF3Encoder.mapChars[i]);
+    }
+  }
+
+  @Test
+  /**
+   * Tests the encoding of escaped characters in GFF files.
+   */
+  public void testEncode() {
+    for (int i=0; i < GFF3Encoder.mappedEscapes.length; i++) {
+      assertEquals(enc.encode("test"+GFF3Encoder.mapChars[i]+"foo"),
+                   "test"+GFF3Encoder.mappedEscapes[i]+"foo");
+      assertEquals(enc.encode("test%"+GFF3Encoder.mapChars[i]+"foo"),
+                   "test%25"+GFF3Encoder.mappedEscapes[i]+"foo");
+    }
+  }
+
+}
diff --git a/uk/ac/sanger/artemis/AlignMatchVector.java b/uk/ac/sanger/artemis/AlignMatchVector.java
index 86dc474..646e82e 100644
--- a/uk/ac/sanger/artemis/AlignMatchVector.java
+++ b/uk/ac/sanger/artemis/AlignMatchVector.java
@@ -3,19 +3,19 @@
  * created: Sat Jun 17 2000
  *
  * This file is part of Artemis
- * 
+ *
  * Copyright (C) 2000,2001  Genome Research Limited
- * 
+ *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation; either version 2
  * of the License, or (at your option) any later version.
- * 
+ *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
@@ -51,7 +51,7 @@ public class AlignMatchVector {
   public void addElement (AlignMatch item) {
     vector.add (item);
   }
-  
+
   /**
    *  Appends the given AlignMatch object to the vector if and only if it
    *  isn't already in the vector.  (same as addElement ()).
@@ -59,7 +59,7 @@ public class AlignMatchVector {
   public void add (AlignMatch item) {
     addElement (item);
   }
-  
+
   /**
    *  Performs the same function as Vector.elementAt ()
    **/
@@ -101,13 +101,13 @@ public class AlignMatchVector {
   public int size () {
     return vector.size ();
   }
-  
+
   /**
    *  Sort this vector.
    *  @param cmp The returned vector will be sorted with this Comparator.
    **/
   public void sort (final Comparator cmp) {
-    vector = vector.sort (cmp);
+    vector = vector.mysort (cmp);
   }
 
   /**
diff --git a/uk/ac/sanger/artemis/FeatureVector.java b/uk/ac/sanger/artemis/FeatureVector.java
index 4f1c4c6..a0623aa 100644
--- a/uk/ac/sanger/artemis/FeatureVector.java
+++ b/uk/ac/sanger/artemis/FeatureVector.java
@@ -154,7 +154,7 @@ public class FeatureVector {
   public FeatureVector sort (final Comparator cmp) {
     final FeatureVector return_vector = (FeatureVector) clone ();
 
-    return_vector.vector = return_vector.vector.sort (cmp);
+    return_vector.vector = return_vector.vector.mysort (cmp);
 
     return return_vector;
   }
diff --git a/uk/ac/sanger/artemis/chado/ArtemisUtils.java b/uk/ac/sanger/artemis/chado/ArtemisUtils.java
index f0eea5f..277a787 100644
--- a/uk/ac/sanger/artemis/chado/ArtemisUtils.java
+++ b/uk/ac/sanger/artemis/chado/ArtemisUtils.java
@@ -47,6 +47,7 @@ import org.gmod.schema.sequence.FeatureProp;
 import org.gmod.schema.sequence.FeatureRelationship;
 
 import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.GFF3Encoder;
 import uk.ac.sanger.artemis.io.GFFStreamFeature;
 import uk.ac.sanger.artemis.util.DatabaseDocument;
 import uk.ac.sanger.artemis.util.StringVector;
@@ -262,7 +263,7 @@ public class ArtemisUtils
       if(this_fcp.getValue().equals(fcp.getValue()))
         return true;
 
-      if(this_fcp.getValue().equals(GFFStreamFeature.decode(fcp.getValue())))
+      if(this_fcp.getValue().equals(GFF3Encoder.decode(fcp.getValue())))
         return true;
     }
     return false;
diff --git a/uk/ac/sanger/artemis/chado/ChadoTransactionManager.java b/uk/ac/sanger/artemis/chado/ChadoTransactionManager.java
index 8e00d32..3150777 100644
--- a/uk/ac/sanger/artemis/chado/ChadoTransactionManager.java
+++ b/uk/ac/sanger/artemis/chado/ChadoTransactionManager.java
@@ -1228,8 +1228,10 @@ public class ChadoTransactionManager
   {
     if(synonym_tags == null)
     {
-      synonym_tags = DatabaseDocument.getSynonymTypeNames(
+      if(GeneUtils.isDatabaseEntry(feature))
+        synonym_tags = DatabaseDocument.getSynonymTypeNames(
                               SYNONYM_TAG_CVNAME, feature);
+
       if(synonym_tags == null || synonym_tags.length < 1)
       {
         logger4j.debug("Using default synonym names");
@@ -1250,6 +1252,12 @@ public class ChadoTransactionManager
     return false;
   }
   
+  public static String[] getSynonymTags()
+  {
+    return synonym_tags;
+  }
+
+  
   /**
    * Compare the old and new keys and qualifiers and find the qualifiers 
    * that have changed or been added and UPDATE, INSERT or DELETE accordingly.
@@ -2674,7 +2682,9 @@ public class ChadoTransactionManager
   private void loadDbXRefsAndPubs(final String searchStr,
                                   final FeatureCvTerm feature_cvterm)
   {
-    List<FeatureCvTermDbXRef> featureCvTermDbXRefs = null;
+    Collection<FeatureCvTermDbXRef> featureCvTermDbXRefs = feature_cvterm.getFeatureCvTermDbXRefs();
+    if(featureCvTermDbXRefs.size() == 0)
+      featureCvTermDbXRefs = null;
     
     //StringVector strings = StringVector.getStrings(searchStr, "|");
     StringTokenizer tok = new StringTokenizer(searchStr, "|,");
diff --git a/uk/ac/sanger/artemis/components/EditMenu.java b/uk/ac/sanger/artemis/components/EditMenu.java
index 50d124a..db10911 100644
--- a/uk/ac/sanger/artemis/components/EditMenu.java
+++ b/uk/ac/sanger/artemis/components/EditMenu.java
@@ -1768,6 +1768,23 @@ public class EditMenu extends SelectionMenu
               {
                 ((Feature)gene1.getGene().getUserData()).addQualifierValues(synQualifier);
                 ((Feature)gene2.getGene().getUserData()).addQualifierValues(synQualifier);
+
+                final Qualifier comment =
+                    new Qualifier("comment", "this gene model has previous ID "+prevId+
+                        " and was reassigned a new ID as changes in the gene model occurred");
+
+                try
+                {
+                  ((Feature)gene1.getProteinOfTranscript( GeneUtils.getUniqueName(
+                            gene1.getTranscripts().get(0)) ).getUserData()).addQualifierValues(comment);
+                  ((Feature)gene2.getProteinOfTranscript( GeneUtils.getUniqueName(
+                            gene2.getTranscripts().get(0)) ).getUserData()).addQualifierValues(comment);
+                }
+                catch (Exception e)
+                {
+                  ((Feature)gene1.getGene().getUserData()).addQualifierValues(comment);
+                  ((Feature)gene2.getGene().getUserData()).addQualifierValues(comment);
+                }
               }
 
             }
diff --git a/uk/ac/sanger/artemis/components/EntryEdit.java b/uk/ac/sanger/artemis/components/EntryEdit.java
index bb7388d..21a3cb7 100644
--- a/uk/ac/sanger/artemis/components/EntryEdit.java
+++ b/uk/ac/sanger/artemis/components/EntryEdit.java
@@ -642,7 +642,7 @@ public class EntryEdit extends JFrame
     return bamPanel;
   }
   
-  protected BamView getJamView()
+  public BamView getJamView()
   {
     return bamView;
   }
diff --git a/uk/ac/sanger/artemis/components/FeatureEdit.java b/uk/ac/sanger/artemis/components/FeatureEdit.java
index 6b459dd..1626ba3 100644
--- a/uk/ac/sanger/artemis/components/FeatureEdit.java
+++ b/uk/ac/sanger/artemis/components/FeatureEdit.java
@@ -76,7 +76,6 @@ import java.util.Comparator;
 
 import javax.swing.*;
 
-
 /**
  *  FeatureEdit class
  *
@@ -860,7 +859,6 @@ public class FeatureEdit extends JPanel
         }
       });
 
-
       final JCheckBox oneView = new JCheckBox("Overview", false);
       oneView.addItemListener(new ItemListener()
       {
@@ -887,6 +885,12 @@ public class FeatureEdit extends JPanel
       fillerBox.add(Box.createHorizontalStrut( 
           tabbedView.getPreferredSize().width ));
     }
+    else if(getFeature().getEmblFeature() instanceof GFFStreamFeature)
+    {
+      propertiesPanel = new PropertiesPanel(getFeature());
+      addGffAnnotationView(lower_panel);
+
+    }
     else
       lower_panel.add(new JScrollPane(qualifier_text_area), "Center");
     
@@ -1438,8 +1442,8 @@ public class FeatureEdit extends JPanel
     
     qualifier_text_area.setText(getQualifierString());
     
-    if(GeneUtils.isDatabaseEntry(getFeature().getEmblFeature()))
-    {  
+    if(getFeature().getEmblFeature() instanceof GFFStreamFeature)
+    {
       // load synonym
       if(cvForm != null)
         cvForm.updateFromFeature(getFeature());
@@ -1541,9 +1545,7 @@ public class FeatureEdit extends JPanel
     {
       qualifiers =
         qualifier_text_area.getParsedQualifiers(getEntryInformation ());
-      
-      updateGffIds(qualifiers);
-      
+           
       // if using controlled vocab form
       if(cvForm != null)
       {
@@ -1569,6 +1571,7 @@ public class FeatureEdit extends JPanel
         if(mapQualifiers != null && mapQualifiers.size() > 0)
           qualifiers.addAll(mapQualifiers);
       }
+      updateGffIds(qualifiers);
       
       if(matchForm != null)
       {
@@ -1679,7 +1682,7 @@ public class FeatureEdit extends JPanel
         {
           final String newName = ((String) (qualifiers.getQualifierByName("ID").getValues().get(0))).trim();
           final String oldName = ((String) (gffFeature.getQualifierByName("ID").getValues().get(0))).trim();
-         
+ 
           if(!newName.equals(oldName))
           {
             int val = JOptionPane.showConfirmDialog(null, 
@@ -1704,7 +1707,7 @@ public class FeatureEdit extends JPanel
             }
           }
         }
-        catch(Exception e){ }
+        catch(Exception e){ e.printStackTrace(); }
       }
     }
   }
diff --git a/uk/ac/sanger/artemis/components/RunBlastAtNCBI.java b/uk/ac/sanger/artemis/components/RunBlastAtNCBI.java
index 2ae2b6a..76d77f5 100644
--- a/uk/ac/sanger/artemis/components/RunBlastAtNCBI.java
+++ b/uk/ac/sanger/artemis/components/RunBlastAtNCBI.java
@@ -58,7 +58,7 @@ class RunBlastAtNCBI extends Thread
     try
     {
       // Send data
-      URL url = new URL("http://www.ncbi.nlm.nih.gov/blast/Blast.cgi");
+      URL url = new URL("https://blast.ncbi.nlm.nih.gov/Blast.cgi");
       URLConnection conn = url.openConnection();
       conn.setDoOutput(true);
       OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
@@ -258,4 +258,4 @@ class RunBlastAtNCBI extends Thread
     RunBlastAtNCBI blast= new RunBlastAtNCBI(data);
     blast.start();
   }
-}
\ No newline at end of file
+}
diff --git a/uk/ac/sanger/artemis/components/RunPfamSearchThread.java b/uk/ac/sanger/artemis/components/RunPfamSearchThread.java
index 9dd070d..364fc19 100644
--- a/uk/ac/sanger/artemis/components/RunPfamSearchThread.java
+++ b/uk/ac/sanger/artemis/components/RunPfamSearchThread.java
@@ -41,8 +41,8 @@ import uk.ac.sanger.artemis.editor.BrowserControl;
  */
 public class RunPfamSearchThread extends Thread
 {
-  protected static String pfamUrl = "http://pfam.sanger.ac.uk/search/sequence";
-  protected static String rfamUrl = "http://rfam.sanger.ac.uk/search/sequence";
+  protected static String pfamUrl = "http://pfam.xfam.org/search/sequence";
+  protected static String rfamUrl = "http://rfam.xfam.org/search/sequence";
   private String searchURL = pfamUrl;
   
   private String residues;
diff --git a/uk/ac/sanger/artemis/components/SelectMenu.java b/uk/ac/sanger/artemis/components/SelectMenu.java
index 4d801fa..c2ac0e9 100644
--- a/uk/ac/sanger/artemis/components/SelectMenu.java
+++ b/uk/ac/sanger/artemis/components/SelectMenu.java
@@ -1000,37 +1000,65 @@ public class SelectMenu extends SelectionMenu
       }
     }
   }
-  
 
   /**
-   *  If there are some selected bases select the features that are
+   *  If there are some selected bases or features select the features that are
    *  fully contained within that selection.
    **/
   private void selectContainedFeatures () {
     final MarkerRange selected_marker_range =
       getSelection ().getMarkerRange ();
 
-    if (selected_marker_range == null) {
-      new MessageDialog (getParentFrame (), "sequence range is not selected");
-      return;
-    } else {
-      try {
-        final FeatureVector featuresInRange =
-            getEntryGroup ().getFeaturesInRange (selected_marker_range.getRawRange ());
-        final FeatureVector featuresContainedByRange = new FeatureVector();
-        for(int i=0; i<featuresInRange.size(); i++)
-        {
-          Feature f = featuresInRange.elementAt(i);
-          Range maxR =  f.getMaxRawRange(); 
-          if(selected_marker_range.getRawStart().getRawPosition() <= maxR.getStart() &&
-             selected_marker_range.getRawEnd().getRawPosition()   >= maxR.getEnd())
-            featuresContainedByRange.add(f);
+    final FeatureVector featuresContainedByRange;
+    try
+    {
+      if (selected_marker_range == null) {
+        final FeatureVector selected_features = getSelection ().getAllFeatures ();
+        if (selected_features.size () == 0) {
+          new MessageDialog (getParentFrame (), "nothing selected");
+          return;
         }
-        getSelection ().set (featuresContainedByRange);
-      } catch (OutOfRangeException e) {
-        throw new Error ("internal error - unexpected exception: " + e);
+
+        featuresContainedByRange = new FeatureVector();
+        for(int i=0; i<selected_features.size(); i++) {
+          Range r = selected_features.elementAt(i).getMaxRawRange();
+          final FeatureVector featuresContained = getFeaturesContainedByRange(r);
+          for(int j=0; j<featuresContained.size(); j++) {
+            final Feature f = featuresContained.elementAt(j);
+            if(!featuresContainedByRange.contains(f) && !selected_features.contains(f))
+              featuresContainedByRange.add(f);
+          }
+        }
+      } else {
+        featuresContainedByRange = getFeaturesContainedByRange(
+            selected_marker_range.getRawRange());
       }
+    } catch (OutOfRangeException e) {
+      throw new Error ("internal error - unexpected exception: " + e);
+    }
+    getSelection ().set (featuresContainedByRange);
+  }
+
+  /**
+   * Return the features that are contained by a given range
+   * @param r
+   * @return
+   * @throws OutOfRangeException
+   */
+  private FeatureVector getFeaturesContainedByRange(Range r) throws OutOfRangeException
+  {
+    final FeatureVector featuresInRange =
+        getEntryGroup ().getFeaturesInRange (r);
+    final FeatureVector featuresContainedByRange = new FeatureVector();
+    for(int i=0; i<featuresInRange.size(); i++)
+    {
+      Feature f = featuresInRange.elementAt(i);
+      Range maxR =  f.getMaxRawRange(); 
+      if(r.getStart() <= maxR.getStart() &&
+         r.getEnd()   >= maxR.getEnd())
+        featuresContainedByRange.add(f);
     }
+    return featuresContainedByRange;
   }
 
   /**
diff --git a/uk/ac/sanger/artemis/components/Splash.java b/uk/ac/sanger/artemis/components/Splash.java
index 34a6d38..7655608 100644
--- a/uk/ac/sanger/artemis/components/Splash.java
+++ b/uk/ac/sanger/artemis/components/Splash.java
@@ -418,7 +418,7 @@ abstract public class Splash extends JFrame
         g.drawString(geneticCode,
                      left_margin, yPos+(font_height * 3));
 
-        g.drawString("Copyright 1998 - 2014",
+        g.drawString("Copyright 1998 - 2016",
                      left_margin, yPos+(font_height * 9 / 2));
         g.drawString("Genome Research Limited",
                      left_margin, yPos+(font_height * 11 / 2));
diff --git a/uk/ac/sanger/artemis/components/alignment/AbstractGraphPanel.java b/uk/ac/sanger/artemis/components/alignment/AbstractGraphPanel.java
index 075eb56..78a02e0 100644
--- a/uk/ac/sanger/artemis/components/alignment/AbstractGraphPanel.java
+++ b/uk/ac/sanger/artemis/components/alignment/AbstractGraphPanel.java
@@ -150,8 +150,17 @@ public class AbstractGraphPanel extends JPanel
    */
   protected void drawMax(Graphics2D g2)
   {
+    drawMax(g2, (float)max/(float)windowSize);
+  }
+  
+  /**
+   * Draw maximum average value.
+   * @param g2
+   */
+  protected void drawMax(Graphics2D g2, float max)
+  {
     DecimalFormat df = new DecimalFormat("0.#");
-    String maxStr = df.format((float)max/(float)windowSize);
+    String maxStr = df.format(max);
 
     FontMetrics fm = getFontMetrics(getFont());
     g2.setColor(Color.black);
diff --git a/uk/ac/sanger/artemis/components/alignment/BamUtils.java b/uk/ac/sanger/artemis/components/alignment/BamUtils.java
index c064061..f668011 100644
--- a/uk/ac/sanger/artemis/components/alignment/BamUtils.java
+++ b/uk/ac/sanger/artemis/components/alignment/BamUtils.java
@@ -28,12 +28,15 @@ import java.util.Hashtable;
 import java.util.List;
 import java.util.Vector;
 
+import javax.swing.JProgressBar;
+
 import net.sf.samtools.AlignmentBlock;
 import net.sf.samtools.SAMFileReader;
 import net.sf.samtools.SAMRecord;
 import net.sf.samtools.util.CloseableIterator;
 import uk.ac.sanger.artemis.Feature;
 import uk.ac.sanger.artemis.FeatureSegmentVector;
+import uk.ac.sanger.artemis.FeatureVector;
 import uk.ac.sanger.artemis.io.Range;
 
 class BamUtils
@@ -69,24 +72,21 @@ class BamUtils
    * @return
    */
   protected static float[] getCount(
+      final BamView bamView,
       final int start,
       final int end,
       final String bam,
-      final String refName,
-      final Hashtable<String, SAMFileReader> samFileReaderHash,
-      final Vector<String> seqNames,
-      final HashMap<String, Integer> offsetLengths,
-      final boolean concatSequences, 
-      final HashMap<String, Integer> seqLengths,
-      final SAMRecordPredicate samRecordFlagPredicate,
-      final SAMRecordMapQPredicate samRecordMapQPredicate,
       final boolean contained,
       final boolean useStrandTag)
   {
+    final Vector<String> seqNames = bamView.getSeqNames();
+    final HashMap<String, Integer> offsetLengths = bamView.getOffsetLengths();
+    final HashMap<String, Integer> seqLengths = bamView.getSeqLengths();
+
     int cnt[] = new int[2];
     cnt[0] = 0;
     cnt[1] = 0;
-    if(concatSequences)
+    if(bamView.isConcatSequences())
     {
       int len = 0;
       int lastLen = 1;
@@ -108,8 +108,7 @@ class BamUtils
           if(thisEnd > thisLength)
             thisEnd = thisLength;
 
-          cnt = count(bam, samFileReaderHash, name, thisStart, thisEnd, 
-              samRecordFlagPredicate, samRecordMapQPredicate, contained, true, useStrandTag);
+          cnt = count(bamView, bam, thisStart, thisEnd, contained, true, useStrandTag);
 
         }
         lastLen = len;
@@ -117,8 +116,7 @@ class BamUtils
     }
     else
     {
-      cnt = count(bam, samFileReaderHash, refName, start, end, 
-          samRecordFlagPredicate, samRecordMapQPredicate, contained, true, useStrandTag);
+      cnt = count(bamView, bam, start, end, contained, true, useStrandTag);
     }
     
     float cntf[] = new float[2];
@@ -127,17 +125,20 @@ class BamUtils
     return cntf;
   }
 
-  protected static int[] count(final String bam, 
-                    final Hashtable<String, SAMFileReader> samFileReaderHash, 
-                    final String refName, 
-                    final int start, 
-                    final int end,
-                    final SAMRecordPredicate samRecordFlagPredicate,
-                    final SAMRecordPredicate samRecordMapQPredicate,
-                    final boolean contained,
-                    final boolean byStrand,
-                    final boolean useStrandTag)
+  protected static int[] count(
+          final BamView bamView,
+          final String bam, 
+          final int start,
+          final int end,
+          final boolean contained,
+          final boolean byStrand,
+          final boolean useStrandTag)
   {
+    final String refName = (String) bamView.getCombo().getSelectedItem();
+    final Hashtable<String, SAMFileReader> samFileReaderHash = bamView.getSamFileReaderHash();
+    final SAMRecordPredicate samRecordFlagPredicate = bamView.getSamRecordFlagPredicate();
+    final SAMRecordPredicate samRecordMapQPredicate = bamView.getSamRecordMapQPredicate();
+
     int cnt[] = new int[2];
     cnt[0] = 0;
     cnt[1] = 0;
@@ -164,29 +165,89 @@ class BamUtils
     it.close();
     return cnt;
   }
+  
+  protected static int[] calc(
+      final BamView bamView, 
+      final String refName, 
+      final int sequenceLength,
+      final boolean useStrandTag,
+      final JProgressBar progressBar)
+  {
+    int mappedReads[] = new int[bamView.bamList.size()];
+    int MAX_BASE_CHUNK = 2000 * 60;
+    boolean contained = false;
+    for (int i = 0; i < sequenceLength; i += MAX_BASE_CHUNK)
+    {
+      if(progressBar != null)
+        progressBar.setValue(i);
+      int sbegc = i;
+      int sendc = i + MAX_BASE_CHUNK - 1;
+
+      for (int j = 0; j < bamView.bamList.size(); j++)
+      {
+        String bam = bamView.bamList.get(j);
+        if (bamView.isConcatSequences())
+        {
+          int len = 0;
+          int lastLen = 1;
+          for (String name : bamView.getSeqNames())
+          {
+            int thisLength = bamView.getSeqLengths().get(name);
+            len += thisLength;
+
+            if ((lastLen >= sbegc && lastLen < sendc)
+                || (len >= sbegc && len < sendc)
+                || (sbegc >= lastLen && sbegc < len)
+                || (sendc >= lastLen && sendc < len))
+            {
+              int offset = bamView.getOffsetLengths().get(name);
+              int thisStart = sbegc - offset;
+              if (thisStart < 1)
+                thisStart = 1;
+              int thisEnd = sendc - offset;
+              if (thisEnd > thisLength)
+                thisEnd = thisLength;
+
+              mappedReads[j] += BamUtils.count(bamView, bam, thisStart, thisEnd, 
+                  contained, false, useStrandTag)[0];
+            }
+            lastLen = len;
+          }
+        }
+        else
+        {
+          mappedReads[j] += BamUtils.count(bamView, bam, sbegc, sendc,
+              contained, false, useStrandTag)[0];
+        }
+      }
+    }
+    return mappedReads;
+  }
 
   /**
    * Return the coverage for each base in a range for the forward and
    * reverse strand.
+   * @param bamView
    * @param bamFile
-   * @param samFileReaderHash
-   * @param refName
    * @param start
    * @param end
-   * @param samRecordFlagPredicate
-   * @param samRecordMapQPredicate
+   * @param concatShift
+   * @param cnt
    * @return
    */
-  protected static int[][] countOverRange(final String bamFile,
-                                          final Hashtable<String, SAMFileReader> samFileReaderHash, 
-                                          final String refName,
-                                          final int start, 
-                                          final int end,
-                                          final int concatShift,
-                                          final int cnt[][],
-                                          final SAMRecordPredicate samRecordFlagPredicate,
-                                          final SAMRecordPredicate samRecordMapQPredicate)
+  protected static int[][] countOverRange(
+      final BamView bamView,
+      final String bamFile, 
+      final int start, 
+      final int end, 
+      final int concatShift, 
+      final int cnt[][])
   {
+    final String refName = (String) bamView.getCombo().getSelectedItem();
+    final Hashtable<String, SAMFileReader> samFileReaderHash = bamView.getSamFileReaderHash();
+    final SAMRecordPredicate samRecordFlagPredicate = bamView.getSamRecordFlagPredicate();
+    final SAMRecordPredicate samRecordMapQPredicate = bamView.getSamRecordMapQPredicate();
+
     SAMFileReader inputSam = samFileReaderHash.get(bamFile);
     final CloseableIterator<SAMRecord> it = 
         inputSam.query(refName, start, end, false);
@@ -226,5 +287,80 @@ class BamUtils
     it.close();
     return cnt;
   }
+
+  /**
+   * For a list of features calculate the read count for each
+   * @param bamView
+   * @param features
+   * @param contained
+   * @param useIntrons
+   * @param useStrandTag
+   * @param mappedReads
+   * @param progressBar
+   * @return
+   */
+  protected static Hashtable<String, List<ReadCount>> calculateMappedReads(
+      final BamView bamView,
+      final FeatureVector features,
+      final boolean contained, 
+      final boolean useIntrons,
+      final boolean useStrandTag,
+      final int mappedReads[],
+      final JProgressBar progressBar)
+  {
+    final Hashtable<String, List<ReadCount>> featureReadCount = 
+        new Hashtable<String, List<ReadCount>>();
+    for (int i = 0; i < features.size(); i++)
+    {
+      final Feature f = features.elementAt(i);
+      if(progressBar != null)
+        progressBar.setValue(i);
+
+      int start = f.getRawFirstBase();
+      int end = f.getRawLastBase();
+      final float fLen = BamUtils.getFeatureLength(f);
+      List<ReadCount> sampleCounts = new Vector<ReadCount>();
+
+      for (int j = 0; j < bamView.bamList.size(); j++)
+      {
+        final String bam = bamView.bamList.get(j);
+        float cnt[] = new float[2];
+
+        cnt = BamUtils.getCount(bamView, start, end, bam, contained, useStrandTag);
+        if (!useIntrons && f.getSegments().size() > 1)
+        {
+          // remove reads contained by intron
+          for (int k = 0; k < f.getSegments().size()-1; k++)
+          {
+            int seg = k;
+            int nextSeg = k+1;
+            if(!f.isForwardFeature())
+            {
+              seg = f.getSegments().size()-k-1;
+              nextSeg = seg-1;
+            }
+
+            start = f.getSegments().elementAt(seg).getRawRange().getEnd();
+            end = f.getSegments().elementAt(nextSeg).getRawRange().getStart();
+
+            float tmpcnt[] = new float[2];
+            tmpcnt = BamUtils.getCount(bamView, start, end, bam, true, useStrandTag);
+            cnt[0] -= tmpcnt[0];
+            cnt[1] -= tmpcnt[1];
+          }
+        }
+        
+        if (mappedReads != null)
+        {
+          cnt[0] = (cnt[0] / (((float) mappedReads[j] / 1000000.f) * (fLen / 1000.f)));
+          cnt[1] = (cnt[1] / (((float) mappedReads[j] / 1000000.f) * (fLen / 1000.f)));
+        }
+
+        sampleCounts.add( new ReadCount(cnt, f.isForwardFeature()) );
+      }
+      featureReadCount.put(ReadCountDialog.getFeatureName(f), sampleCounts);
+    }
+    return featureReadCount;
+  }
 }
 
diff --git a/uk/ac/sanger/artemis/components/alignment/BamView.java b/uk/ac/sanger/artemis/components/alignment/BamView.java
index 8dcac22..0228a84 100644
--- a/uk/ac/sanger/artemis/components/alignment/BamView.java
+++ b/uk/ac/sanger/artemis/components/alignment/BamView.java
@@ -1837,9 +1837,13 @@ public class BamView extends JPanel
       else
         g2.setColor(Color.blue);
 
+      Color c = g2.getColor();
       drawRead(g2, pr.sam1, pixPerBase, ypos, baseAtStartOfView, getSNPs(pr.sam1.sam), ydiff);
       if(pr.sam2 != null)
+      {
+        g2.setColor(c);
         drawRead(g2, pr.sam2, pixPerBase, ypos, baseAtStartOfView, getSNPs(pr.sam2.sam), ydiff);
+      }
     }
   }
   
@@ -1946,7 +1950,7 @@ public class BamView extends JPanel
     coverageView.drawSelectionRange(g2, pixPerBase, start, end, getHeight(), Color.PINK);
     coverageView.draw(g2, getWidth(), hgt, hideBamList);
     if(!coverageView.isPlotHeatMap())
-      coverageView.drawMax(g2);  
+      coverageView.drawMax(g2, coverageView.getMaxCoverage());  
   }
   
   /**
@@ -2582,9 +2586,7 @@ public class BamView extends JPanel
           return;
         //JOptionPane.showMessageDialog(null, yBox, "Read Count Option", JOptionPane.INFORMATION_MESSAGE);
         
-        new MappedReads(features, (String)combo.getSelectedItem(), samFileReaderHash, bamList,
-            seqNames, offsetLengths, concatSequences, seqLengths, 
-            samRecordFlagPredicate, samRecordMapQPredicate,
+        new MappedReads(BamView.this, features,
             !overlap.isSelected(), spliced.isSelected(), colourByStrandTag.isSelected());
       } 
     });
@@ -2622,16 +2624,12 @@ public class BamView extends JPanel
         else if(bases != null)
           seqlen = bases.getLength();
         
-        new MappedReads(features, (String)combo.getSelectedItem(),
-            samFileReaderHash, bamList, seqNames, offsetLengths, concatSequences, 
-            seqLengths, seqlen, samRecordFlagPredicate, samRecordMapQPredicate,
+        new MappedReads(BamView.this, features, seqlen,
             !overlap.isSelected(), spliced.isSelected(), allRefSeqs.isSelected(),
             colourByStrandTag.isSelected());
-      } 
+      }
     });
-    
-    
-    
+
     final JMenuItem createFeatures = new JMenuItem("Create features from coverage peaks ...");
     analyse.add(createFeatures);
     if(feature_display == null)
@@ -2687,6 +2685,7 @@ public class BamView extends JPanel
         if(cbStackView.isSelected())
           logMenuItem.setEnabled(false);
         getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        setViewportBtm();
         repaint();
       }
     });
@@ -3188,31 +3187,18 @@ public class BamView extends JPanel
       public void mouseMoved(MouseEvent e)
       {
         lastMousePoint = e.getPoint();
-        
         int thisHgt = HEIGHT-2;
         if (thisHgt < 5)
           thisHgt = 15;
 
         int y = (int) (e.getY() - jspView.getViewport().getViewRect().getY());
-        Point p = jspView.getViewport().getViewPosition();
-        boolean isVis = topPanel.isVisible();
-        
         if (y < thisHgt)
-        {
           topPanel.setVisible(true);
-          if(!isVis)
-            p.y += topPanel.getHeight();
-        }
         else
         {
-          if (buttonAutoHide.isSelected())
+          if (buttonAutoHide.isSelected() && topPanel.isVisible())
             topPanel.setVisible(false); 
         }
-        
-        if(!showBaseAlignment && topPanel.isVisible())
-          jspView.getViewport().setViewPosition(p);
-        mainPanel.repaint();
-        //mainPanel.revalidate();
       }
     };
     addMouseMotionListener(mouseMotionListener);
@@ -3396,18 +3382,17 @@ public class BamView extends JPanel
         cbLastSelected.setSelected(true);
         cbLastSelected = null;
       }
-      
+
       jspView.setColumnHeaderView(null);
-      
-      if(!isStrandStackView())
+      if(isCoverageView(pixPerBase))
         setViewportBtm();
-      else
+      else if (isStrandStackView())
         setViewportMidPoint();
       showBaseAlignment = false;
       baseQualityColour.setEnabled(false);
       markInsertions.setEnabled(false);
     }
-    
+
     if(scrollBar != null)
     {
       scrollBar.setValues(startValue, nbasesInView, 1, 
@@ -3689,6 +3674,26 @@ public class BamView extends JPanel
     return combo;
   }
   
+  protected Hashtable<String, SAMFileReader>  getSamFileReaderHash()
+  {
+    return samFileReaderHash;
+  }
+  
+  protected Vector<String> getSeqNames()
+  {
+    return seqNames;
+  }
+  
+  protected HashMap<String, Integer> getSeqLengths()
+  {
+    return seqLengths;
+  }
+  
+  protected HashMap<String, Integer> getOffsetLengths()
+  {
+    return offsetLengths;
+  }
+  
   private String getVersion()
   {
     final ClassLoader cl = this.getClass().getClassLoader();
@@ -4288,8 +4293,7 @@ public class BamView extends JPanel
         minBams.setValue(groupsFrame.getMaximumBamsInGroup());
       }
 
-      new MappedReads((String)combo.getSelectedItem(),BamView.this, samFileReaderHash,
-          seqNames, offsetLengths, concatSequences, seqLengths, 
+      new MappedReads(BamView.this,
           (useGroup.isSelected() ? groupsFrame : null), threshold.getValue(), 
           minSize.getValue(), minBams.getValue(), cbOpposite.isSelected(), true);
     }
diff --git a/uk/ac/sanger/artemis/components/alignment/CRAMReferenceSequenceFile.java b/uk/ac/sanger/artemis/components/alignment/CRAMReferenceSequenceFile.java
index ae2fb2d..155be81 100644
--- a/uk/ac/sanger/artemis/components/alignment/CRAMReferenceSequenceFile.java
+++ b/uk/ac/sanger/artemis/components/alignment/CRAMReferenceSequenceFile.java
@@ -107,5 +107,8 @@ import uk.ac.sanger.artemis.util.OutOfRangeException;
     public void reset()
     {
     }
-    
-  }
\ No newline at end of file
+
+    public void close()
+    {
+    }
+  }
diff --git a/uk/ac/sanger/artemis/components/alignment/CoveragePanel.java b/uk/ac/sanger/artemis/components/alignment/CoveragePanel.java
index 64dbaa5..2d01720 100644
--- a/uk/ac/sanger/artemis/components/alignment/CoveragePanel.java
+++ b/uk/ac/sanger/artemis/components/alignment/CoveragePanel.java
@@ -47,6 +47,7 @@ import javax.swing.JLabel;
 import javax.swing.JMenuItem;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
+import javax.swing.JSeparator;
 import javax.swing.JTextField;
 
 import uk.ac.sanger.artemis.components.Plot;
@@ -71,11 +72,14 @@ import net.sf.samtools.SAMRecord;
     private List<String> selected = new Vector<String>();
     private boolean showGrid = false;
     private boolean logScale = false;
+    private int maxCoverage = Integer.MAX_VALUE;
+    private JCheckBox fixHgt = new JCheckBox("Fix the graph height to cut-off", false);
 
     protected CoveragePanel(final BamView bamView)
     {
       this();
       this.bamView = bamView;
+      setMaxBases = false;
       createMenus(popup);
       addMouseListener(new PopupListener());
     }
@@ -129,7 +133,18 @@ import net.sf.samtools.SAMRecord;
       if(!plotHeatMap)
         drawSelectionRange(g2, pixPerBase, start, end, getHeight(), Color.PINK);
       drawPlot(g2);
-      drawMax(g2);
+      drawMax(g2, getMaxCoverage());
+    }
+    
+    protected float getMaxCoverage()
+    {
+      return (useMaxCutOff() ? maxCoverage: (float)max/(float)windowSize);
+    }
+    
+    private boolean useMaxCutOff()
+    {
+      return (maxCoverage < Integer.MAX_VALUE && maxCoverage < (float)max/(float)windowSize) ||
+             (maxCoverage < Integer.MAX_VALUE && fixHgt.isSelected());
     }
     
     protected void init(BamView bamView, float pixPerBase, int start, int end)
@@ -187,7 +202,7 @@ import net.sf.samtools.SAMRecord;
             max = thisPlot[i][0];
         }
       }
-      
+
       draw(g2, getWidth(), getHeight(), null);
     }
     
@@ -310,7 +325,12 @@ import net.sf.samtools.SAMRecord;
     {
       g2.setColor(line.getLineColour());
       int hgt2 = hgt/2;
-      float maxVal = getValue(max, logScale);
+      float maxVal;
+
+      if(useMaxCutOff())
+        maxVal = getValue((maxCoverage*windowSize), logScale);
+      else
+        maxVal = getValue(max, logScale);
       
       if(line.getPlotType() == LineAttributes.PLOT_TYPES[0])
       {
@@ -538,25 +558,45 @@ import net.sf.samtools.SAMRecord;
       final GridBagConstraints c = new GridBagConstraints();
 
       final JTextField newBaseMax = new JTextField(Integer.toString(bamView.getMaxBases()), 10);
-      c.gridy = 0;
+      c.gridy+=1;
+      c.gridwidth = 1;
       if(setMaxBases)
       {
         final JLabel labMax1 = new JLabel("Zoom level before switching");
         final JLabel labMax2 = new JLabel("to coverage view (in bases):");
         c.anchor = GridBagConstraints.WEST;
+        c.gridy+=1;
         opts.add(labMax1, c);
-        c.gridy = c.gridy+1;
+        c.gridy+=1;
         opts.add(labMax2, c);
         opts.add(newBaseMax, c);
+        addSeparator(c, opts);
       }
       
+      // cut-off
+      final JTextField maxCutoff = new JTextField(
+          (maxCoverage < Integer.MAX_VALUE ? Integer.toString(maxCoverage) : ""), 10);
+      c.gridy+=1;
+      c.anchor = GridBagConstraints.WEST;
+      opts.add(new JLabel("Coverage cut-off:"), c);
+      c.anchor = GridBagConstraints.EAST;
+      opts.add(maxCutoff, c);
+      
+      c.gridy+=1;
+      c.gridwidth = GridBagConstraints.REMAINDER;
+      c.anchor = GridBagConstraints.WEST;
+      opts.add(fixHgt, c);
+      
+      addSeparator(c, opts);
+      
+      // window size
       final JTextField newWinSize = new JTextField(Integer.toString(userWinSize), 10);
       final JLabel lab = new JLabel("Window size:");
       lab.setEnabled(!autoWinSize);
       newWinSize.setEnabled(!autoWinSize);
       
-      c.gridy = c.gridy+1;
-      c.anchor = GridBagConstraints.EAST;
+      c.gridy+=1;
+      c.anchor = GridBagConstraints.WEST;
       opts.add(lab, c);
       opts.add(newWinSize, c);
 
@@ -570,22 +610,24 @@ import net.sf.samtools.SAMRecord;
         }
       });
       c.anchor = GridBagConstraints.WEST;
-      c.gridy = c.gridy+1;
+      c.gridy+=1;
       c.gridwidth = GridBagConstraints.REMAINDER;
       opts.add(autoSet, c);
 
+      addSeparator(c, opts);
+      
       final JCheckBox showCombined = new JCheckBox("Show combined plot", includeCombined);
       if(bamView.bamList.size() == 1)
         showCombined.setEnabled(false);
-      c.gridy = c.gridy+1;
+      c.gridy+=1;
       opts.add(showCombined, c);
       
       final JCheckBox byStrand = new JCheckBox("Plot by strand", plotByStrand);
-      c.gridy = c.gridy+1;
+      c.gridy+=1;
       opts.add(byStrand, c);
 
       final JCheckBox logMenu = new JCheckBox("Log scale", logScale);
-      c.gridy = c.gridy+1;
+      c.gridy+=1;
       opts.add(logMenu, c);
 
       String window_options[] = { "OK", "Cancel" };
@@ -601,10 +643,14 @@ import net.sf.samtools.SAMRecord;
       includeCombined = showCombined.isSelected();
       plotByStrand = byStrand.isSelected();
       logScale = logMenu.isSelected();
-      
+
       try
       {
         userWinSize = Integer.parseInt(newWinSize.getText().trim());
+        if(maxCutoff.getText().length() > 0)
+          maxCoverage = Integer.parseInt(maxCutoff.getText().trim());
+        else
+          maxCoverage = Integer.MAX_VALUE;
         if(setMaxBases)
           bamView.setMaxBases(Integer.parseInt(newBaseMax.getText().trim()));
       }
@@ -615,6 +661,22 @@ import net.sf.samtools.SAMRecord;
     }
     
     /**
+     * Add a separator 
+     * @param c
+     * @param opts
+     */
+    private void addSeparator(GridBagConstraints c, final JPanel opts)
+    {
+      c.gridy+=1;
+      c.gridwidth = GridBagConstraints.REMAINDER;
+      final JSeparator sep = new JSeparator();
+      c.fill = GridBagConstraints.HORIZONTAL;
+      opts.add(sep, c);
+      c.gridwidth = 1;
+      c.fill = GridBagConstraints.NONE;
+    }
+    
+    /**
      * Click on heatmap
      * @param y
      */
diff --git a/uk/ac/sanger/artemis/components/alignment/MappedReads.java b/uk/ac/sanger/artemis/components/alignment/MappedReads.java
index 7b13366..d47b528 100644
--- a/uk/ac/sanger/artemis/components/alignment/MappedReads.java
+++ b/uk/ac/sanger/artemis/components/alignment/MappedReads.java
@@ -9,7 +9,6 @@ import java.awt.event.ActionListener;
 import java.io.File;
 import java.text.DecimalFormat;
 import java.util.Enumeration;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.List;
@@ -37,188 +36,93 @@ import uk.ac.sanger.artemis.sequence.MarkerRange;
 import uk.ac.sanger.artemis.util.OutOfRangeException;
 import uk.ac.sanger.artemis.util.ReadOnlyException;
 
-import net.sf.samtools.SAMFileReader;
-
 public class MappedReads
 {
   private JProgressBar progressBar;
   private JLabel progressTxt = new JLabel();
 
   private FeatureVector features;
-  private String refName;
-  private Hashtable<String, SAMFileReader> samFileReaderHash;
-  private List<String> bamList;
-  private List<Short> hideBamList;
-  private Vector<String> seqNames;
-  private HashMap<String, Integer> offsetLengths;
-  private boolean concatSequences;
-  private HashMap<String, Integer> seqLengths;
-  private int sequenceLength;
-  private SAMRecordPredicate samRecordFlagPredicate;
-  private SAMRecordMapQPredicate samRecordMapQPredicate;
+  private BamView bamView;
+
   private boolean contained;
   private boolean useIntrons;
   private boolean useStrandTag = false;
-  private JDialog dialog = new JDialog((JFrame)null, "Calculating", true);;
-    
-  private int mappedReads[];
+  private JDialog dialog = new JDialog((JFrame)null, "Calculating", true);
     
   /**
    * Calculate the total number of mapped reads.
-   * @param refName
-   * @param samFileReaderHash
-   * @param bamList
-   * @param seqNames
-   * @param offsetLengths
-   * @param concatSequences
-   * @param seqLengths
+   * @param bamView
+   * @param features
    * @param sequenceLength
+   * @param contained
+   * @param useIntrons
+   * @param allRefSeqs
+   * @param useStrandTag
    */
   public MappedReads(
+      final BamView bamView,
       final FeatureVector features,
-      final String refName,
-      final Hashtable<String, SAMFileReader> samFileReaderHash,
-      final List<String> bamList, 
-      final Vector<String> seqNames,
-      final HashMap<String, Integer> offsetLengths,
-      final boolean concatSequences,
-      final HashMap<String, Integer> seqLengths, 
       final int sequenceLength,
-      final SAMRecordPredicate samRecordFlagPredicate,
-      SAMRecordMapQPredicate samRecordMapQPredicate,
       final boolean contained, 
       final boolean useIntrons,
       final boolean allRefSeqs,
       final boolean useStrandTag)
   {
     this.features = features;
-    this.refName = refName;
-    this.samFileReaderHash = samFileReaderHash;
-    this.bamList = bamList;
-    this.seqNames = seqNames;
-    this.offsetLengths = offsetLengths;
-    this.concatSequences = concatSequences;
-    this.seqLengths = seqLengths;
-    this.sequenceLength = sequenceLength;
-    this.samRecordFlagPredicate = samRecordFlagPredicate;
-    this.samRecordMapQPredicate = samRecordMapQPredicate;
+    this.bamView = bamView;
     this.contained = contained;
     this.useIntrons = useIntrons;
     this.useStrandTag = useStrandTag;
-    
-    progressBar = new JProgressBar(0, sequenceLength);
-    progressBar.setValue(0);
-    progressBar.setStringPainted(true);
 
-    JPanel panel = new JPanel(new BorderLayout());
-    progressTxt.setText("Total number of mapped reads");
-    panel.add(progressTxt, BorderLayout.NORTH);
-    panel.add(progressBar, BorderLayout.CENTER);
-
-    dialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
-    panel.setOpaque(true);
-    dialog.setContentPane(panel);
-    dialog.pack();
-    centerDialog();
-
-    CalculateTotalMappedReads cmr = new CalculateTotalMappedReads(allRefSeqs);
+    showProgress(sequenceLength, "Total number of mapped reads");
+    CalculateTotalMappedReads cmr = new CalculateTotalMappedReads(allRefSeqs, sequenceLength);
     cmr.start();
     dialog.setVisible(true);
   }
 
   /**
    * Read count for selected features.
+   * @param bamView
    * @param features
-   * @param refName
-   * @param samFileReaderHash
-   * @param bamList
-   * @param seqNames
-   * @param offsetLengths
-   * @param concatSequences
-   * @param seqLengths
-   * @param samRecordFlagPredicate
-   * @param samRecordMapQPredicate
    * @param contained
    * @param useIntrons
+   * @param useStrandTag
    */
   public MappedReads(
+      final BamView bamView,
       final FeatureVector features, 
-      final String refName,
-      final Hashtable<String, SAMFileReader> samFileReaderHash,
-      final List<String> bamList, 
-      final Vector<String> seqNames,
-      final HashMap<String, Integer> offsetLengths,
-      final boolean concatSequences,
-      final HashMap<String, Integer> seqLengths,
-      final SAMRecordPredicate samRecordFlagPredicate,
-      final SAMRecordMapQPredicate samRecordMapQPredicate,
       final boolean contained, 
       final boolean useIntrons,
       final boolean useStrandTag)
   {
+    this.bamView = bamView;
     this.features = features;
-    this.refName = refName;
-    this.samFileReaderHash = samFileReaderHash;
-    this.bamList = bamList;
-    this.seqNames = seqNames;
-    this.offsetLengths = offsetLengths;
-    this.concatSequences = concatSequences;
-    this.seqLengths = seqLengths;
-    this.samRecordFlagPredicate = samRecordFlagPredicate;
-    this.samRecordMapQPredicate = samRecordMapQPredicate;
+    this.bamView = bamView;
     this.contained = contained;
     this.useIntrons = useIntrons;
     this.useStrandTag = useStrandTag;
-    
-    progressBar = new JProgressBar(0, features.size());
-    progressBar.setValue(0);
-    progressBar.setStringPainted(true);
 
-    JPanel panel = new JPanel(new BorderLayout());
-    progressTxt.setText("Number of mapped reads for "+features.size()+" features");
-    panel.add(progressTxt, BorderLayout.NORTH);
-    panel.add(progressBar, BorderLayout.CENTER);
-
-    panel.setOpaque(true);
-    dialog.setContentPane(panel);
-    dialog.pack();
-    centerDialog();
-    
-    CalculateMappedReads cmr = new CalculateMappedReads();
+    showProgress(features.size(), "Number of mapped reads for "+features.size()+" features");
+    CalculateMappedReads cmr = new CalculateMappedReads(null);
     cmr.start();
     dialog.setVisible(true);
   }
-  
-  
 
   /**
    * Search for new features based on a threshold of read counts in the intergenic 
    * and anti-sense regions of existing annotations. This should exclude rRNA and 
    * tRNA regions. If two or more BAM files are loaded it should create features 
    * based on the combined read peak span.
-   * @param refName
    * @param bamView
-   * @param samFileReaderHash
-   * @param bamList
-   * @param seqNames
-   * @param offsetLengths
-   * @param concatSequences
-   * @param seqLengths
    * @param groupsFrame
    * @param threshold
    * @param minSize
    * @param minBams
-   * @param readsOnOppositeStrand assume reads are on the opposite strand if true.
+   * @param readsOnOppositeStrand
    * @param contained
    */
   public MappedReads(  
-      final String refName,
       final BamView bamView,
-      final Hashtable<String, SAMFileReader> samFileReaderHash,
-      final Vector<String> seqNames,
-      final HashMap<String, Integer> offsetLengths,
-      final boolean concatSequences,
-      final HashMap<String, Integer> seqLengths,
       final GroupBamFrame groupsFrame,
       final int threshold,
       final int minSize,
@@ -226,26 +130,26 @@ public class MappedReads
       final boolean readsOnOppositeStrand,
       final boolean contained)
   {
-    this.refName = refName;
-    this.samFileReaderHash = samFileReaderHash;
-    this.bamList = bamView.bamList;
-    this.hideBamList = bamView.hideBamList;
-    this.seqNames = seqNames;
-    this.offsetLengths = offsetLengths;
-    this.concatSequences = concatSequences;
-    this.seqLengths = seqLengths;
-    this.samRecordFlagPredicate = bamView.getSamRecordFlagPredicate();
-    this.samRecordMapQPredicate = bamView.getSamRecordMapQPredicate();
-    
+    this.bamView = bamView;
     this.contained = contained;
     
     final FeatureDisplay feature_display = bamView.getFeatureDisplay();
-    progressBar = new JProgressBar(0, feature_display.getSequenceLength());
+    showProgress(feature_display.getSequenceLength(), "");
+    final CalculateNewFeatures cmr = new CalculateNewFeatures(
+        feature_display, (String) bamView.getCombo().getSelectedItem(), groupsFrame, 
+        threshold, minSize, minBams, readsOnOppositeStrand);
+    cmr.start();
+    dialog.setVisible(true);
+  }
+  
+  private void showProgress(int progressBarMax, String txt)
+  {
+    progressBar = new JProgressBar(0, progressBarMax);
     progressBar.setValue(0);
     progressBar.setStringPainted(true);
 
     JPanel panel = new JPanel(new BorderLayout());
-    progressTxt.setText("");
+    progressTxt.setText(txt);
     panel.add(progressTxt, BorderLayout.NORTH);
     panel.add(progressBar, BorderLayout.CENTER);
 
@@ -254,11 +158,6 @@ public class MappedReads
     dialog.setContentPane(panel);
     dialog.pack();
     centerDialog();
-    
-    final CalculateNewFeatures cmr = new CalculateNewFeatures(
-        feature_display, refName, groupsFrame, threshold, minSize, minBams, readsOnOppositeStrand);
-    cmr.start();
-    dialog.setVisible(true);
   }
 
   private void centerDialog()
@@ -274,55 +173,18 @@ public class MappedReads
   
   class CalculateMappedReads extends SwingWorker
   {
-    Hashtable<String, List<ReadCount>> featureReadCount;
+    private Hashtable<String, List<ReadCount>> featureReadCount;
+    private int mappedReads[];
+    
+    public CalculateMappedReads(final int mappedReads[])
+    {
+      this.mappedReads = mappedReads;
+    }
+    
     public Object construct()
     {
-      featureReadCount = new Hashtable<String, List<ReadCount>>();
-      for (int i = 0; i < features.size(); i++)
-      {
-        Feature f = features.elementAt(i);
-        progressBar.setValue(i);
-
-        int start = f.getRawFirstBase();
-        int end = f.getRawLastBase();
-        float fLen = BamUtils.getFeatureLength(f);
-        List<ReadCount> sampleCounts = new Vector<ReadCount>();
-
-        for (int j = 0; j < bamList.size(); j++)
-        {
-          String bam = bamList.get(j);
-          float cnt[] = new float[2];
-
-          if (!useIntrons && f.getSegments().size() > 1)
-          {
-            for (int k = 0; k < f.getSegments().size(); k++)
-            {
-              start = f.getSegments().elementAt(k).getRawRange().getStart();
-              end = f.getSegments().elementAt(k).getRawRange().getEnd();
-              float tmpcnt[] = new float[2];
-              tmpcnt = BamUtils.getCount(start, end, bam, refName, samFileReaderHash,
-                  seqNames, offsetLengths, concatSequences, seqLengths,
-                  samRecordFlagPredicate, samRecordMapQPredicate, contained, useStrandTag);
-              cnt[0] += tmpcnt[0];
-              cnt[1] += tmpcnt[1];
-            }
-          }
-          else
-            cnt = BamUtils.getCount(start, end, bam, refName, samFileReaderHash, seqNames,
-                offsetLengths, concatSequences, seqLengths,
-                samRecordFlagPredicate, samRecordMapQPredicate, contained, useStrandTag);
-
-          if (mappedReads != null)
-          {
-            cnt[0] = (cnt[0] / (((float) mappedReads[j] / 1000000.f) * (fLen / 1000.f)));
-            cnt[1] = (cnt[1] / (((float) mappedReads[j] / 1000000.f) * (fLen / 1000.f)));
-          }
-
-          sampleCounts.add( new ReadCount(cnt, f.isForwardFeature()) );
-        }
-        
-        featureReadCount.put(ReadCountDialog.getFeatureName(f), sampleCounts);
-      }
+      featureReadCount = BamUtils.calculateMappedReads(bamView, features, 
+          contained, useIntrons, useStrandTag, mappedReads, progressBar);
       return null;
     }
        
@@ -341,9 +203,9 @@ public class MappedReads
       final StringBuilder titleToSave = new StringBuilder();
       final StringBuilder body = new StringBuilder();
       
-      for (int j = 0; j < bamList.size(); j++)
+      for (int j = 0; j < bamView.bamList.size(); j++)
       {
-        String bam = bamList.get(j);
+        String bam = bamView.bamList.get(j);
         hdr.append("#BAM: " + bam);
         if (mappedReads != null)
         {
@@ -369,7 +231,7 @@ public class MappedReads
           }
           titleToSave.append("\t");
           title.append("\t");
-          for (String bam: bamList)
+          for (String bam: bamView.bamList)
           {
             String name = new File(bam).getName();
             titleToSave.append(name+"\t");
@@ -409,7 +271,7 @@ public class MappedReads
           }
           title.append("\t");
           titleToSave.append("\t");
-          for (int j = 0; j < bamList.size(); j++)
+          for (int j = 0; j < bamView.bamList.size(); j++)
           {
             if(mappedReads != null)
             {
@@ -481,88 +343,43 @@ public class MappedReads
   class CalculateTotalMappedReads extends SwingWorker
   {
     private boolean useAllRefSeqs;
-    CalculateTotalMappedReads(boolean useAllRefSeqs)
+    private int sequenceLength;
+    
+    CalculateTotalMappedReads(boolean useAllRefSeqs, final int sequenceLength)
     {
       this.useAllRefSeqs = useAllRefSeqs;
+      this.sequenceLength = sequenceLength;
     }
     
     public Object construct()
     {
-      mappedReads = new int[bamList.size()];
-      if(concatSequences || !useAllRefSeqs)
-        calc(refName, sequenceLength);
+      int mappedReads[] = null;
+      if(bamView.isConcatSequences() || !useAllRefSeqs)
+      {
+        String refName = (String) bamView.getCombo().getSelectedItem();
+        mappedReads = BamUtils.calc(bamView, refName, sequenceLength, 
+            useStrandTag, progressBar);
+      }
       else
       {
-        for (String name : seqNames)
+        for (String name : bamView.getSeqNames())
         {
           progressTxt.setText(name);
-          int thisLength = seqLengths.get(name);
+          int thisLength = bamView.getSeqLengths().get(name);
           progressBar.setValue(0);
           progressBar.setMaximum(thisLength);
-          calc(name, thisLength);
+          mappedReads = BamUtils.calc(bamView, name, thisLength, 
+              useStrandTag, progressBar);
         }
       }
       
       progressBar.setValue(0);
       progressBar.setMaximum(features.size());
       progressTxt.setText("RPKM values for "+features.size()+" features");
-      CalculateMappedReads cmr = new CalculateMappedReads();
+      CalculateMappedReads cmr = new CalculateMappedReads(mappedReads);
       cmr.start();
       return null;
     }
-    
-    private void calc(final String refName, final int sequenceLength)
-    {
-      int MAX_BASE_CHUNK = 2000 * 60;
-      boolean contained = false;
-      for (int i = 0; i < sequenceLength; i += MAX_BASE_CHUNK)
-      {
-        progressBar.setValue(i);
-        int sbegc = i;
-        int sendc = i + MAX_BASE_CHUNK - 1;
-
-        for (int j = 0; j < bamList.size(); j++)
-        {
-          String bam = bamList.get(j);
-          if (concatSequences)
-          {
-            int len = 0;
-            int lastLen = 1;
-            for (String name : seqNames)
-            {
-              int thisLength = seqLengths.get(name);
-              len += thisLength;
-
-              if ((lastLen >= sbegc && lastLen < sendc)
-                  || (len >= sbegc && len < sendc)
-                  || (sbegc >= lastLen && sbegc < len)
-                  || (sendc >= lastLen && sendc < len))
-              {
-                int offset = offsetLengths.get(name);
-                int thisStart = sbegc - offset;
-                if (thisStart < 1)
-                  thisStart = 1;
-                int thisEnd = sendc - offset;
-                if (thisEnd > thisLength)
-                  thisEnd = thisLength;
-
-                mappedReads[j] += BamUtils.count(bam, samFileReaderHash, name,
-                    thisStart, thisEnd, samRecordFlagPredicate,
-                    samRecordMapQPredicate, contained, false, useStrandTag)[0];
-
-              }
-              lastLen = len;
-            }
-          }
-          else
-          {
-            mappedReads[j] += BamUtils.count(bam, samFileReaderHash, refName, sbegc,
-                sendc, samRecordFlagPredicate, samRecordMapQPredicate,
-                contained, false, useStrandTag)[0];
-          }
-        }
-      }
-    }
   }
   
   /**
@@ -572,7 +389,6 @@ public class MappedReads
   {
     private EntryGroup entryGroup;
     private Bases bases;
-    private String refSeq;
     private int threshold;
     private int minSize;
     private int minBams;
@@ -589,7 +405,7 @@ public class MappedReads
     {
       entryGroup = feature_display.getEntryGroup();
       bases = feature_display.getBases();
-      this.refSeq = refSeq;
+
       this.groupsFrame = groupsFrame;
       this.threshold = threshold;
       this.minSize = minSize;
@@ -620,14 +436,14 @@ public class MappedReads
       int revStart = -1;
       final List<MarkerObj> fwdMarkers = new Vector<MarkerObj>();
       final List<MarkerObj> revMarkers = new Vector<MarkerObj>();
-      for (short i = 0; i < bamList.size(); i++)
+      for (short i = 0; i < bamView.bamList.size(); i++)
       {
-        if(hideBamList.contains(i))
+        if(bamView.hideBamList.contains(i))
           continue;
         
         for(int j=beg; j<end; j+=MAX_BASE_CHUNK)
         {
-          progressBar.setValue((j + (i*end)) / bamList.size());
+          progressBar.setValue((j + (i*end)) / bamView.bamList.size());
           if(j > end)
             continue;
           int start = j;
@@ -640,12 +456,12 @@ public class MappedReads
               for (int col = 0; col < 2; col++)
                 cnt[row][col] = 0;
             
-            if (concatSequences)
+            if (bamView.isConcatSequences())
             {
-              for (String name : seqNames)
+              for (String name : bamView.getSeqNames())
               {
-                int len = seqLengths.get(name);
-                int offset = offsetLengths.get(name);
+                int len = bamView.getSeqLengths().get(name);
+                int offset = bamView.getOffsetLengths().get(name);
                 
                 if( (start >= offset && start <= offset+len) ||
                     (stop >= offset  && start <= offset+len) )
@@ -662,17 +478,15 @@ public class MappedReads
                     concatShift = offset-start;
 
                   cnt =
-                    BamUtils.countOverRange(bamList.get(i), samFileReaderHash, 
-                        name, thisStart, thisEnd, concatShift, cnt,
-                        samRecordFlagPredicate, samRecordMapQPredicate);
+                    BamUtils.countOverRange(bamView, bamView.bamList.get(i), 
+                        thisStart, thisEnd, concatShift, cnt);
                 }
               }
             }
             else
               cnt =
-                BamUtils.countOverRange(bamList.get(i), samFileReaderHash, 
-                    refSeq, start, stop, 0, cnt,
-                    samRecordFlagPredicate, samRecordMapQPredicate);
+                BamUtils.countOverRange(bamView, bamView.bamList.get(i),  
+                    start, stop, 0, cnt);
 
             for(int k=0; k<cnt.length; k++)
             {
@@ -741,7 +555,7 @@ public class MappedReads
             Hashtable<String, Integer> groupCluster = new Hashtable<String, Integer>();
             for(int j=0; j<bamIdxList.size(); j++)
             {
-              File f = new File(bamList.get(j));
+              File f = new File(bamView.bamList.get(j));
               String grp = groupsFrame.getGroupName( f.getName() );
               if(groupCluster.containsKey(grp))
               {
@@ -894,23 +708,4 @@ public class MappedReads
       dialog.dispose();
     }
   }
-  
-  class ReadCount
-  {
-    private float senseCnt = 0;
-    private float antiCnt  = 0;
-    ReadCount(float[] cnt, boolean isFwd)
-    {
-      if(isFwd)
-      {
-        senseCnt = cnt[0];
-        antiCnt  = cnt[1];
-      }
-      else
-      {
-        senseCnt = cnt[1];
-        antiCnt  = cnt[0];
-      }
-    }
-  }
 }
diff --git a/uk/ac/sanger/artemis/components/alignment/ReadCount.java b/uk/ac/sanger/artemis/components/alignment/ReadCount.java
new file mode 100644
index 0000000..5d940ed
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/ReadCount.java
@@ -0,0 +1,40 @@
+/* 
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2014  Genome Research Limited
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.alignment;
+
+class ReadCount
+{
+  protected float senseCnt = 0;
+  protected float antiCnt  = 0;
+  ReadCount(float[] cnt, boolean isFwd)
+  {
+    if(isFwd)
+    {
+      senseCnt = cnt[0];
+      antiCnt  = cnt[1];
+    }
+    else
+    {
+      senseCnt = cnt[1];
+      antiCnt  = cnt[0];
+    }
+  }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/ReadCountDialog.java b/uk/ac/sanger/artemis/components/alignment/ReadCountDialog.java
index 17861ff..35b5bc8 100644
--- a/uk/ac/sanger/artemis/components/alignment/ReadCountDialog.java
+++ b/uk/ac/sanger/artemis/components/alignment/ReadCountDialog.java
@@ -138,11 +138,14 @@ import uk.ac.sanger.artemis.util.StringVector;
       if(qNames == null)
       {
         qNames = new StringVector();
-        Object objs[] = systematicListSelectionPanel.getResultArray();
+        Object objs[];
+        if(systematicListSelectionPanel != null)
+          objs = systematicListSelectionPanel.getResultArray();
+        else
+          objs = Options.getOptions().getSystematicQualifierNames().toArray();
         for(Object o: objs)
           qNames.add((String)o);
       }
-      
       return pickName(f, qNames);
     }
     
diff --git a/uk/ac/sanger/artemis/components/genebuilder/GeneEditorPanel.java b/uk/ac/sanger/artemis/components/genebuilder/GeneEditorPanel.java
index 5cf4c1d..2851efa 100644
--- a/uk/ac/sanger/artemis/components/genebuilder/GeneEditorPanel.java
+++ b/uk/ac/sanger/artemis/components/genebuilder/GeneEditorPanel.java
@@ -40,7 +40,6 @@ import uk.ac.sanger.artemis.components.genebuilder.cv.CVPanel;
 import uk.ac.sanger.artemis.components.genebuilder.gff.PropertiesPanel;
 import uk.ac.sanger.artemis.components.genebuilder.ortholog.MatchPanel;
 
-
 /**
  * Panel for display controlled vocabulary terms for Chado
  */
@@ -92,22 +91,32 @@ public class GeneEditorPanel extends JPanel
     coreButton = addOpenClosePanel("Core",qualifier_text_area, this, null);
     add(qualifier_text_area);
     
-    addDarkSeparator(this);
-    refButton = addOpenClosePanel("References",refPanel, this, null);
-    add(refPanel);
-    
-    addDarkSeparator(this);
-    cvButton = addOpenClosePanel("Controlled Vocabulary", cvForm, this,
+    if(refPanel != null)
+    {
+      addDarkSeparator(this);
+      refButton = addOpenClosePanel("References",refPanel, this, null);
+      add(refPanel);
+    }
+
+    if(cvForm != null)
+    {
+      addDarkSeparator(this);
+      cvButton = addOpenClosePanel("Controlled Vocabulary", cvForm, this,
         CVPanel.getDescription());
-    add(cvForm);
-    
-    addDarkSeparator(this);
-    matchButton = addOpenClosePanel("Match", matchForm, this,
+      add(cvForm);
+    }
+
+    if(matchForm != null)
+    {
+      addDarkSeparator(this);
+      matchButton = addOpenClosePanel("Match", matchForm, this,
         MatchPanel.getDescription());
-    add(matchForm);
-    
+      add(matchForm);
+    }
+
     add(Box.createVerticalGlue());
   }
+  
 
   /**
    * Open/close the sections if they contain elements or
@@ -116,9 +125,12 @@ public class GeneEditorPanel extends JPanel
   public void updatePanelState()
   {
     coreButton.setOpen(!qualifier_text_area.getText().equals(""));
-    cvButton.setOpen(!cvForm.isEmpty());
-    refButton.setOpen(!refPanel.isEmpty());
-    matchButton.setOpen(!matchForm.isEmpty());
+    if(cvForm != null)
+      cvButton.setOpen(!cvForm.isEmpty());
+    if(refPanel != null)
+      refButton.setOpen(!refPanel.isEmpty());
+    if(matchForm != null)
+      matchButton.setOpen(!matchForm.isEmpty());
     propertiesButton.setOpen(!propertiesPanel.isEmpty());
   }
   
diff --git a/uk/ac/sanger/artemis/components/genebuilder/GeneUtils.java b/uk/ac/sanger/artemis/components/genebuilder/GeneUtils.java
index bee966d..7d8f160 100644
--- a/uk/ac/sanger/artemis/components/genebuilder/GeneUtils.java
+++ b/uk/ac/sanger/artemis/components/genebuilder/GeneUtils.java
@@ -61,6 +61,7 @@ import uk.ac.sanger.artemis.io.DatabaseInferredFeature;
 import uk.ac.sanger.artemis.io.DocumentEntry;
 import uk.ac.sanger.artemis.io.EntryInformationException;
 import uk.ac.sanger.artemis.io.Feature;
+import uk.ac.sanger.artemis.io.GFF3Encoder;
 import uk.ac.sanger.artemis.io.GFFDocumentEntry;
 import uk.ac.sanger.artemis.io.GFFStreamFeature;
 import uk.ac.sanger.artemis.io.InvalidRelationException;
@@ -126,7 +127,7 @@ public class GeneUtils
       String value = featureSynonym.getSynonym().getName();
       
       if(!featureSynonym.isCurrent())
-        value.concat(GFFStreamFeature.encode(";current=false"));      
+        value.concat(GFF3Encoder.encode(";current=false"));      
       
       Qualifier qualifier = feature.getQualifiers().getQualifierByName(name);
       if(qualifier == null)
@@ -198,7 +199,7 @@ public class GeneUtils
         final String qualifierString = new String(this_buff.getBytes());
         int ind = qualifierString.indexOf('=');
         final String name  = qualifierString.substring(0, ind);
-        final String value = GFFStreamFeature.decode(
+        final String value = GFF3Encoder.decode(
           qualifierString.substring(ind+1, qualifierString.length()-1));
       
         Qualifier qualifier = feature.getQualifiers().getQualifierByName(name);
diff --git a/uk/ac/sanger/artemis/components/genebuilder/gff/PropertiesPanel.java b/uk/ac/sanger/artemis/components/genebuilder/gff/PropertiesPanel.java
index a02fe75..03a72b4 100644
--- a/uk/ac/sanger/artemis/components/genebuilder/gff/PropertiesPanel.java
+++ b/uk/ac/sanger/artemis/components/genebuilder/gff/PropertiesPanel.java
@@ -34,7 +34,6 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.util.Iterator;
 import java.util.Set;
-import java.util.Vector;
 
 import javax.swing.BorderFactory;
 import javax.swing.Box;
@@ -184,7 +183,9 @@ public class PropertiesPanel extends JPanel
   {
     Qualifier idQualifier   = gffQualifiers.getQualifierByName("ID");
     Qualifier nameQualifier = gffQualifiers.getQualifierByName("Name");
-   
+
+    if(idQualifier == null)
+      return;
     final String uniquename = idQualifier.getValues().get(0);
     uniquenameTextField = new JTextField(uniquename);
     uniquenameTextField.setPreferredSize(calcPreferredMaxTextFieldWidth());
@@ -780,9 +781,13 @@ public class PropertiesPanel extends JPanel
   
   private void addSynonym()
   {
-    final Vector<CvTerm> synonyms = DatabaseDocument.getCvterms("", 
-        ChadoTransactionManager.SYNONYM_TAG_CVNAME, false);
-    final JExtendedComboBox list = new JExtendedComboBox(synonyms);
+    final JExtendedComboBox list;
+    if(GeneUtils.isDatabaseEntry(feature.getEmblFeature()))
+      list = new JExtendedComboBox(DatabaseDocument.getCvterms("", 
+          ChadoTransactionManager.SYNONYM_TAG_CVNAME, false));
+    else
+      list = new JExtendedComboBox(ChadoTransactionManager.getSynonymTags());
+
     final String options[] = { "CANCEL", "NEXT>"};   
     
     int select = JOptionPane.showOptionDialog(null, list,
@@ -790,12 +795,15 @@ public class PropertiesPanel extends JPanel
          JOptionPane.YES_NO_CANCEL_OPTION,
          JOptionPane.QUESTION_MESSAGE,
          null, options, options[1]);
-    
     if(select == 0)
       return;
-    
+
     Box xBox = Box.createHorizontalBox();
-    final String synonymName = ((CvTerm)list.getSelectedItem()).getName();
+    final String synonymName;
+    if(list.getSelectedItem() instanceof CvTerm)
+      synonymName = ((CvTerm)list.getSelectedItem()).getName();
+    else
+      synonymName = (String) list.getSelectedItem();
     final JLabel name = new JLabel( synonymName );
     xBox.add(name);
     
diff --git a/uk/ac/sanger/artemis/io/DocumentEntryFactory.java b/uk/ac/sanger/artemis/io/DocumentEntryFactory.java
index f530a08..a83b7b2 100644
--- a/uk/ac/sanger/artemis/io/DocumentEntryFactory.java
+++ b/uk/ac/sanger/artemis/io/DocumentEntryFactory.java
@@ -31,6 +31,8 @@ import uk.ac.sanger.artemis.util.LinePushBackReader;
 import java.io.File;
 import java.io.IOException;
 
+import javax.swing.JOptionPane;
+
 
 /**
  *  This class contains the method makeDocumentEntry (), which creates a
@@ -79,7 +81,18 @@ abstract public class DocumentEntryFactory
         document.getInputStream() instanceof net.sf.samtools.util.BlockCompressedInputStream)
     {
       if(IndexedGFFDocumentEntry.isIndexed( ((File)document.getLocation()) ))
-        return new IndexedGFFDocumentEntry(document);
+      {
+        Object[] possibleValues = { "Use index", "Ignore index" };
+        int sel = JOptionPane.showOptionDialog(null, 
+            "Use the GFF index file (to increase the performance)\n"+ 
+            "or concatenate the sequences together?",
+            "Indexed GFF", 
+            JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
+            null, possibleValues, possibleValues[0]);
+
+        if(sel == 0)
+          return new IndexedGFFDocumentEntry(document);
+      }
     }
     
     final LinePushBackReader document_reader =
diff --git a/uk/ac/sanger/artemis/io/GFF3AttributeAggregator.java b/uk/ac/sanger/artemis/io/GFF3AttributeAggregator.java
new file mode 100644
index 0000000..4a8a737
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/GFF3AttributeAggregator.java
@@ -0,0 +1,38 @@
+/* GFF3AttributeAggregator.java
+ * *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2014 Genome Research Limited
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.StringVector;
+
+/**
+ * Abstraction for processing various Artemis qualifiers into usable GFF3 strings.
+ */
+public interface GFF3AttributeAggregator {
+  /**
+   * Prepare a set of string values as a GFF attribute value.
+   * @param values the value set to convert to a <code>String</code>
+   * @return  the <code>String</code> representation
+   */
+  public abstract String process(StringVector values);
+  
+}
diff --git a/uk/ac/sanger/artemis/io/GFF3AttributeBuilder.java b/uk/ac/sanger/artemis/io/GFF3AttributeBuilder.java
new file mode 100644
index 0000000..b8bba7e
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/GFF3AttributeBuilder.java
@@ -0,0 +1,214 @@
+/* GFF3AttributeBuilder.java
+ * *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2014 Genome Research Limited
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.lang.StringBuilder;
+
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.GFF3AttributeAggregator;
+import uk.ac.sanger.artemis.util.StringVector;
+
+public class GFF3AttributeBuilder {
+  private HashMap<String, String>                  attrs        = new HashMap<String, String>();
+  private HashMap<String, String>                  mappings     = new HashMap<String, String>();
+  private HashMap<String, String>                  glue         = new HashMap<String, String>();
+  private HashSet<String>                          ignores      = new HashSet<String>();
+  private HashMap<String, String>                  clones       = new HashMap<String, String>();
+  private HashMap<String, GFF3AttributeAggregator> aggs         = new HashMap<String, GFF3AttributeAggregator>();
+  private HashSet<String>                          reserved     = new HashSet<String>(
+                                                                    13);
+  private static final GFF3AttributeAggregator defaultAgg = new GFF3AttributeAggregator() {
+    @Override
+    public String process(StringVector values) {
+      StringBuilder buffer = new StringBuilder();
+      if (values != null && values.size() > 0) {
+        for (int value_index = 0; value_index < values.size(); ++value_index) {
+          final String this_value = GFF3Encoder.encode(values.elementAt(value_index));
+          if (value_index > 0 && value_index < (values.size())) {
+            buffer.append(",");
+          }
+          buffer.append(this_value);
+        }
+      }
+      return buffer.toString();
+    }
+  };
+
+  final String  reserved_a[] = { "ID",
+      "Name", "Alias", "Parent", "Derives_from", "Target", "Gap", "Note",
+      "Dbxref", "Ontology_term", "Start_range", "End_range", "Is_circular" };
+
+  public GFF3AttributeBuilder() {
+    for (String s : reserved_a) {
+      reserved.add(s);
+    }
+  }
+
+  public void setMapping(String attr, String mapsto) {
+    /* XXX: make sure only one level of mapping is used */
+    mappings.put(attr, mapsto);
+  }
+
+  public void unsetMapping(String attr, String mapsto) {
+    mappings.remove(attr);
+  }
+
+  public void setGlue(String attr, String glue_str) {
+    glue.put(attr, glue_str);
+  }
+
+  public void unsetGlue(String attr) {
+    glue.remove(attr);
+  }
+
+  public void setClone(String attr, String glue_str) {
+    clones.put(attr, glue_str);
+  }
+
+  public void unsetClone(String attr) {
+    clones.remove(attr);
+  }
+
+  public void setAggregator(String attr, GFF3AttributeAggregator agg) {
+    aggs.put(attr, agg);
+  }
+
+  public void unsetAggregator(String attr, GFF3AttributeAggregator agg) {
+    aggs.remove(attr);
+  }
+
+  public void ignore(String attr) {
+    ignores.add(attr);
+  }
+
+  public void unignore(String attr) {
+    ignores.remove(attr);
+  }
+
+  public void add(String attr, String val) {
+    StringVector v = new StringVector(val);
+    add(attr, v);
+  }
+
+  public void add(String attr, StringVector val) {
+    String origAttr = attr;
+    ArrayList<String> targetAttrs = new ArrayList<String>();
+    // expand attributes
+    if (clones.containsKey(attr))
+      targetAttrs.add(clones.get(attr));
+    if (mappings.containsKey(attr)) {
+      attr = mappings.get(attr);
+      targetAttrs.add(attr);
+      if (clones.containsKey(attr))
+        targetAttrs.add(clones.get(attr));
+    } else {
+      targetAttrs.add(attr);
+    }
+    // drop attributes with null or empty values
+    if (val == null || (val.size() == 1
+        && val.elementAt(0).replaceAll("\\s+", "").equals("")) )
+      return;
+    // process expanded list of attributes
+    for (String this_attr : targetAttrs) {
+      String aggregatedVal;
+      // do we have an aggregator for this type?
+      if (aggs.containsKey(origAttr)) {
+        GFF3AttributeAggregator agg = aggs.get(origAttr);
+        aggregatedVal = agg.process(val);
+      } else if (aggs.containsKey(this_attr)) {
+        GFF3AttributeAggregator agg = aggs.get(this_attr);
+        aggregatedVal = agg.process(val);
+      } else {
+        aggregatedVal = defaultAgg.process(val);
+      }
+      // do not add empty values
+      if (aggregatedVal == null)
+        return;
+      // append or set?
+      if (attrs.containsKey(this_attr)) {
+        String this_val = attrs.get(this_attr),
+            this_glue = " ";
+        if (glue.containsKey(this_attr))
+          this_glue = glue.get(this_attr);
+        this_val = this_val + this_glue + aggregatedVal;
+        attrs.put(this_attr, this_val);
+      } else {
+        attrs.put(this_attr, aggregatedVal);
+      }
+    }
+  }
+
+  private String decapitalize(String line) {
+    if (!reserved.contains(line)
+        && Character.toUpperCase(line.charAt(0)) == line.charAt(0)) {
+      return Character.toLowerCase(line.charAt(0)) + line.substring(1);
+    } else {
+      return line;
+    }
+  }
+
+  public String get(String attr) throws EntryInformationException {
+    if (mappings.containsKey(attr)) {
+      attr = mappings.get(attr);
+    }
+    if (attrs.containsKey(attr)) {
+      return attrs.get(attr);
+    } else {
+      throw new EntryInformationException("empty attribute value for " + attr);
+    }
+  }
+
+  private Comparator<String> comparator = new Comparator<String>() {
+    // make sure 'ID' is always at the beginning of the attribute list
+    public int compare(String o1, String o2) {
+      if (o1.equals("ID"))
+        return -1;
+      if (o2.equals("ID"))
+        return 1;
+      return o1.compareTo(o2);
+    }
+  };
+
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    int i = 0;
+    ArrayList<String> sortedAttrs = new ArrayList<String>(attrs.keySet());
+    Collections.sort(sortedAttrs, comparator);
+    for (String key : sortedAttrs) {
+      if (!ignores.contains(key)) {
+        String value = attrs.get(key);
+        if (value.length() > 0) {
+          if (i++ != 0)
+            b.append(";");
+          b.append(decapitalize(key) + "=" + value);
+        }
+      }
+    }
+    return b.toString();
+  }
+}
diff --git a/uk/ac/sanger/artemis/io/GFF3Encoder.java b/uk/ac/sanger/artemis/io/GFF3Encoder.java
new file mode 100644
index 0000000..49f5507
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/GFF3Encoder.java
@@ -0,0 +1,38 @@
+/* GFF3Encoder.java
+ * *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2014 Genome Research Limited
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import org.apache.commons.lang.StringUtils;
+
+public class GFF3Encoder {
+  public static final String mapChars[] = { "%", "&", ",", ";", "=", "\t", "\n", "\r" };
+  public static final String mappedEscapes[] = { "%25", "%26", "%2C", "%3B", "%3D", "%09", "%0A", "%0D" };
+
+  public static String decode(String s) {
+    return StringUtils.replaceEach(s, mappedEscapes, mapChars);
+  }
+
+  public static String encode(String s) {
+    return StringUtils.replaceEach(s, mapChars, mappedEscapes);
+  }
+}
diff --git a/uk/ac/sanger/artemis/io/GFFStreamFeature.java b/uk/ac/sanger/artemis/io/GFFStreamFeature.java
index 8e68ac3..7478608 100644
--- a/uk/ac/sanger/artemis/io/GFFStreamFeature.java
+++ b/uk/ac/sanger/artemis/io/GFFStreamFeature.java
@@ -25,11 +25,12 @@
 
 package uk.ac.sanger.artemis.io;
 
-
 import java.util.Hashtable;
 import java.util.HashSet;
 import java.util.Enumeration;
 import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.Set;
 import java.util.StringTokenizer;
 import java.util.Vector;
@@ -47,330 +48,273 @@ import uk.ac.sanger.artemis.util.LinePushBackReader;
 import uk.ac.sanger.artemis.util.OutOfRangeException;
 import uk.ac.sanger.artemis.util.ReadOnlyException;
 import uk.ac.sanger.artemis.util.StringVector;
-
+import uk.ac.sanger.artemis.io.GFF3Encoder;
 
 /**
- *  A StreamFeature that thinks it is a GFF feature.
- *  @author Kim Rutherford
+ * A StreamFeature that thinks it is a GFF feature.
+ *
+ * @author Kim Rutherford
  **/
-public class GFFStreamFeature extends SimpleDocumentFeature
-                       implements DocumentFeature, StreamFeature, ComparableFeature
-{
+public class GFFStreamFeature extends SimpleDocumentFeature implements
+    DocumentFeature, StreamFeature, ComparableFeature {
 
-  private static org.apache.log4j.Logger logger4j =
-    org.apache.log4j.Logger.getLogger(GFFStreamFeature.class);
+  private static org.apache.log4j.Logger    logger4j        = org.apache.log4j.Logger
+                                                                .getLogger(GFFStreamFeature.class);
 
   /** store for spliced features containing id and range of each segment */
-  private Hashtable<String, Range> id_range_store;
+  private Hashtable<String, Range>          id_range_store;
 
   /** store a record of the new and old uniquenames that have been changed */
-  private Hashtable<String, String> newIdMapToOldId;
+  private Hashtable<String, String>         newIdMapToOldId;
 
   /** store the Timestamp for the feature */
-  private Timestamp timelastmodified;
+  private Timestamp                         timelastmodified;
 
-  private ChadoCanonicalGene chadoGene;
+  private ChadoCanonicalGene                chadoGene;
 
-  private boolean visible = true;
+  private boolean                           visible         = true;
 
   /** combined feature_relationship.rank store for exons */
-  private Hashtable<String, Integer> feature_relationship_rank_store;
+  private Hashtable<String, Integer>        feature_relationship_rank_store;
 
-  /** first tabbed parameter  */
-  private String gffSeqName;
+  /** first tabbed parameter */
+  private String                            gffSeqName;
   /** second tabbed parameter */
-  private String gffSource;
+  private String                            gffSource;
   /** duplication count */
-  private short duplicate = 0;
+  private short                             duplicate       = 0;
 
   protected static Hashtable<String, Range> contig_ranges;
-  private boolean lazyLoaded = false;
-  private org.gmod.schema.sequence.Feature chadoLazyFeature;
-  private boolean readOnlyFeature = false;
-
-
-  private static String MAP_DECODE[][] = {
-    { " ",  "%20" },  // white space
-    { ",",  "%2C" },  // comma
-    { ";",  "%3B" },  // semi-colon
-    { "=",  "%3D" },  // equals
-    { "\t", "%09" },  // tab
-    { " ",  "+"   },  // white space
-    { "+",  "%2B" },
-    { "(",  "%28" },  // left bracket
-    { ")",  "%29" },  // right bracket
-    { "'", "\"" }
-  };
-
-  private static String MAP_ENCODE[][] = {
-//  { " ",  "%20" },  // white space
-    { ",",  "%2C" },  // comma
-    { ";",  "%3B" },  // semi-colon
-    { "=",  "%3D" },  // equals
-    { "\t", "%09" },  // tab
-    { "+",  "%2B" },
-    { " ",  "+"   },  // white space
-    { "(",  "%28" },  // left bracket
-    { ")",  "%29" },  // right bracket
-    { "\n", "%5C" }   // new-line
-  };
-
-  private static Set<String> attrs_to_filter = new HashSet<String>();
+  private boolean                           lazyLoaded      = false;
+  private org.gmod.schema.sequence.Feature  chadoLazyFeature;
+  private boolean                           readOnlyFeature = false;
+
+  private static Set<String>                attrs_to_filter = new HashSet<String>();
 
   /**
-   *  Registers an attribute not to be included in the GFF3 output for
-   *  GFFStreamFeatures
-   *  @param attr The GFF3 attribute to remove
+   * Registers an attribute not to be included in the GFF3 output for
+   * GFFStreamFeatures
+   *
+   * @param attr
+   *          The GFF3 attribute to remove
    **/
-  public static void removeAttribute(String attr)
-  {
+  public static void removeAttribute(String attr) {
     attrs_to_filter.add(attr);
   }
 
   /**
-   *  Registers an attribute to be included in the GFF3 output for
-   *  GFFStreamFeatures
-   *  @param attr The GFF3 attribute to include
+   * Registers an attribute to be included in the GFF3 output for
+   * GFFStreamFeatures
+   *
+   * @param attr
+   *          The GFF3 attribute to include
    **/
-  public static void includeAttribute(String attr)
-  {
+  public static void includeAttribute(String attr) {
     attrs_to_filter.remove(attr);
   }
 
   /**
-   *  Create a new GFFStreamFeature object.  The feature should be added
-   *  to an Entry (with Entry.add()).
-   *  @param key The new feature key
-   *  @param location The Location object for the new feature
-   *  @param qualifiers The qualifiers for the new feature
+   * Create a new GFFStreamFeature object. The feature should be added to an
+   * Entry (with Entry.add()).
+   *
+   * @param key
+   *          The new feature key
+   * @param location
+   *          The Location object for the new feature
+   * @param qualifiers
+   *          The qualifiers for the new feature
    **/
   public GFFStreamFeature(final Key key, final Location location,
-                          final QualifierVector qualifiers)
-  {
+      final QualifierVector qualifiers) {
     super(null);
 
-    try
-    {
+    try {
       setKey(key);
       setLocation(location);
       setQualifiers(qualifiers);
 
-      if(getQualifierByName("ID") == null)
-      {
+      if (getQualifierByName("ID") == null) {
         String idStr = null;
         StringVector v = Options.getOptions().getSystematicQualifierNames();
-        for(int i=0; i<v.size(); i++)
-        {
-          final String sysName = (String)v.get(i);
-          if(getQualifierByName(sysName) != null)
-          {
-            idStr = (String)getQualifierByName(sysName).getValues().get(0);
+        for (int i = 0; i < v.size(); i++) {
+          final String sysName = (String) v.get(i);
+          if (getQualifierByName(sysName) != null) {
+            idStr = (String) getQualifierByName(sysName).getValues().get(0);
             break;
           }
         }
         // autogenerate ID
-        if(idStr == null)
-          idStr = key.getKeyString()+":"+location.toString();
+        if (idStr == null)
+          idStr = key.getKeyString() + ":" + location.toString();
         setQualifier(new Qualifier("ID", idStr));
       }
 
-    }
-    catch(EntryInformationException e)
-    {
+    } catch (EntryInformationException e) {
       // this should never happen because the feature will not be in an Entry
       throw new Error("internal error - unexpected exception: " + e);
-    }
-    catch(ReadOnlyException e)
-    {
+    } catch (ReadOnlyException e) {
       // this should never happen because the feature will not be in an Entry
       throw new Error("internal error - unexpected exception: " + e);
-    }
-    catch(OutOfRangeException e)
-    {
+    } catch (OutOfRangeException e) {
       // this should never happen because the feature will not be in an Entry
       throw new Error("internal error - unexpected exception: " + e);
     }
   }
 
-  public GFFStreamFeature(final Feature feature)
-  {
+  public GFFStreamFeature(final Feature feature) {
     this(feature, false);
   }
 
   /**
-   *  Create a new GFFStreamFeature with the same key, location and
-   *  qualifiers as the given feature.  The feature should be added to an
-   *  Entry (with Entry.add()).
-   *  @param feature The feature to copy.
+   * Create a new GFFStreamFeature with the same key, location and qualifiers as
+   * the given feature. The feature should be added to an Entry (with
+   * Entry.add()).
+   *
+   * @param feature
+   *          The feature to copy.
    **/
-  public GFFStreamFeature(final Feature feature, final boolean isDuplicatedInChado)
-  {
+  @SuppressWarnings("unchecked")
+  public GFFStreamFeature(final Feature feature,
+      final boolean isDuplicatedInChado) {
     this(feature.getKey(), feature.getLocation(), feature.getQualifiers());
 
-    if(feature instanceof GFFStreamFeature)
-    {
-      if(((GFFStreamFeature)feature).id_range_store != null)
-        this.id_range_store =
-          (Hashtable)(((GFFStreamFeature)feature).id_range_store).clone();
+    if (feature instanceof GFFStreamFeature) {
+      if (((GFFStreamFeature) feature).id_range_store != null)
+        this.id_range_store = (Hashtable<String, Range>) (((GFFStreamFeature) feature).id_range_store)
+            .clone();
 
-      if(((GFFStreamFeature)feature).feature_relationship_rank_store != null)
-        this.feature_relationship_rank_store =
-          (Hashtable)(((GFFStreamFeature)feature).feature_relationship_rank_store).clone();
+      if (((GFFStreamFeature) feature).feature_relationship_rank_store != null)
+        this.feature_relationship_rank_store = (Hashtable<String, Integer>) (((GFFStreamFeature) feature).feature_relationship_rank_store)
+            .clone();
 
-      this.setGffSeqName(((GFFStreamFeature)feature).getGffSeqName());
-      this.setGffSource(((GFFStreamFeature)feature).getGffSource());
+      this.setGffSeqName(((GFFStreamFeature) feature).getGffSeqName());
+      this.setGffSource(((GFFStreamFeature) feature).getGffSource());
 
-      if(isDuplicatedInChado)
-      {
-        try
-        {
+      if (isDuplicatedInChado) {
+        try {
           final String uniquename;
           final String duplicatePrefix;
 
-          if(feature instanceof GFFStreamFeature)
-          {
-            ((GFFStreamFeature)feature).duplicate++;
-            duplicatePrefix = "DUP"+Short.toString(((GFFStreamFeature)feature).duplicate)+"-";
-          }
-          else
+          if (feature instanceof GFFStreamFeature) {
+            ((GFFStreamFeature) feature).duplicate++;
+            duplicatePrefix = "DUP"
+                + Short.toString(((GFFStreamFeature) feature).duplicate) + "-";
+          } else
             duplicatePrefix = "DUP";
-          if(id_range_store != null)
-          {
-            final Hashtable<String, Range> new_id_range_store = new Hashtable<String, Range>(id_range_store.size());
+          if (id_range_store != null) {
+            final Hashtable<String, Range> new_id_range_store = new Hashtable<String, Range>(
+                id_range_store.size());
             final Enumeration<String> enumIdRangeStore = id_range_store.keys();
-            while(enumIdRangeStore.hasMoreElements())
-            {
+            while (enumIdRangeStore.hasMoreElements()) {
               final String keyId = enumIdRangeStore.nextElement();
-              final Range range  = id_range_store.get(keyId);
-              new_id_range_store.put(duplicatePrefix+keyId, range);
+              final Range range = id_range_store.get(keyId);
+              new_id_range_store.put(duplicatePrefix + keyId, range);
             }
             id_range_store.clear();
-            this.id_range_store = (Hashtable) new_id_range_store.clone();
+            this.id_range_store = (Hashtable<String, Range>) new_id_range_store
+                .clone();
 
-
-            if(getLocation().getRanges().size() > 1)
+            if (getLocation().getRanges().size() > 1)
               uniquename = getSegmentID(getLocation().getRanges());
-            else
-            {
-              if( ((String)getQualifierByName("ID").getValues().get(0)).endsWith("}") )
+            else {
+              if (((String) getQualifierByName("ID").getValues().get(0))
+                  .endsWith("}"))
                 uniquename = id_range_store.keys().nextElement();
               else
-                uniquename = duplicatePrefix+ (String)getQualifierByName("ID").getValues().get(0);
+                uniquename = duplicatePrefix
+                    + (String) getQualifierByName("ID").getValues().get(0);
             }
-          }
-          else
-            uniquename = duplicatePrefix+ (String)getQualifierByName("ID").getValues().get(0);
+          } else
+            uniquename = duplicatePrefix
+                + (String) getQualifierByName("ID").getValues().get(0);
           setQualifier(new Qualifier("ID", uniquename));
 
-          if(getQualifierByName("Parent") != null)
-          {
-            final String parent =
-              (String) getQualifierByName("Parent").getValues().get(0);
-            setQualifier(new Qualifier("Parent", duplicatePrefix+parent));
+          if (getQualifierByName("Parent") != null) {
+            final String parent = (String) getQualifierByName("Parent")
+                .getValues().get(0);
+            setQualifier(new Qualifier("Parent", duplicatePrefix + parent));
           }
 
-          if(getQualifierByName("Derives_from") != null)
-          {
-            final String derives_from =
-              (String) getQualifierByName("Derives_from").getValues().get(0);
-            setQualifier(new Qualifier("Derives_from", duplicatePrefix+derives_from));
+          if (getQualifierByName("Derives_from") != null) {
+            final String derives_from = (String) getQualifierByName(
+                "Derives_from").getValues().get(0);
+            setQualifier(new Qualifier("Derives_from", duplicatePrefix
+                + derives_from));
           }
 
           // remove qualifiers that don't get transferred to duplicate
-          final String removeQualifierNames[] =
-            {  "feature_id",
-          		 "timelastmodified",
-          		 "feature_relationship_rank",
-          		 ProteinMapPanel.POLYPEPTIDE_DOMAIN,
-          		 ProteinMapPanel.TMHMM[0],
-          		 ProteinMapPanel.TMHMM[1],
-          		 ProteinMapPanel.TMHMM[2],
-          		 ProteinMapPanel.TMHMM[3],
-          		 MatchPanel.ORTHOLOG,
-          		 MatchPanel.ORTHOLOG
-          	};
-
-          for(int i=0;i<removeQualifierNames.length; i++)
+          final String removeQualifierNames[] = { "feature_id",
+              "timelastmodified", "feature_relationship_rank",
+              ProteinMapPanel.POLYPEPTIDE_DOMAIN, ProteinMapPanel.TMHMM[0],
+              ProteinMapPanel.TMHMM[1], ProteinMapPanel.TMHMM[2],
+              ProteinMapPanel.TMHMM[3], MatchPanel.ORTHOLOG,
+              MatchPanel.ORTHOLOG };
+
+          for (int i = 0; i < removeQualifierNames.length; i++)
             removeQualifierByName(removeQualifierNames[i]);
+        } catch (ReadOnlyException e) {
+        } catch (EntryInformationException e) {
         }
-        catch(ReadOnlyException e){}
-        catch(EntryInformationException e){}
-      }
-      else
-      {
-        chadoGene = ((GFFStreamFeature)feature).chadoGene;
+      } else {
+        chadoGene = ((GFFStreamFeature) feature).chadoGene;
       }
     }
   }
 
   /**
-   *  Create a new GFFStreamFeature from the given line.  The String should be
-   *  in gene finder format.
+   * Create a new GFFStreamFeature from the given line. The String should be in
+   * gene finder format.
    **/
-  public GFFStreamFeature(final String line)
-      throws ReadFormatException
-  {
+  public GFFStreamFeature(final String line) throws ReadFormatException {
     super(null);
 
     final StringVector line_bits = StringVector.getStrings(line, "\t", true);
-    if(line_bits.size() < 8)
-      throw new ReadFormatException("invalid GFF line: 8 fields needed " +
-                                    "(got " + line_bits.size () +
-                                    " fields) from: " + line);
+    if (line_bits.size() < 8)
+      throw new ReadFormatException("invalid GFF line: 8 fields needed "
+          + "(got " + line_bits.size() + " fields) from: " + line);
 
     final String start_base_str = line_bits.elementAt(3).trim();
-    final String end_base_str   = line_bits.elementAt(4).trim();
+    final String end_base_str = line_bits.elementAt(4).trim();
 
     final int start_base;
     final int end_base;
-    try
-    {
+    try {
       start_base = Integer.parseInt(start_base_str);
-      end_base   = Integer.parseInt(end_base_str);
-    }
-    catch(NumberFormatException e)
-    {
-      throw new ReadFormatException("Could not understand the start or end base " +
-                                    "of a GFF feature: " + start_base_str +
-                                    " " + end_base_str);
+      end_base = Integer.parseInt(end_base_str);
+    } catch (NumberFormatException e) {
+      throw new ReadFormatException(
+          "Could not understand the start or end base " + "of a GFF feature: "
+              + start_base_str + " " + end_base_str);
     }
 
     // start of qualifier parsing and setting
-    try
-    {
+    try {
       final boolean complement_flag;
-      if(line_bits.elementAt(6).equals("+"))
+      if (line_bits.elementAt(6).equals("+"))
         complement_flag = false;
-      else if(line_bits.elementAt(6).equals("-"))
+      else if (line_bits.elementAt(6).equals("-"))
         complement_flag = true;
-      else
-      {
+      else {
         // must be unstranded
         complement_flag = false;
       }
 
-      if(line_bits.size() == 9)
-      {
+      if (line_bits.size() == 9) {
         final String rest_of_line = line_bits.elementAt(8);
         final Hashtable<String, StringVector> attributes = parseAttributes(rest_of_line);
-        for(final Enumeration<String> attribute_enum = attributes.keys();
-            attribute_enum.hasMoreElements();)
-        {
+        for (final Enumeration<String> attribute_enum = attributes.keys(); attribute_enum
+            .hasMoreElements();) {
           String name = attribute_enum.nextElement();
           final StringVector values = attributes.get(name);
 
-          if(MatchPanel.isClusterTag(name))
-          {
+          if (MatchPanel.isClusterTag(name)) {
             List<ClusterLazyQualifierValue> lazyValues = new Vector<ClusterLazyQualifierValue>();
-            for(int i=0; i<values.size(); i++)
-              lazyValues.add(
-                  new ClusterLazyQualifierValue( (String)values.get(i), name,
-                                         this ));
+            for (int i = 0; i < values.size(); i++)
+              lazyValues.add(new ClusterLazyQualifierValue((String) values
+                  .get(i), name, this));
             setQualifier(new QualifierLazyLoading(name, lazyValues));
-          }
-          else
-          {
-            if(values.size() == 0)
+          } else {
+            if (values.size() == 0)
               setQualifier(new Qualifier(name));
             else
               setQualifier(new Qualifier(name, values));
@@ -378,200 +322,175 @@ public class GFFStreamFeature extends SimpleDocumentFeature
         }
       }
 
-      if( !line_bits.elementAt(0).equals("null") )
-        setGffSeqName( decode(line_bits.elementAt(0)) );
+      if (!line_bits.elementAt(0).equals("null"))
+        setGffSeqName(GFF3Encoder.decode(line_bits.elementAt(0)));
 
       setKey(new Key(line_bits.elementAt(2)));
       setGffSource(line_bits.elementAt(1));
 
-      if( !line_bits.elementAt(5).equals(".") )
-      {
-        final Qualifier score_qualifier =
-          new Qualifier("score", line_bits.elementAt(5));
+      if (!line_bits.elementAt(5).equals(".")) {
+        final Qualifier score_qualifier = new Qualifier("score",
+            line_bits.elementAt(5));
         setQualifier(score_qualifier);
       }
 
       String frame = line_bits.elementAt(7);
 
-      if(frame.equals ("0"))
+      if (frame.equals("0"))
         frame = "1";
-      else if(frame.equals("1"))
+      else if (frame.equals("1"))
         frame = "2";
-      else if(frame.equals("2"))
+      else if (frame.equals("2"))
         frame = "3";
       else
         frame = ".";
 
-      if(!frame.equals("."))
-      {
-        final Qualifier codon_start_qualifier =
-          new Qualifier("codon_start", frame);
+      if (!frame.equals(".")) {
+        final Qualifier codon_start_qualifier = new Qualifier("codon_start",
+            frame);
 
         setQualifier(codon_start_qualifier);
       }
 
-      if(start_base > end_base)
-        throw new ReadFormatException("start position is greater than end " +
-                                      "position: " + start_base + " > " +
-                                      end_base+"\n"+line);
+      if (start_base > end_base)
+        throw new ReadFormatException("start position is greater than end "
+            + "position: " + start_base + " > " + end_base + "\n" + line);
 
-      if(start_base < 0)
-        throw new ReadFormatException("start position must be positive: " +
-                                      start_base);
+      if (start_base < 0)
+        throw new ReadFormatException("start position must be positive: "
+            + start_base);
 
       final Range location_range = new Range(start_base, end_base);
       final RangeVector location_ranges = new RangeVector(location_range);
       setLocation(new Location(location_ranges, complement_flag));
-    }
-    catch(ReadOnlyException e)
-    {
+    } catch (ReadOnlyException e) {
       throw new Error("internal error - unexpected exception: " + e);
-    }
-    catch(EntryInformationException e)
-    {
+    } catch (EntryInformationException e) {
       throw new Error("internal error - unexpected exception: " + e);
-    }
-    catch(OutOfRangeException e)
-    {
+    } catch (OutOfRangeException e) {
       throw new Error("internal error - unexpected exception: " + e);
     }
 
-    //this.gff_lines = new StringVector(line);
+    // this.gff_lines = new StringVector(line);
   }
 
   /**
-  *
-  * Store for spliced regions of segments ID's and ranges.
-  *
-  */
-  public void setSegmentRangeStore(Hashtable<String, Range> id_range_store)
-  {
+   *
+   * Store for spliced regions of segments ID's and ranges.
+   *
+   */
+  public void setSegmentRangeStore(Hashtable<String, Range> id_range_store) {
     this.id_range_store = id_range_store;
   }
 
-  public Hashtable<String, Range> getSegmentRangeStore()
-  {
-    if(id_range_store == null)
-    {
+  public Hashtable<String, Range> getSegmentRangeStore() {
+    if (id_range_store == null) {
       id_range_store = new Hashtable<String, Range>();
-      id_range_store.put((String)this.getQualifierByName("ID").getValues().get(0),
-                         this.getLocation().getTotalRange());
+      id_range_store.put((String) this.getQualifierByName("ID").getValues()
+          .get(0), this.getLocation().getTotalRange());
     }
     return id_range_store;
   }
 
-  public Hashtable<String, String> getNewIdMapToOldId()
-  {
+  public Hashtable<String, String> getNewIdMapToOldId() {
     return newIdMapToOldId;
   }
 
   /**
    * Used when changing spliced feature uniquenames
+   *
    * @param newIdMapToOldId
    */
-  public void setNewIdMapToOldId(Hashtable<String, String> newIdMapToOldId)
-  {
+  public void setNewIdMapToOldId(Hashtable<String, String> newIdMapToOldId) {
     this.newIdMapToOldId = newIdMapToOldId;
   }
 
   /**
    * Store for ID's and CHADO feature_relationship.rank
+   *
    * @param feature_relationship_rank_store
    */
   public void setFeature_relationship_rank_store(
-      Hashtable<String, Integer> feature_relationship_rank_store)
-  {
+      Hashtable<String, Integer> feature_relationship_rank_store) {
     this.feature_relationship_rank_store = feature_relationship_rank_store;
   }
 
   /**
    * Store for ID's and CHADO feature_relationship.rank
+   *
    * @return
    */
-  public Hashtable<String, Integer> getFeature_relationship_rank_store()
-  {
+  public Hashtable<String, Integer> getFeature_relationship_rank_store() {
     return feature_relationship_rank_store;
   }
 
-
   /**
    * Get the chado uniquename
+   *
    * @param r
    * @return
    */
-  public String getSegmentID(final Range r)
-  {
-    if(id_range_store != null)
-    {
+  public String getSegmentID(final Range r) {
+    if ( id_range_store != null &&
+         getKey().getKeyString().indexOf("gene") == -1 &&
+         getKey().getKeyString().indexOf("RNA")  == -1 ) {
       int offset = 0;
-      if(getGffSeqName() != null && contig_ranges != null &&
-         contig_ranges.containsKey(getGffSeqName()))
-      {
+      if (getGffSeqName() != null && contig_ranges != null
+          && contig_ranges.containsKey(getGffSeqName())) {
         // adjust for coordinates in multi-sequence GFF
         Range offset_range = contig_ranges.get(getGffSeqName());
-        offset = offset_range.getStart()-1;
+        offset = offset_range.getStart() - 1;
       }
 
       Enumeration<String> enum_ranges = id_range_store.keys();
-      while(enum_ranges.hasMoreElements())
-      {
-        String key  = enum_ranges.nextElement();
+      while (enum_ranges.hasMoreElements()) {
+        String key = enum_ranges.nextElement();
         Range range = id_range_store.get(key);
-        if(range.getStart() == r.getStart()-offset &&
-           range.getEnd()   == r.getEnd()-offset)
+        if (range.getStart() == r.getStart() - offset
+            && range.getEnd() == r.getEnd() - offset)
           return key;
       }
-    }
-    else if (getQualifierByName("ID") != null)
-    {
-      return (String)getQualifierByName("ID").getValues().get(0);
+    } else if (getQualifierByName("ID") != null) {
+      return (String) getQualifierByName("ID").getValues().get(0);
     }
 
-    logger4j.warn("RANGE NOT FOUND "+r.toString());
+    logger4j.warn("RANGE NOT FOUND " + r.toString());
 
     return null;
   }
 
   /**
-   * Get the feature ID based on the segments chado
-   * uniquename's.
+   * Get the feature ID based on the segments chado uniquename's.
+   *
    * @param rv
    * @return
    */
-  public String getSegmentID(final RangeVector rv)
-  {
+  public String getSegmentID(final RangeVector rv) {
     String id = "";
-    if(id_range_store != null)
-    {
+    if (id_range_store != null) {
       String id_new;
       Range range;
       int index;
-      for(int i=0; i<rv.size(); i++)
-      {
-        range  = (Range)rv.get(i);
+      for (int i = 0; i < rv.size(); i++) {
+        range = (Range) rv.get(i);
         id_new = getSegmentID(range);
 
         String prefix[] = getPrefix(id_new, ':');
-        if(prefix[0] != null)
-        {
+        if (prefix[0] != null) {
           index = id.indexOf(prefix[0]);
-          if(id.equals("") || index < 0)
-          {
-            if(!id.equals(""))
-              id = id +",";
-            id = id+prefix[0] + "{" + prefix[1] + "}";
+          if (id.equals("") || index < 0) {
+            if (!id.equals(""))
+              id = id + ",";
+            id = id + prefix[0] + "{" + prefix[1] + "}";
             continue;
           }
 
           index = id.indexOf('}', index);
-          id = id.substring(0,index) + "," +
-               prefix[1] + id.substring(index);
-        }
-        else if(id_new != null)
-        {
-          if(!id.equals(""))
-            id = id +",";
-          id = id+id_new;
+          id = id.substring(0, index) + "," + prefix[1] + id.substring(index);
+        } else if (id_new != null) {
+          if (!id.equals(""))
+            id = id + ",";
+          id = id + id_new;
         }
       }
     }
@@ -580,204 +499,149 @@ public class GFFStreamFeature extends SimpleDocumentFeature
   }
 
   /**
-   * Get the ID prefix, e.g. for SPAC1556.06.1:exon:2
-   * returns SPAC1556.06.1:exon as the prefix and 2 as the
-   * index.
+   * Get the ID prefix, e.g. for SPAC1556.06.1:exon:2 returns SPAC1556.06.1:exon
+   * as the prefix and 2 as the index.
+   *
    * @param id
    * @return
    */
-  public String[] getPrefix(final String id,
-                            final char separator)
-  {
+  public String[] getPrefix(final String id, final char separator) {
     String prefix[] = new String[2];
     int index = id.lastIndexOf(separator);
 
-    if(index > -1)
-    {
-      prefix[0] = id.substring(0,index);
-      prefix[1] = id.substring(index+1);
+    if (index > -1) {
+      prefix[0] = id.substring(0, index);
+      prefix[1] = id.substring(index + 1);
     }
     return prefix;
   }
 
   /**
    * Used to automatically generate
+   *
    * @param prefix
    * @return
    */
-  public int getAutoNumber(final String prefix,
-                           final char separator)
-  {
-    int auto   = 1;
+  public int getAutoNumber(final String prefix, final char separator) {
+    int auto = 1;
     String val = prefix + separator + auto;
-    while(id_range_store.containsKey(val))
-    {
+    while (id_range_store.containsKey(val)) {
       auto++;
       val = prefix + separator + auto;
     }
     return auto;
   }
 
-
-  /**
-  * For gff-version 3:
-  * http://www.sequenceontology.org/gff3.shtml
-  * Remove URL escaping rule (e.g. space="%20" or "+")
-  */
-  public static String decode(String s)
-  {
-    int ind;
-    String enc;
-    String dec;
-
-    for(int i=0; i<MAP_DECODE.length; i++)
-    {
-      enc = MAP_DECODE[i][1];
-      dec = MAP_DECODE[i][0];
-      while( (ind = s.indexOf(enc)) > -1)
-        s = s.substring(0,ind) + dec + s.substring(ind+enc.length());
-    }
-    return s;
-  }
-
-
   /**
-  * For gff-version 3:
-  * http://www.sequenceontology.org/gff3.shtml
-  * Add URL escaping rule (e.g. space="%20" or "+")
-  */
-  public static String encode(String s)
-  {
-    int ind;
-    String enc;
-    String dec;
-
-    for(int i=0; i<MAP_ENCODE.length; i++)
-    {
-      enc = MAP_ENCODE[i][1];
-      dec = MAP_ENCODE[i][0];
-      while( (ind = s.indexOf(dec)) > -1 )
-        s = s.substring(0,ind) + enc + s.substring(ind+1);
-    }
-    return s;
-  }
-
-
-  /**
-   *  Return the reference of a new copy of this Feature.
+   * Return the reference of a new copy of this Feature.
    **/
-  public Feature copy()
-  {
+  public Feature copy() {
     final Feature return_value = new GFFStreamFeature(this);
     return return_value;
   }
 
   /**
-   *  Read and return a GFFStreamFeature from a stream.  A feature must be the
-   *  next thing in the stream.
-   *  @param stream the Feature is read from this stream
-   *  @exception IOException thrown if there is a problem reading the Feature -
-   *    most likely ReadFormatException.
-   *  @exception InvalidRelationException Thrown if this Feature cannot contain
-   *    the given Qualifier.
-   *  @return null if in_stream is at the end of file when the method is
-   *    called
+   * Read and return a GFFStreamFeature from a stream. A feature must be the
+   * next thing in the stream.
+   *
+   * @param stream
+   *          the Feature is read from this stream
+   * @exception IOException
+   *              thrown if there is a problem reading the Feature - most likely
+   *              ReadFormatException.
+   * @exception InvalidRelationException
+   *              Thrown if this Feature cannot contain the given Qualifier.
+   * @return null if in_stream is at the end of file when the method is called
    */
   protected static GFFStreamFeature readFromStream(LinePushBackReader stream)
-      throws IOException, InvalidRelationException
-  {
+      throws IOException, InvalidRelationException {
     final String line = stream.readLine();
-    if(line == null)
+    if (line == null)
       return null;
 
-    try
-    {
+    try {
       return new GFFStreamFeature(line);
-    }
-    catch(ReadFormatException exception)
-    {
+    } catch (ReadFormatException exception) {
       // re-throw the exception with the line number added
       final String new_error_string = exception.getMessage();
 
-      throw new ReadFormatException(new_error_string,
-                                    stream.getLineNumber());
+      throw new ReadFormatException(new_error_string, stream.getLineNumber());
     }
   }
 
   /**
-   *  Read the details of a feature from an EMBL stream into the current
-   *  object.
-   *  @param entry_information The EntryInformation object of the Entry that
-   *    will contain the Feature.
-   *  @param in_stream the Feature is read from this stream
-   *  @exception IOException thrown if there is a problem reading the Feature -
-   *    most likely ReadFormatException if the stream does not contain GFF
-   *    feature.
+   * Read the details of a feature from an EMBL stream into the current object.
+   *
+   * @param entry_information
+   *          The EntryInformation object of the Entry that will contain the
+   *          Feature.
+   * @param in_stream
+   *          the Feature is read from this stream
+   * @exception IOException
+   *              thrown if there is a problem reading the Feature - most likely
+   *              ReadFormatException if the stream does not contain GFF
+   *              feature.
    **/
   public void setFromStream(final EntryInformation entry_information,
-                            final LinePushBackReader in_stream)
-      throws IOException, InvalidRelationException, ReadOnlyException
-  {
+      final LinePushBackReader in_stream) throws IOException,
+      InvalidRelationException, ReadOnlyException {
     throw new ReadOnlyException();
   }
 
   /**
-   *  Write this Feature to the given stream.
-   *  @param writer The stream to write to.
-   *  @exception IOException thrown if there is an io problem while writing
-   *    the Feature.
+   * Write this Feature to the given stream.
+   *
+   * @param writer
+   *          The stream to write to.
+   * @exception IOException
+   *              thrown if there is an io problem while writing the Feature.
    **/
-  public void writeToStream(final Writer writer)
-      throws IOException
-  {
+  public void writeToStream(final Writer writer) throws IOException {
     final RangeVector ranges = getLocation().getRanges();
     final int ranges_size = ranges.size();
 
-//  final Hashtable contig_ranges = SimpleDocumentEntry.getContigRanges();
-    for(int i = 0; i < ranges_size; ++i)
-    {
-      Range this_range = (Range)ranges.elementAt(i);
+    // final Hashtable contig_ranges = SimpleDocumentEntry.getContigRanges();
+    for (int i = 0; i < ranges_size; ++i) {
+      Range this_range = (Range) ranges.elementAt(i);
 
       String seqname = getGffSeqName();
-      String source  = getGffSource();
-      Qualifier score   = getQualifierByName("score");
-      Qualifier group   = getQualifierByName("group");
+      String source = getGffSource();
+      Qualifier score = getQualifierByName("score");
+      Qualifier group = getQualifierByName("group");
 
       // source becomes a Dbxref in chado
       String source_str = null;
-      if(getQualifierByName("Dbxref") != null)
-      {
+      if (getQualifierByName("Dbxref") != null) {
         source_str = getDbxrefGFFSource(getQualifierByName("Dbxref"));
       }
 
       int start = this_range.getStart();
-      int end   = this_range.getEnd();
+      int end = this_range.getEnd();
 
-      if(seqname == null && ((GFFDocumentEntry)getEntry()).getDocument() != null)
-        seqname = ((GFFDocumentEntry)getEntry()).getDocument().getName();
-      if(seqname == null)
+      if (seqname == null
+          && ((GFFDocumentEntry) getEntry()).getDocument() != null)
+        seqname = ((GFFDocumentEntry) getEntry()).getDocument().getName();
+      if (seqname == null)
         seqname = deriveSeqName(start);
 
-      if(source == null)
+      if (source == null)
         source = "artemis";
 
-      if(score == null)
+      if (score == null)
         score = new Qualifier("score", ".");
 
-      if(seqname != null && contig_ranges != null &&
-         contig_ranges.containsKey(seqname))
-      {
+      if (seqname != null && contig_ranges != null
+          && contig_ranges.containsKey(seqname)) {
         Range offset_range = contig_ranges.get(seqname);
-        start = start-offset_range.getStart()+1;
-        end   = end-offset_range.getStart()+1;
+        start = start - offset_range.getStart() + 1;
+        end = end - offset_range.getStart() + 1;
       }
 
-      if(group == null || group.getValues() == null ||
-         group.getValues().elementAt(0).equals(""))
-      {
+      if (group == null || group.getValues() == null
+          || group.getValues().elementAt(0).equals("")) {
         final Qualifier gene = getQualifierByName("gene");
 
-        if(gene == null)
+        if (gene == null)
           group = new Qualifier("group", "");
         else
           group = gene;
@@ -786,333 +650,402 @@ public class GFFStreamFeature extends SimpleDocumentFeature
       String frame = ".";
       final Qualifier codon_start = getQualifierByName("codon_start");
 
-      if(codon_start != null)
-      {
-        frame = (String)(codon_start.getValues()).elementAt(0);
+      if (codon_start != null) {
+        frame = (String) (codon_start.getValues()).elementAt(0);
 
-        if(frame.equals ("1"))
+        if (frame.equals("1"))
           frame = "0";
-        else if(frame.equals("2"))
+        else if (frame.equals("2"))
           frame = "1";
-        else if(frame.equals("3"))
+        else if (frame.equals("3"))
           frame = "2";
         else
           frame = ".";
       }
 
       // phase is REQUIRED for all CDS features
-      if(getKey().equals("CDS") && frame.equals("."))
+      if (getKey().equals("CDS") && frame.equals("."))
         frame = "0";
 
       final String myId = getSegmentID(this_range);
       String attribute_string = unParseAttributes(myId);
 
-      if(source_str == null && source != null)
-       source_str = source;
+      if (source_str == null && source != null)
+        source_str = source;
 
       final String translation = getTranslation();
-      if(translation != null)
+      if (translation != null)
         attribute_string = attribute_string + ";" + translation;
-      writer.write(seqname + "\t" +
-                   source_str + "\t" +
-                   getKey().getKeyString() + "\t" +
-                   start + "\t" +
-                   end + "\t" +
-                   score.getValues() .elementAt(0)+ "\t" +
-                   (getLocation().isComplement() ? "-\t" : "+\t") +
-                   frame + "\t" +
-                   attribute_string + "\n");
+      writer.write(seqname + "\t" + source_str + "\t" + getKey().getKeyString()
+          + "\t" + start + "\t" + end + "\t" + score.getValues().elementAt(0)
+          + "\t" + (getLocation().isComplement() ? "-\t" : "+\t") + frame
+          + "\t" + attribute_string + "\n");
     }
   }
 
   /**
-   * If the seqname is not set for this feature try to derive the contig/chromosome
-   * it is located on
+   * If the seqname is not set for this feature try to derive the
+   * contig/chromosome it is located on
+   *
    * @param start
    * @return
    */
-  private String deriveSeqName(int start)
-  {
+  private String deriveSeqName(int start) {
     String seqname = null;
-    if(contig_ranges != null)
-    {
+    if (contig_ranges != null) {
       final Enumeration<String> contigEnum = contig_ranges.keys();
-      while(contigEnum.hasMoreElements())
-      {
+      while (contigEnum.hasMoreElements()) {
         final String key = contigEnum.nextElement();
         final Range r = contig_ranges.get(key);
-        if(r.getStart() > start)
+        if (r.getStart() > start)
           continue;
-        if(r.getEnd() > start)
+        if (r.getEnd() > start)
           return key;
       }
-    }
-    else
-    {
-      try
-      {
-        seqname = ((GFFStreamFeature)(getEntry().getAllFeatures().elementAt(0))).getGffSeqName();
+    } else {
+      try {
+        seqname = ((GFFStreamFeature) (getEntry().getAllFeatures().elementAt(0)))
+            .getGffSeqName();
+      } catch (Exception e) {
       }
-      catch(Exception e) {}
     }
 
-    if(seqname == null)
+    if (seqname == null)
       seqname = "gff_seqname";
     return seqname;
   }
 
   /**
-   *  Return a String containing the qualifiers of this feature in a form
-   *  suitable for using as the last field of a GFF line.  The codon_start
-   *  attribute is not included since GFF has a frame field.  gff_seqname,
-   *  gff_source and score aren't included since they have corresponding
-   *  fields.
+   * Return a String containing the qualifiers of this feature in a form
+   * suitable for using as the last field of a GFF line.
    **/
-  private String unParseAttributes(final String myId)
-  {
-    final StringBuffer buffer = new StringBuffer();
+  private String unParseAttributes(final String myId) {
     final QualifierVector qualifiers = getQualifiers();
+    GFF3AttributeBuilder abuf = new GFF3AttributeBuilder();
+    prepareProcessors(abuf);
 
-    final String names[] = { "ID", "Name", "Alias", "Parent",
-                             "Derives_from",
-                             "Target", "Gap", "Note",
-                             "Dbxref", "Ontology_term",
-                             "Start_range", "End_range",
-                             "Is_circular"};
-    int count = 0;
-    final int names_length = names.length;
-
-    if(myId != null)
-    {
-      buffer.append("ID=");
-      buffer.append(encode(myId));
-      count++;
+    for (String attr : attrs_to_filter) {
+      abuf.ignore(attr);
     }
 
-    for(int i=1; i<names_length; i++)
-    {
-      Qualifier this_qualifier = qualifiers.getQualifierByName(names[i]);
+    final int names_length = abuf.reserved_a.length;
 
-      if(this_qualifier == null)
-        continue;
+    // add ID attribute
+    if (myId != null) {
+     abuf.add("ID", myId);
+    }
+
+    // build reserved attributes
+    for (int i = 1; i < names_length; i++) {
+      Qualifier this_qualifier = qualifiers.getQualifierByName(abuf.reserved_a[i]);
 
-      final String this_qualifier_str = getQualifierString(this_qualifier, true);
-      if(this_qualifier_str == null)
+      if (this_qualifier == null)
         continue;
 
-      if(count != 0)
-        buffer.append(";");
-      buffer.append(this_qualifier_str);
-      count++;
+      abuf.add(this_qualifier.getName(), this_qualifier.getValues());
     }
 
+    // build remaining attributes
     boolean lname;
-    for(Qualifier this_qualifier: qualifiers)
-    {
+    for (Qualifier this_qualifier : qualifiers) {
       lname = false;
-      for(int j=0; j<names_length; j++)
-        if(this_qualifier.getName().equals(names[j]))
-          lname = true;
-
-      if(lname)
-        continue;
-
-      if(attrs_to_filter.contains(this_qualifier.getName()))
-        continue;
 
-      if( (this_qualifier.getName().equals("private") && System.getProperty("noprivate") != null) ||
-          (this_qualifier.getName().equals("history") && System.getProperty("nohistory") != null) )
+      // skip reserved names
+      for (int j = 0; j < names_length; j++)
+        if (this_qualifier.getName().equals(abuf.reserved_a[j]))
+          lname = true;
+      if (lname)
         continue;
 
-      final String this_qualifier_str = getQualifierString(this_qualifier, false);
-
-      if(this_qualifier_str == null)
+      // skip internal qualifiers
+      if ( (this_qualifier.getName().equals("private") &&
+            System.getProperty("noprivate") != null) ||
+           (this_qualifier.getName().equals("history") &&
+            System.getProperty("nohistory") != null) ||
+            this_qualifier.getName().equals("codon_start"))
         continue;
 
-      if(count != 0)
-        buffer.append(";");
-      buffer.append(this_qualifier_str);
+      abuf.add(this_qualifier.getName(), this_qualifier.getValues());
     }
 
-    return buffer.toString();
+    return abuf.toString();
   }
 
-
-  /**
-   * Get the translation qualifier string for polypeptide features.
-   */
-  private String getTranslation()
-  {
-    if (! getKey().getKeyString().equals("polypeptide"))
-        return null;
-    if (chadoGene != null)
-    {
-      if(getUserData() == null)
-        new uk.ac.sanger.artemis.Feature(this);
-      // the above line constructs the appropriate userData within this current GFFStreamFeature object,
-      // which is required by the following GeneUtils.deriveResidues()
-      String residues = GeneUtils.deriveResidues(this);
-      if (residues != null)
-        return "translation="+residues;
+  private static String strJoin(String[] aArr, String sSep) {
+    StringBuilder sbStr = new StringBuilder();
+    for (int i = 0, il = aArr.length; i < il; i++) {
+        if (i > 0)
+            sbStr.append(sSep);
+        sbStr.append(aArr[i]);
     }
-    return null;
+    return sbStr.toString();
   }
 
-  /**
-   * Used to write out the GFF attributes.
-   * @param q the qualifier to represent as a <code>String</code>
-   * @param reserved indicate if this is one of the reserved tags or not
-   * @return  the <code>String</code> representation
-   */
-  private String getQualifierString(Qualifier q, boolean reserved )
-  {
-    StringBuffer buffer = new StringBuffer();
-    final String name = q.getName();
-
-    if(name.equals("codon_start") || name.equals("gff_source") ||
-       name.equals("gff_seqname") || name.equals("score"))
-      return null;
-
-    final StringVector values = q.getValues();
-
-    /* ignore qualifiers with just one empty value, will mess up GFF3 output */
-    if(values != null && values.size() == 1)
-    {
-      if (values.elementAt(0).replaceAll("\\s+","").equals(""))
-        return null;
-    }
-
-    /*
-     * GSV :
-     * The Bio::FeatureIO perl module falls over if there are Uppercased
-     * attribute names for tags which aren't part of the standard reserved
-     * set. So we lowercase these, since in the specification it says :
-     *
-     * "All attributes that begin with an uppercase letter are reserved for
-     * later use.  Attributes that begin with a lowercase letter can be used
-     * freely by applications."
-     * see http://www.sequenceontology.org/gff3.shtml
-     */
-    String nameToBuffer = encode(name);
-
-    if (! reserved)
-    	nameToBuffer = Character.toLowerCase(nameToBuffer.charAt(0)) + nameToBuffer.substring(1);
-    buffer.append(nameToBuffer);
-
-    if(values != null && values.size() > 0)
-    {
-      buffer.append('=');
-      for(int value_index = 0; value_index < values.size();
-          ++value_index)
-      {
-        final String this_value;
-        if(name.equals("class"))
-        {
-          int index = values.elementAt(value_index).indexOf("::");
-          if(index > -1)
-            this_value = encode(values.elementAt(value_index).substring(0,index));
-          else
-            this_value = encode(values.elementAt(value_index));
+  void prepareProcessors(GFF3AttributeBuilder abuf) {
+    GFF3AttributeAggregator productProc = new GFF3AttributeAggregator() {
+      @Override
+      public String process(StringVector values) {
+        StringBuilder buffer = new StringBuilder();
+        if (values != null && values.size() > 0) {
+          for (int value_index = 0; value_index < values.size(); ++value_index) {
+            String this_value = GFF3Encoder.encode(values.elementAt(value_index));
+            if (value_index > 0 && value_index < (values.size())) {
+              buffer.append(",");
+            }
+            buffer.append(this_value);
+          }
         }
-        else
-          this_value = encode(values.elementAt(value_index));
-
-        if(value_index>0)
-          buffer.append("%2C");
-
-        if(name.equals("Parent"))
-          buffer.append(this_value);
-        else
-        {
-          try
-          {
-            buffer.append(Integer.valueOf(this_value));
+        return buffer.toString();
+      }
+    };
+
+    GFF3AttributeAggregator ecProc = new GFF3AttributeAggregator() {
+      @Override
+      public String process(StringVector values) {
+        StringBuilder buffer = new StringBuilder();
+        if (values != null && values.size() > 0) {
+          for (int value_index = 0; value_index < values.size(); ++value_index) {
+            final String this_value = "EC:"
+                + GFF3Encoder.encode(values.elementAt(value_index));
+            if (value_index > 0 && value_index < (values.size())) {
+              buffer.append(",");
+            }
+            buffer.append(this_value);
+          }
+        }
+        return buffer.toString();
+      }
+    };
+
+    GFF3AttributeAggregator psysIDProc = new GFF3AttributeAggregator() {
+      @Override
+      public String process(StringVector values) {
+        StringBuilder buffer = new StringBuilder();
+        if (values != null && values.size() > 0) {
+          for (int value_index = 0; value_index < values.size(); ++value_index) {
+            final String this_value;
+            int index = values.elementAt(value_index).indexOf(";current=");
+            if (index > -1)
+              this_value = GFF3Encoder.encode(values.elementAt(value_index)
+                  .substring(0, index));
+            else
+              this_value = GFF3Encoder.encode(values.elementAt(value_index));
+            if (value_index > 0 && value_index < (values.size())) {
+              buffer.append(",");
+            }
+            buffer.append(this_value);
+          }
+        }
+        return buffer.toString();
+      }
+    };
+
+    GFF3AttributeAggregator classProc = new GFF3AttributeAggregator() {
+      @Override
+      public String process(StringVector values) {
+        StringBuilder buffer = new StringBuilder();
+        if (values != null && values.size() > 0) {
+          for (int value_index = 0; value_index < values.size(); ++value_index) {
+            final String this_value;
+            int index = values.elementAt(value_index).indexOf("::");
+            if (index > -1)
+              this_value = GFF3Encoder.encode(values.elementAt(value_index)
+                  .substring(0, index));
+            else
+              this_value = GFF3Encoder.encode(values.elementAt(value_index));
+            if (value_index > 0 && value_index < (values.size())) {
+              buffer.append(",");
+            }
+            buffer.append(this_value);
           }
-          catch(NumberFormatException _)
-          {
-            // not an integer
-            try
-            {
-              buffer.append(Double.valueOf(this_value));
+        }
+        return buffer.toString();
+      }
+    };
+
+    GFF3AttributeAggregator startEndRangeProc = new GFF3AttributeAggregator() {
+      @Override
+      public String process(StringVector values) {
+        StringBuilder buffer = new StringBuilder();
+         if (values != null && values.size() > 0) {
+          for (int value_index = 0; value_index < values.size(); ++value_index) {
+            if (value_index > 0 && value_index < (values.size())) {
+              buffer.append(",");
             }
-            catch (NumberFormatException __)
-            {
-              // not a double or integer so quote it
-              buffer.append(this_value);
+            buffer.append(values.elementAt(value_index));
+          }
+        }
+        return buffer.toString();
+      }
+    };
+
+    GFF3AttributeAggregator goProc = new GFF3AttributeAggregator() {
+      @Override
+      public String process(StringVector values) {
+        StringBuilder buffer = new StringBuilder();
+        if (values != null && values.size() > 0) {
+          for (int value_index = 0; value_index < values.size(); ++value_index) {
+            int goindex = values.elementAt(value_index).indexOf("GOid=");
+            int termindex = values.elementAt(value_index).indexOf(";term=");
+            if (goindex > -1 && termindex > -1) {
+              buffer.append(GFF3Encoder.encode(values.elementAt(value_index)
+                  .substring(goindex + 5, termindex)));
+              if (value_index < (values.size()) - 1)
+                buffer.append(",");
             }
           }
         }
+        return buffer.toString();
       }
+    };
+
+    GFF3AttributeAggregator ucProc = new GFF3AttributeAggregator() {
+      @Override
+      public String process(StringVector values) {
+        StringBuilder buffer = new StringBuilder();
+        Set<String> set = new HashSet<String>();
+        if (values != null && values.size() > 0) {
+          for (int value_index = 0; value_index < values.size(); ++value_index) {
+            String regex = "eupathdb_uc[:=]\"?([a-zA-Z0-9]+)";
+            Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
+            Matcher matcher = pattern.matcher(values.elementAt(value_index));
+            while (matcher.find()) {
+              set.add(GFF3Encoder.encode(matcher.group(1)));
+            }
+          }
+        }
+        return strJoin(set.toArray(new String[set.size()]), ",");
+      }
+    };
+
+    GFF3AttributeAggregator curcomProc = new GFF3AttributeAggregator() {
+      @Override
+      public String process(StringVector values) {
+        StringBuilder buffer = new StringBuilder();
+        if (values != null && values.size() > 0) {
+          for (int value_index = 0; value_index < values.size(); ++value_index) {
+            buffer.append(GFF3Encoder.encode(values.elementAt(value_index)));
+            if (value_index < (values.size()) - 1)
+              buffer.append(" ");
+          }
+        }
+        return buffer.toString();
+      }
+    };
+
+    // map GO -> full_GO
+    abuf.setMapping("GO", "full_GO");
+    abuf.setGlue("full_GO", ",");
+
+    // merge curation and comment
+    abuf.setMapping("curation", "comment");
+    abuf.setGlue("comment", " ");
+    abuf.setAggregator("comment", curcomProc);
+
+    // also put GOs in Ontology_term
+    abuf.setClone("full_GO", "Ontology_term");
+    abuf.setAggregator("Ontology_term", goProc);
+    abuf.setGlue("Ontology_term", ",");
+
+    // also put EuPathDB UC numbers into separate attribute
+    abuf.setClone("history", "eupathdb_uc");
+    abuf.setAggregator("eupathdb_uc", ucProc);
+    abuf.setGlue("eupathdb_uc", ",");
+
+    // class
+    abuf.setAggregator("class", classProc);
+
+    // EC numbers go into Dbxref
+    abuf.setMapping("EC_number", "Dbxref");
+    abuf.setAggregator("EC_number", ecProc);
+    abuf.setGlue("Dbxref", ",");
+
+    // start/end ranges
+    abuf.setAggregator("Start_range", startEndRangeProc);
+    abuf.setAggregator("End_range", startEndRangeProc);
+
+    // previous_systematic_id
+    abuf.setAggregator("previous_systematic_id", psysIDProc);
+
+    // product
+    abuf.setAggregator("product", productProc);
+  }
+
+  /**
+   * Get the translation qualifier string for polypeptide features.
+   */
+  private String getTranslation() {
+    if (!getKey().getKeyString().equals("polypeptide"))
+      return null;
+    if (chadoGene != null) {
+      if (getUserData() == null)
+        new uk.ac.sanger.artemis.Feature(this);
+      // the above line constructs the appropriate userData within this current
+      // GFFStreamFeature object,
+      // which is required by the following GeneUtils.deriveResidues()
+      String residues = GeneUtils.deriveResidues(this);
+      if (residues != null)
+        return "translation=" + residues;
     }
-    if (buffer.toString().charAt(buffer.toString().length()-1) == '=')
-      System.out.println(buffer.toString() + " ----- values length was " + values.size() + ": '" + values.elementAt(0) + "'");
-    return buffer.toString();
+    return null;
   }
 
   /**
-   *  Parse the given String as ACeDB format attributes.
-   *  Adapted from code by Matthew Pocock for the BioJava project.
+   * Parse the given String as ACeDB format attributes. Adapted from code by
+   * Matthew Pocock for the BioJava project.
+   *
+   * Modified for gff-version 3.
    *
-   *  Modified for gff-version 3.
-   *  @return Return a Hashtable.  Each key is an attribute name and each value
-   *    of the Hashtable is a StringVector containing the attribute values.
-   *    If the attribute has no value then the Hashtable value will be a zero
-   *    length vector.
+   * @return Return a Hashtable. Each key is an attribute name and each value of
+   *         the Hashtable is a StringVector containing the attribute values. If
+   *         the attribute has no value then the Hashtable value will be a zero
+   *         length vector.
    **/
-  private Hashtable<String, StringVector> parseAttributes(final String att_val_list)
-  {
+  private Hashtable<String, StringVector> parseAttributes(
+      final String att_val_list) {
     final Hashtable<String, StringVector> attr = new Hashtable<String, StringVector>();
 
     int ind_start = 0;
     int ind_end;
-    while( (ind_end = att_val_list.indexOf(";",ind_start)) > -1 ||
-           ind_start < att_val_list.length() )
-    {
-      if(ind_end < 0)
+    while ((ind_end = att_val_list.indexOf(";", ind_start)) > -1
+        || ind_start < att_val_list.length()) {
+      if (ind_end < 0)
         ind_end = att_val_list.length();
 
-      final String this_token = decode(att_val_list.substring(ind_start, ind_end).trim());
-      ind_start = ind_end+1;
+      final String this_token = GFF3Encoder.decode(att_val_list.substring(ind_start,
+          ind_end).trim());
+      ind_start = ind_end + 1;
 
       int index_of_first_space = this_token.indexOf(" ");
 
       final String att_name;
       StringVector att_values = new StringVector();
 
-      if( this_token.indexOf("=") > -1 &&
-         (this_token.indexOf("=") < index_of_first_space ||
-          index_of_first_space == -1) )
-      {
+      if (this_token.indexOf("=") > -1
+          && (this_token.indexOf("=") < index_of_first_space || index_of_first_space == -1)) {
         index_of_first_space = this_token.indexOf("=");
         att_name = this_token.substring(0, index_of_first_space);
-        att_values.add(this_token.substring(index_of_first_space+1).trim());
-      }
-      else if(index_of_first_space == -1)
+        att_values.add(this_token.substring(index_of_first_space + 1).trim());
+      } else if (index_of_first_space == -1)
         att_name = this_token;
-      else
-      {
+      else {
         att_name = this_token.substring(0, index_of_first_space);
 
-        String rest_of_token =
-          this_token.substring(index_of_first_space+1).trim();
+        String rest_of_token = this_token.substring(index_of_first_space + 1)
+            .trim();
 
-        while(rest_of_token.length() > 0)
-        {
-          if(rest_of_token.startsWith("\""))
-          {
+        while (rest_of_token.length() > 0) {
+          if (rest_of_token.startsWith("\"")) {
             int quote_index = 0;
-            do
-            {
+            do {
               quote_index++;
               quote_index = rest_of_token.indexOf("\"", quote_index);
-            } while(quote_index > -1 &&
-                    rest_of_token.charAt(quote_index - 1) == '\\');
+            } while (quote_index > -1
+                && rest_of_token.charAt(quote_index - 1) == '\\');
 
-            if(quote_index < 0)
-            {
+            if (quote_index < 0) {
               // no closing quote - panic
-              final Hashtable<String, StringVector> panic_attributes =
-                  new Hashtable<String, StringVector>();
+              final Hashtable<String, StringVector> panic_attributes = new Hashtable<String, StringVector>();
               final StringVector notes = new StringVector();
               notes.add(att_val_list);
               panic_attributes.put("note", notes);
@@ -1123,60 +1056,52 @@ public class GFFStreamFeature extends SimpleDocumentFeature
             final String next_bit = rest_of_token.substring(1, quote_index);
             att_values.add(next_bit);
             rest_of_token = rest_of_token.substring(quote_index + 1).trim();
-          }
-          else
-          {
+          } else {
             final int index_of_next_space = rest_of_token.indexOf(" ");
 
-            if(index_of_next_space == -1)
-            {
+            if (index_of_next_space == -1) {
               att_values.add(rest_of_token);
               rest_of_token = "";
-            }
-            else
-            {
-              final String next_bit =
-                rest_of_token.substring(0, index_of_next_space);
+            } else {
+              final String next_bit = rest_of_token.substring(0,
+                  index_of_next_space);
 
               att_values.add(next_bit);
-              rest_of_token =
-                rest_of_token.substring(index_of_next_space).trim();
+              rest_of_token = rest_of_token.substring(index_of_next_space)
+                  .trim();
             }
           }
         }
 
-        if(!rest_of_token.equals(""))
+        if (!rest_of_token.equals(""))
           att_values.add(rest_of_token);
       }
 
-      if(att_name.equals("Dbxref") || att_name.equals("Alias")) // convert to multi-line
+      if (att_name.equals("Dbxref") || att_name.equals("Alias")) // convert to
+                                                                 // multi-line
       {
-        StringTokenizer stok =
-            new StringTokenizer((String)att_values.get(0), ",");
+        StringTokenizer stok = new StringTokenizer((String) att_values.get(0),
+            ",");
         StringVector str_values = new StringVector();
-        while(stok.hasMoreTokens())
+        while (stok.hasMoreTokens())
           str_values.add(stok.nextToken());
 
         att_values = str_values;
       }
 
-      if(att_name.equals("timelastmodified"))
-      {
-        try
-        {
-          this.timelastmodified =
-                  new Timestamp( Long.parseLong((String)att_values.get(0)) );
-          SimpleDateFormat date_format =
-                  new SimpleDateFormat("dd.MM.yyyy hh:mm:ss z");
-          att_values.set(0,date_format.format(timelastmodified));
-        }
-        catch(NumberFormatException e)
-        {
-          att_values.set(0,(String)att_values.get(0));
+      if (att_name.equals("timelastmodified")) {
+        try {
+          this.timelastmodified = new Timestamp(
+              Long.parseLong((String) att_values.get(0)));
+          SimpleDateFormat date_format = new SimpleDateFormat(
+              "dd.MM.yyyy hh:mm:ss z");
+          att_values.set(0, date_format.format(timelastmodified));
+        } catch (NumberFormatException e) {
+          att_values.set(0, (String) att_values.get(0));
         }
       }
 
-      if(attr.get(att_name) != null)
+      if (attr.get(att_name) != null)
         attr.get(att_name).add(att_values);
       else
         attr.put(att_name, att_values);
@@ -1187,32 +1112,30 @@ public class GFFStreamFeature extends SimpleDocumentFeature
 
   /**
    * Get the feature time last modified timestamp.
+   *
    * @return
    */
-  public Timestamp getLastModified()
-  {
+  public Timestamp getLastModified() {
     return timelastmodified;
   }
 
   /**
    * Get the GFF_source value of a Dbxref qualifier.
+   *
    * @param qualifier
-   * @return  the gff_source value or NULL
+   * @return the gff_source value or NULL
    */
-  private String getDbxrefGFFSource(final Qualifier qualifier)
-  {
-    StringVector qualifier_strings =
-      StreamQualifier.toStringVector(null, qualifier);
+  private String getDbxrefGFFSource(final Qualifier qualifier) {
+    StringVector qualifier_strings = StreamQualifier.toStringVector(null,
+        qualifier);
 
-    for(int i=0; i<qualifier_strings.size(); i++)
-    {
-      String qualifier_string = (String)qualifier_strings.elementAt(i);
+    for (int i = 0; i < qualifier_strings.size(); i++) {
+      String qualifier_string = (String) qualifier_strings.elementAt(i);
 
-      if(qualifier_string.indexOf("GFF_source:") >-1)
-      {
-        int index = qualifier_string.indexOf(":")+1;
+      if (qualifier_string.indexOf("GFF_source:") > -1) {
+        int index = qualifier_string.indexOf(":") + 1;
         int len = qualifier_string.length();
-        if(qualifier_string.endsWith("\""))
+        if (qualifier_string.endsWith("\""))
           len--;
         return qualifier_string.substring(index, len);
       }
@@ -1222,136 +1145,112 @@ public class GFFStreamFeature extends SimpleDocumentFeature
 
   /**
    * Set the feature time last modified timestamp.
+   *
    * @param timelastmodified
    */
-  public void setLastModified(final Timestamp timelastmodified)
-  {
+  public void setLastModified(final Timestamp timelastmodified) {
     this.timelastmodified = timelastmodified;
 
     // now update the qualifier value itself
     QualifierVector qualifiers = getQualifiers();
     Qualifier qualifier = qualifiers.getQualifierByName("timelastmodified");
-    SimpleDateFormat date_format =
-      new SimpleDateFormat("dd.MM.yyyy hh:mm:ss z");
-
-    if(qualifier != null)
-      qualifier.removeValue((String)qualifier.getValues().get(0));
-    else
-    {
-      try
-      {
+    SimpleDateFormat date_format = new SimpleDateFormat("dd.MM.yyyy hh:mm:ss z");
+
+    if (qualifier != null)
+      qualifier.removeValue((String) qualifier.getValues().get(0));
+    else {
+      try {
         qualifier = new Qualifier("timelastmodified",
-               date_format.format(timelastmodified));
+            date_format.format(timelastmodified));
         setQualifier(qualifier);
         return;
+      } catch (EntryInformationException eie) {
+      } catch (ReadOnlyException roe) {
       }
-      catch(EntryInformationException eie)
-      {}
-      catch(ReadOnlyException roe)
-      {}
     }
 
     qualifier.addValue(date_format.format(timelastmodified));
   }
 
   /**
-   *  Returns true if and only if this Feature can't be changed or can't be
-   *  removed from it's entry.
+   * Returns true if and only if this Feature can't be changed or can't be
+   * removed from it's entry.
    **/
-  public boolean isReadOnly ()
-  {
-    if(readOnlyFeature)
+  public boolean isReadOnly() {
+    if (readOnlyFeature)
       return true;
     return super.isReadOnly();
   }
 
-  public void setReadOnlyFeature(boolean readOnlyFeature)
-  {
+  public void setReadOnlyFeature(boolean readOnlyFeature) {
     this.readOnlyFeature = readOnlyFeature;
   }
 
-  public ChadoCanonicalGene getChadoGene()
-  {
+  public ChadoCanonicalGene getChadoGene() {
     return chadoGene;
   }
 
-  public void setChadoGene(ChadoCanonicalGene chadoGene)
-  {
+  public void setChadoGene(ChadoCanonicalGene chadoGene) {
     this.chadoGene = chadoGene;
   }
 
-  public boolean isVisible()
-  {
+  public boolean isVisible() {
     return visible;
   }
 
-  public void setVisible(boolean visible)
-  {
+  public void setVisible(boolean visible) {
     this.visible = visible;
   }
 
-  public String getGffSeqName()
-  {
+  public String getGffSeqName() {
     return gffSeqName;
   }
 
-  public void setGffSeqName(String gffSeqName)
-  {
+  public void setGffSeqName(String gffSeqName) {
     this.gffSeqName = gffSeqName;
   }
 
-  public String getGffSource()
-  {
+  public String getGffSource() {
     return gffSource;
   }
 
-  public void setGffSource(String gffSource)
-  {
+  public void setGffSource(String gffSource) {
     this.gffSource = gffSource;
   }
 
-  public boolean isLazyLoaded()
-  {
+  public boolean isLazyLoaded() {
     return lazyLoaded;
   }
 
-  public void setLazyLoaded(boolean lazyLoaded)
-  {
+  public void setLazyLoaded(boolean lazyLoaded) {
     this.lazyLoaded = lazyLoaded;
   }
 
-  public org.gmod.schema.sequence.Feature getChadoLazyFeature()
-  {
+  public org.gmod.schema.sequence.Feature getChadoLazyFeature() {
     return chadoLazyFeature;
   }
 
   public void setChadoLazyFeature(
-      org.gmod.schema.sequence.Feature chadoLazyFeature)
-  {
+      org.gmod.schema.sequence.Feature chadoLazyFeature) {
     this.chadoLazyFeature = chadoLazyFeature;
   }
 
-  protected static boolean isGTF(Feature feature)
-  {
-    if(!(feature instanceof GFFStreamFeature))
+  protected static boolean isGTF(Feature feature) {
+    if (!(feature instanceof GFFStreamFeature))
       return false;
 
-    final String names[] = { "ID", "Name", "Alias", "Parent",
-        "Derives_from",
-        "Target", "Gap", "Note",
-        "Dbxref", "Ontology_term" };
+    final String names[] = { "ID", "Name", "Alias", "Parent", "Derives_from",
+        "Target", "Gap", "Note", "Dbxref", "Ontology_term" };
 
-    for(String name: names)
-    {
-      if(feature.getQualifiers().getQualifierByName(name) != null)
+    for (String name : names) {
+      if (feature.getQualifiers().getQualifierByName(name) != null)
         return false;
     }
 
-    if(feature.getQualifiers().getQualifierByName("gene_id") != null &&
-       feature.getQualifiers().getQualifierByName("transcript_id") != null)
-    {
-      if(feature.getEntry() != null)
-        logger4j.debug(feature.getEntry().getName()+" is in GTF format");
+    if (feature.getQualifiers().getQualifierByName("gene_id") != null
+        && feature.getQualifiers().getQualifierByName("transcript_id") != null) {
+      if (feature.getEntry() != null)
+        logger4j.debug(feature.getEntry().getName() + " is in GTF format");
       return true;
     }
     return false;
diff --git a/uk/ac/sanger/artemis/io/GenbankTblOutputStream.java b/uk/ac/sanger/artemis/io/GenbankTblOutputStream.java
index 82ae21e..4083fac 100644
--- a/uk/ac/sanger/artemis/io/GenbankTblOutputStream.java
+++ b/uk/ac/sanger/artemis/io/GenbankTblOutputStream.java
@@ -38,14 +38,12 @@ import uk.ac.sanger.artemis.FeatureVector;
 import uk.ac.sanger.artemis.util.FileDocument;
 import uk.ac.sanger.artemis.util.StringVector;
 
-
 /**
  *  Handle writing tbl format:
  *  http://www.ncbi.nlm.nih.gov/Sequin/table.html
  */
 public class GenbankTblOutputStream
 {
-  
   /**
    * Write out an entry as tbl format.
    * @param entry
@@ -76,10 +74,9 @@ public class GenbankTblOutputStream
     {
       final Writer writer = fileDocument.getWriter();
       writer.write(">Feature "+entry.getName()+"\n");
-      
+
       final FeatureVector features = entry.getAllFeatures();
       final EntryInformation entry_information = entry.getEntryInformation ();
-      
       int count = 0;
       for(int i=0; i<features.size(); i++)
       {
@@ -88,7 +85,7 @@ public class GenbankTblOutputStream
           continue;
         if(count > 0)
           writer.write("\n");
-        
+
         count++;
         writeRanges(feature, writer);
         writeQualifiers(feature, entry_information, writer);
@@ -113,27 +110,94 @@ public class GenbankTblOutputStream
     final RangeVector ranges = feature.getLocation().getRanges();
     if(!feature.isForwardFeature() && ranges.size() > 1)
       ranges.reverse();
-    
-    Range r = (Range)ranges.elementAt(0);
-    if(feature.isForwardFeature())
-      writer.write(r.getStart()+"\t"+r.getEnd());
+
+    boolean isStartPartial = false;
+    boolean isEndPartial   = false;
+    if(feature.getEmblFeature() instanceof GFFStreamFeature)
+    {
+      try
+      {
+        if(feature.getQualifierByName("Start_range") != null)
+          isStartPartial = true;
+        if(feature.getQualifierByName("End_range") != null)
+          isEndPartial = true;
+      }
+      catch (InvalidRelationException e){}
+    }
     else
-      writer.write(r.getEnd()+"\t"+r.getStart());
-    
+    {
+      if(feature.getLocation().isPartial(true)) // 5prime
+      {
+        if(feature.isForwardFeature()) 
+          isStartPartial = true;
+        else
+          isEndPartial = true;
+      }
+      if(feature.getLocation().isPartial(false)) // 3prime
+      {
+        if(feature.isForwardFeature())
+          isEndPartial = true;
+        else
+          isStartPartial = true;
+      }
+    }
+
+    writer.write( getPositionsStr(feature, ranges.elementAt(0), 
+        isStartPartial, isEndPartial) );
     writer.write("\t"+feature.getKey().getKeyString());
     
     for(int j=1; j<ranges.size(); j++)
     {
       writer.write("\n");
-      r = (Range)ranges.elementAt(j);
-      if(feature.isForwardFeature())
-        writer.write(r.getStart()+"\t"+r.getEnd());
-      else
-        writer.write(r.getEnd()+"\t"+r.getStart());
+      writer.write( getPositionsStr(feature, ranges.elementAt(j), 
+          isStartPartial, isEndPartial) );
     }
   }
   
   /**
+   * Get the start and end positions as a tab delimited string
+   * @param feature
+   * @param r
+   * @param isStartRangePartial
+   * @param isEndRangePartial
+   * @return
+   */
+  private static String getPositionsStr(
+      final Feature feature, 
+      final Range r,
+      final boolean isStartPartial,
+      final boolean isEndPartial)
+  {
+    boolean firstBase;
+    boolean lastBase;
+    final int low_marker  = feature.getFirstBaseMarker().getPosition();
+    final int high_marker = feature.getLastBaseMarker().getPosition();
+    String low_pos;
+    String high_pos;
+    if(feature.isForwardFeature())
+    {
+      firstBase = (r.getStart() == low_marker);
+      lastBase  = (r.getEnd()   == high_marker);
+      low_pos   = Integer.toString(r.getStart());
+      high_pos  = Integer.toString(r.getEnd());
+    }
+    else
+    {
+      firstBase = (r.getEnd()   == high_marker);
+      lastBase  = (r.getStart() == low_marker);
+      low_pos   = Integer.toString(r.getEnd());
+      high_pos  = Integer.toString(r.getStart());
+    }
+
+    // set partials
+    if(firstBase && isStartPartial)
+      low_pos  = (feature.isForwardFeature() ? "<" : ">")+low_pos;
+    if(lastBase && isEndPartial)
+      high_pos = (feature.isForwardFeature() ? ">" : "<")+high_pos;
+    return low_pos+"\t"+high_pos;
+  }
+  
+  /**
    * Write out qualifiers
    * @param feature
    * @param entry_information
diff --git a/uk/ac/sanger/artemis/io/GffToEMBL.java b/uk/ac/sanger/artemis/io/GffToEMBL.java
index 838247b..cb62d11 100644
--- a/uk/ac/sanger/artemis/io/GffToEMBL.java
+++ b/uk/ac/sanger/artemis/io/GffToEMBL.java
@@ -224,7 +224,7 @@ class GffToEMBL
         (args == null || args.length < 1))
     {
       System.out.println("-h\tshow help");
-      System.out.println("-s\tspace separated list of sequences to read and write out");
+      System.out.println("-s\tspace separated list of GFF files to read and write out");
       System.out.println("-o\toutput directory");
       System.out.println("-f\t[y|n] flatten the gene model, default is y");
       System.out.println("-z\t[y|n] gzip output, default is y");
diff --git a/uk/ac/sanger/artemis/io/KeyVector.java b/uk/ac/sanger/artemis/io/KeyVector.java
index 95aa455..b5b8993 100644
--- a/uk/ac/sanger/artemis/io/KeyVector.java
+++ b/uk/ac/sanger/artemis/io/KeyVector.java
@@ -3,19 +3,19 @@
  * created: Fri Apr 16 1999
  *
  * This file is part of Artemis
- * 
+ *
  * Copyright (C) 1999  Genome Research Limited
- * 
+ *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation; either version 2
  * of the License, or (at your option) any later version.
- * 
+ *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
@@ -39,13 +39,13 @@ public class KeyVector extends FastVector
 
   public KeyVector ()
   {
-    super();  
+    super();
   }
-  
+
   /**
    *  Create a new vector which contains only the given Key.
    **/
-  public KeyVector (final Key new_key) 
+  public KeyVector (final Key new_key)
   {
     super();
     add (new_key);
@@ -54,7 +54,7 @@ public class KeyVector extends FastVector
   /**
    *  Return a new copy of this object.
    **/
-  public KeyVector copy () 
+  public KeyVector copy ()
   {
     final KeyVector new_key_vector = (KeyVector)clone();
 
@@ -65,21 +65,21 @@ public class KeyVector extends FastVector
    * Sorts the elements of the vector using a simple O(n^2) selection
    * sort.
    */
-  public void sort() 
+  public void mysort()
   {
     int smallest;
 
-    for (int i = 0; i < size (); ++i) 
+    for (int i = 0; i < size (); ++i)
     {
       //find smallest remaining element
       smallest = i;
-      for(int j = i + 1 ; j < size () ; ++j) 
+      for(int j = i + 1 ; j < size () ; ++j)
       {
-        if(((Key)get(j)).compareTo( (Key)get(smallest)) < 0) 
+        if(((Key)get(j)).compareTo( (Key)get(smallest)) < 0)
           smallest = j;
       }
       //exchange smallest and i
-      if (smallest != i) 
+      if (smallest != i)
       {
         final Key tmp = (Key)get(i);
         setElementAt (get(smallest), i);
diff --git a/uk/ac/sanger/artemis/io/PublicDBDocumentEntry.java b/uk/ac/sanger/artemis/io/PublicDBDocumentEntry.java
index 11284b4..1c1756f 100644
--- a/uk/ac/sanger/artemis/io/PublicDBDocumentEntry.java
+++ b/uk/ac/sanger/artemis/io/PublicDBDocumentEntry.java
@@ -473,27 +473,31 @@ public class PublicDBDocumentEntry extends SimpleDocumentEntry
   {
     final StringVector values = productQualifier.getValues();
     final StringVector tmpNewValues = new StringVector();
-    for(int j=0; j<values.size(); j++)
+    for(String val: values)
     {
-      String val = (String)values.get(j);
-      
       int ind = 0;
-      
       if((ind=val.indexOf(";db_xref="))>-1 && this instanceof EmblDocumentEntry)
         val = val.substring(0,ind);
-      
+
       if((ind=val.indexOf(";evidence="))>-1 && this instanceof EmblDocumentEntry)
         val = val.substring(0,ind);
-      
+
       if((ind=val.indexOf(";with="))>-1 && this instanceof EmblDocumentEntry)
         val = val.substring(0,ind);
-      
+
+      if((ind=val.indexOf("rank="))>-1 && this instanceof EmblDocumentEntry)
+      {
+        int ind2 = val.indexOf(";", ind);
+        if(ind == 0 && ind2 > -1)
+          val = val.substring(ind2+1);
+      }
+
       if(val.startsWith("term="))
         val = val.substring(5,  val.length());
-      
+
       if(val.endsWith(";"))
         val = val.substring(0,  val.length()-1);
-      
+
       tmpNewValues.add(val);
     }
     return tmpNewValues;
@@ -741,7 +745,7 @@ public class PublicDBDocumentEntry extends SimpleDocumentEntry
     Enumeration qualifiersenum = qualifierMapProperties.propertyNames();
     n = 0;
 
-    Vector qualifiersToRemove = new Vector();
+    Vector<String> qualifiersToRemove = new Vector<String>();
     while(qualifiersenum.hasMoreElements()) 
     {
       String current_map_name = (String) qualifiersenum.nextElement();
diff --git a/uk/ac/sanger/artemis/io/SimpleEntryInformation.java b/uk/ac/sanger/artemis/io/SimpleEntryInformation.java
index 81cdcb0..db4dde0 100644
--- a/uk/ac/sanger/artemis/io/SimpleEntryInformation.java
+++ b/uk/ac/sanger/artemis/io/SimpleEntryInformation.java
@@ -175,7 +175,7 @@ public class SimpleEntryInformation
       return null;
     }
 
-    return_vector.sort ();
+    return_vector.mysort ();
     return return_vector;
   }
 
@@ -187,11 +187,11 @@ public class SimpleEntryInformation
 
     if (isValidKey (misc_feature_key))
       return misc_feature_key;
-    
+
     misc_feature_key = new Key ("region");
     if (isValidKey (misc_feature_key))
       return misc_feature_key;
-   
+
     return (Key)getValidKeys ().get (0);
   }
 
@@ -397,7 +397,7 @@ public class SimpleEntryInformation
       return getQualifierInfoHash ().copy ();
     }
   }
-    
+
   /**
    *  Fix this EntryInformation so that the given exception won't happen
    *  again.
diff --git a/uk/ac/sanger/artemis/io/StreamQualifier.java b/uk/ac/sanger/artemis/io/StreamQualifier.java
index 5cb78f2..9afa821 100644
--- a/uk/ac/sanger/artemis/io/StreamQualifier.java
+++ b/uk/ac/sanger/artemis/io/StreamQualifier.java
@@ -106,10 +106,14 @@ class StreamQualifier {
         buffer.append ('/');
         buffer.append (qualifier.getName ());
         if (values.elementAt (i) != null) {
+          /* Escape double quotes */
+          String processedValue = (String)values.elementAt (i).replaceAll("(^|[^\"])\"([^\"]|$)","$1\"\"$2");
+          /* Mask line breaks in entries (e.g. notes/history) */
+          processedValue = processedValue.replaceAll("\n", " ");
           buffer.append ('=');
-          buffer.append (quotedValue (qualifier_info,
-                                      qualifier.getName (),
-                                      (String)values.elementAt (i)));
+          buffer.append (quotedValue(qualifier_info,
+                                      qualifier.getName(),
+                                      processedValue));
         }
       }
 
@@ -140,10 +144,14 @@ class StreamQualifier {
         buffer.append ('/');
         buffer.append (qualifier.getName ());
         if (values.elementAt (i) != null) {
+          /* Escape double quotes */
+          String processedValue = (String)values.elementAt (i).replaceAll("(^|[^\"])\"([^\"]|$)","$1\"\"$2");
+          /* Mask line breaks in entries (e.g. notes/history) */
+          processedValue = processedValue.replaceAll("\n", " ");
           buffer.append ('=');
           buffer.append (quotedValue (qualifier_info,
                                       qualifier.getName (),
-                                      (String)values.elementAt (i)));
+                                      processedValue));
         }
         return_vector.add (buffer.toString ());
       }
diff --git a/uk/ac/sanger/artemis/plot/Algorithm.java b/uk/ac/sanger/artemis/plot/Algorithm.java
index 1ffeb9e..a160d2f 100644
--- a/uk/ac/sanger/artemis/plot/Algorithm.java
+++ b/uk/ac/sanger/artemis/plot/Algorithm.java
@@ -51,7 +51,7 @@ public abstract class Algorithm {
   /**
    *  Set by disableMaxAndMin () and enableMaxAndMin ().
    **/
-  private boolean max_min_disabled = false;
+  protected boolean max_min_disabled = false;
 
   /**
    *  Set by the constructor by looking at the options with this name:
diff --git a/uk/ac/sanger/artemis/plot/UserDataAlgorithm.java b/uk/ac/sanger/artemis/plot/UserDataAlgorithm.java
index fb4b8b0..6767966 100644
--- a/uk/ac/sanger/artemis/plot/UserDataAlgorithm.java
+++ b/uk/ac/sanger/artemis/plot/UserDataAlgorithm.java
@@ -21,6 +21,7 @@
 package uk.ac.sanger.artemis.plot;
 
 import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.Options;
 import uk.ac.sanger.artemis.sequence.Bases;
 import uk.ac.sanger.artemis.sequence.Strand;
 import uk.ac.sanger.artemis.util.Document;
@@ -183,6 +184,18 @@ public class UserDataAlgorithm extends BaseAlgorithm
       readWiggle(pushback_reader);
     pushback_reader.close();
     doc_reader.close();
+
+    max_min_disabled = Options.getOptions ().getPropertyTruthValue(
+        getAlgorithmShortName () +  "_scaling_on");
+
+    String propStr = getAlgorithmShortName () +  "_data_max";
+    if(Options.getOptions ().getProperty(propStr) != null)
+      data_max = Float.valueOf(
+          Options.getOptions ().getProperty(propStr));
+    propStr = getAlgorithmShortName () +  "_data_min";
+    if(Options.getOptions ().getProperty(propStr) != null)
+      data_min = Float.valueOf(
+          Options.getOptions ().getProperty(propStr));
   }
   
   /**
@@ -211,6 +224,13 @@ public class UserDataAlgorithm extends BaseAlgorithm
     final int seqLength = getStrand ().getSequenceLength ();
     final Pattern patt = Pattern.compile("\\s+");
     
+    final boolean useEstimate = 
+        Options.getOptions ().getIntegerProperty (getAlgorithmShortName () +
+        "_default_window_size") == null;
+    if(!useEstimate)
+      default_window_size = Options.getOptions ().getIntegerProperty (getAlgorithmShortName () +
+          "_default_window_size");
+    
     while ((line = pushback_reader.readLine ()) != null)
     {
       if (count >= seqLength) 
@@ -278,7 +298,7 @@ public class UserDataAlgorithm extends BaseAlgorithm
     else
     {
       average_value = average_value/countAll;
-      if(estimate_window_size != Integer.MAX_VALUE)
+      if(estimate_window_size != Integer.MAX_VALUE && useEstimate)
         default_window_size = estimate_window_size;
     }
   }
@@ -782,7 +802,11 @@ public class UserDataAlgorithm extends BaseAlgorithm
    **/
   public Integer getDefaultMaxWindowSize ()
   {
-    return new Integer (100);
+    if(Options.getOptions ().getIntegerProperty (getAlgorithmShortName () +
+       "_default_max_window") == null)
+      return new Integer (100);
+    else
+      return super.getDefaultMaxWindowSize ();
   }
 
   /**
@@ -792,7 +816,11 @@ public class UserDataAlgorithm extends BaseAlgorithm
    **/
   public Integer getDefaultMinWindowSize () 
   {
-    return new Integer (1);
+    if(Options.getOptions ().getIntegerProperty (getAlgorithmShortName () +
+       "_default_min_window") == null)
+      return new Integer (1);
+    else
+      return super.getDefaultMinWindowSize();
   }
 
   /**
diff --git a/uk/ac/sanger/artemis/util/DatabaseDocument.java b/uk/ac/sanger/artemis/util/DatabaseDocument.java
index 6cba75d..d3ff19c 100644
--- a/uk/ac/sanger/artemis/util/DatabaseDocument.java
+++ b/uk/ac/sanger/artemis/util/DatabaseDocument.java
@@ -27,11 +27,11 @@ package uk.ac.sanger.artemis.util;
 import uk.ac.sanger.artemis.Options;
 import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
 import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.io.GFF3Encoder;
 import uk.ac.sanger.artemis.io.GFFStreamFeature;
 import uk.ac.sanger.artemis.io.PartialSequence;
 import uk.ac.sanger.artemis.io.Range;
 import uk.ac.sanger.artemis.io.ReadFormatException;
-
 import uk.ac.sanger.artemis.chado.ArtemisUtils;
 import uk.ac.sanger.artemis.chado.ChadoCvTermView;
 import uk.ac.sanger.artemis.chado.ChadoTransactionManager;
@@ -67,10 +67,12 @@ import org.postgresql.largeobject.LargeObjectManager;
 import com.ibatis.common.jdbc.SimpleDataSource;
 
 import java.sql.*;
+import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.io.*;
 import java.net.ConnectException;
 import java.net.InetAddress;
+import java.util.Calendar;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
@@ -551,7 +553,11 @@ public class DatabaseDocument extends Document
       ByteBuffer entryBuffer = new ByteBuffer();
       try
       {
+        DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
+        Calendar cal = Calendar.getInstance();
+
         entryBuffer.append("##gff-version 3\n");
+        entryBuffer.append("#created " + dateFormat.format(cal.getTime()) + "\n");
         
         ByteBuffer sequenceBuffer = new ByteBuffer();
         if(dao instanceof IBatisDAO)
@@ -1301,7 +1307,7 @@ public class DatabaseDocument extends Document
           // ortholog/paralog/cluster data
           int orthologueFeature = fr.getFeatureByObjectId().getFeatureId();
           clusterOrthoParalog.append(cvTermName+"="+
-              GFFStreamFeature.encode("object_id="+orthologueFeature+"; rank="+fr.getRank())+";");
+              GFF3Encoder.encode("object_id="+orthologueFeature+"; rank="+fr.getRank())+";");
         }
       }
     }
@@ -1393,10 +1399,10 @@ public class DatabaseDocument extends Document
         if(qualifier_name == null)
           continue;
         if(featprop.getValue() != null)
-          this_buff.append(GFFStreamFeature.encode(qualifier_name)+ "=" +
-                           GFFStreamFeature.encode(featprop.getValue())+";");
+          this_buff.append(GFF3Encoder.encode(qualifier_name)+ "=" +
+                           GFF3Encoder.encode(featprop.getValue())+";");
         else
-          this_buff.append(GFFStreamFeature.encode(qualifier_name)+";");
+          this_buff.append(GFF3Encoder.encode(qualifier_name)+";");
       }
     }
 
@@ -1408,7 +1414,7 @@ public class DatabaseDocument extends Document
     if(feat.getDbXRef() != null)
     {
       this_buff.append("Dbxref=");
-      this_buff.append(GFFStreamFeature.encode(
+      this_buff.append(GFF3Encoder.encode(
           feat.getDbXRef().getDb().getName()+":"+feat.getDbXRef().getAccession()));
       foundPrimaryDbXRef = true;
       if(dbxref == null || dbxref.size() == 0)
@@ -1423,7 +1429,7 @@ public class DatabaseDocument extends Document
         this_buff.append("Dbxref=");
       for(int j=0; j<dbxref.size(); j++)
       {
-        this_buff.append(GFFStreamFeature.encode(dbxref.get(j)));
+        this_buff.append(GFF3Encoder.encode(dbxref.get(j)));
         if(j<dbxref.size()-1)
           this_buff.append(",");
       }
@@ -1442,7 +1448,7 @@ public class DatabaseDocument extends Document
         this_buff.append(alias.getSynonym().getName());
         
         if(!alias.isCurrent())
-          this_buff.append(GFFStreamFeature.encode(";current=false"));
+          this_buff.append(GFF3Encoder.encode(";current=false"));
         
         //if(j<v_synonyms.size()-1)
         this_buff.append(";");
@@ -1511,9 +1517,9 @@ public class DatabaseDocument extends Document
       attr_buff.append("controlled_curation=");
       
       attr_buff.append("term="+
-          GFFStreamFeature.encode(feature_cvterm.getCvTerm().getName())+"%3B");
+          GFF3Encoder.encode(feature_cvterm.getCvTerm().getName())+"%3B");
       attr_buff.append("cv="+
-          GFFStreamFeature.encode(feature_cvterm.getCvTerm().getCv().getName())+"%3B");   
+          GFF3Encoder.encode(feature_cvterm.getCvTerm().getCv().getName())+"%3B");   
       
       // N.B. the db_xref may be a FeatureCvTermDbXRef or a Pub for /controlled_curation
       int nfound_dbxref = 0;
@@ -1584,7 +1590,7 @@ public class DatabaseDocument extends Document
         attr_buff.append(getCvtermName(feature_cvtermprop.getCvTerm()
             .getCvTermId(), dao, gene_builder));
         attr_buff.append("=");
-        attr_buff.append(GFFStreamFeature.encode(feature_cvtermprop.getValue()));
+        attr_buff.append(GFF3Encoder.encode(feature_cvtermprop.getValue()));
         if(i < feature_cvtermprops.size()-1)
           attr_buff.append("%3B");
       }
@@ -1655,10 +1661,10 @@ public class DatabaseDocument extends Document
     if(showDbId)
       attr_buff.append("GOid="+dbXRef.getDb().getName() + ":"
                        + dbXRef.getAccession() + "%3B");
-    
+
     attr_buff.append("term="+
-        GFFStreamFeature.encode(feature_cvterm.getCvTerm().getName())+"%3B");
-    
+        GFF3Encoder.encode(feature_cvterm.getCvTerm().getName())+"%3B");
+
     // PMID
     int nfound_pub = 0;
     if(feature_cvterm.getPub() != null &&
@@ -1666,62 +1672,68 @@ public class DatabaseDocument extends Document
        !feature_cvterm.getPub().getUniqueName().equalsIgnoreCase("NULL"))
     {
       Pub pub = feature_cvterm.getPub();
-      attr_buff.append("db_xref="+
-          pub.getUniqueName());
+      attr_buff.append("db_xref="+pub.getUniqueName());
       nfound_pub++;
     }
-    
-    if(featureCvTermPubs != null &&
-       featureCvTermPubs.size() > 0)
+
+    if(featureCvTermPubs != null && featureCvTermPubs.size() > 0)
     {
       for(FeatureCvTermPub featureCvTermPub: featureCvTermPubs)
       {
         if(feature_cvterm.getFeatureCvTermId() != 
           featureCvTermPub.getFeatureCvTerm().getFeatureCvTermId())
           continue;
-        
-        if(nfound_pub == 0)
-          attr_buff.append("db_xref=");
-        else if(nfound_pub > 0)
-          attr_buff.append("|");
 
+        attr_buff.append((nfound_pub == 0 ? "db_xref=" : "|"));
         attr_buff.append(featureCvTermPub.getPub().getUniqueName());
         nfound_pub++;
       }
     }
-    
+
+    // GO_REF is stored as a dbxref and displayed in the dbxref column
+    if(featureCvTermDbXRefs != null && featureCvTermDbXRefs.size() > 0 )
+    {
+      for(FeatureCvTermDbXRef featureCvTermDbXRef: featureCvTermDbXRefs)
+      {
+        if(feature_cvterm.getFeatureCvTermId() != 
+          featureCvTermDbXRef.getFeatureCvTerm().getFeatureCvTermId())
+          continue;
+
+        DbXRef fc_dbXRef = featureCvTermDbXRef.getDbXRef();
+        if(!fc_dbXRef.getDb().getName().equals("GO_REF"))
+          continue;
+        attr_buff.append((nfound_pub == 0 ? "db_xref=" : "|"));
+        attr_buff.append(fc_dbXRef.getDb().getName()+":");
+        attr_buff.append(fc_dbXRef.getAccession());
+        nfound_pub++;
+      }
+    }
     if(nfound_pub > 0)
       attr_buff.append("%3B");
-    
-    if(featureCvTermDbXRefs != null &&
-       featureCvTermDbXRefs.size() > 0 )
+
+    if(featureCvTermDbXRefs != null && featureCvTermDbXRefs.size() > 0 )
     {  
       int nfound = 0;
       for(FeatureCvTermDbXRef featureCvTermDbXRef : featureCvTermDbXRefs)
       {
         if(feature_cvterm.getFeatureCvTermId() != 
           featureCvTermDbXRef.getFeatureCvTerm().getFeatureCvTermId())
-        {
           continue;
-        }
 
-        if(nfound == 0)
-          attr_buff.append("with=");
-        else if(nfound > 0)
-          attr_buff.append("|");
-        
         DbXRef fc_dbXRef = featureCvTermDbXRef.getDbXRef();
+        if(fc_dbXRef.getDb().getName().equals("GO_REF"))
+          continue;
+        attr_buff.append((nfound == 0 ? "with=" : "|"));
         attr_buff.append(fc_dbXRef.getDb().getName()+":");
         attr_buff.append(fc_dbXRef.getAccession());
         nfound++;
       }
-      
       if(nfound > 0)
         attr_buff.append("%3B");
-
     }
 
-    List<FeatureCvTermProp> feature_cvtermprops = (List<FeatureCvTermProp>)feature_cvterm.getFeatureCvTermProps();
+    List<FeatureCvTermProp> feature_cvtermprops = 
+        (List<FeatureCvTermProp>)feature_cvterm.getFeatureCvTermProps();
     for(int i = 0; i < feature_cvtermprops.size(); i++)
     {
       FeatureCvTermProp feature_cvtermprop = feature_cvtermprops.get(i);
@@ -1731,11 +1743,10 @@ public class DatabaseDocument extends Document
       attr_buff.append(getCvtermName(feature_cvtermprop.getCvTerm()
           .getCvTermId(), dao, gene_builder));
       attr_buff.append("=");
-      attr_buff.append(GFFStreamFeature.encode(feature_cvtermprop.getValue()));
+      attr_buff.append(GFF3Encoder.encode(feature_cvtermprop.getValue()));
       if(i < feature_cvtermprops.size()-1)
         attr_buff.append("%3B");
     }
-    
     attr_buff.append(";");
   }
   
diff --git a/uk/ac/sanger/artemis/util/FastVector.java b/uk/ac/sanger/artemis/util/FastVector.java
index b9d1712..d79dc47 100644
--- a/uk/ac/sanger/artemis/util/FastVector.java
+++ b/uk/ac/sanger/artemis/util/FastVector.java
@@ -3,19 +3,19 @@
  * created: Sun Feb 20 2000
  *
  * This file is part of Artemis
- * 
+ *
  * Copyright (C) 2000  Genome Research Limited
- * 
+ *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation; either version 2
  * of the License, or (at your option) any later version.
- * 
+ *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
@@ -43,9 +43,9 @@ public class FastVector extends ArrayList
   /**
    *  Performs the same function as Vector.addElement()
    */
-  public boolean add(Object object) 
+  public boolean add(Object object)
   {
-    if(object == null) 
+    if(object == null)
       throw new Error("internal error - adding a null object");
     else if(contains(object))
       throw new Error("internal error - object added a second time");
@@ -56,24 +56,24 @@ public class FastVector extends ArrayList
   /**
    *  Performs the same function as Vector.lastElement()
    **/
-  public Object lastElement() 
+  public Object lastElement()
   {
     return (Object)get(size() - 1);
   }
-  
+
   /**
    *  Insert an Object after another.
    *  @param old_object The new_object will be inserted after this object
    *    or at the start if old_object isn't in the vector.
    *  @param new_object The new object to insert.
    **/
-  public void insertElementAfter(Object old_object, Object new_object) 
+  public void insertElementAfter(Object old_object, Object new_object)
   {
     final int old_object_index = indexOf(old_object);
 
-    if(old_object_index == -1) 
+    if(old_object_index == -1)
       add(0, new_object);
-    else 
+    else
       add(old_object_index + 1, new_object);
   }
 
@@ -81,7 +81,7 @@ public class FastVector extends ArrayList
    *  Replace the Object at the given index. (Performs the same function as
    *  Vector.elementAt())
    **/
-  public void setElementAt(final Object object, final int index) 
+  public void setElementAt(final Object object, final int index)
   {
     remove(index);
     add(index, object);
@@ -91,7 +91,7 @@ public class FastVector extends ArrayList
    *  Return a sorted copy of this vector.
    *  @param cmp The returned vector will be sorted with this Comparator.
    **/
-  public FastVector sort(final Comparator cmp) 
+  public FastVector mysort(final Comparator cmp)
   {
     final FastVector return_vector = (FastVector)clone();
     Collections.sort(return_vector, cmp);
diff --git a/uk/ac/sanger/artemis/util/StringVector.java b/uk/ac/sanger/artemis/util/StringVector.java
index 95f560d..ce3495b 100644
--- a/uk/ac/sanger/artemis/util/StringVector.java
+++ b/uk/ac/sanger/artemis/util/StringVector.java
@@ -51,11 +51,11 @@ public class StringVector extends Vector<String>
   /**
    *  Create a new vector which contains the given Strings.
    **/
-  public StringVector(final String[] new_strings) 
+  public StringVector(final String[] new_strings)
   {
     super(new_strings.length);
     int len = new_strings.length;
-    for(int i = 0; i < len; ++i) 
+    for(int i = 0; i < len; ++i)
       add(new_strings[i]);
   }
 
@@ -68,7 +68,7 @@ public class StringVector extends Vector<String>
   /**
    *  Create a new vector which contains the given Strings.
    **/
-  public StringVector(final StringVector new_strings) 
+  public StringVector(final StringVector new_strings)
   {
     super(new_strings);
   }
@@ -76,7 +76,7 @@ public class StringVector extends Vector<String>
   /**
    *  Call add() on each of the String objects in the given StringVector.
    **/
-  public void add(final StringVector new_strings) 
+  public void add(final StringVector new_strings)
   {
     for (int i = 0; i < new_strings.size(); ++i)
       add (new_strings.elementAt(i));
@@ -87,19 +87,19 @@ public class StringVector extends Vector<String>
    *  package.
    */
   public void sort()
-  { 
+  {
     final Comparator<String> comparator = new Comparator<String>()
     {
-      public int compare(String fst, String snd) 
+      public int compare(String fst, String snd)
       {
-        if(fst == null) 
+        if(fst == null)
         {
           if(snd == null)
             return 0;
           else
             return -1;
-        } 
-        else 
+        }
+        else
         {
           if(snd == null)
             return 1;
@@ -114,7 +114,7 @@ public class StringVector extends Vector<String>
   /**
    *  Return a new copy of this object.
    **/
-  public StringVector copy() 
+  public StringVector copy()
   {
     return new StringVector(this);
   }
@@ -132,7 +132,7 @@ public class StringVector extends Vector<String>
    **/
   public static StringVector getStrings(final String argument,
                                         final String delim,
-                                        final boolean keep_zero_char_toks) 
+                                        final boolean keep_zero_char_toks)
   {
     final StringVector strVector = new StringVector();
 
@@ -153,23 +153,23 @@ public class StringVector extends Vector<String>
 
       if(idx2 < 0)
         idx2 = argLen;
- 
+
       tok = argument.substring(idx1,idx2);
       idx1 = idx2+1;
 
       if(tok.length() == 1 &&
-          delim.indexOf(tok.charAt(0)) != -1) 
+          delim.indexOf(tok.charAt(0)) != -1)
       {
         // ignore the split characters
         if(keep_zero_char_toks &&
            (lastTok == null ||
             lastTok != null && lastTok.length () == 1 &&
-            delim.indexOf (lastTok) != -1)) 
+            delim.indexOf (lastTok) != -1))
         {
           // add a space because of two split_characters in a row
           strVector.add("");
         }
-      } 
+      }
       else
         strVector.add(tok);
 
@@ -186,7 +186,7 @@ public class StringVector extends Vector<String>
    *  vector will be zero length.
    **/
   public static StringVector getStrings(final String argument,
-                                        final String split_characters) 
+                                        final String split_characters)
   {
     return getStrings(argument, split_characters, false);
   }
@@ -198,7 +198,7 @@ public class StringVector extends Vector<String>
    *  String is zero length or it consists only of whitespace, the return
    *  vector will be zero length.
    **/
-  public static StringVector getStrings(final String argument) 
+  public static StringVector getStrings(final String argument)
   {
     return getStrings(argument, " ", false);
   }

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/artemis.git



More information about the debian-med-commit mailing list