org.xwiki.refactoring.internal.splitter.DefaultDocumentSplitter.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.refactoring.internal.splitter.DefaultDocumentSplitter.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.refactoring.internal.splitter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import javax.inject.Singleton;

import org.apache.commons.lang3.StringUtils;
import org.xwiki.component.annotation.Component;
import org.xwiki.refactoring.WikiDocument;
import org.xwiki.refactoring.splitter.DocumentSplitter;
import org.xwiki.refactoring.splitter.criterion.SplittingCriterion;
import org.xwiki.refactoring.splitter.criterion.naming.NamingCriterion;
import org.xwiki.rendering.block.Block;
import org.xwiki.rendering.block.Block.Axes;
import org.xwiki.rendering.block.BlockFilter;
import org.xwiki.rendering.block.HeaderBlock;
import org.xwiki.rendering.block.IdBlock;
import org.xwiki.rendering.block.LinkBlock;
import org.xwiki.rendering.block.NewLineBlock;
import org.xwiki.rendering.block.SectionBlock;
import org.xwiki.rendering.block.SpaceBlock;
import org.xwiki.rendering.block.SpecialSymbolBlock;
import org.xwiki.rendering.block.WordBlock;
import org.xwiki.rendering.block.XDOM;
import org.xwiki.rendering.block.match.ClassBlockMatcher;
import org.xwiki.rendering.listener.reference.DocumentResourceReference;
import org.xwiki.rendering.listener.reference.ResourceReference;
import org.xwiki.rendering.listener.reference.ResourceType;

/**
 * Default implementation of {@link DocumentSplitter}.
 *
 * @version $Id: fc0fdd10dbe96c433b09736622c62de752a4bce5 $
 * @since 1.9M1
 */
@Component
@Singleton
public class DefaultDocumentSplitter implements DocumentSplitter {
    /**
     * The name of the anchor link parameter.
     */
    private static final String ANCHOR_PARAMETER = "anchor";

    @Override
    public List<WikiDocument> split(WikiDocument rootDoc, SplittingCriterion splittingCriterion,
            NamingCriterion namingCriterion) {
        List<WikiDocument> result = new ArrayList<WikiDocument>();
        // Add the rootDoc into the result
        result.add(rootDoc);
        // Recursively split the root document.
        split(rootDoc, rootDoc.getXdom().getChildren(), 1, result, splittingCriterion, namingCriterion);
        updateAnchors(result);
        return result;
    }

    /**
     * A recursive method for traversing the xdom of the root document and splitting it into sub documents.
     *
     * @param parentDoc the parent {@link WikiDocument} under which the given list of children reside.
     * @param children current list of blocks being traversed.
     * @param depth the depth from the root xdom to current list of children.
     * @param result space for storing the resulting documents.
     * @param splittingCriterion the {@link SplittingCriterion}.
     * @param namingCriterion the {@link NamingCriterion}.
     */
    private void split(WikiDocument parentDoc, List<Block> children, int depth, List<WikiDocument> result,
            SplittingCriterion splittingCriterion, NamingCriterion namingCriterion) {
        ListIterator<Block> it = children.listIterator();
        while (it.hasNext()) {
            Block block = it.next();
            if (splittingCriterion.shouldSplit(block, depth)) {
                // Split a new document and add it to the results list.
                XDOM xdom = new XDOM(block.getChildren());
                String newDocumentName = namingCriterion.getDocumentName(xdom);
                WikiDocument newDoc = new WikiDocument(newDocumentName, xdom, parentDoc);
                result.add(newDoc);
                // Remove the original block from the parent document.
                it.remove();
                // Place a link from the parent to child.
                it.add(new NewLineBlock());
                it.add(createLink(block, newDocumentName));
                // Check whether this node should be further traversed.
                if (splittingCriterion.shouldIterate(block, depth)) {
                    split(newDoc, newDoc.getXdom().getChildren(), depth + 1, result, splittingCriterion,
                            namingCriterion);
                }
            } else if (splittingCriterion.shouldIterate(block, depth)) {
                split(parentDoc, block.getChildren(), depth + 1, result, splittingCriterion, namingCriterion);
            }
        }
    }

    /**
     * Creates a {@link LinkBlock} suitable to be placed in the parent document.
     *
     * @param block the {@link Block} that has just been split into a separate document.
     * @param target name of the target wiki document.
     * @return a {@link LinkBlock} representing the link from the parent document to new document.
     */
    private LinkBlock createLink(Block block, String target) {
        Block firstBlock = block.getChildren().get(0);
        if (firstBlock instanceof HeaderBlock) {
            DocumentResourceReference reference = new DocumentResourceReference(target);
            // Clone the header block and remove any unwanted stuff
            Block clonedHeaderBlock = firstBlock.clone(new BlockFilter() {
                @Override
                public List<Block> filter(Block block) {
                    List<Block> blocks = new ArrayList<Block>();
                    if (block instanceof WordBlock || block instanceof SpaceBlock
                            || block instanceof SpecialSymbolBlock) {
                        blocks.add(block);
                    }
                    return blocks;
                }
            });
            return new LinkBlock(clonedHeaderBlock.getChildren(), reference, false);
        } else if (firstBlock instanceof SectionBlock) {
            return createLink(firstBlock, target);
        } else {
            throw new IllegalArgumentException(
                    "A SectionBlock should either begin with a HeaderBlock or another SectionBlock.");
        }
    }

    /**
     * Update the links to internal document fragments after those fragments have been moved as a result of the split.
     * For instance the "#Chapter1" anchor will be updated to "ChildDocument#Chapter1" if the document fragment
     * identified by "Chapter1" has been moved to "ChildDocument" as a result of the split.
     *
     * @param documents the list of documents whose anchors to update
     */
    private void updateAnchors(List<WikiDocument> documents) {
        // First we need to collect all the document fragments and map them to their new location.
        Map<String, String> fragments = collectDocumentFragments(documents);

        // Update the anchors.
        for (WikiDocument document : documents) {
            updateAnchors(document, fragments);
        }
    }

    /**
     * @param document the document whose anchors to update
     * @param fragments see {@link #collectDocumentFragments(List)}
     */
    private void updateAnchors(WikiDocument document, Map<String, String> fragments) {
        for (LinkBlock linkBlock : document.getXdom().<LinkBlock>getBlocks(new ClassBlockMatcher(LinkBlock.class),
                Axes.DESCENDANT)) {
            ResourceReference reference = linkBlock.getReference();
            ResourceType resoureceType = reference.getType();
            String fragment = null;
            if ((ResourceType.DOCUMENT.equals(resoureceType) || ResourceType.SPACE.equals(resoureceType))
                    && StringUtils.isEmpty(reference.getReference())) {
                fragment = reference.getParameter(ANCHOR_PARAMETER);
            } else if (StringUtils.startsWith(reference.getReference(), "#")
                    && (ResourceType.PATH.equals(resoureceType) || ResourceType.URL.equals(resoureceType))) {
                fragment = reference.getReference().substring(1);
            }

            String targetDocument = fragments.get(fragment);
            if (targetDocument != null && !targetDocument.equals(document.getFullName())) {
                // The fragment has been moved so we need to update the link.
                reference.setType(ResourceType.DOCUMENT);
                reference.setReference(targetDocument);
                reference.setParameter(ANCHOR_PARAMETER, fragment);
            }
        }
    }

    /**
     * Looks for document fragments in the given documents. A document fragment is identified by an {@link IdBlock} for
     * instance.
     *
     * @param documents the list of documents whose fragments to collect
     * @return the collection of document fragments mapped to the document that contains them
     */
    private Map<String, String> collectDocumentFragments(List<WikiDocument> documents) {
        Map<String, String> fragments = new HashMap<String, String>();
        for (WikiDocument document : documents) {
            for (IdBlock idBlock : document.getXdom().<IdBlock>getBlocks(new ClassBlockMatcher(IdBlock.class),
                    Axes.DESCENDANT)) {
                fragments.put(idBlock.getName(), document.getFullName());
            }
        }
        return fragments;
    }
}