/* * 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.Arrays; import java.util.Map; import com.google.common.collect.Maps; import org.novelang.common.Problem; import org.novelang.common.SimpleTree; import org.novelang.common.SyntacticTree; import org.novelang.common.tree.RobustPath; import org.novelang.common.tree.Traversal; import org.novelang.common.tree.Treepath; import org.novelang.common.tree.TreepathTools; import org.novelang.designator.FragmentIdentifier; import org.novelang.logger.Logger; import org.novelang.logger.LoggerFactory; import org.novelang.parser.NodeKind; import org.novelang.treemangling.designator.BabyInterpreter; import org.novelang.treemangling.designator.DesignatorTools; import org.novelang.treemangling.designator.FragmentMapper; /** * Creates a Mapping from designators ({@link FragmentIdentifier}s or, in the future, tags) * and to {@code Treepath}s with nodes containing expanded identifiers (both explicit * and implicit, resolved to absolute identifiers). * <p> * Implementation: due to the immutable nature of the {@code Treepath} this is done * in several passes. * <ol> * <li> * The {@link BabyInterpreter} (used internally) gathers * references on the nodes containing identifiers, resolving implicit references and * finding problems. * </li> * <li> * The {@link #enrich(Treepath, FragmentMapper)} method (used internally) enhances the * {@code Treepath} with new nodes ( * {@link NodeKind#_IMPLICIT_IDENTIFIER} and * {@link NodeKind#_EXPLICIT_IDENTIFIER} * ) containing resolved identifiers. * Those nodes enhance document readability as they show resolved identifiers. * The same method removes * {@link NodeKind#ABSOLUTE_IDENTIFIER} * nodes which don't improve readability. * </li> * <li> * Identifiers from {@link BabyInterpreter} are re-mapped * on the new {@code Treepath}. * </li> * </ol> * * * @author Laurent Caillette */ public class DesignatorInterpreter { private static final Logger LOGGER = LoggerFactory.getLogger( DesignatorInterpreter.class ) ; /** * Contains only pure identifiers (defined explicitely). */ private final Map< FragmentIdentifier, Treepath< SyntacticTree > > pureIdentifiers ; /** * Contains implicit identifiers, or implicit identifiers mixed with explicit identifiers. */ private final Map< FragmentIdentifier, Treepath< SyntacticTree > > derivedIdentifiers ; private final Iterable< Problem > problems ; private final Treepath< SyntacticTree > enrichedTreepath; public DesignatorInterpreter( final Treepath< SyntacticTree > treepath ) { final BabyInterpreter babyInterpreter = new BabyInterpreter( treepath ) ; enrichedTreepath = enrich( DesignatorTools.TRAVERSAL.first( treepath ), babyInterpreter ); pureIdentifiers = remap( babyInterpreter.getPureIdentifierMap(), enrichedTreepath ) ; derivedIdentifiers = remap( babyInterpreter.getDerivedIdentifierMap(), enrichedTreepath ) ; problems = babyInterpreter.getProblems() ; if ( LOGGER.isDebugEnabled() ) { final StringBuilder stringBuilder = new StringBuilder() ; stringBuilder.append( "\n Pure identifiers:" ) ; DesignatorTools.dumpIdentifierMap( stringBuilder, pureIdentifiers, "\n " ) ; stringBuilder.append( "\n Derived identifiers:" ) ; DesignatorTools.dumpIdentifierMap( stringBuilder, derivedIdentifiers, "\n " ) ; // LOGGER.debug( "Created ", this, stringBuilder ) ; } } /** * Returns the {@code Treepath} with enriched Designators. * @return an object with the same content as the one passed to the constructor, except * for Identifiers. */ public Treepath< SyntacticTree > getEnrichedTreepath() { return enrichedTreepath ; } /** * Given a {@link FragmentIdentifier}, returns corresponding {@link SyntacticTree}. */ public Treepath< SyntacticTree > get( final FragmentIdentifier fragmentIdentifier ) { Treepath< SyntacticTree > treepath = pureIdentifiers.get( fragmentIdentifier ) ; if( treepath == null ) { treepath = derivedIdentifiers.get( fragmentIdentifier ) ; } return treepath ; } /** * Returns problems like duplicate identifier. */ public Iterable< Problem > getProblems() { return problems ; } public boolean hasProblem() { return problems.iterator().hasNext() ; } // =============== // Transformations // =============== private static Map< FragmentIdentifier, Treepath< SyntacticTree > > remap( final Map< FragmentIdentifier, RobustPath< SyntacticTree > > map, final Treepath< SyntacticTree > treepath ) { final Map< FragmentIdentifier, Treepath< SyntacticTree > > result = Maps.newHashMap() ; for( final Map.Entry< FragmentIdentifier, RobustPath< SyntacticTree > > entry : map.entrySet() ) { result.put( entry.getKey(), entry.getValue().apply( treepath.getTreeAtStart() ) ) ; } return result ; } /** * (Made protected for tests only.) * Transform a whole tree adding new "synthetic" identifiers that were calculated by * {@link BabyInterpreter} and removing original ones. * The tree transformation uses mirrored postorder traversal. This kind of traversal guarantees * that addind/removing trees doesn't affect indexes of unprocessed trees. * * @param treepath The * {@link Traversal.MirroredPostorder#first(Treepath) first} tree in a mirrored postorder * traversal. */ protected static Treepath< SyntacticTree > enrich( Treepath< SyntacticTree > treepath, final FragmentMapper< RobustPath< SyntacticTree > > mapper ) { while( true ) { // treepath = removeDirectChildren( // treepath, // NodeKind._IMPLICIT_IDENTIFIER // ) ; final FragmentIdentifier pureIdentifier = findIdentifier( treepath, mapper.getPureIdentifierMap() ) ; if( pureIdentifier != null ) { treepath = add( treepath, pureIdentifier, NodeKind._EXPLICIT_IDENTIFIER ) ; } final FragmentIdentifier derivedIdentifier = findIdentifier( treepath, mapper.getDerivedIdentifierMap() ) ; if( derivedIdentifier != null && mayAddImplicitIdentifier( treepath ) ) { treepath = add( treepath, derivedIdentifier, NodeKind._IMPLICIT_IDENTIFIER ) ; } treepath = removeDirectChildren( treepath, NodeKind.ABSOLUTE_IDENTIFIER ) ; final Treepath< SyntacticTree > next = DesignatorTools.TRAVERSAL.next( treepath ) ; if( next == null ) { return treepath ; } else { treepath = next ; } } } /** * This is useful because we need to generate implicit identifiers at {@link org.novelang.opus.Opus} * level since implicit identifiers may not be the same as those calculate at * {@link org.novelang.novella.Novella} level. So new identifiers shouldn't collapse with old ones. * <ul> * <li>There should be no implicit identifier if an explicit identifier is already present. * <li>Don't add an implicit identifier if there is already one. * </ul> */ private static boolean mayAddImplicitIdentifier( final Treepath< SyntacticTree > treepath ) { final SyntacticTree tree = treepath.getTreeAtEnd() ; for( final SyntacticTree child : tree.getChildren() ) { if( child.isOneOf( NodeKind._EXPLICIT_IDENTIFIER, NodeKind._IMPLICIT_IDENTIFIER ) ) { return false ; } } return true ; } private static Treepath< SyntacticTree > removeDirectChildren( final Treepath< SyntacticTree > treepath, final NodeKind... nodeKindsToRemove ) { final SyntacticTree tree = treepath.getTreeAtEnd() ; for( int i = 0 ; i < tree.getChildCount() ; i ++ ) { final SyntacticTree child = tree.getChildAt( i ) ; if( child.isOneOf( nodeKindsToRemove ) ) { return TreepathTools.removeEnd( Treepath.create( treepath, i ) ) ; } } return treepath ; } private static Treepath< SyntacticTree > add( final Treepath< SyntacticTree > treepath, final FragmentIdentifier fragmentIdentifier, final NodeKind explicitIdentifier ) { final SyntacticTree identifierTree = new SimpleTree( explicitIdentifier, new SimpleTree( fragmentIdentifier.getAbsoluteRepresentation() ) ) ; return TreepathTools.addChildFirst( treepath, identifierTree ).getPrevious() ; } private static FragmentIdentifier findIdentifier( final Treepath< SyntacticTree > treepath, final Map< FragmentIdentifier, RobustPath< SyntacticTree > > map ) { final int[] indexesInParent = treepath.getIndicesInParent() ; for( final Map.Entry< FragmentIdentifier, RobustPath< SyntacticTree > > entry : map.entrySet() ) { final Treepath< SyntacticTree > found = entry.getValue().apply( treepath.getTreeAtStart() ) ; if( Arrays.equals( indexesInParent, found.getIndicesInParent() ) ) { return entry.getKey() ; } } return null ; } }