org.obeonetwork.m2doc.generator.BookmarkManager.java Source code

Java tutorial

Introduction

Here is the source code for org.obeonetwork.m2doc.generator.BookmarkManager.java

Source

/*******************************************************************************
 *  Copyright (c) 2016 Obeo. 
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  which accompanies this distribution, and is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 *   
 *   Contributors:
 *       Obeo - initial API and implementation
 *  
 *******************************************************************************/
package org.obeonetwork.m2doc.generator;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;

import org.apache.poi.xwpf.usermodel.IRunBody;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.xmlbeans.impl.xb.xmlschema.SpaceAttribute.Space;
import org.obeonetwork.m2doc.parser.ValidationMessageLevel;
import org.obeonetwork.m2doc.util.M2DocUtils;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBookmark;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTMarkupRange;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType;

/**
 * Manage bookmarks.
 * 
 * @author <a href="mailto:yvan.lussaud@obeo.fr">Yvan Lussaud</a>
 */
public class BookmarkManager {
    /**
     * The buffer size.
     */
    private static final int BUFFER_SIZE = 16;

    /**
     * The reference text.
     */
    private static final String REF_TAG = " REF %s \\h ";

    /**
     * Known bookmarks so far.
     */
    private final Map<String, CTBookmark> bookmarks = new LinkedHashMap<String, CTBookmark>();

    /**
     * Open bookmarks.
     */
    private final Map<String, CTBookmark> startedBookmarks = new LinkedHashMap<String, CTBookmark>();

    /**
     * Pending references for a given bookmark name.
     */
    private final Map<String, Set<CTText>> pendingReferences = new HashMap<String, Set<CTText>>();

    /**
     * Bookmark name to reference ID.
     */
    private final Map<String, byte[]> referenceIDs = new HashMap<String, byte[]>();

    /**
     * Position to insert message for a given reference or bookmark.
     */
    private final Map<Object, XWPFRun> messagePositions = new HashMap<Object, XWPFRun>();

    /**
     * Starts a bookmark in the given {@link CTBody} with the given name.
     * 
     * @param paragraph
     *            the current {@link XWPFParagraph}
     * @param name
     *            the bookmark name
     */
    public void startBookmark(XWPFParagraph paragraph, String name) {
        if (bookmarks.containsKey(name)) {
            M2DocUtils.appendMessageRun(paragraph, ValidationMessageLevel.ERROR,
                    "Can't start duplicated bookmark " + name);
        } else {
            final CTBookmark bookmark = paragraph.getCTP().addNewBookmarkStart();
            // we create a new run for future error messages.
            messagePositions.put(bookmark, paragraph.createRun());
            bookmark.setName(name);
            final BigInteger id = getRandomID();
            bookmark.setId(id);
            bookmarks.put(name, bookmark);
            startedBookmarks.put(name, bookmark);
            Set<CTText> pendingRefs = pendingReferences.remove(name);
            if (pendingRefs != null) {
                for (CTText pendingRef : pendingRefs) {
                    // we remove the created for error messages.
                    final XWPFRun run = messagePositions.get(pendingRef);
                    final IRunBody parent = run.getParent();
                    if (parent instanceof XWPFParagraph) {
                        ((XWPFParagraph) parent).removeRun(((XWPFParagraph) parent).getRuns().indexOf(run));
                    } else {
                        throw new IllegalStateException("this should not happend");
                    }
                    pendingRef.setStringValue(String.format(REF_TAG, bookmark.getName()));
                }
            }
        }
    }

    /**
     * Ends the bookmark with the given name.
     * 
     * @param paragraph
     *            the current {@link XWPFParagraph}
     * @param name
     *            the bookmark name
     */
    public void endBookmark(XWPFParagraph paragraph, String name) {
        final CTBookmark bookmark = startedBookmarks.remove(name);
        if (bookmark != null) {
            final CTMarkupRange range = paragraph.getCTP().addNewBookmarkEnd();
            range.setId(bookmarks.get(name).getId());
            // we remove the created for error messages.
            final XWPFRun run = messagePositions.get(bookmark);
            final IRunBody parent = run.getParent();
            if (parent instanceof XWPFParagraph) {
                ((XWPFParagraph) parent).removeRun(((XWPFParagraph) parent).getRuns().indexOf(run));
            } else {
                throw new IllegalStateException("this should not happend");
            }
        } else if (bookmarks.containsKey(name)) {
            M2DocUtils.appendMessageRun(paragraph, ValidationMessageLevel.ERROR,
                    "Can't can't end already closed bookmark " + name);
        } else {
            M2DocUtils.appendMessageRun(paragraph, ValidationMessageLevel.ERROR,
                    "Can't can't end not existing bookmark " + name);
        }
    }

    /**
     * Gets a random ID.
     * 
     * @return a random ID
     */
    private BigInteger getRandomID() {
        final UUID uuid = UUID.randomUUID();

        ByteBuffer buffer = ByteBuffer.wrap(new byte[BUFFER_SIZE]);
        buffer.putLong(uuid.getMostSignificantBits());
        buffer.putLong(uuid.getLeastSignificantBits());

        return new BigInteger(buffer.array()).abs();
    }

    /**
     * Inserts a pending reference to the given name in the given {@link XWPFParagraph}.
     * 
     * @param paragraph
     *            the {@link XWPFParagraph}
     * @param name
     *            the bookmark name
     * @param text
     *            the text
     */
    public void insertReference(XWPFParagraph paragraph, String name, String text) {
        final CTBookmark bookmark = bookmarks.get(name);
        if (bookmark != null) {
            insertReference(paragraph, bookmark, text);
        } else {
            final XWPFRun messageRun = paragraph.createRun();
            final CTText ref = insertPendingReference(paragraph, name, text);
            messagePositions.put(ref, messageRun);
            Set<CTText> pendingRefs = pendingReferences.get(name);
            if (pendingRefs == null) {
                pendingRefs = new LinkedHashSet<CTText>();
                pendingReferences.put(name, pendingRefs);
            }
            pendingRefs.add(ref);
        }
    }

    /**
     * Inserts a reference to the given {@link CTBookmark} with the given text in the given {@link XWPFParagraph}.
     * 
     * @param paragraph
     *            the {@link XWPFParagraph}
     * @param bookmark
     *            the {@link CTBookmark}
     * @param text
     *            the text
     */
    private void insertReference(XWPFParagraph paragraph, CTBookmark bookmark, String text) {
        final String name = bookmark.getName();
        final CTText pgcttext = insertPendingReference(paragraph, name, text);
        pgcttext.setStringValue(String.format(REF_TAG, name));
    }

    /**
     * Inserts a reference to the given {@link CTBookmark} with the given text in the given {@link XWPFParagraph}.
     * 
     * @param paragraph
     *            the {@link XWPFParagraph}
     * @param name
     *            the bookmark name
     * @param text
     *            the text
     * @return the {@link CTText} corresponding to the reference.
     */
    private CTText insertPendingReference(XWPFParagraph paragraph, String name, String text) {
        final byte[] id = getReferenceID(name);
        final XWPFRun beginRun = paragraph.createRun();
        beginRun.getCTR().setRsidR(id);
        beginRun.getCTR().addNewFldChar().setFldCharType(STFldCharType.BEGIN);

        final XWPFRun preservedRun = paragraph.createRun();
        preservedRun.getCTR().setRsidR(id);
        final CTText pgcttext = preservedRun.getCTR().addNewInstrText();
        pgcttext.setSpace(Space.PRESERVE);

        final XWPFRun separateRun = paragraph.createRun();
        separateRun.getCTR().setRsidR(id);
        separateRun.getCTR().addNewFldChar().setFldCharType(STFldCharType.SEPARATE);

        final XWPFRun textRun = paragraph.createRun();
        textRun.getCTR().setRsidR(id);
        textRun.getCTR().addNewRPr().addNewNoProof();
        textRun.setText(text);
        textRun.setBold(true);

        final XWPFRun endRun = paragraph.createRun();
        endRun.getCTR().setRsidR(id);
        endRun.getCTR().addNewFldChar().setFldCharType(STFldCharType.END);

        return pgcttext;
    }

    /**
     * Gets the reference ID for the given bookmark name.
     * 
     * @param name
     *            the bookmark name
     * @return the reference ID for the given bookmark name
     */
    private byte[] getReferenceID(String name) {
        final byte[] res;

        final byte[] cachedID = referenceIDs.get(name);
        if (cachedID == null) {
            res = getRandomID().toByteArray();
            referenceIDs.put(name, res);
        } else {
            res = cachedID;
        }

        return res;
    }

    /**
     * Marks the bookmarks that are still open.
     * 
     * @return <code>true</code> if any open bookmarks was found, <code>false</code> otherwise
     */
    public boolean markOpenBookmarks() {
        final boolean res = !startedBookmarks.isEmpty();

        if (res) {
            for (Entry<String, CTBookmark> entry : startedBookmarks.entrySet()) {
                final XWPFRun positionRun = messagePositions.remove(entry.getValue());
                M2DocUtils.setRunMessage(positionRun, ValidationMessageLevel.ERROR,
                        "unclosed bookmark " + entry.getKey());
            }
        }

        return res;
    }

    /**
     * Marks dangling references.
     * 
     * @return <code>true</code> if any dangling reference was found, <code>false</code> otherwise
     */
    public boolean markDanglingReferences() {
        final boolean res = !pendingReferences.isEmpty();

        if (res) {
            for (Entry<String, Set<CTText>> entry : pendingReferences.entrySet()) {
                for (CTText ref : entry.getValue()) {
                    final XWPFRun refRun = messagePositions.remove(ref);
                    M2DocUtils.insertMessageAfter(refRun, ValidationMessageLevel.ERROR,
                            "dangling reference for bookmark " + entry.getKey());
                }
            }
        }

        return res;
    }
}