/*
* Copyright (C) 2011 apurv
*
* 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/>.
*/
package nescent.phylogeoref.validator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import nescent.phylogeoref.validator.exception.InvalidEdgeLengthException;
import nescent.phylogeoref.validator.exception.InvalidLatitudeException;
import nescent.phylogeoref.validator.exception.InvalidLongitudeException;
import nescent.phylogeoref.validator.exception.LocationNotFoundException;
import nescent.phylogeoref.validator.exception.MissingEdgeLengthException;
import nescent.phylogeoref.validator.exception.SingleChildException;
import nescent.phylogeoref.validator.exception.ZeroChildException;
import org.forester.phylogeny.Phylogeny;
import org.forester.phylogeny.PhylogenyNode;
import org.forester.phylogeny.data.Distribution;
import org.forester.phylogeny.data.NodeData;
import org.forester.phylogeny.iterators.PhylogenyNodeIterator;
/**
* Validates a phylogeny.
* @author apurv
*/
public class PhylogenyValidator {
private final static Logger LOGGER = Logger.getLogger("nescent");
/**
* A true value denotes a tree with edge lengths specified is to be validated.
*/
private boolean weightedTree;
public PhylogenyValidator(boolean weightedTree){
this.weightedTree = weightedTree;
}
/**
* Validates the phylogeny by checking that all external nodes have been assigned coordinates.
* @param phy
*/
public void validatePhylogeny(Phylogeny phy){
checkExternalNodeLocations(phy);
if(weightedTree){
checkEdgeLengths(phy);
}
checkStructure(phy);
}
/**
* Checks to see if all the external nodes have been assigned valid locations.
* @param phy
*/
private void checkExternalNodeLocations(Phylogeny phy){
Set<PhylogenyNode> extNodeSet = phy.getExternalNodes();
for(PhylogenyNode node:extNodeSet){
try{
NodeData nodeData = node.getNodeData();
Distribution dist = nodeData.getDistribution();
String name = node.getNodeName();
Integer id = node.getNodeId();
if(dist != null){
if(dist.getLatitude()==null || dist.getLongitude()==null ||dist.getAltitude()==null){
throw new LocationNotFoundException( id.toString(), name);
}else{
double latitude = dist.getLatitude().doubleValue();
double longitude = dist.getLongitude().doubleValue();
double altitude = dist.getAltitude().doubleValue();
if(latitude>90 || latitude <-90){
nodeData.setDistribution(null);
throw new InvalidLatitudeException(id.toString(), name, latitude);
}
if(longitude>180 || longitude<-180){
nodeData.setDistribution(null);
throw new InvalidLongitudeException(id.toString(), name, longitude);
}
}
}
else{
throw new LocationNotFoundException(id.toString(), name);
}
}
catch(LocationNotFoundException ex){
LOGGER.log(Level.INFO, ex.getMessage(), ex);
}
catch(InvalidLatitudeException ex){
LOGGER.log(Level.INFO, ex.getMessage(), ex);
}
catch(InvalidLongitudeException ex){
LOGGER.log(Level.INFO, ex.getMessage(), ex);
}
}
}
/**
* Checks whether a weighted tree has all its edge lengths specified.
* @param phylogeny
*/
private void checkEdgeLengths(Phylogeny phylogeny){
for( PhylogenyNodeIterator it = phylogeny.iteratorPostorder(); it.hasNext();) {
try{
PhylogenyNode node = it.next();
double edgeLength = node.getDistanceToParent();
//Edge length from the root node is undefined.
if(node.isRoot()){
continue;
}
if(edgeLength <= 0.0){
String name = node.getNodeName();
Integer id = node.getNodeId();
if(edgeLength == 0.0){
throw new MissingEdgeLengthException(id.toString(), name);
}else if(edgeLength < 0 ){
throw new InvalidEdgeLengthException(id.toString(), name);
}
}
}catch (MissingEdgeLengthException ex){
LOGGER.log(Level.SEVERE,ex.getMessage(),ex);
System.exit(1);
}catch (InvalidEdgeLengthException ex){
LOGGER.log(Level.SEVERE,ex.getMessage(),ex);
System.exit(1);
}
}
}
//TODO: Handle the case when a node has only one child node or no child.
/**
* Checks the basic structure of a tree.
* Cases handled.
*
* 1) An internal node with only 1 child is considered an invalid case.
* 2) An internal node with zero child is considered an invalid case,
*
* @param phylogeny
*/
private void checkStructure(Phylogeny phylogeny){
for( PhylogenyNodeIterator it = phylogeny.iteratorPostorder(); it.hasNext();) {
try{
PhylogenyNode node = it.next();
String label = node.getNodeName();
Integer id = node.getNodeId();
if(node.isInternal()){
int numChildren = node.getNumberOfDescendants();
if(numChildren == 0){
throw new ZeroChildException(id.toString(), label);
}else if(numChildren == 1){
throw new SingleChildException(id.toString(), label);
}
}
}catch(ZeroChildException ex){
LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
System.exit(1);
}catch(SingleChildException ex){
LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
}
}
}
}