Java tutorial
/******************************************************************************* * 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; } }