/*
* 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.opus.function;
import java.util.List;
import java.util.Map;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import static org.novelang.parser.NodeKind.*;
import org.novelang.common.SyntacticTree;
import org.novelang.designator.FragmentIdentifier;
import org.novelang.opus.function.builtin.FileOrdering;
import org.novelang.opus.function.builtin.InsertCommand;
import org.novelang.opus.function.builtin.MapstylesheetCommand;
import org.novelang.opus.function.builtin.insert.LevelHead;
import org.novelang.parser.NodeKind;
import org.novelang.parser.NodeKindTools;
/**
* Creates instances of {@link Command}.
*
* @author Laurent Caillette
*/
public class CommandFactory {
/**
* Creates the {@link Command} instance from given {@code SyntacticTree}'s root.
*
* @param treeOfCommand A {@code SyntacticTree} instance of appropriate node type
* @return a non-null object.
*/
public static Command createFunctionCall( final SyntacticTree treeOfCommand )
throws CommandParameterException
{
final NodeKind nodeKind = NodeKindTools.ofRoot( treeOfCommand ) ;
switch( nodeKind ) {
case COMMAND_INSERT_ :
final String fileName = getTextOfChild( treeOfCommand, URL_LITERAL, true ) ;
final boolean recurse = hasChild( treeOfCommand, COMMAND_INSERT_RECURSE_ ) ;
final FileOrdering fileOrdering = createFileOrdering(
getTextOfChild( treeOfCommand, COMMAND_INSERT_SORT_, false ) ) ;
final LevelHead levelHead =
hasChild( treeOfCommand, COMMAND_INSERT_CREATELEVEL_ ) ? LevelHead.CREATE_LEVEL :
hasChild( treeOfCommand, COMMAND_INSERT_NOHEAD_ ) ? LevelHead.NO_HEAD : null
;
final String levelAboveAsString =
getTextOfChild( treeOfCommand, COMMAND_INSERT_LEVELABOVE_, false ) ;
final String styleName = getTextOfChild( treeOfCommand, COMMAND_INSERT_STYLE_, false ) ;
final Iterable< FragmentIdentifier > fragmentIdentifiers =
getFragmentIdentifiers( treeOfCommand ) ;
return new InsertCommand(
treeOfCommand.getLocation(),
fileName,
recurse,
fileOrdering,
levelHead,
levelAboveAsString == null ? 0 : Integer.parseInt( levelAboveAsString ),
styleName,
fragmentIdentifiers
) ;
case COMMAND_MAPSTYLESHEET_ :
final Map< String, String > styleMap = Maps.newHashMap() ;
for( final SyntacticTree child : treeOfCommand.getChildren() ) {
styleMap.put( child.getChildAt( 0 ).getText(), child.getChildAt( 1 ).getText() ) ;
}
return new MapstylesheetCommand( treeOfCommand.getLocation(), styleMap ) ;
default : throw new IllegalArgumentException( "Unsupported: " + nodeKind ) ;
}
}
private static Iterable< SyntacticTree > getChildren(
final SyntacticTree tree,
final NodeKind childNodeKind
) {
final List< SyntacticTree > filtered = Lists.newArrayList() ;
for( final SyntacticTree child : tree.getChildren() ) {
if( child.isOneOf( childNodeKind ) ) {
filtered.add( child ) ;
}
}
return filtered ;
}
private static Iterable<FragmentIdentifier> getFragmentIdentifiers( final SyntacticTree tree ) {
final ImmutableList.Builder< FragmentIdentifier > fragmentIdentifiersBuilder =
ImmutableList.builder() ;
final Iterable< SyntacticTree > childrenWithAbsoluteIdentifier =
getChildren( tree, ABSOLUTE_IDENTIFIER ) ;
for( final SyntacticTree child : childrenWithAbsoluteIdentifier ) {
fragmentIdentifiersBuilder.add( getFragmentIdentifier( child ) ) ;
}
return fragmentIdentifiersBuilder.build() ;
}
private static FragmentIdentifier getFragmentIdentifier( final SyntacticTree tree ) {
Preconditions.checkArgument( tree.isOneOf( ABSOLUTE_IDENTIFIER ) ) ;
return new FragmentIdentifier( tree.getChildAt( 0 ).getText() ) ;
}
private static String getTextOfChild(
final SyntacticTree tree,
final NodeKind childNodeKind,
final boolean mandatory
) throws CommandParameterException {
for( final SyntacticTree child : tree.getChildren() ) {
if( child.isOneOf( childNodeKind ) ) {
return child.getChildAt( 0 ).getText() ;
}
}
if ( mandatory ) {
throw new CommandParameterException(
"Found no URL. " +
"This should not happen if the parser created this tree: " + tree
) ;
} else {
return null ;
}
}
private static boolean hasChild( final SyntacticTree tree, final NodeKind nodeKind ) {
for( final SyntacticTree child : tree.getChildren() ) {
if( child.isOneOf( nodeKind ) ) {
return true ;
}
}
return false ;
}
private static final Map< String, FileOrdering > FILE_ORDERINGS_BY_NAME =
new ImmutableMap.Builder< String, FileOrdering >().
put( "path", FileOrdering.BY_ABSOLUTE_PATH ).
put( "version", FileOrdering.BY_VERSION_NUMBER ).
build()
;
private static FileOrdering createFileOrdering( final String ordering )
throws CommandParameterException
{
if( ordering == null ) {
return null ;
}
// We suppose the parser did its job and returned a sort method followed by an order flag.
final char sortOrderChar = ordering.charAt( ordering.length() - 1 ) ;
final String sortMethodName = ordering.substring( 0, ordering.length() - 1 ) ;
final boolean reverse ;
switch( sortOrderChar ) {
case '+' :
reverse = false ;
break ;
case '-' :
reverse = true ;
break ;
default :
throw new IllegalArgumentException(
"Missing sort order at the end, must be '+' or '-', " +
" the parser should have detected that"
) ;
}
final FileOrdering fileOrdering = FILE_ORDERINGS_BY_NAME.get( sortMethodName ) ;
if( null == fileOrdering ) {
throw new CommandParameterException( "Unknown ordering: '" + sortMethodName + "'" ) ;
}
if( reverse ) {
return fileOrdering.inverse() ;
} else {
return fileOrdering ;
}
}
}