org.openflexo.dg.action.ReinjectDocx.java Source code

Java tutorial

Introduction

Here is the source code for org.openflexo.dg.action.ReinjectDocx.java

Source

/*
 * (c) Copyright 2010-2011 AgileBirds
 *
 * This file is part of OpenFlexo.
 *
 * OpenFlexo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
 *
 */
package org.openflexo.dg.action;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.openflexo.docxparser.DocxFileParser;
import org.openflexo.docxparser.dto.api.IParsedDocx;
import org.openflexo.docxparser.dto.api.IParsedFlexoEPI;
import org.openflexo.docxparser.dto.api.IParsedFlexoObject;
import org.openflexo.docxparser.dto.api.IParsedHtml;
import org.openflexo.docxparser.dto.api.IParsedHtmlResource;
import org.openflexo.foundation.FlexoEditor;
import org.openflexo.foundation.FlexoException;
import org.openflexo.foundation.FlexoModelObject;
import org.openflexo.foundation.IOFlexoException;
import org.openflexo.foundation.InvalidArgumentException;
import org.openflexo.foundation.action.FlexoActionType;
import org.openflexo.foundation.cg.CGObject;
import org.openflexo.foundation.cg.DGRepository;
import org.openflexo.foundation.cg.GeneratedDoc;
import org.openflexo.foundation.cg.action.AbstractGCAction;
import org.openflexo.foundation.ontology.EditionPatternInstance;
import org.openflexo.foundation.ontology.EditionPatternReference;
import org.openflexo.foundation.rm.FlexoProject;
import org.openflexo.foundation.rm.FlexoStorageResource;
import org.openflexo.foundation.rm.StorageResourceData;
import org.openflexo.foundation.toc.TOCEntry;
import org.openflexo.foundation.utils.FlexoModelObjectReference;
import org.openflexo.foundation.view.View;
import org.openflexo.foundation.view.ViewDefinition;
import org.openflexo.foundation.viewpoint.EditionPattern;
import org.openflexo.localization.FlexoLocalization;
import org.openflexo.toolbox.FileUtils;
import org.openflexo.toolbox.StringUtils;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

public class ReinjectDocx extends AbstractGCAction<ReinjectDocx, CGObject> {
    private static final Logger logger = Logger.getLogger(ReinjectDocx.class.getPackage().getName());

    private File docxToReinject;
    private int numberOfDescriptionUpdated;
    private int numberOfNameUpdated;
    private int numberOfTocEntryTitleUpdated;
    private int numberOfTocEntryContentUpdated;
    private int numberOfObjectNotFound;
    private int numberOfEPIUpdated;
    private StringBuilder errorReport;

    public static final FlexoActionType<ReinjectDocx, CGObject, CGObject> actionType = new FlexoActionType<ReinjectDocx, CGObject, CGObject>(
            "reinject_docx", FlexoActionType.importMenu, FlexoActionType.defaultGroup,
            FlexoActionType.NORMAL_ACTION_TYPE) {

        @Override
        public boolean isEnabledForSelection(CGObject object, Vector<CGObject> globalSelection) {
            return true;
        }

        @Override
        public boolean isVisibleForSelection(CGObject object, Vector<CGObject> globalSelection) {
            return true;
        }

        @Override
        public ReinjectDocx makeNewAction(CGObject focusedObject, Vector<CGObject> globalSelection,
                FlexoEditor editor) {
            return new ReinjectDocx(focusedObject, globalSelection, editor);
        }

    };

    protected ReinjectDocx(CGObject focusedObject, Vector<CGObject> globalSelection, FlexoEditor editor) {
        super(actionType, focusedObject, globalSelection, editor);
    }

    static {
        FlexoModelObject.addActionForClass(ReinjectDocx.actionType, DGRepository.class);
        FlexoModelObject.addActionForClass(ReinjectDocx.actionType, GeneratedDoc.class);
    }

    @Override
    protected void doAction(Object context) throws FlexoException {
        numberOfDescriptionUpdated = 0;
        numberOfNameUpdated = 0;
        numberOfTocEntryTitleUpdated = 0;
        numberOfTocEntryContentUpdated = 0;
        numberOfEPIUpdated = 0;
        numberOfObjectNotFound = 0;
        errorReport = new StringBuilder();

        FileInputStream in;
        try {
            in = new FileInputStream(getDocxToReinject());
        } catch (IOException e) {
            throw new IOFlexoException(e);
        }

        try {
            makeFlexoProgress(FlexoLocalization.localizedForKey("reinjecting_docx_file"), 2);
            setProgress(FlexoLocalization.localizedForKey("parsing"));

            File imagesDir = getEditor().getProject().getImportedImagesDir();
            imagesDir = new File(imagesDir, "DocxReinject");
            imagesDir.mkdirs();
            String imageRelativePath = FileUtils.makeFilePathRelativeToDir(imagesDir,
                    getEditor().getProject().getProjectDirectory());
            DocxFileParser docxParser = new DocxFileParser(in, getAvailableCssClasses(), imageRelativePath);

            IParsedDocx parsedDocx = docxParser.getParsedDocx();
            setProgress(FlexoLocalization.localizedForKey("reinjecting_docx_file_in_model"));
            resetSecondaryProgress(parsedDocx.getAllParsedFlexoDescriptions().size()
                    + parsedDocx.getAllParsedFlexoNames().size() + parsedDocx.getAllParsedFlexoTitles().size()
                    + parsedDocx.getAllParsedFlexoContents().size() + parsedDocx.getAllParsedEPIs().size() + 1);

            // First load all unloaded resources
            for (FlexoStorageResource<? extends StorageResourceData> resource : getFocusedObject().getProject()
                    .getStorageResources()) {
                resource.getResourceData();// no need to mark as modified .setIsModified();
            }

            // Build a hash containing all flexoobject by "FlexoID_UserID"
            Map<String, FlexoModelObject> allObjects = new HashMap<String, FlexoModelObject>();
            List<FlexoModelObject> registeredObjects = new ArrayList<FlexoModelObject>();
            registeredObjects.addAll(getFocusedObject().getProject().getAllRegisteredObjects()); // Create new to avoid concurrent
            // modification exception
            for (FlexoModelObject flexoObject : registeredObjects) {
                allObjects.put(flexoObject.getSerializationIdentifier(), flexoObject);
            }

            Map<FlexoModelObject, String[]> duplicatedFlexoNames = reinjectContent(imagesDir, parsedDocx,
                    allObjects);
            handleDuplicatedNames(duplicatedFlexoNames);
            reinjectEPI(parsedDocx);

        } catch (InvalidFormatException e) {
            throw new InvalidArgumentException(e.getMessage());
        } catch (IOException e) {
            throw new FlexoException("IO exception while trying to handle docx reinjection file resources", e);
        } finally {
            hideFlexoProgress();
            try {
                in.close();
            } catch (IOException e) {
                throw new IOFlexoException(e);
            }
        }
    }

    private void reinjectEPI(IParsedDocx parsedDocx) {
        Multimap<EditionPatternInstance, IParsedFlexoEPI> epis = lookUpEditionPatternInstances(parsedDocx);
        Multimap<EditionPatternInstance, IParsedFlexoEPI> episToReinject = removeConflictingParsedDocX(epis);
        for (Entry<EditionPatternInstance, Collection<IParsedFlexoEPI>> e : episToReinject.asMap().entrySet()) {
            EditionPatternInstance epi = e.getKey();
            for (IParsedFlexoEPI parsedFlexoEPI : e.getValue()) {
                setSecondaryProgress(FlexoLocalization.localizedForKey("reinjecting_edition_pattern_value") + " "
                        + parsedFlexoEPI.getValue());
                boolean result = epi.setBindingValue(parsedFlexoEPI.getBindingPath(), parsedFlexoEPI.getValue());
                if (result) {
                    numberOfEPIUpdated++;
                }
            }
        }
    }

    public Multimap<EditionPatternInstance, IParsedFlexoEPI> removeConflictingParsedDocX(
            Multimap<EditionPatternInstance, IParsedFlexoEPI> epis) {
        Multimap<EditionPatternInstance, IParsedFlexoEPI> episToReinject = ArrayListMultimap.create();
        Multimap<String, IParsedFlexoEPI> paths = ArrayListMultimap.create();
        for (Entry<EditionPatternInstance, Collection<IParsedFlexoEPI>> e : epis.asMap().entrySet()) {
            if (e.getValue().size() > 1) {
                // There are multiple parsed DocX EPI for this EditionPatternInstance
                // Let's see if it is for the same binding path
                for (IParsedFlexoEPI epi : e.getValue()) {
                    paths.put(epi.getBindingPath(), epi);
                }
                for (Entry<String, Collection<IParsedFlexoEPI>> e1 : paths.asMap().entrySet()) {
                    boolean conflict = false;
                    if (e1.getValue().size() > 1) {
                        // There are multiple parsed DocX EPI for the same EPI and the same binding path
                        Object currentValue = e.getKey().evaluate(e1.getKey());
                        List<IParsedFlexoEPI> modified = new ArrayList<IParsedFlexoEPI>();
                        for (IParsedFlexoEPI epi : e1.getValue()) {
                            if (!epi.getValue().equals(currentValue)) {
                                modified.add(epi);
                            }
                        }
                        if (modified.size() > 1) {
                            // There is more than one parsed DocX EPI that has a different value than the current one
                            // Let's see if they are not all the same.
                            String value = modified.get(0).getValue();
                            for (int i = 1; i < modified.size(); i++) {
                                if (!value.equals(modified.get(i).getValue())) {
                                    conflict = true;
                                    errorReport.append("Conflicting values: ").append(value + " ")
                                            .append(modified.get(i).getValue()).append("\n");
                                    break;
                                }
                            }
                        }
                    }
                    if (!conflict) {
                        episToReinject.putAll(e.getKey(), e1.getValue());
                    }
                }
            } else {
                // There is a single parsed DocX EPI for this EditionPatternInstance
                episToReinject.putAll(e.getKey(), e.getValue());
            }
            paths.clear();
        }
        return episToReinject;
    }

    public Multimap<EditionPatternInstance, IParsedFlexoEPI> lookUpEditionPatternInstances(IParsedDocx parsedDocx) {
        Multimap<EditionPatternInstance, IParsedFlexoEPI> epis = ArrayListMultimap.create();
        List<IParsedFlexoEPI> parsedEPIS = parsedDocx.getAllParsedEPIs();
        FlexoProject project = getFocusedObject().getProject();
        for (IParsedFlexoEPI epi : parsedEPIS) {
            if (epi.getEditionPatternURI() == null) {
                continue;
            }
            if (epi.getBindingPath() == null) {
                continue;
            }
            if (epi.getEditionPatternInstanceID() == null || !epi.getEditionPatternInstanceID().matches("-?\\d+")) {
                continue;
            }
            if (epi.getModelObjectReference() == null) {
                continue;
            }
            FlexoModelObject object = new FlexoModelObjectReference<FlexoModelObject>(project,
                    epi.getModelObjectReference()).getObject(true);
            long instanceID = Long.valueOf(epi.getEditionPatternInstanceID());
            if (object != null) {
                boolean found = false;
                for (EditionPatternReference ref : object.getEditionPatternReferences()) {
                    if (ref.getEditionPattern() != null
                            && ref.getEditionPattern().getURI().equals(epi.getEditionPatternURI())
                            && ref.getInstanceId() == instanceID) {
                        if (ref.getEditionPatternInstance() != null) {
                            epis.put(ref.getEditionPatternInstance(), epi);
                            found = true;
                            break;
                        }
                    }
                }
                if (found) {
                    continue;
                }
            }
            boolean found = false;
            for (ViewDefinition vd : project.getShemaLibrary().getAllShemaList()) {
                View view = vd.getShema();
                if (view == null) {
                    continue;
                }
                EditionPattern pattern = null;
                for (EditionPattern ep : view.getCalc().getEditionPatterns()) {
                    if (ep.getURI().equals(epi.getEditionPatternURI())) {
                        pattern = ep;
                        break;
                    }
                }
                if (pattern != null) {
                    for (EditionPatternInstance inst : vd.getShema().getEPInstances(pattern)) {
                        if (inst.getInstanceId() == instanceID) {
                            epis.put(inst, epi);
                            found = true;
                            break;
                        }
                    }
                }
                if (found) {
                    break;
                }
            }
            if (found) {
                continue;
            }
            if (logger.isLoggable(Level.WARNING)) {
                logger.warning("Could not find Edition pattern instance " + epi.getEditionPatternURI() + " "
                        + epi.getEditionPatternInstanceID());
            }
        }
        return epis;
    }

    public void handleDuplicatedNames(Map<FlexoModelObject, String[]> duplicatedFlexoNames) {
        for (FlexoModelObject flexoObject : duplicatedFlexoNames.keySet()) {
            try {
                flexoObject.setName(duplicatedFlexoNames.get(flexoObject)[1]); // Try to set the new name
                numberOfNameUpdated++;
            } catch (Exception e) {
                try {
                    // Ok cannot set the new name -> rollback to the old one
                    addToErrorReport(
                            "Cannot change name of object '" + duplicatedFlexoNames.get(flexoObject)[0] + "' to '"
                                    + duplicatedFlexoNames.get(flexoObject)[1] + "': the new name already exists");
                    flexoObject.setName(duplicatedFlexoNames.get(flexoObject)[0]);
                } catch (Exception e1) { // Should not occur
                    logger.log(Level.WARNING,
                            "Reinject docx: cannot roll back name for object '" + flexoObject.getName() + " ("
                                    + flexoObject.getUserIdentifier() + "_" + flexoObject.getFlexoID() + ")' to '"
                                    + duplicatedFlexoNames.get(flexoObject)[0] + "'",
                            e1);
                    addToErrorReport("Cannot rollback name of object '" + flexoObject.getName() + "' to '"
                            + duplicatedFlexoNames.get(flexoObject)[0] + "': " + e1.getMessage());
                }
            }
        }
    }

    public Map<FlexoModelObject, String[]> reinjectContent(File imagesDir, IParsedDocx parsedDocx,
            Map<String, FlexoModelObject> allObjects) {
        Map<FlexoModelObject, String[]> duplicatedFlexoNames = new HashMap<FlexoModelObject, String[]>();
        for (IParsedFlexoObject parsedFlexoObject : parsedDocx.getAllParsedFlexoObjects()) {

            FlexoModelObject flexoObject = allObjects.get(FlexoModelObject
                    .getSerializationIdentifier(parsedFlexoObject.getUserId(), parsedFlexoObject.getFlexoId()));

            if (flexoObject != null) {
                if (parsedFlexoObject.getParsedFlexoDescription() != null) {
                    numberOfDescriptionUpdated++;
                    setSecondaryProgress(
                            FlexoLocalization.localizedForKey("reinjecting_description_in_model_for_object") + " "
                                    + flexoObject.getName());

                    for (String target : parsedFlexoObject.getParsedFlexoDescription().getAllTargets()) {
                        logger.info("Updating object '" + flexoObject.getSerializationIdentifier() + "' description"
                                + (target.length() > 0 ? " for target '" + target + "'" : ""));
                        IParsedHtml parsedHtml = parsedFlexoObject.getParsedFlexoDescription()
                                .getHtmlDescription(target);
                        if (target.length() == 0) {
                            flexoObject.setDescription(parsedHtml.getHtml());
                        } else {
                            flexoObject.setSpecificDescriptionsForKey(parsedHtml.getHtml(), target);
                            flexoObject.setHasSpecificDescriptions(true);
                        }

                        copyNeededResourcesFromParsedHtml(parsedHtml, imagesDir);
                    }
                }

                if (parsedFlexoObject.getParsedFlexoName() != null) {
                    setSecondaryProgress(FlexoLocalization.localizedForKey("reinjecting_name_in_model_for_object")
                            + " " + flexoObject.getName());

                    String oldName = flexoObject.getName();
                    String newName = parsedFlexoObject.getParsedFlexoName().getFlexoName();

                    if (newName != null && (oldName == null || !StringUtils.replaceBreakLinesBy(newName, " ")
                            .equals(StringUtils.replaceBreakLinesBy(oldName, " ")))) {
                        try {
                            logger.info("Updating object '" + flexoObject.getSerializationIdentifier() + "' name");
                            flexoObject.setName(newName);
                            numberOfNameUpdated++;
                        } catch (Exception e) {
                            // Can be a duplicate Exception -> try to add a unique suffix to allow name switch between two objects
                            // This suffix will be removed at the end of the process. Either the object name will be set to the newly
                            // set one if possible or it will be reset to its old name.
                            // Ideally, this should be performed only on duplicateException but there is too much different possible
                            // duplicate Exception type (with no common hierarchy).

                            try {
                                flexoObject.setName(
                                        newName + flexoObject.getUserIdentifier() + flexoObject.getFlexoID());
                                duplicatedFlexoNames.put(flexoObject, new String[] { oldName, newName });
                            } catch (Exception e1) {
                                logger.log(Level.WARNING,
                                        "Reinject docx: cannot set name for object '" + flexoObject.getName() + " ("
                                                + flexoObject.getUserIdentifier() + "_" + flexoObject.getFlexoID()
                                                + ")' to '" + newName + "'",
                                        e1);
                                addToErrorReport("Cannot change name of object '" + oldName + "' to '" + newName
                                        + "': " + (e.getMessage() != null ? e.getMessage() : e.getClass()));
                            }
                        }
                    }
                }

                if (parsedFlexoObject.getParsedFlexoTitle() != null && flexoObject instanceof TOCEntry) {
                    TOCEntry tocEntry = (TOCEntry) flexoObject;
                    setSecondaryProgress(
                            FlexoLocalization.localizedForKey("reinjecting_title_in_model_for_tocentry") + " "
                                    + tocEntry.getTitle());

                    String oldTitle = tocEntry.getTitle();
                    String newTitle = parsedFlexoObject.getParsedFlexoTitle().getFlexoTitle();

                    if (newTitle != null && (oldTitle == null || !StringUtils.replaceBreakLinesBy(newTitle, " ")
                            .equals(StringUtils.replaceBreakLinesBy(oldTitle, " ")))) {
                        logger.info("Updating object '" + flexoObject.getSerializationIdentifier() + "' title");
                        tocEntry.setTitle(newTitle);
                        numberOfTocEntryTitleUpdated++;
                    }
                }

                if (parsedFlexoObject.getParsedFlexoContent() != null && flexoObject instanceof TOCEntry) {
                    TOCEntry tocEntry = (TOCEntry) flexoObject;
                    setSecondaryProgress(
                            FlexoLocalization.localizedForKey("reinjecting_content_in_model_for_tocentry") + " "
                                    + tocEntry.getTitle());

                    logger.info("Updating object '" + flexoObject.getSerializationIdentifier() + "' content");
                    try {
                        tocEntry.setContent(parsedFlexoObject.getParsedFlexoContent().getFlexoContent().getHtml());
                        numberOfTocEntryContentUpdated++;

                        copyNeededResourcesFromParsedHtml(
                                parsedFlexoObject.getParsedFlexoContent().getFlexoContent(), imagesDir);
                    } catch (IllegalAccessException e) {
                        logger.log(Level.WARNING,
                                "Reinject docx: cannot set content for toc entry '" + tocEntry.getTitle() + " ("
                                        + flexoObject.getUserIdentifier() + "_" + flexoObject.getFlexoID() + ")'",
                                e);
                        addToErrorReport("Cannot change content of toc entry '" + tocEntry.getTitle() + "': "
                                + (e.getMessage() != null ? e.getMessage() : e.getClass()));
                    }
                }
            } else {
                setSecondaryProgress(""); // Skip one, not found
                logger.warning("ReinjectDocx: cannot find object with flexoid '" + parsedFlexoObject.getFlexoId()
                        + "' and user id '" + parsedFlexoObject.getUserId() + "'");
                numberOfObjectNotFound++;
            }
        }
        return duplicatedFlexoNames;
    }

    private void copyNeededResourcesFromParsedHtml(IParsedHtml parsedHtml, File imagesDir) {
        for (IParsedHtmlResource htmlResource : parsedHtml.getNeededResources()) { // Copy needed resources (images) in imported images
            // directory
            try {
                File resourceFile = new File(imagesDir, htmlResource.getIdentifier());
                resourceFile.createNewFile();
                FileOutputStream outStream = new FileOutputStream(resourceFile);
                outStream.write(htmlResource.getFile());
            } catch (IOException e) {
                logger.log(Level.WARNING, "Reinject docx: cannot write resource file with identifier '"
                        + htmlResource.getIdentifier() + "'", e);
                addToErrorReport(
                        "Cannot write resource file with identifier '" + htmlResource.getIdentifier() + "'");
            }
        }
    }

    private Set<String> getAvailableCssClasses() {
        try {
            Set<String> availableCssClasses = new HashSet<String>();

            Enumeration<?> en = getEditor().getProject().getDocumentationCssResource().getStyleSheet()
                    .getStyleNames();
            while (en.hasMoreElements()) {
                String styleName = String.valueOf(en.nextElement());

                if (styleName.startsWith(".") && styleName.trim().length() > 1) {
                    availableCssClasses.add(styleName.substring(1).trim());
                }
            }

            return availableCssClasses;
        } catch (MalformedURLException e) {
            logger.log(Level.WARNING, "Cannot load css for reinject.", e);
            return new HashSet<String>();
        }
    }

    private void addToErrorReport(String msg) {
        if (errorReport == null) {
            errorReport = new StringBuilder();
        } else if (errorReport.length() > 0) {
            errorReport.append("\n");
        }
        errorReport.append("* " + msg);
    }

    public File getDocxToReinject() {
        return docxToReinject;
    }

    public void setDocxToReinject(File docxToReinject) {
        this.docxToReinject = docxToReinject;
    }

    public int getNumberOfDescriptionUpdated() {
        return numberOfDescriptionUpdated;
    }

    public int getNumberOfNameUpdated() {
        return numberOfNameUpdated;
    }

    public int getNumberOfTocEntryTitleUpdated() {
        return numberOfTocEntryTitleUpdated;
    }

    public int getNumberOfTocEntryContentUpdated() {
        return numberOfTocEntryContentUpdated;
    }

    public int getNumberOfObjectNotFound() {
        return numberOfObjectNotFound;
    }

    public int getNumberOfEPIUpdated() {
        return numberOfEPIUpdated;
    }

    public String getErrorReport() {
        return errorReport != null ? errorReport.toString() : "";
    }

    public boolean hasError() {
        return errorReport != null && errorReport.length() > 0;
    }
}