Skip to content
Snippets Groups Projects
LocationParseNode.java 28.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • tjc's avatar
    tjc committed
    /* LocationParseNode.java
     *
     * created: Mon Oct  5 1998
     *
     * This file is part of Artemis
     *
     * Copyright (C) 1998,1999,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.
     *
    
    tjc's avatar
    tjc committed
     * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/LocationParseNode.java,v 1.7 2005-11-15 14:02:37 tjc Exp $
    
    tjc's avatar
    tjc committed
     */
    
    package uk.ac.sanger.artemis.io;
    
    
    tjc's avatar
    tjc committed
    import javax.swing.JOptionPane;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.util.OutOfRangeException;
    
    /**
     *  LocationParseNode class represents one node in the parse tree tree of a
     *  Location.  It is a utility class for EMBL.Location.
     *
     *  @author Kim Rutherford
    
    tjc's avatar
    tjc committed
     *  @version $Id: LocationParseNode.java,v 1.7 2005-11-15 14:02:37 tjc Exp $
    
    tjc's avatar
    tjc committed
     *
     **/
    
    
    tjc's avatar
    tjc committed
    class LocationParseNode extends EMBLObject
    {
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
      /** Node type for an unknown or error node. */
    
    tjc's avatar
    tjc committed
      final static public int UNKNOWN     = 0;
    
    tjc's avatar
    tjc committed
    
      /** Node type for complement (...)          */
    
    tjc's avatar
    tjc committed
      final static public int COMPLEMENT  = 1;
    
    tjc's avatar
    tjc committed
    
      /** Node type for join (...)                */
    
    tjc's avatar
    tjc committed
      final static public int JOIN        = 2;
    
    tjc's avatar
    tjc committed
    
      /** Node type for order (...)               */
      final static public int ORDER       = 3;
    
      /** Node type for number..number            */
      final static public int RANGE       = 4;
    
      /** Node type for a location reference to another entry */
      final static public int ENTRY_RANGE = 5;
    
    
    tjc's avatar
    tjc committed
      /**
    
    tjc's avatar
    tjc committed
       *  The node type can be COMPLEMENT, JOIN, ORDER, RANGE or ENTRY_RANGE
    
    tjc's avatar
    tjc committed
       **/
    
    tjc's avatar
    tjc committed
      private int node_type;
    
    
    tjc's avatar
    tjc committed
      /**
    
    tjc's avatar
    tjc committed
       *  If this LocationParseNode has a node type of JOIN or ORDER this member
       *  will point to an LocationParseNode object containing the children of
       *  this node.  If the node type is COMPLEMENT this member will point to an
       *  LocationParseNode object containing the child node of the complement.
       *  If the node type is RANGE this member will point to a Range object.
       *  If the node type is ENTRY_RANGE this will point to a LocationParseNode
       *  of type RANGE and the entry_name member this object will be set.
    
    tjc's avatar
    tjc committed
       **/
    
    tjc's avatar
    tjc committed
      private Object child;
    
    
    tjc's avatar
    tjc committed
      /**
    
    tjc's avatar
    tjc committed
       *  If this LocationParseNode has a node type of ENTRY_RANGE this member
       *  will give the name of the entry, otherwise it will be null.
    
    tjc's avatar
    tjc committed
       **/
    
    tjc's avatar
    tjc committed
      private String entry_name;
    
    tjc's avatar
    tjc committed
    
      /**
       *  Create a new LocationParseNode object with a type given by the first
       *  argument and sub nodes given by the second.  The type should be JOIN or
       *  ORDER.
       **/
    
    tjc's avatar
    tjc committed
      LocationParseNode(final int node_type,
                        final LocationParseNodeVector children) 
      {
        if(!(node_type == JOIN || node_type == ORDER)) 
          throw new Error("LocationParseNode constructor was called with the wrong type");
    
    tjc's avatar
    tjc committed
    
        this.node_type = node_type;
        this.child = children;
    
    
    tjc's avatar
    tjc committed
        if(children.size() < 1) 
    
    tjc's avatar
    tjc committed
          throw new Error("A functional must have at least one argument");
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Create a new LocationParseNode object of type COMPLEMENT with child node
       *  given by argument.
       *  @param type must be COMPLEMENT
       **/
    
    tjc's avatar
    tjc committed
      LocationParseNode(final int type,
                        final LocationParseNode child) 
      {
        if(type != COMPLEMENT) 
          throw new Error("LocationParseNode constructor was called with the wrong type");
    
    
    tjc's avatar
    tjc committed
        this.node_type = COMPLEMENT;
        this.child = child;
      }
    
      /**
       *  Create a new LocationParseNode object of type ENTRY_RANGE with the given
       *  node as the child.
       **/
    
    tjc's avatar
    tjc committed
      LocationParseNode(final String entry_name, final LocationParseNode node) 
      {
        this.node_type  = ENTRY_RANGE;
        this.child      = node;
    
    tjc's avatar
    tjc committed
        this.entry_name = entry_name;
      }
    
      /**
       *  Create a new LocationParseNode object of type RANGE with range
       *  given by argument.
       **/
    
    tjc's avatar
    tjc committed
      public LocationParseNode(final Range range) 
      {
    
    tjc's avatar
    tjc committed
        this.node_type = RANGE;
        this.child = range;
      }
    
      /**
       *  Return the type of this node.
       **/
    
    tjc's avatar
    tjc committed
      public int getType() 
      {
    
    tjc's avatar
    tjc committed
        return node_type;
      }
    
      /**
       *  Returns the value of this LocationParseNode and it's children as a
    
    tjc's avatar
    tjc committed
       *  string.  The method will call toString() on the children of this node
    
    tjc's avatar
    tjc committed
       *  and return those strings as part of the result.
       **/
    
    tjc's avatar
    tjc committed
      public String toString() 
      {
    
    tjc's avatar
    tjc committed
        final String return_string;
    
    tjc's avatar
    tjc committed
        switch(getType())
        {
            case COMPLEMENT:
    
    tjc's avatar
    tjc committed
            return_string = "complement(" + toStringChild() + ")";
    
    tjc's avatar
    tjc committed
            break;
          case JOIN:
    
    tjc's avatar
    tjc committed
            return_string = "join(" + toStringChildren() + ")";
    
    tjc's avatar
    tjc committed
            break;
          case ORDER:
    
    tjc's avatar
    tjc committed
            return_string = "order(" + toStringChildren() + ")";
    
    tjc's avatar
    tjc committed
            break;
          case RANGE:
    
    tjc's avatar
    tjc committed
            return_string = child.toString();
    
    tjc's avatar
    tjc committed
            break;
          case ENTRY_RANGE:
    
    tjc's avatar
    tjc committed
            return_string = entry_name + ":" + toStringChild();
    
    tjc's avatar
    tjc committed
            break;
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
          default:
            return_string = null;
    
    tjc's avatar
    tjc committed
        }
    
        return return_string;
      }
    
      /**
       *  Find the node containing old_range and replace the Range with new_range.
       *  @return true if the node was successfully replaced.
       **/
    
    tjc's avatar
    tjc committed
      boolean changeRange(final Range old_range,
                          final Range new_range) 
      {
        switch(getType())
        {
          case ENTRY_RANGE:
          case COMPLEMENT:
    
    tjc's avatar
    tjc committed
            return ((LocationParseNode) child).changeRange(old_range, new_range);
    
    tjc's avatar
    tjc committed
          case JOIN:
          case ORDER:
    
    tjc's avatar
    tjc committed
            final LocationParseNodeVector children = getChildren();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            for(int i = 0; i < children.size(); ++i) 
            {
    
    tjc's avatar
    tjc committed
              final LocationParseNode this_child = children.elementAt(i);
    
    tjc's avatar
    tjc committed
      
              final boolean return_value =
    
    tjc's avatar
    tjc committed
                this_child.changeRange(old_range, new_range);
    
    tjc's avatar
    tjc committed
      
              if(return_value) 
                return true;
    
    tjc's avatar
    tjc committed
            }
            return false;
    
    tjc's avatar
    tjc committed
          case RANGE:
            if(getRange().equals(old_range))
            {
    
    tjc's avatar
    tjc committed
              setRange(new_range);
    
    tjc's avatar
    tjc committed
              return true;
            } 
            else 
              return false;
          default:
            return false;
    
    tjc's avatar
    tjc committed
        }
      }
    
      /**
       *  Make a JOIN node from the given RANGE node.
       **/
    
    tjc's avatar
    tjc committed
      private LocationParseNode makeJoinFromRange(final LocationParseNode node) 
      {
        final LocationParseNodeVector children = new LocationParseNodeVector();
        children.addElement(node);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        return new LocationParseNode(JOIN, children);
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Add a RANGE node to the current JOIN or ORDER node.
       **/
    
    tjc's avatar
    tjc committed
      private void addRangeToJoin(final LocationParseNode new_node) 
      {
        final LocationParseNodeVector children = getChildren();
        final int new_range_start = new_node.getRange().getStart();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        if(children.elementAt(0).getType() == COMPLEMENT)
        {
    
    tjc's avatar
    tjc committed
          final LocationParseNode complement_node =
    
    tjc's avatar
    tjc committed
            new LocationParseNode(COMPLEMENT, new_node);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
          for(int i = children.size() - 1 ; i >= 0 ; --i) 
          {
            final LocationParseNode this_child = children.elementAt(i);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            if(this_child.getType() != COMPLEMENT) 
    
    tjc's avatar
    tjc committed
              continue;
    
            final LocationParseNode complement_child =
    
    tjc's avatar
    tjc committed
              this_child.getComplementChild();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            if(complement_child.getType() == RANGE)
            {
              final Range child_range = complement_child.getRange();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
              if(child_range.getStart() > new_range_start) 
              {
                children.insertElementAt(complement_node, i + 1);
    
    tjc's avatar
    tjc committed
                return;
              }
            }
          }
    
    tjc's avatar
    tjc committed
          children.insertElementAt(complement_node, 0);
        }
        else
        {
    
    tjc's avatar
    tjc committed
          for(int i = 0 ; i < children.size() ; ++i) 
    
    tjc's avatar
    tjc committed
          {
    
    tjc's avatar
    tjc committed
            final LocationParseNode this_child = children.elementAt(i);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            if(this_child.getType() == RANGE &&
               this_child.getRange().getStart() > new_range_start) 
    
    tjc's avatar
    tjc committed
            {
    
    tjc's avatar
    tjc committed
              children.insertElementAt(new_node, i);
    
    tjc's avatar
    tjc committed
              return;
            }
    
          }
    
    tjc's avatar
    tjc committed
          children.addElementAtEnd(new_node);
    
    tjc's avatar
    tjc committed
        }
      }
    
      /**
       *  Add new_node to this LocationParseNode (or its children).  new_node must
       *  be a RANGE node.
       *  @return a changed tree if the node was successfully added or the old
       *    tree otherwise.
       **/
    
    tjc's avatar
    tjc committed
      LocationParseNode addRangeNode(final LocationParseNode new_node) 
      {
        switch(getType())
        {
          case ENTRY_RANGE:
            return this;
          case RANGE:
          case COMPLEMENT:
            return makeJoinFromRange(this).addRangeNode(new_node);
          case JOIN:
          case ORDER:
            addRangeToJoin(new_node);
            return this;
          default:
            throw new Error("internal error - unknown location node type");
    
    tjc's avatar
    tjc committed
        }
      }
    
      /**
       *  Remove a RANGE node to the current JOIN or ORDER node.
       *  @return a new COMPLEMENT or RANGE node if this node currently has two
       *    children.  returns this node otherwise.
       **/
    
    tjc's avatar
    tjc committed
      private LocationParseNode removeRangeFromJoin(final Range remove_range) 
      {
        final LocationParseNodeVector children = getChildren();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        if(children.size() > 2) 
        {
          for(int i = 0 ; i < children.size() ; ++i) 
          {
            final LocationParseNode this_child = children.elementAt(i);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            if(this_child.getType() == COMPLEMENT) 
            {
    
    tjc's avatar
    tjc committed
              final LocationParseNode complement_child =
    
    tjc's avatar
    tjc committed
                this_child.getComplementChild();
              if(complement_child.getType() == RANGE &&
                 complement_child.getRange().equals(remove_range)) 
              {
                children.removeElement(this_child);
    
    tjc's avatar
    tjc committed
                return this;
              }
    
    tjc's avatar
    tjc committed
            } 
            else 
            {
              if(this_child.getType() == RANGE &&
                  this_child.getRange().equals(remove_range)) 
              {
                children.removeElement(this_child);
    
    tjc's avatar
    tjc committed
                return this;
              }
            }
          }
    
    tjc's avatar
    tjc committed
        } 
        else
        {
          for(int i = 0 ; i < children.size() ; ++i) 
          {
            final LocationParseNode this_child = children.elementAt(i);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            if(this_child.getType() == COMPLEMENT) 
            {
    
    tjc's avatar
    tjc committed
              final LocationParseNode complement_child =
    
    tjc's avatar
    tjc committed
                this_child.getComplementChild();
              if(complement_child.getType() == RANGE &&
                  complement_child.getRange().equals(remove_range)) 
              {
                children.removeElement(this_child);
                return children.elementAt(0);
    
    tjc's avatar
    tjc committed
              }
    
    tjc's avatar
    tjc committed
            } 
            else
            {
              if(this_child.getType() == RANGE &&
                  this_child.getRange().equals(remove_range)) 
              {
                children.removeElement(this_child);
                return children.elementAt(0);
    
    tjc's avatar
    tjc committed
              }
            }
          }
        }
        return this;
      }
    
      /**
       *  Remove the node containing remove_range from this LocationParseNode (or
       *  its children).
       *  @return a changed tree if the node was successfully removed or the old
       *    tree otherwise.
       **/
    
    tjc's avatar
    tjc committed
      LocationParseNode removeRange(final Range remove_range) 
      {
        switch(getType()) 
        {
          case ENTRY_RANGE:
            return this;
          case RANGE:
          case COMPLEMENT:
    
    tjc's avatar
    tjc committed
            throw new Error("internal error - inconsistent location");
    
    tjc's avatar
    tjc committed
          case JOIN:
          case ORDER:
    
    tjc's avatar
    tjc committed
            return removeRangeFromJoin(remove_range);
    
    tjc's avatar
    tjc committed
          default:
    
    tjc's avatar
    tjc committed
            throw new Error("internal error - unknown location node type");
    
    tjc's avatar
    tjc committed
        }
      }
    
    
      /**
       *  Return a reversed and complemented copy of this Location.
       *  @param sequence_length The length of the sequence that this Location is
       *    associated with.
       *  @param offset this is set to zero if the whole sequence is being
       *    operated on or to the start of the region being reverse complemented.
       *  @return a reversed and complemented tree.
       **/
      LocationParseNode reverseComplement(final int sequence_length,
                                          final int offset) 
    
    tjc's avatar
    tjc committed
      {
        try
        {
          switch(getType())
          {
            case ENTRY_RANGE:
              return this;
            case RANGE:
    
    tjc's avatar
    tjc committed
            {
    
    tjc's avatar
    tjc committed
              final Range range = getRange();
    
              final int start = sequence_length - (range.getEnd() - offset + 1) + offset;
              final int end   = sequence_length - (range.getStart() - offset + 1) + offset;
    
    //        System.out.println("LocationParseNode.reverseComplement() HERE "+ 
    //                  start+ ".."+ end +"   sequence_length="+sequence_length+
    //                 "   range.getEnd()="+range.getEnd()+ "   range.getStart()="+range.getStart()+
    
    tjc's avatar
    tjc committed
    //                 "   offset="+offset+ " "+
    //                 start_old+ ".."+end_old);
    
    
              final Range new_range = new Range(start, end);
    
    tjc's avatar
    tjc committed
              final LocationParseNode new_range_node =
    
    tjc's avatar
    tjc committed
                new LocationParseNode(new_range);
              return new LocationParseNode(COMPLEMENT, new_range_node);
    
    tjc's avatar
    tjc committed
            }
    
    tjc's avatar
    tjc committed
            case COMPLEMENT:
    
    tjc's avatar
    tjc committed
            {
    
    tjc's avatar
    tjc committed
              final LocationParseNode child = getComplementChild();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
              if(child.getType() == RANGE) 
              {
                final Range range = child.getRange();
    
                final int start = sequence_length - (range.getEnd() - offset + 1) + offset;
                final int end   = sequence_length - (range.getStart() - offset + 1) + offset;
    
                final Range new_range = new Range(start, end);
    //            new Range(sequence_length - (range.getEnd() - offset + 1) + offset,
    //                      sequence_length - (range.getStart() - offset + 1) + offset);
    
    tjc's avatar
    tjc committed
                return new LocationParseNode(new_range);
    
    tjc's avatar
    tjc committed
              }
    
    tjc's avatar
    tjc committed
              else
                return child;
    
    tjc's avatar
    tjc committed
            }
    
    tjc's avatar
    tjc committed
            case JOIN:
            case ORDER:
    
    tjc's avatar
    tjc committed
            {
    
    tjc's avatar
    tjc committed
              final LocationParseNodeVector children = getChildren();
    
    tjc's avatar
    tjc committed
    
              final LocationParseNodeVector new_children =
    
    tjc's avatar
    tjc committed
                new LocationParseNodeVector();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
              for(int i = 0 ; i < children.size() ; ++i) 
              {
                final LocationParseNode this_child = children.elementAt(i);
    
    tjc's avatar
    tjc committed
    
                final LocationParseNode new_child =
    
                  this_child.reverseComplement(sequence_length, offset);
    
    tjc's avatar
    tjc committed
                new_children.addElementAtEnd(new_child);
    
    tjc's avatar
    tjc committed
              }
    
              child = new_children;
    
              return this;
            }
    
    tjc's avatar
    tjc committed
            default:
              throw new Error("internal error - unknown location node type: " +
                             getType());
    
    tjc's avatar
    tjc committed
          }
    
    tjc's avatar
    tjc committed
        } 
        catch(OutOfRangeException e) 
        {
          throw new Error("internal error - unexpected exception: " + e);
    
    tjc's avatar
    tjc committed
        }
      }
    
      /**
       *  Returns a "canonical" version of the parse tree starting at the current
       *  node.  In this context a canonical location is one where any join
       *  or order functional is first in the location string.  In a canonical
       *  parse tree only the head node can be a JOIN or ORDER node.  Also in a
       *  "join(complement(..." location the exons must be listed in high to low
       *  order ie. (30..40,10..20) not (10..20,30..40).  Here is an example: <p>
       *  The canonical version of this string: <p>
       *  "complement(join(100..200,400..500))" is: <p>
       *  "join(complement(400..500),complement(100..200))" <p>
       *  The canonical version is less readable but is more standard.
       *  @return A canonical tree or null if the location is nonsense, for
       *    example "join(complement(join(..."
       **/
    
    tjc's avatar
    tjc committed
      LocationParseNode getCanonical() 
      {
        if(!isValid()) 
    
    tjc's avatar
    tjc committed
          return null;
    
        // this is a new parse tree (a copy of tree starting at this node) with
        // the JOIN or ORDER node (if any) at the top.
    
    tjc's avatar
    tjc committed
        final LocationParseNode new_tree = getCanonicalComplement();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        if(new_tree.getType() != JOIN && new_tree.getType() != ORDER) 
    
    tjc's avatar
    tjc committed
          return new_tree;
    
        // now put the children of the JOIN or ORDER node in the correct order
        final LocationParseNodeVector new_children =
    
    tjc's avatar
    tjc committed
          (LocationParseNodeVector)new_tree.child;
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        if(new_children.size() >= 2)
        {
    
    tjc's avatar
    tjc committed
          // first move all ENTRY_RANGE nodes to the end
    
          final LocationParseNodeVector entry_range_nodes =
    
    tjc's avatar
    tjc committed
            new LocationParseNodeVector();
    
    
    tjc's avatar
    tjc committed
          for(int i = new_children.size() - 1 ; i >= 0 ; --i) 
    
    tjc's avatar
    tjc committed
          {
            final LocationParseNode node = new_children.elementAt(i);
            if(node.getType() == ENTRY_RANGE) 
            {
              new_children.removeElementAt(i);
              entry_range_nodes.addElementAtEnd(node);
            }
            else
            {
              if(node.getType() == COMPLEMENT &&
                 node.getComplementChild().getType() == ENTRY_RANGE) 
              {
                new_children.removeElementAt(i);
                entry_range_nodes.addElementAtEnd(node);
    
    tjc's avatar
    tjc committed
              }
            }
          }
    
    
    tjc's avatar
    tjc committed
          if(new_children.size() > 0)
    
    tjc's avatar
    tjc committed
          {
    
    tjc's avatar
    tjc committed
          
    
    tjc's avatar
    tjc committed
            // the new head node is a JOIN or ORDER and the children are RANGE
            // nodes so put the children in order - ascending left to right
    
    tjc's avatar
    tjc committed
            // or
            // the children are COMPLEMENT nodes so put the children in order 
            // - ascending right to left
    
    tjc's avatar
    tjc committed
    
            // bubble sort in place putting the RANGE with the smallest start
            // base first
    
    
    tjc's avatar
    tjc committed
            boolean change_happened = true;
            while(change_happened) 
    
    tjc's avatar
    tjc committed
            {
    
    tjc's avatar
    tjc committed
              change_happened = false;
              int this_start;
              int next_start;
    
    
    tjc's avatar
    tjc committed
              for(int i = 0 ; i < new_children.size() - 1 ; ++i) 
    
    tjc's avatar
    tjc committed
              {
                final LocationParseNode this_node = new_children.elementAt(i);
    
    tjc's avatar
    tjc committed
                final LocationParseNode next_node = new_children.elementAt(i+1);
    
                if(this_node.getType() == RANGE)
                  this_start = this_node.getRange().getStart();
                else
                  this_start = this_node.getComplementChild().getRange().getEnd();
    
                if(next_node.getType() == RANGE)
                  next_start = next_node.getRange().getStart();
                else
                  next_start = next_node.getComplementChild().getRange().getEnd();
    
                if(this_node.getType() != RANGE && next_node.getType() != RANGE)
                {
                  if(this_start < next_start)      // swap the two nodes
                  {
                    new_children.setElementAt(next_node, i);
                    new_children.setElementAt(this_node, i + 1);
                    change_happened = true;
                  }
                }
                else if(this_start > next_start)   // swap the two nodes
    
    tjc's avatar
    tjc committed
                {
                  new_children.setElementAt(next_node, i);
                  new_children.setElementAt(this_node, i + 1);
    
    tjc's avatar
    tjc committed
                  change_happened = true;
                }
              }
            }
          }
    
    
    tjc's avatar
    tjc committed
          for(int i = 0 ; i < entry_range_nodes.size() ; ++i) 
            new_children.addElementAtEnd(entry_range_nodes.elementAt(i));
    
    tjc's avatar
    tjc committed
        } 
        else 
    
    tjc's avatar
    tjc committed
          throw new Error("internal error - a JOIN should have > 1 child");
    
    tjc's avatar
    tjc committed
    
        return new_tree;
      }
    
      /**
       *  Return true if and only if the tree starting at this node is valid.
       *  "valid" means it has at most two levels of functionals and the lowest
       *  level consists only of RANGE nodes.  Also the children of a JOIN or
       *  ORDER node must be all RANGE nodes or all COMPLEMENT nodes - there can't
       *  be a mixture.
       **/
    
    tjc's avatar
    tjc committed
      public boolean isValid() 
    
    tjc's avatar
    tjc committed
      {
    
    tjc's avatar
    tjc committed
        // 2 means check that we have at most two levels.
        // UNKNOWN means the parent isn't a JOIN, ORDER or COMPLEMENT node (there
        // is no parent at all.)
    
    tjc's avatar
    tjc committed
        return isValid(2, UNKNOWN);
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Return the child node of this node.  This node must be a COMPLEMENT type
       *  or an Error will be thrown.
       **/
    
    tjc's avatar
    tjc committed
      public LocationParseNode getComplementChild()
      {
        if(getType() != COMPLEMENT) 
    
    tjc's avatar
    tjc committed
          throw new Error("in LocationParseNode.getComplementChild() - node " +
    
    tjc's avatar
    tjc committed
                          "is not a COMPLEMENT");
        
        return (LocationParseNode)child;
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Return the children of this node.  If this node is not a JOIN or ORDER
       *  type then an error will be thrown.
       **/
    
    tjc's avatar
    tjc committed
      public LocationParseNodeVector getChildren()
      {
        if(!(getType() == JOIN || getType() == ORDER)) 
          throw new Error("in LocationParseNode.getChildren() - node " +
                          "is not a JOIN or ORDER");
        
        return (LocationParseNodeVector)child;
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Return the children of this node.  This node must be a JOIN type node or
       *  an Error will be thrown.
       **/
    
    tjc's avatar
    tjc committed
      public LocationParseNodeVector getJoinChildren()
      {
        if(getType() != JOIN) 
    
    tjc's avatar
    tjc committed
          throw new Error("in LocationParseNode.getJoinChildren() - node " +
    
    tjc's avatar
    tjc committed
                          "is not a JOIN");
        
    
    tjc's avatar
    tjc committed
        return (LocationParseNodeVector) child;
      }
    
      /**
       *  Return the children of this node.  This node must be a JOIN type node or
       *  an Error will be thrown.
       **/
    
    tjc's avatar
    tjc committed
      public LocationParseNodeVector getOrderChildren()
      {
        if(getType() != ORDER) 
    
    tjc's avatar
    tjc committed
          throw new Error("in LocationParseNode.getOrderChildren() - node " +
    
    tjc's avatar
    tjc committed
                           "is not an ORDER");
    
    tjc's avatar
    tjc committed
        
        return(LocationParseNodeVector) child;
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Return the child of this ENTRY_RANGE node.  This node must be an
       *  ENTRY_RANGE node or an Error will be thrown.  The returned node will be
       *  of type RANGE.
       **/
    
    tjc's avatar
    tjc committed
      public LocationParseNode getEntryRangeChild()
      {
        if(getType() != ENTRY_RANGE) 
    
    tjc's avatar
    tjc committed
          throw new Error("in LocationParseNode.getEntryRangeChild() - node " +
    
    tjc's avatar
    tjc committed
                          "is not an ENTRY_RANGE");
        
        return (LocationParseNode)child;
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Return the Range object for this node.  This node must be a RANGE type
       *  node or an Error will be thrown.
       **/
    
    tjc's avatar
    tjc committed
      public Range getRange() 
      {
        if(getType() != RANGE)
    
    tjc's avatar
    tjc committed
          throw new Error("in LocationParseNode.getRange() - node " +
                          "is not a RANGE " + getType());
    
    tjc's avatar
    tjc committed
        
        return (Range)child;
    
    tjc's avatar
    tjc committed
      }
    
    
      /**
    
    tjc's avatar
    tjc committed
       *  Internal method that implements copy() and copyClean().
    
    tjc's avatar
    tjc committed
       *  @param copy_user_data user_data (from EMBLObject) will be copied if and
       *    only if this parameter is true.
       **/
    
    tjc's avatar
    tjc committed
      private LocationParseNode copyInternal(final boolean copy_user_data) 
      {
    
    tjc's avatar
    tjc committed
        final LocationParseNode new_node;
    
    
    tjc's avatar
    tjc committed
        switch(getType())
        {
          case COMPLEMENT:
    
    tjc's avatar
    tjc committed
          {
            final LocationParseNode new_child =
    
    tjc's avatar
    tjc committed
              getComplementChild().copyInternal(copy_user_data);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            new_node = new LocationParseNode(getType(), new_child);
    
    tjc's avatar
    tjc committed
          }
          break;
    
    tjc's avatar
    tjc committed
          case JOIN:
          case ORDER:
            // copy the children
            final LocationParseNodeVector children_copy =
              new LocationParseNodeVector();
            final LocationParseNodeVector children = (LocationParseNodeVector)child;
            for(int i = 0; i < children.size(); ++i) 
            {
              final LocationParseNode new_child =
                children.elementAt(i).copyInternal(copy_user_data);
              children_copy.addElement(new_child);
            }
            new_node = new LocationParseNode(getType(), children_copy);
            break;
          case RANGE:
            new_node = new LocationParseNode(getRange().copy());
            break;
          case ENTRY_RANGE:
    
    tjc's avatar
    tjc committed
          {
            final LocationParseNode new_child =
    
    tjc's avatar
    tjc committed
              ((LocationParseNode)child).copyInternal(copy_user_data);
            new_node = new LocationParseNode(entry_name, new_child);
    
    tjc's avatar
    tjc committed
          }
          break;
    
    tjc's avatar
    tjc committed
          default:
            throw new Error("internal error - unknown location node type");
    
    tjc's avatar
    tjc committed
        }
    
    
    tjc's avatar
    tjc committed
        if(copy_user_data)
    
    tjc's avatar
    tjc committed
          new_node.setUserData(getUserData());
    
    tjc's avatar
    tjc committed
        else 
          new_node.setUserData(null);
       
    
    tjc's avatar
    tjc committed
        return new_node;
      }
    
      /**
       *  Return a copy of this node.  All children of this node will be copied as
       *  well.  No user_data will be copied.
       **/
    
    tjc's avatar
    tjc committed
      LocationParseNode copyClean()
      {
        return copyInternal(false);
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Return a copy of this node.  All children of this node and all user_data
       *  fields will be copied as well.
       **/
    
    tjc's avatar
    tjc committed
      LocationParseNode copy() 
      {
        return copyInternal(true);
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Set the value of a RANGE node.  If this node is not a RANGE node an
       *  Error will be thrown.
       **/
    
    tjc's avatar
    tjc committed
      void setRange(final Range range) 
      {
        if(getType() != RANGE) 
    
    tjc's avatar
    tjc committed
          throw new Error("in LocationParseNode.getRange() - node " +
                          "is not a RANGE " + getType());
    
    tjc's avatar
    tjc committed
        this.child = range;
      }
    
      /**
    
    tjc's avatar
    tjc committed
       *  Returns the result of calling toString() on each of the children of
    
    tjc's avatar
    tjc committed
       *  this node separated by ",".
       **/
    
    tjc's avatar
    tjc committed
      private String toStringChildren() 
      {
    
    tjc's avatar
    tjc committed
        String return_string = "";
    
        final LocationParseNodeVector children = (LocationParseNodeVector) child;
    
    
    tjc's avatar
    tjc committed
        if(getType() == JOIN || getType() == ORDER) 
    
    tjc's avatar
    tjc committed
        {
    
    tjc's avatar
    tjc committed
          for(int i = 0; i<children.size(); ++i) 
    
    tjc's avatar
    tjc committed
          {
            if(0 != i) 
    
    tjc's avatar
    tjc committed
              return_string = return_string + ",";
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            //        System.out.println("adding:" + children.elementAt(i).toString());
            return_string = return_string + children.elementAt(i).toString();
    
    tjc's avatar
    tjc committed
          }
        }
    
    tjc's avatar
    tjc committed
        else 
    
    tjc's avatar
    tjc committed
          throw new Error ("LocationParseNode.toStringChildren(): Illegal type");
    
    tjc's avatar
    tjc committed
    
        return return_string;
      }
    
      /**
       *  Turn an Object into a String.  The Object must be an instance of String,
       *  LocationParseNode or Range.
       **/
    
    tjc's avatar
    tjc committed
      private String toStringChild() 
      {
        if(child instanceof LocationParseNode) 
          return ((LocationParseNode)child).toString();
        else 
    
    tjc's avatar
    tjc committed
          throw new Error("LocationParseNode.toStringChild() was called on " +
    
    tjc's avatar
    tjc committed
                          "the wrong type of Object");
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Return a complemented copy of a node.  ie. COMPLEMENT(THING) will become
       *  THING and THING will become COMPLEMENT(THING).
       **/
    
    tjc's avatar
    tjc committed
      LocationParseNode getNodeComplement() 
      {
        if(getType() == COMPLEMENT) 
    
    tjc's avatar
    tjc committed
          return getComplementChild().copy();
    
    tjc's avatar
    tjc committed
        else 
    
    tjc's avatar
    tjc committed
          return new LocationParseNode(COMPLEMENT, copy());
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Return a copy of the tree starting at this node with any
       *  "COMPLEMENT(JOIN(..." nodes turned to "JOIN(COMPLEMENT(..."
       **/
    
    tjc's avatar
    tjc committed
      private LocationParseNode getCanonicalComplement() 
      {
        if(getType() == COMPLEMENT &&
           (getComplementChild().getType() == JOIN ||
            getComplementChild().getType() == ORDER)) 
        {
          final LocationParseNode complement_child = getComplementChild();
    
    tjc's avatar
    tjc committed
    
          // complement each child of the JOIN or ORDER
          final LocationParseNodeVector complemented_children =
    
    tjc's avatar
    tjc committed
            new LocationParseNodeVector();
    
    tjc's avatar
    tjc committed
    
          final LocationParseNodeVector complement_children =
    
    tjc's avatar
    tjc committed
            (LocationParseNodeVector)complement_child.child;
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
          for(int i = 0 ; i < complement_children.size() ; ++i) 
          {
    
    tjc's avatar
    tjc committed
            final LocationParseNode complemented_child =
    
    tjc's avatar
    tjc committed
              complement_children.elementAt(i).getNodeComplement();
            complemented_children.addElement(complemented_child);
    
    tjc's avatar
    tjc committed
          }
    
    
    tjc's avatar
    tjc committed
          return new LocationParseNode(complement_child.getType(),
    
    tjc's avatar
    tjc committed
                                        complemented_children);
        }
    
    tjc's avatar
    tjc committed
        else   // no changes needed so just make a copy
          return this.copy();
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Return true if and only if the tree starting at this node is valid.
    
    tjc's avatar
    tjc committed
       *  @see #isValid()
    
    tjc's avatar
    tjc committed
       *  @param max_levels The maximum number of levels below this node - more
       *    than this number of levels means that the tree is invalid.
       **/
    
    tjc's avatar
    tjc committed
      private boolean isValid(final int max_levels, final int parent_type) 
      {
        if(getType() == RANGE) 
        {
    
    tjc's avatar
    tjc committed
          // a RANGE a valid possibility at any level
          return true;
        }
    
    
    tjc's avatar
    tjc committed
        if(getType() == ENTRY_RANGE) 
        {
          return getEntryRangeChild().isValid();
    
    tjc's avatar
    tjc committed
        }
    
    
    tjc's avatar
    tjc committed
        if(max_levels == 0) 
    
    tjc's avatar
    tjc committed
        {
    
    tjc's avatar
    tjc committed
          // we are at the lowest level and this isn't a RANGE so this tree is
          // nonsense
          return false;
    
    tjc's avatar
    tjc committed
        } 
        else 
        {
          switch(getType()) 
          {
            case JOIN:
            case ORDER:
    
    tjc's avatar
    tjc committed
              if(parent_type == UNKNOWN || parent_type == COMPLEMENT) 
    
    tjc's avatar
    tjc committed
              {
                // parent has the correct type
    
                // this is set to true if any of the children are RANGE nodes
                boolean found_range_child = false;
    
                // this is set to true if any of the children are COMPLEMENT nodes
                boolean found_complement_child = false;
      
                final LocationParseNodeVector children =
                  (LocationParseNodeVector) child;
    
    
    tjc's avatar
    tjc committed
                for(int i = 0; i < children.size(); ++i) 
    
    tjc's avatar
    tjc committed
                {
                  final LocationParseNode child_node = children.elementAt(i);
                  final boolean return_val =
                    child_node.isValid(max_levels - 1, getType());
    
    tjc's avatar
    tjc committed
    
                  // one of the children is invalid so this node is invalid
    
    tjc's avatar
    tjc committed
                  if(!return_val) 
                    return false;
    
                  if(child_node.getType() == RANGE)
                    found_range_child = true;
      
                  if(child_node.getType() == COMPLEMENT) 
                    found_complement_child = true;
    
    tjc's avatar
    tjc committed
                }
    
    
    tjc's avatar
    tjc committed
                // all the children were OK
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
                if(found_range_child && found_complement_child) 
                {
                  // a mixture of RANGE and COMPLEMENT nodes is not valid
    
    tjc's avatar
    tjc committed
                  //return false;
    
    tjc's avatar
    tjc committed
                  JOptionPane.showMessageDialog(null, "Contains trans-spliced feature!\n"+toString(),
    
    tjc's avatar
    tjc committed
                                                "Trans-spliced site found",
                                                JOptionPane.INFORMATION_MESSAGE);
                  return true;
    
    tjc's avatar
    tjc committed
                } 
                else 
                  return true;
    
    tjc's avatar
    tjc committed
              }
    
    tjc's avatar
    tjc committed
              else   // parent has the wrong type
                return false;
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            case COMPLEMENT:
              if(parent_type == UNKNOWN ||
                 parent_type == JOIN ||
                 parent_type == ORDER) 
              {
    
    tjc's avatar
    tjc committed
                return getComplementChild().isValid(max_levels - 1, getType());
    
    tjc's avatar
    tjc committed
              } 
              else    // parent has the wrong type (ie. it is a COMPLEMENT)
    
    tjc's avatar
    tjc committed
                return false;
    
    
    tjc's avatar
    tjc committed
            default:
              // anything else is invalid
    
    tjc's avatar
    tjc committed
              return false;
          }
        }
      }
    
    }