/*
* Copyright (C) 2011 Laurent Caillette
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser 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 org.novelang.treemangling;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import com.google.common.base.Preconditions;
import org.novelang.common.SyntacticTree;
import org.novelang.common.TagBehavior;
import org.novelang.common.tree.Treepath;
import org.novelang.common.tree.TreepathTools;
import org.novelang.designator.Tag;
import org.novelang.logger.Logger;
import org.novelang.logger.LoggerFactory;
import org.novelang.parser.NodeKind;
import org.novelang.parser.NodeKindTools;
/**
* Retains nodes which have at least one of given tags, or a child with at least one of the
* given tags.
*
* TODO: use precalculated {@link NodeKind#_IMPLICIT_TAG} instead of recalculating.
*
* @author Laurent Caillette
*/
public class TagFilter {
private static final Logger LOGGER = LoggerFactory.getLogger( TagFilter.class );
private TagFilter() {}
public static Treepath< SyntacticTree > filter(
Treepath< SyntacticTree > treepath,
final Set< Tag > tags
) {
LOGGER.debug( "Filtering on ", tags ) ;
if( ! tags.isEmpty() ) {
final SyntacticTree tree = treepath.getTreeAtEnd() ;
final SyntacticTree filteredTree = doFilter( tree, tags ).tree ;
if( tree != filteredTree ) {
treepath = TreepathTools.replaceTreepathEnd( treepath, filteredTree ) ;
}
}
LOGGER.debug( "Done filtering on ", tags ) ;
return treepath ;
}
private static Result doFilter( final SyntacticTree tree, final Set< Tag > tags ) {
final NodeKind nodeKind = NodeKindTools.ofRoot( tree ) ;
final TagBehavior behavior = nodeKind.getTagBehavior() ;
switch( behavior ) {
case TERMINAL :
if( hasTag( tree, tags ) ) {
return new Result( true, tree ) ;
} else {
return null ;
}
case SCOPE :
if( hasTag( tree, tags ) ) {
return new Result( true, tree ) ;
} // 'else' clause handled below:
case TRAVERSABLE :
final List< SyntacticTree > newChildList =
new ArrayList< SyntacticTree >( tree.getChildCount() ) ;
// Gets true if one child (TERMINAL or SCOPE) has one of the wanted tags.
boolean hasTaggedChild = false ;
for( final SyntacticTree child : tree.getChildren() ) {
final TagBehavior childTagBehavior = NodeKindTools.ofRoot( child ).getTagBehavior() ;
if( childTagBehavior == TagBehavior.NON_TRAVERSABLE ) {
newChildList.add( child ) ;
} else {
final Result result = doFilter( child, tags ) ;
if( result != null ) {
if( result.hasTag ) {
newChildList.add( result.tree ) ;
}
hasTaggedChild = hasTaggedChild || result.hasTag ;
}
}
}
if( ( behavior == TagBehavior.SCOPE || behavior == TagBehavior.TERMINAL )
&& ! hasTaggedChild ) {
return null ;
}
// final SyntacticTree[] newChildArray =
// newChildList.toArray( new SyntacticTree[ newChildList.size() ] ) ;
return new Result( hasTaggedChild, tree.adopt( newChildList ) ) ;
default :
return new Result( false, tree ) ;
}
}
private static boolean hasTag(
final SyntacticTree tree,
final Set< Tag > tags
) {
for( final SyntacticTree child : tree.getChildren() ) {
if( child.isOneOf( NodeKind._EXPLICIT_TAG, NodeKind._IMPLICIT_TAG ) ) {
if( Tag.contains( tags, child.getChildAt( 0 ).getText() ) ) {
return true ;
}
}
}
// if( NodeKind._LEVEL.isRoot( tree ) ) {
// for( final SyntacticTree child : tree.getChildren() ) {
// if( child.isOneOf( NodeKind.LEVEL_TITLE ) ) {
// final Set< Tag > implicitTags = RenderingTools.toImplicitTagSet( child ) ;
// return ! Sets.intersection( tags, implicitTags ).isEmpty() ;
// }
// }
// }
return false ;
}
private static class Result {
public final boolean hasTag ;
public final SyntacticTree tree ;
private Result( final boolean hasTag, final SyntacticTree tree ) {
this.hasTag = hasTag;
this.tree = Preconditions.checkNotNull( tree ) ;
}
}
}