All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xwiki.rendering.block.AbstractBlock Maven / Gradle / Ivy

There is a newer version: 16.9.0
Show newest version
/*
 * 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.rendering.block;

import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.xwiki.rendering.block.match.BlockMatcher;
import org.xwiki.rendering.block.match.BlockNavigator;
import org.xwiki.rendering.block.match.CounterBlockMatcher;
import org.xwiki.rendering.block.match.FunctionBlockMatcher;
import org.xwiki.rendering.block.match.MetadataBlockMatcher;
import org.xwiki.rendering.listener.Listener;
import org.xwiki.rendering.listener.MetaData;
import org.xwiki.rendering.syntax.Syntax;

/**
 * Implementation for Block operations. All blocks should extend this class. Supports the notion of generic parameters
 * which can be added to a block (see {@link #getParameter(String)} for more details.
 *
 * @version $Id: d54488d4ae6954cb387b7104d3364ad3395f570f $
 * @since 1.5M2
 */
public abstract class AbstractBlock implements Block
{
    /**
     * Store parameters, see {@link #getParameter(String)} for more explanations on what parameters are.
     */
    private Map parameters;

    /**
     * Store attributes, see {@link #getAttribute(String)} for more explanations what attributes are.
     */
    private Map attributes;

    /**
     * The Blocks this Block contains.
     */
    private List childrenBlocks;

    /**
     * The Block containing this Block.
     */
    private Block parentBlock;

    /**
     * The next Sibling Block or null if no next sibling exists.
     */
    private Block nextSiblingBlock;

    /**
     * The previous Sibling Block or null if no previous sibling exists.
     */
    private Block previousSiblingBlock;

    /**
     * Empty constructor to construct an empty block.
     */
    public AbstractBlock()
    {
        // Nothing to do
    }

    /**
     * Construct a block with parameters.
     *
     * @param parameters the parameters to set
     */
    public AbstractBlock(Map parameters)
    {
        setParameters(parameters);
    }

    /**
     * Constructs a block with a child block.
     *
     * @param childBlock the child block of this block
     * @since 3.0M1
     */
    public AbstractBlock(Block childBlock)
    {
        this(childBlock, Collections.emptyMap());
    }

    /**
     * Constructs a block with children blocks.
     *
     * @param childrenBlocks the list of children blocks of the block to construct
     * @since 3.0M1
     */
    public AbstractBlock(List childrenBlocks)
    {
        this(childrenBlocks, Collections.emptyMap());
    }

    /**
     * Construct a block with a child block and parameters.
     *
     * @param childBlock the child block of this block
     * @param parameters the parameters to set
     * @since 3.0M1
     */
    public AbstractBlock(Block childBlock, Map parameters)
    {
        this(parameters);

        addChild(childBlock);
    }

    /**
     * Construct a block with children blocks and parameters.
     *
     * @param childrenBlocks the list of children blocks of the block to construct
     * @param parameters the parameters to set
     * @since 3.0M1
     */
    public AbstractBlock(List childrenBlocks, Map parameters)
    {
        this(parameters);

        addChildren(childrenBlocks);
    }

    /**
     * Get the position of the provided block in the provided list of blocks.
     * 

* Can't use {@link List#indexOf(Object)} since it's using {@link Object#equals(Object)} internally which is not * what we want since two WordBlock with the same text or two spaces are equals for example but we want to be able * to target one specific Block. * * @param block the block for which to find the position * @param blocks the list of blocks in which to look for the passed block * @return the position of the block, -1 if the block can't be found */ private static int indexOfBlock(Block block, List blocks) { int position = 0; for (Block child : blocks) { if (child == block) { return position; } ++position; } return -1; } @Override public void addChild(Block blockToAdd) { insertChildAfter(blockToAdd, null); } @Override public void addChildren(List blocksToAdd) { if (!blocksToAdd.isEmpty()) { if (this.childrenBlocks == null) { // Create the list with just the exact required size this.childrenBlocks = new ArrayList<>(blocksToAdd.size()); } for (Block blockToAdd : blocksToAdd) { addChild(blockToAdd); } } } @Override public void setChildren(List children) { if (children.isEmpty()) { if (this.childrenBlocks != null) { this.childrenBlocks.clear(); } } else { if (this.childrenBlocks != null) { this.childrenBlocks.clear(); } addChildren(children); } } @Override public void setNextSiblingBlock(Block nextSiblingBlock) { this.nextSiblingBlock = nextSiblingBlock; } @Override public void setPreviousSiblingBlock(Block previousSiblingBlock) { this.previousSiblingBlock = previousSiblingBlock; } @Override public void insertChildBefore(Block blockToInsert, Block nextBlock) { blockToInsert.setParent(this); if (nextBlock == null) { // Last block becomes last but one if (this.childrenBlocks != null && !this.childrenBlocks.isEmpty()) { Block lastBlock = this.childrenBlocks.get(this.childrenBlocks.size() - 1); blockToInsert.setPreviousSiblingBlock(lastBlock); lastBlock.setNextSiblingBlock(blockToInsert); } else { blockToInsert.setPreviousSiblingBlock(null); if (this.childrenBlocks == null) { this.childrenBlocks = new ArrayList<>(1); } } blockToInsert.setNextSiblingBlock(null); this.childrenBlocks.add(blockToInsert); } else { // If there's a previous block to nextBlock then get it to set its next sibling Block previousBlock = nextBlock.getPreviousSibling(); if (previousBlock != null) { previousBlock.setNextSiblingBlock(blockToInsert); blockToInsert.setPreviousSiblingBlock(previousBlock); } else { blockToInsert.setPreviousSiblingBlock(null); } blockToInsert.setNextSiblingBlock(nextBlock); nextBlock.setPreviousSiblingBlock(blockToInsert); if (this.childrenBlocks == null || this.childrenBlocks.isEmpty()) { this.childrenBlocks = new ArrayList<>(1); this.childrenBlocks.add(blockToInsert); } else { this.childrenBlocks.add(indexOfChild(nextBlock), blockToInsert); } } } @Override public void insertChildAfter(Block blockToInsert, Block previousBlock) { if (previousBlock == null) { insertChildBefore(blockToInsert, null); } else { // If there's a next block to previousBlock then get it to set its previous sibling Block nextBlock = previousBlock.getNextSibling(); if (nextBlock != null) { nextBlock.setPreviousSiblingBlock(blockToInsert); blockToInsert.setNextSiblingBlock(nextBlock); } else { blockToInsert.setNextSiblingBlock(null); } blockToInsert.setPreviousSiblingBlock(previousBlock); previousBlock.setNextSiblingBlock(blockToInsert); if (this.childrenBlocks == null) { this.childrenBlocks = new ArrayList<>(1); } this.childrenBlocks.add(indexOfChild(previousBlock) + 1, blockToInsert); } } @Override public void replaceChild(Block newBlock, Block oldBlock) { replaceChild(Collections.singletonList(newBlock), oldBlock); } @Override public void replaceChild(List newBlocks, Block oldBlock) { int position = indexOfChild(oldBlock); if (position == -1) { throw new InvalidParameterException("Provided Block to replace is not a child"); } List blocks = getChildren(); // Remove old child blocks.remove(position); oldBlock.setParent(null); // Insert new children Block previousBlock = oldBlock.getPreviousSibling(); if (newBlocks.isEmpty() && previousBlock != null) { previousBlock.setNextSiblingBlock(oldBlock.getNextSibling()); } Block lastBlock = null; for (Block block : newBlocks) { block.setParent(this); block.setPreviousSiblingBlock(previousBlock); if (previousBlock != null) { previousBlock.setNextSiblingBlock(block); } previousBlock = block; lastBlock = block; } Block nextBlock = oldBlock.getNextSibling(); if (nextBlock != null) { nextBlock.setPreviousSiblingBlock(lastBlock); } if (lastBlock != null) { lastBlock.setNextSiblingBlock(nextBlock); } blocks.addAll(position, newBlocks); oldBlock.setNextSiblingBlock(null); oldBlock.setPreviousSiblingBlock(null); } /** * Get the position of the provided block in the list of children. *

* Can't use {@link List#indexOf(Object)} since it's using {@link Object#equals(Object)} internally which is not * what we want since two WordBlock with the same text or two spaces are equals for example but we want to be able * to target one specific Block. * * @param block the block * @return the position of the block, -1 if the block can't be found */ private int indexOfChild(Block block) { return indexOfBlock(block, getChildren()); } /** * Find the index of the block in the tree. * * @param child the block for which to find the index * @return the index of the passed block in the tree, 0 is the current block and -1 means that it was not found * @since 10.10RC1 */ public long indexOf(Block child) { CounterBlockMatcher counter = new CounterBlockMatcher(child); Block found = getFirstBlock(counter, Axes.DESCENDANT_OR_SELF); return found != null ? counter.getCount() : -1; } @Override public List getChildren() { return this.childrenBlocks == null ? Collections.emptyList() : this.childrenBlocks; } @Override public Block getParent() { return this.parentBlock; } @Override public Map getParameters() { return this.parameters == null ? Collections.emptyMap() : Collections.unmodifiableMap(this.parameters); } @Override public String getParameter(String name) { return this.parameters == null ? null : this.parameters.get(name); } @Override public void setParameter(String name, String value) { if (this.parameters == null) { this.parameters = new LinkedHashMap<>(1); } this.parameters.put(name, value); } @Override public void setParameters(Map parameters) { if (this.parameters == null) { this.parameters = new LinkedHashMap<>(parameters); } else { this.parameters.clear(); this.parameters.putAll(parameters); } } @Override public Map getAttributes() { return this.attributes == null ? Collections.emptyMap() : Collections.unmodifiableMap(this.attributes); } @Override public Object getAttribute(String name) { return this.attributes == null ? null : this.attributes.get(name); } @Override public void setAttribute(String name, Object value) { if (this.attributes == null) { this.attributes = new LinkedHashMap<>(1); } this.attributes.put(name, value); } @Override public void setAttributes(Map attributes) { if (this.attributes == null) { this.attributes = new LinkedHashMap<>(attributes); } else { this.attributes.clear(); this.attributes.putAll(attributes); } } @Override public void setParent(Block parentBlock) { this.parentBlock = parentBlock; } @Override public Block getRoot() { Block block = this; while (block.getParent() != null) { block = block.getParent(); } return block; } @Override public Block getNextSibling() { return this.nextSiblingBlock; } @Override public Block getPreviousSibling() { return this.previousSiblingBlock; } @Override public void removeBlock(Block childBlockToRemove) { // Remove block List children = getChildren(); int position = indexOfBlock(childBlockToRemove, children); if (position == -1) { throw new InvalidParameterException("Provided Block to remove is not a child"); } getChildren().remove(position); // Re-calculate internal links between blocks if (childBlockToRemove != null) { Block previousBlock = childBlockToRemove.getPreviousSibling(); if (previousBlock != null) { previousBlock.setNextSiblingBlock(childBlockToRemove.getNextSibling()); } Block nextBlock = childBlockToRemove.getNextSibling(); if (nextBlock != null) { nextBlock.setPreviousSiblingBlock(previousBlock); } childBlockToRemove.setNextSiblingBlock(null); childBlockToRemove.setPreviousSiblingBlock(null); } } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof Block) { EqualsBuilder builder = new EqualsBuilder(); builder.append(getChildren(), ((Block) obj).getChildren()); builder.append(getParameters(), ((Block) obj).getParameters()); builder.append(getAttributes(), ((Block) obj).getAttributes()); return builder.isEquals(); } return false; } @Override public int hashCode() { HashCodeBuilder builder = new HashCodeBuilder(); builder.append(this.childrenBlocks); builder.append(this.parameters); builder.append(this.attributes); return builder.toHashCode(); } @Override public Block clone() { return clone(null); } /** * {@inheritDoc} * * @since 1.8RC2 */ @Override public Block clone(BlockFilter blockFilter) { Block block; try { block = (AbstractBlock) super.clone(); } catch (CloneNotSupportedException e) { // Should never happen throw new RuntimeException("Failed to clone object", e); } if (this.parameters != null) { ((AbstractBlock) block).parameters = new LinkedHashMap<>(this.parameters); } // Clone attribute values if possible as documented in getAttribute(). this.getAttributes().forEach((key, value) -> block.setAttribute(key, ObjectUtils.cloneIfPossible(value))); if (this.childrenBlocks != null) { ((AbstractBlock) block).childrenBlocks = new ArrayList<>(this.childrenBlocks.size()); for (Block childBlock : this.childrenBlocks) { if (blockFilter != null) { Block clonedChildBlocks = childBlock.clone(blockFilter); List filteredBlocks = blockFilter.filter(clonedChildBlocks); if (filteredBlocks.isEmpty()) { filteredBlocks = clonedChildBlocks.getChildren(); } block.addChildren(filteredBlocks); } else { block.addChild(childBlock.clone()); } } } return block; } @Override public void traverse(Listener listener) { before(listener); for (Block block : getChildren()) { block.traverse(listener); } after(listener); } /** * Send {@link org.xwiki.rendering.listener.Listener} events corresponding to the start of the block. For example * for a Bold block, this allows an XHTML Listener (aka a Renderer) to output <b>. * * @param listener the listener that will receive the events sent by this block before its children blocks have * emitted their own events. */ public void before(Listener listener) { // Do nothing by default, should be overridden by extending Blocks } /** * Send {@link Listener} events corresponding to the end of the block. For example for a Bold block, this allows an * XHTML Listener (aka a Renderer) to output </b>. * * @param listener the listener that will receive the events sent by this block before its children blocks have * emitted their own events. */ public void after(Listener listener) { // Do nothing by default, should be overridden by extending Blocks } @Override public List getBlocks(BlockMatcher matcher, Axes axes) { BlockNavigator navigator = new BlockNavigator(matcher); return navigator.getBlocks(this, axes); } @Override public T getFirstBlock(BlockMatcher matcher, Axes axes) { BlockNavigator navigator = new BlockNavigator(matcher); return navigator.getFirstBlock(this, axes); } @Override public Optional getSyntaxMetadata() { MetaDataBlock metaDataBlock = getFirstBlock(MetadataBlockMatcher.SYNTAX, Axes.ANCESTOR_OR_SELF); if (metaDataBlock != null) { return Optional.ofNullable((Syntax) metaDataBlock.getMetaData().getMetaData(MetaData.SYNTAX)); } return Optional.empty(); } @Override public Optional get(Function> searcher, Axes axes) { FunctionBlockMatcher matcher = new FunctionBlockMatcher<>(searcher); getFirstBlock(matcher, Axes.ANCESTOR_OR_SELF); return matcher.getValue(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy