001package com.hfg.bio.phylogeny;
002
003import java.util.*;
004import java.util.List;
005import java.util.regex.Pattern;
006import java.awt.*;
007
008import com.hfg.util.AttributeMgr;
009import com.hfg.util.collection.CollectionUtil;
010import com.hfg.util.StringUtil;
011import com.hfg.network.Edge;
012
013
014//------------------------------------------------------------------------------
015/**
016 Object representation of a node in a Newick format phylogenetic tree.
017 Does not work with negative distance values.
018 <div>
019  @author J. Alex Taylor, hairyfatguy.com
020 </div>
021 */
022//------------------------------------------------------------------------------
023// com.hfg XML/HTML Coding Library
024//
025// This library is free software; you can redistribute it and/or
026// modify it under the terms of the GNU Lesser General Public
027// License as published by the Free Software Foundation; either
028// version 2.1 of the License, or (at your option) any later version.
029//
030// This library is distributed in the hope that it will be useful,
031// but WITHOUT ANY WARRANTY; without even the implied warranty of
032// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
033// Lesser General Public License for more details.
034//
035// You should have received a copy of the GNU Lesser General Public
036// License along with this library; if not, write to the Free Software
037// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
038//
039// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
040// jataylor@hairyfatguy.com
041//------------------------------------------------------------------------------
042
043public class PhyloNode
044{
045   private List<Edge<PhyloNode>> mEdges;
046   private String          mId;
047   private String          mLabel;
048   private Color           mColor;
049   private String          mTooltip;
050
051   private Integer         mLineIndex;
052   private Integer         mVericalLineStartIndex;
053   private Integer         mVericalLineEndIndex;
054   private Double          mAngle;
055
056   private AttributeMgr mAttributeMgr;
057
058   private static SubnodeCountComparator sSubnodeCountComparator = new PhyloNode().new SubnodeCountComparator();
059
060   private static final Pattern sWhitespacePattern = Pattern.compile("\\s+");
061
062   // cached values
063   private Float mDistanceFromRoot;
064
065   //--------------------------------------------------------------------------
066   public PhyloNode setId(int inValue)
067   {
068      return setId(inValue + "");
069   }
070
071   //--------------------------------------------------------------------------
072   public PhyloNode setId(String inValue)
073   {
074      mId = inValue;
075      return this;
076   }
077
078   //--------------------------------------------------------------------------
079   public String getId()
080   {
081      return mId;
082   }
083
084
085   //--------------------------------------------------------------------------
086   public PhyloNode setLabel(String inValue)
087   {
088      mLabel = inValue;
089      return this;
090   }
091
092   //--------------------------------------------------------------------------
093   public String getLabel()
094   {
095      return mLabel;
096   }
097
098
099   //--------------------------------------------------------------------------
100   public PhyloNode setTooltip(String inValue)
101   {
102      mTooltip = inValue;
103      return this;
104   }
105
106   //--------------------------------------------------------------------------
107   public String getTooltip()
108   {
109      return mTooltip;
110   }
111
112
113   //--------------------------------------------------------------------------
114   public PhyloNode setColor(Color inValue)
115   {
116      mColor = inValue;
117      return this;
118   }
119
120   //--------------------------------------------------------------------------
121   public Color getColor()
122   {
123      return mColor;
124   }
125
126
127   //--------------------------------------------------------------------------
128   public List<Edge<PhyloNode>> getEdges()
129   {
130      return mEdges;
131   }
132
133   //--------------------------------------------------------------------------
134   public void addEdge(PhyloNode in2ndNode, Float inDistance)
135   {
136      if (null == mEdges)
137      {
138         mEdges = new ArrayList<>(3);
139      }
140
141      Edge<PhyloNode> edge = new Edge<>(this, in2ndNode, inDistance);
142      mEdges.add(edge);
143      in2ndNode.addEdge(edge);
144   }
145
146
147   //--------------------------------------------------------------------------
148   public PhyloNode setAngle(Double inAngleInRadians)
149   {
150      mAngle = inAngleInRadians;
151      return this;
152   }
153
154   //--------------------------------------------------------------------------
155   public Double getAngle()
156   {
157      return mAngle;
158   }
159
160   //--------------------------------------------------------------------------
161   protected void addEdge(Edge<PhyloNode> inEdge)
162   {
163      if (null == mEdges)
164      {
165         mEdges = new ArrayList<>(3);
166      }
167
168      mEdges.add(inEdge);
169   }
170
171   //--------------------------------------------------------------------------
172   protected boolean removeEdge(Edge<PhyloNode> inEdge)
173   {
174      boolean result = false;
175      if (mEdges != null)
176      {
177         result = mEdges.remove(inEdge);
178         // Remove the edge from the other node as well.
179         if (this == inEdge.getFrom())
180         {
181            inEdge.setFrom(null);
182            if (inEdge.getTo() != null) inEdge.getTo().removeEdge(inEdge);
183         }
184         else if (this == inEdge.getTo())
185         {
186            inEdge.setTo(null);
187            if (inEdge.getFrom() != null) inEdge.getFrom().removeEdge(inEdge);
188         }
189      }
190
191      return result;
192   }
193
194   //--------------------------------------------------------------------------
195   protected boolean removeEdgeFrom(PhyloNode inNode)
196   {
197      boolean result = false;
198      if (mEdges != null)
199      {
200         for (Edge<PhyloNode> edge : mEdges)
201         {
202            if (edge.getFrom() == inNode)
203            {
204               mEdges.remove(edge);
205               edge.setTo(null);
206               // Remove the edge from the other node as well.
207               edge.getFrom().removeEdge(edge);
208               result = true;
209               break;
210            }
211         }
212      }
213
214      return result;
215   }
216
217   //--------------------------------------------------------------------------
218   public boolean isLeaf()
219   {
220      boolean isLeaf = true;
221      if (mEdges != null)
222      {
223         for (Edge edge : mEdges)
224         {
225            if (edge.getFrom().equals(this))
226            {
227               isLeaf = false;
228               break;
229            }
230         }
231      }
232      
233      return isLeaf;
234   }
235
236   //--------------------------------------------------------------------------
237   public void makeRoot()
238   {
239      // Fan out through the tree setting the directionality to flow from the root.
240      for (Edge<PhyloNode> edge : mEdges)
241      {
242         if (edge.getFrom() != this) edge.switchDirection();
243
244         edge.getTo().setRootDirection(edge);
245      }
246   }
247
248   //--------------------------------------------------------------------------
249   protected void setRootDirection(Edge<PhyloNode> inEdge)
250   {
251      for (Edge<PhyloNode> edge : mEdges)
252      {
253         if (edge == inEdge) continue;
254
255         if (edge.getFrom() != this) edge.switchDirection();
256
257         edge.getTo().setRootDirection(edge);
258      }
259   }
260
261 /*
262   //--------------------------------------------------------------------------
263   public Float getMaxDistance()
264   {
265      float maxChildDistance = 0;
266
267      if (CollectionUtil.hasValues(mChildNodes))
268      {
269         for (PhyloNode child : mChildNodes)
270         {
271            float childDistance = child.getMaxDistance();
272            if (childDistance > maxChildDistance)
273            {
274               maxChildDistance = childDistance;
275            }
276         }
277      }
278
279      return maxChildDistance + (getDistance() != null ? getDistance() : 0);
280   }
281
282   //--------------------------------------------------------------------------
283   public Float getDistance()
284   {
285      return mDistance;
286   }
287*/
288   //--------------------------------------------------------------------------
289   public PhyloNode getParentNode()
290   {
291      PhyloNode parent = null;
292
293      Edge<PhyloNode> parentEdge = getParentEdge();
294      if (parentEdge != null)
295      {
296         parent = parentEdge.getFrom();
297      }
298
299      return parent;
300   }
301
302   //--------------------------------------------------------------------------
303   public List<PhyloNode> getChildNodes()
304   {
305      List<PhyloNode> childNodes = null;
306      if (CollectionUtil.hasValues(mEdges))
307      {
308         for (Edge<PhyloNode> edge : mEdges)
309         {
310            if (edge.getFrom() == this)
311            {
312               if (null == childNodes)
313               {
314                  childNodes = new ArrayList<>(4);
315               }
316
317               childNodes.add(edge.getTo());
318            }
319         }
320      }
321
322      return childNodes;
323   }
324
325   //--------------------------------------------------------------------------
326   /**
327    Returns the root-facing edge.
328    @return the root-facing Edge for the node
329    */
330   public Edge<PhyloNode> getParentEdge()
331   {
332      Edge<PhyloNode> parentEdge = null;
333
334      if (CollectionUtil.hasValues(mEdges))
335      {
336         for (Edge<PhyloNode> edge : mEdges)
337         {
338            if (edge.getTo() == this)
339            {
340               parentEdge = edge;
341               break;
342            }
343         }
344      }
345
346      return parentEdge;
347   }
348
349   //--------------------------------------------------------------------------
350   /**
351    Returns the leaf-facing edges.
352    @return the leaf-facing Edge for the node
353    */
354   public List<Edge<PhyloNode>> getLeafFacingEdges()
355   {
356      List<Edge<PhyloNode>> leafFacingEdges = new ArrayList<>(3);
357
358      if (CollectionUtil.hasValues(mEdges))
359      {
360         for (Edge<PhyloNode> edge : mEdges)
361         {
362            if (edge.getFrom() == this)
363            {
364               leafFacingEdges.add(edge);
365            }
366         }
367      }
368
369      return leafFacingEdges;
370   }
371
372   //--------------------------------------------------------------------------
373   public void orderEdgesByLeafCount()
374   {
375      if (! isLeaf())
376      {
377         List<Edge<PhyloNode>> leafFacingEdges = getLeafFacingEdges();
378         if (CollectionUtil.hasValues(leafFacingEdges))
379         {
380            Collections.sort(leafFacingEdges, sSubnodeCountComparator);
381         }
382
383         List<Edge<PhyloNode>> newEdgeList = new ArrayList<>(mEdges.size());
384         for (Edge<PhyloNode> edge : mEdges)
385         {
386            if (edge.getFrom() != this)
387            {
388               newEdgeList.add(edge);
389            }
390         }
391
392         newEdgeList.addAll(leafFacingEdges);
393
394         mEdges = newEdgeList;
395      }
396   }
397
398   //--------------------------------------------------------------------------
399   public Float getDistanceFromRoot()
400   {
401      if (null == mDistanceFromRoot)
402      {
403         float distance = 0;
404         Edge<PhyloNode> parentEdge = getParentEdge();
405         if (parentEdge != null)
406         {
407            do
408            {
409               if (parentEdge.getDistance() != null) distance += parentEdge.getDistance();
410            }
411            while (parentEdge.getFrom() != null
412                  && (parentEdge = parentEdge.getFrom().getParentEdge()) != null);
413         }
414
415         mDistanceFromRoot = distance;
416      }
417
418      return mDistanceFromRoot;
419   }
420
421   //--------------------------------------------------------------------------
422   public Float getMaxDistanceToLeaf()
423   {
424      float maxDistance = 0;
425
426      if (! isLeaf())
427      {
428         for (Edge<PhyloNode> edge : mEdges)
429         {
430            if (edge.getFrom() == this)
431            {
432               float childDistance = 0;
433               if (edge.getDistance() != null) childDistance += edge.getDistance();
434               PhyloNode child = edge.getTo();
435               if (! child.isLeaf()) childDistance += child.getMaxDistanceToLeaf();
436
437               if (childDistance > maxDistance)
438               {
439                  maxDistance = childDistance;
440               }
441            }
442         }
443      }
444
445      return maxDistance;
446   }
447
448   //--------------------------------------------------------------------------
449   public int getSubnodeCount()
450   {
451      int subnodeCount = 0;
452
453      if (! isLeaf())
454      {
455         for (Edge<PhyloNode> edge : mEdges)
456         {
457            if (edge.getFrom() == this)
458            {
459               subnodeCount += 1 + edge.getTo().getSubnodeCount();
460            }
461         }
462      }
463
464      return subnodeCount;
465   }
466
467   //--------------------------------------------------------------------------
468   /**
469    Calculates the distance between two nodes in the tree.
470    @param inNode2 the node to calculate the distance to
471    @return the distance between this node and the specified node
472    */
473   // Three cases:
474   // 1. node2 is above node1
475   // 2. node2 is below node1
476   // 3. node2 and node1 have a common ancestor
477   public Float distanceTo(PhyloNode inNode2)
478   {
479      float result = 0;
480      float distance1 = 0;
481      float distance2 = 0;
482
483      Map<PhyloNode, Float> map1 = new HashMap<>();
484
485      boolean done = false;
486      Edge<PhyloNode> parentEdge = getParentEdge();
487      if (parentEdge != null)
488      {
489         do
490         {
491            map1.put(parentEdge.getTo(), distance1);
492            if (parentEdge.getTo() == inNode2)
493            {
494               result = distance1;
495               done = true;
496               break;
497            }
498
499            if (parentEdge.getDistance() != null) distance1 += parentEdge.getDistance();
500         }
501         while (parentEdge.getFrom() != null
502                && (parentEdge = parentEdge.getFrom().getParentEdge()) != null);
503      }
504
505
506      if (!done)
507      {
508         parentEdge = inNode2.getParentEdge();
509         if (parentEdge != null)
510         {
511            do
512            {
513               if (parentEdge.getDistance() != null) distance2 += parentEdge.getDistance();
514
515               Float distance = map1.get(parentEdge.getFrom());
516               if (distance != null)
517               {
518                  result = distance + distance2;
519                  done = true;
520                  break;
521               }
522            }
523            while (parentEdge.getFrom() != null
524                   && (parentEdge = parentEdge.getFrom().getParentEdge()) != null);
525         }
526
527         if (!done)
528         {
529            result = distance1 + distance2;
530         }
531      }
532
533      return result;
534   }
535
536   //--------------------------------------------------------------------------
537   @Override
538   public String toString()
539   {
540      StringBuilder buffer = new StringBuilder();
541      if (! isLeaf())
542      {
543         buffer.append("(");
544         for (Edge<PhyloNode> edge : mEdges)
545         {
546            if (edge.getFrom() == this)
547            {
548               PhyloNode child = edge.getTo();
549               if (buffer.length() > 1) buffer.append(",");
550               buffer.append(child.toString());
551            }
552         }
553         buffer.append(")");
554      }
555
556      if (StringUtil.isSet(getLabel()))
557      {
558         // Convert spaces to underscores.
559         String label = StringUtil.replaceAllRegexp(mLabel, sWhitespacePattern, "_");
560         // Convert colons to underscores.
561         label = StringUtil.replaceAll(label, ":", "_");
562         buffer.append(label);
563      }
564
565      Edge<PhyloNode> parentEdge = getParentEdge();
566      if (parentEdge != null
567          && parentEdge.getDistance() != null)
568      {
569         buffer.append(":" + parentEdge.getDistance());
570      }
571
572      return buffer.toString();
573   }
574
575
576   //--------------------------------------------------------------------------
577   public void setAttribute(String inName, Object inValue)
578   {
579      getOrInitAttributeMgr().setAttribute(inName, inValue);
580   }
581
582   //--------------------------------------------------------------------------
583   public boolean hasAttributes()
584   {
585      return mAttributeMgr != null && mAttributeMgr.hasAttributes();
586   }
587
588   //--------------------------------------------------------------------------
589   public boolean hasAttribute(String inName)
590   {
591      return mAttributeMgr != null && getOrInitAttributeMgr().hasAttribute(inName);
592   }
593
594   //--------------------------------------------------------------------------
595   public Object getAttribute(String inName)
596   {
597      return getOrInitAttributeMgr().getAttribute(inName);
598   }
599
600   //--------------------------------------------------------------------------
601   public Collection<String> getAttributeNames()
602   {
603      return getOrInitAttributeMgr().getAttributeNames();
604   }
605
606   //--------------------------------------------------------------------------
607   public void clearAttributes()
608   {
609      if (mAttributeMgr != null)
610      {
611         mAttributeMgr.clearAttributes();
612      }
613   }
614
615   //--------------------------------------------------------------------------
616   public Object removeAttribute(String inName)
617   {
618      Object attr = null;
619      if (mAttributeMgr != null)
620      {
621         attr  = getOrInitAttributeMgr().removeAttribute(inName);
622      }
623
624      return attr;
625   }
626
627   //--------------------------------------------------------------------------
628   protected void setLineIndex(int inValue)
629   {
630      mLineIndex = inValue;
631   }
632
633   //--------------------------------------------------------------------------
634   protected Integer getLineIndex()
635   {
636      return mLineIndex;
637   }
638
639   //--------------------------------------------------------------------------
640   protected void setVerticalLineStartIndex(int inValue)
641   {
642      mVericalLineStartIndex = inValue;
643   }
644
645   //--------------------------------------------------------------------------
646   protected Integer getVericalLineStartIndex()
647   {
648      return mVericalLineStartIndex;
649   }
650
651   //--------------------------------------------------------------------------
652   protected void setVerticalLineEndIndex(int inValue)
653   {
654      mVericalLineEndIndex = inValue;
655   }
656
657   //--------------------------------------------------------------------------
658   protected Integer getVericalLineEndIndex()
659   {
660      return mVericalLineEndIndex;
661   }
662
663
664   //--------------------------------------------------------------------------
665   private AttributeMgr getOrInitAttributeMgr()
666   {
667      if (null == mAttributeMgr)
668      {
669         mAttributeMgr = new AttributeMgr();
670      }
671
672      return mAttributeMgr;
673   }
674
675
676   private class SubnodeCountComparator implements Comparator<Edge<PhyloNode>>
677   {
678      public int compare(Edge<PhyloNode> edge1, Edge<PhyloNode> edge2)
679      {
680         int node1SubnodeCount = edge1.getTo().getSubnodeCount();
681         int node2SubnodeCount = edge2.getTo().getSubnodeCount();
682
683         if (node1SubnodeCount > node2SubnodeCount) return -1;
684         if (node1SubnodeCount < node2SubnodeCount) return 1;
685
686         if (edge1.getTo().getLabel() != null)
687         {
688            return edge1.getTo().getLabel().compareTo(edge2.getTo().getLabel());
689         }
690
691         return 0;
692      }
693   }
694
695}