Package org.zanata.rest.service

Source Code of org.zanata.rest.service.ResourceUtils

package org.zanata.rest.service;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.fedorahosted.tennera.jgettext.HeaderFields;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.web.FileUploadException;
import org.zanata.ApplicationConfiguration;
import org.zanata.common.ContentState;
import org.zanata.common.LocaleId;
import org.zanata.common.MergeType;
import org.zanata.common.ResourceType;
import org.zanata.model.HDocument;
import org.zanata.model.HLocale;
import org.zanata.model.HPerson;
import org.zanata.model.HSimpleComment;
import org.zanata.model.HTextFlow;
import org.zanata.model.HTextFlowTarget;
import org.zanata.model.po.HPoHeader;
import org.zanata.model.po.HPoTargetHeader;
import org.zanata.model.po.HPotEntryData;
import org.zanata.rest.dto.Person;
import org.zanata.rest.dto.extensions.ExtensionType;
import org.zanata.rest.dto.extensions.comment.SimpleComment;
import org.zanata.rest.dto.extensions.gettext.AbstractResourceMetaExtension;
import org.zanata.rest.dto.extensions.gettext.HeaderEntry;
import org.zanata.rest.dto.extensions.gettext.PoHeader;
import org.zanata.rest.dto.extensions.gettext.PoTargetHeader;
import org.zanata.rest.dto.extensions.gettext.PotEntryHeader;
import org.zanata.rest.dto.extensions.gettext.TextFlowExtension;
import org.zanata.rest.dto.extensions.gettext.TextFlowTargetExtension;
import org.zanata.rest.dto.extensions.gettext.TranslationsResourceExtension;
import org.zanata.rest.dto.resource.AbstractResourceMeta;
import org.zanata.rest.dto.resource.ExtensionSet;
import org.zanata.rest.dto.resource.Resource;
import org.zanata.rest.dto.resource.TextFlow;
import org.zanata.rest.dto.resource.TextFlowTarget;
import org.zanata.rest.dto.resource.TranslationsResource;
import org.zanata.util.ServiceLocator;
import org.zanata.util.StringUtil;

import com.google.common.base.Optional;

@Name("resourceUtils")
@Scope(ScopeType.STATELESS)
@Slf4j
public class ResourceUtils {
    /**
     * Newline character used for multi-line comments
     */
    private static final char NEWLINE = '\n';
    private static final String ZANATA_GENERATOR_PREFIX = "Zanata";
    private static final String ZANATA_TAG = "#zanata";
    private static final String PO_DATE_FORMAT = "yyyy-MM-dd hh:mmZ";
    private static final String PO_DEFAULT_CONTENT_TYPE =
            "text/plain; charset=UTF-8";

    /**
     * PO Header entries
     */
    private static final String LAST_TRANSLATOR_HDR = "Last-Translator";
    private static final String PO_REVISION_DATE_HDR =
            HeaderFields.KEY_PoRevisionDate;
    private static final String LANGUAGE_TEAM_HDR =
            HeaderFields.KEY_LanguageTeam;
    private static final String X_GENERATOR_HDR = "X-Generator";
    private static final String LANGUAGE_HDR = HeaderFields.KEY_Language;
    private static final String CONTENT_TYPE_HDR = HeaderFields.KEY_ContentType;
    private static final String PLURAL_FORMS_HDR = "Plural-Forms";

    private static final Pattern NPLURALS_TAG_PATTERN = Pattern
            .compile("nplurals=");
    private static final Pattern NPLURALS_PATTERN = Pattern
            .compile("nplurals=[0-9]+");
    private static final String PLURALS_FILE = "pluralforms.properties";
    private static final String DEFAULT_PLURAL_FORM = "nplurals=1; plural=0";

    // private static int MAX_TARGET_CONTENTS = 6;

    private static Properties pluralForms;

    @In
    private EntityManager entityManager;

    @PostConstruct
    public void create() {
        try {
            if (pluralForms == null) {
                pluralForms = new Properties();
                pluralForms.load(this.getClass().getClassLoader()
                        .getResourceAsStream(PLURALS_FILE));
            }
        } catch (IOException e) {
            log.error("There was an error loading plural forms.", e);
        }
    }

    static Properties getPluralForms() {
        return pluralForms;
    }

    /**
     * Merges the list of TextFlows into the target HDocument, adding and
     * obsoleting TextFlows as necessary.
     *
     * @param from
     * @param to
     * @return
     */
    boolean transferFromTextFlows(List<TextFlow> from, HDocument to,
            Set<String> enabledExtensions, int nextDocRev) {
        boolean changed = false;
        to.getTextFlows().clear();
        Set<String> incomingIds = new HashSet<String>();
        Set<String> previousIds =
                new HashSet<String>(to.getAllTextFlows().keySet());
        int count = 0;
        for (TextFlow tf : from) {
            if (!incomingIds.add(tf.getId())) {
                Response response =
                        Response.status(Status.BAD_REQUEST)
                                .entity("encountered TextFlow with duplicate ID "
                                        + tf.getId()).build();
                throw new WebApplicationException(response);
            }
            HTextFlow textFlow;
            if (previousIds.contains(tf.getId())) {
                previousIds.remove(tf.getId());
                textFlow = to.getAllTextFlows().get(tf.getId());
                textFlow.setObsolete(false);
                to.getTextFlows().add(textFlow);
                // avoid changing revision when resurrecting an unchanged TF
                if (transferFromTextFlow(tf, textFlow, enabledExtensions)) {// content
                                                                            // has
                                                                            // changed
                    textFlow.setRevision(nextDocRev);
                    changed = true;
                    for (HTextFlowTarget targ : textFlow.getTargets().values()) {
                        // if (targ.getState() != ContentState.New)
                        if (targ.getState().isTranslated()) {
                            targ.setState(ContentState.NeedReview);
                            targ.setVersionNum(targ.getVersionNum() + 1);
                        }
                    }
                    log.debug("TextFlow with id {} has changed", tf.getId());
                }
            } else {
                textFlow = new HTextFlow();
                textFlow.setDocument(to);
                textFlow.setResId(tf.getId());
                textFlow.setRevision(nextDocRev);
                transferFromTextFlow(tf, textFlow, enabledExtensions);
                changed = true;
                to.getAllTextFlows().put(textFlow.getResId(), textFlow);
                to.getTextFlows().add(textFlow);
                entityManager.persist(textFlow);
                log.debug("TextFlow with id {} is new", tf.getId());
            }
            count++;

            if (count % 100 == 0) {
                entityManager.flush();
            }
            // FIXME we can't clear entityManager here. See
            // org.zanata.feature.rest.CopyTransTest.testPushTranslationAndCopyTrans.
            // If you clear, last copyTran REST call will trigger exception on
            // the server (hDocument.getTextFlows() will contain null entries -
            // corrupted collection.
            // see https://github.com/zanata/zanata-server/pull/571#issuecomment-55547577)

            /*
            if (count % 500 == 0) {
            // this in some cases seem to slow down overall performance
                entityManager.clear();
                to = entityManager.find(HDocument.class, to.getId());
            }
            */
        }

        // set remaining textflows to obsolete.
        for (String id : previousIds) {
            HTextFlow textFlow = to.getAllTextFlows().get(id);
            if (!textFlow.isObsolete()) {
                changed = true;
                log.debug("TextFlow with id {} is now obsolete", id);
                textFlow.setRevision(to.getRevision());
                textFlow.setObsolete(true);
            }
        }
        if (changed)
            to.setRevision(nextDocRev);
        return changed;
    }

    /**
     * Merges from the DTO Resource into HDocument, adding and obsoleting
     * textflows, including metadata and the specified extensions
     *
     * @param from
     * @param to
     * @param enabledExtensions
     * @return
     */
    public boolean transferFromResource(Resource from, HDocument to,
            Set<String> enabledExtensions, HLocale locale, int nextDocRev) {
        boolean changed = false;
        changed |=
                transferFromResourceMetadata(from, to, enabledExtensions,
                        locale, nextDocRev);
        changed |=
                transferFromTextFlows(from.getTextFlows(), to,
                        enabledExtensions, nextDocRev);
        return changed;
    }

    /**
     * Transfers metadata and the specified extensions from DTO
     * AbstractResourceMeta into HDocument
     *
     * @param from
     * @param to
     * @param enabledExtensions
     * @return
     */
    public boolean transferFromResourceMetadata(AbstractResourceMeta from,
            HDocument to, Set<String> enabledExtensions, HLocale locale,
            int nextDocRev) {
        boolean changed = false;

        // name
        if (!equals(from.getName(), to.getDocId())) {
            to.setFullPath(from.getName());
            changed = true;
        }

        // locale
        if (!equals(from.getLang(), to.getLocale().getLocaleId())) {
            log.debug("locale:" + from.getLang());
            to.setLocale(locale);
            changed = true;
        }

        // contentType
        if (!equals(from.getContentType(), to.getContentType())) {
            to.setContentType(from.getContentType());
            changed = true;
        }
        // handle extensions
        changed |=
                transferFromResourceExtensions(from.getExtensions(true), to,
                        enabledExtensions);
        if (changed)
            to.setRevision(nextDocRev);
        return changed;
    }

    /**
     * Transfers the specified extensions from DTO AbstractResourceMeta into
     * HDocument
     *
     * @param from
     * @param to
     * @param enabledExtensions
     * @return
     * @see #transferFromResource
     */
    private boolean transferFromResourceExtensions(
            ExtensionSet<AbstractResourceMetaExtension> from, HDocument to,
            Set<String> enabledExtensions) {
        boolean changed = false;

        if (enabledExtensions.contains(PoHeader.ID)) {
            PoHeader poHeaderExt = from.findByType(PoHeader.class);
            if (poHeaderExt != null) {
                HPoHeader poHeader = to.getPoHeader();
                if (poHeader == null) {
                    log.debug("create a new HPoHeader");
                    poHeader = new HPoHeader();
                }
                changed |= transferFromPoHeader(poHeaderExt, poHeader);

                if (to.getPoHeader() == null && changed) {
                    to.setPoHeader(poHeader);
                }

            }
        }

        return changed;
    }

    /**
     * Transfers enabled extensions from TranslationsResource into HDocument for
     * a single locale
     *
     * @param from
     * @param doc
     * @param enabledExtensions
     * @param locale
     * @param mergeType
     * @return
     * @see #transferToTranslationsResourceExtensions
     */
    public boolean transferFromTranslationsResourceExtensions(
            ExtensionSet<TranslationsResourceExtension> from, HDocument doc,
            Set<String> enabledExtensions, HLocale locale, MergeType mergeType) {
        boolean changed = false;
        if (enabledExtensions.contains(PoTargetHeader.ID)) {
            PoTargetHeader fromTargetHeader =
                    from.findByType(PoTargetHeader.class);
            if (fromTargetHeader != null) {
                log.debug("found PO header for locale: {}", locale);
                try {
                    changed =
                            tryGetOrCreateTargetHeader(doc, locale, mergeType,
                                    changed, fromTargetHeader);
                } catch (org.hibernate.exception.ConstraintViolationException e) {
                    entityManager.refresh(doc);
                    changed =
                            tryGetOrCreateTargetHeader(doc, locale, mergeType,
                                    changed, fromTargetHeader);
                }
            } else {
                changed |= doc.getPoTargetHeaders().remove(locale) != null;
            }
        }
        return changed;
    }

    private boolean tryGetOrCreateTargetHeader(HDocument doc, HLocale locale,
            MergeType mergeType, boolean changed,
            PoTargetHeader fromTargetHeader) {
        HPoTargetHeader toTargetHeader = doc.getPoTargetHeaders().get(locale);
        if (toTargetHeader == null) {
            changed = true;
            toTargetHeader = new HPoTargetHeader();
            toTargetHeader.setTargetLanguage(locale);
            toTargetHeader.setDocument(doc);
            transferFromPoTargetHeader(fromTargetHeader, toTargetHeader,
                    MergeType.IMPORT); // return
                                       // value
                                       // not
                                       // needed
            entityManager.persist(toTargetHeader);
            entityManager.flush();
        } else {
            changed |=
                    transferFromPoTargetHeader(fromTargetHeader,
                            toTargetHeader, mergeType);
        }
        return changed;
    }

    private boolean transferFromTextFlowExtensions(
            TextFlow from, HTextFlow to,
            Set<String> enabledExtensions) {
        boolean changed = false;
        ExtensionSet<TextFlowExtension> extensions = from.getExtensions(true);
        if (enabledExtensions.contains(PotEntryHeader.ID)) {
            PotEntryHeader entryHeader =
                    extensions.findByType(PotEntryHeader.class);
            if (entryHeader != null) {
                HPotEntryData hEntryHeader = to.getPotEntryData();

                if (hEntryHeader == null) {
                    changed = true;
                    hEntryHeader = new HPotEntryData();
                    to.setPotEntryData(hEntryHeader);
                    log.debug("set potentryheader");
                }
                changed |=
                        transferFromPotEntryHeader(entryHeader, hEntryHeader,
                                from);
            }
        }
        if (enabledExtensions.contains(SimpleComment.ID)) {
            SimpleComment comment = extensions.findByType(SimpleComment.class);
            if (comment != null) {
                HSimpleComment hComment = to.getComment();

                if (hComment == null) {
                    hComment = new HSimpleComment();
                }
                if (!equals(comment.getValue(), hComment.getComment())) {
                    changed = true;
                    hComment.setComment(comment.getValue());
                    to.setComment(hComment);
                    log.debug("set comment:{}", comment.getValue());
                }
            }
        }

        return changed;

    }

    /**
     * @see #transferToPotEntryHeader(HPotEntryData, PotEntryHeader)
     * @param from
     * @param to
     * @param textFlow
     * @return
     */
    private boolean transferFromPotEntryHeader(PotEntryHeader from,
            HPotEntryData to, TextFlow textFlow) {
        boolean changed = false;

        if (!equals(from.getContext(), to.getContext())) {
            changed = true;
            to.setContext(from.getContext());
        }

        List<String> flagList = from.getFlags();
        // rhbz1012502 - should not store fuzzy tag in source document
        if (flagList.contains("fuzzy")) {
            throw new FileUploadException(String.format(
                    "Please remove fuzzy flags from document. "
                            + "First fuzzy flag was found on "
                            + "text flow %s with content %s", textFlow.getId(),
                    textFlow.getContents()));
        }
        String flags = StringUtil.concat(flagList, ',');
        if (flagList.isEmpty()) {
            flags = null;
        }
        if (!equals(flags, to.getFlags())) {
            changed = true;
            to.setFlags(flags);
        }

        List<String> refList = from.getReferences();
        String refs = StringUtil.concat(from.getReferences(), ',');
        if (refList.isEmpty()) {
            refs = null;
        }
        if (!equals(refs, to.getReferences())) {
            changed = true;
            to.setReferences(refs);
        }

        return changed;
    }

    /**
     *
     * @param from
     * @param to
     * @param mergeType
     * @return
     * @see #transferFromTranslationsResourceExtensions
     * @see #transferToPoTargetHeader
     */
    private boolean transferFromPoTargetHeader(PoTargetHeader from,
            HPoTargetHeader to, MergeType mergeType) {
        boolean changed = pushPoTargetComment(from, to, mergeType);

        // TODO we should probably block PoHeader/POT-specific entries
        // ie POT-Creation-Date, Project-Id-Version, Report-Msgid-Bugs-To
        String entries = PoUtility.listToHeader(from.getEntries());
        if (!equals(entries, to.getEntries())) {
            to.setEntries(entries);
            changed = true;
        }

        return changed;
    }

    /**
     *
     * @param fromHeader
     * @param toHeader
     * @param mergeType
     * @return
     * @see #pullPoTargetComment
     */
    protected boolean pushPoTargetComment(PoTargetHeader fromHeader,
            HPoTargetHeader toHeader, MergeType mergeType) {
        boolean changed = false;
        HSimpleComment hComment = toHeader.getComment();
        if (hComment == null) {
            hComment = new HSimpleComment();
        }
        String fromComment = fromHeader.getComment();
        String toComment = hComment.getComment();
        if (!equals(fromComment, toComment)) {
            // skip #zanata lines
            List<String> fromLines = splitLines(fromComment, ZANATA_TAG);
            StringBuilder sb = new StringBuilder(fromComment.length());
            switch (mergeType) {
            case IMPORT:
                for (String line : fromLines) {
                    if (sb.length() != 0)
                        sb.append(NEWLINE);
                    sb.append(line);
                    changed = true;
                }
                break;

            default: // AUTO or anything else will merge comments
                // to merge, we just append new lines, skip old lines
                List<String> toLines = Collections.emptyList();
                if (toComment != null) {
                    sb.append(toComment);
                    toLines = splitLines(toComment, null);
                }

                for (String line : fromLines) {
                    if (!toLines.contains(line)) {
                        if (sb.length() != 0)
                            sb.append(NEWLINE);
                        sb.append(line);
                        changed = true;
                    }
                }
                break;
            }
            if (changed) {
                hComment.setComment(sb.toString());
                toHeader.setComment(hComment);
            }
        }
        return changed;
    }

    /**
     * splits s into lines, skipping any which contain tagToSkip
     *
     * @param s
     * @param tagToSkip
     * @return
     */
    static List<String> splitLines(String s, String tagToSkip) {
        if (s.isEmpty()) {
            return Collections.emptyList();
        }
        try {
            List<String> lineList = new ArrayList<String>(s.length() / 40);
            BufferedReader reader = new BufferedReader(new StringReader(s));
            String line;
            while ((line = reader.readLine()) != null) {
                if (tagToSkip == null || !line.contains(tagToSkip)) {
                    lineList.add(line);
                }
            }
            return lineList;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private boolean transferFromPoHeader(PoHeader from, HPoHeader to) {
        boolean changed = false;

        HSimpleComment comment = to.getComment();
        if (comment == null) {
            comment = new HSimpleComment();
        }
        if (!equals(from.getComment(), comment.getComment())) {
            changed = true;
            comment.setComment(from.getComment());
            to.setComment(comment);
        }

        String entries = PoUtility.listToHeader(from.getEntries());
        if (!equals(entries, to.getEntries())) {
            to.setEntries(entries);
            changed = true;
        }

        return changed;

    }

    public static <T> boolean equals(T a, T b) {
        if (a == null && b == null) {
            return true;
        }
        if (a == null || b == null) {
            return false;
        }

        return a.equals(b);
    }

    private boolean transferFromTextFlow(TextFlow from, HTextFlow to,
            Set<String> enabledExtensions) {
        boolean changed = false;
        if (!equals(from.getContents(), to.getContents())) {
            to.setContents(from.getContents());
            changed = true;
        }
        if (!equals(from.isPlural(), to.isPlural())) {
            to.setPlural(from.isPlural());
            changed = true;
        }

        // TODO from.getLang()

        transferFromTextFlowExtensions(from, to,
                enabledExtensions);

        return changed;
    }

    public void transferToResource(HDocument from, Resource to) {
        transferToAbstractResourceMeta(from, to);
    }

    private void transferToPoHeader(HPoHeader from, PoHeader to) {
        if (from.getComment() != null) {
            to.setComment(from.getComment().getComment());
        }
        to.getEntries().addAll(PoUtility.headerToList(from.getEntries()));
    }

    /**
     *
     * @param from
     * @param to
     * @param hTargets
     * @param locale
     * @see #transferToTranslationsResourceExtensions
     * @see #transferFromPoTargetHeader
     */
    private void transferToPoTargetHeader(HPoTargetHeader from,
            PoTargetHeader to, List<HTextFlowTarget> hTargets, HLocale locale) {
        pullPoTargetComment(from, to, hTargets);
        to.getEntries().addAll(this.headerToList(from.getEntries()));
        populateHeaderEntries(to.getEntries(), hTargets, locale);
    }

    /**
     * Transforms a set of header entries from a String to a list of POJOs.
     *
     * @param entries
     *            The header entries' string.
     */
    private List<HeaderEntry> headerToList(final String entries) {
        return PoUtility.headerToList(entries);
    }

    /**
     * Populates a list of header entries with values stored in the system. For
     * certain headers, the original value will remain if present.
     *
     * @param headerEntries
     *            The header entries to be populated.
     * @param hTargets
     *            The Text Flow Targets that the header applies to.
     * @param locale
     *            The locale that is bein
     */
    private void populateHeaderEntries(final List<HeaderEntry> headerEntries,
            final List<HTextFlowTarget> hTargets, final HLocale locale) {
        final Map<String, HeaderEntry> containedHeaders =
                new LinkedHashMap<String, HeaderEntry>(headerEntries.size());
        HTextFlowTarget lastTranslatedTarget =
                this.getLastTranslatedTarget(hTargets);

        // Collect the existing header entries
        for (HeaderEntry entry : headerEntries) {
            containedHeaders.put(entry.getKey(), entry);
        }

        // Add / Replace headers
        Date revisionDate =
                this.getRevisionDate(headerEntries, lastTranslatedTarget);
        HeaderEntry headerEntry = containedHeaders.get(PO_REVISION_DATE_HDR);
        if (headerEntry == null) {
            headerEntry =
                    new HeaderEntry(PO_REVISION_DATE_HDR,
                            this.toPoHeaderString(revisionDate));
            headerEntries.add(headerEntry);
        } else {
            headerEntry.setValue(this.toPoHeaderString(revisionDate));
        }

        headerEntry = containedHeaders.get(LAST_TRANSLATOR_HDR);
        if (headerEntry == null) {
            headerEntry =
                    new HeaderEntry(LAST_TRANSLATOR_HDR,
                            this.getLastTranslator(lastTranslatedTarget,
                                    headerEntries));
            headerEntries.add(headerEntry);
        } else {
            headerEntry.setValue(this.getLastTranslator(lastTranslatedTarget,
                    headerEntries));
        }

        headerEntry = containedHeaders.get(LANGUAGE_TEAM_HDR);
        if (headerEntry == null) {
            headerEntry =
                    new HeaderEntry(LANGUAGE_TEAM_HDR,
                            this.getLanguageTeam(locale));
            headerEntries.add(headerEntry);
        } else {
            // Keep the original value if provided
        }

        headerEntry = containedHeaders.get(LANGUAGE_HDR);
        if (headerEntry == null) {
            headerEntry =
                    new HeaderEntry(LANGUAGE_HDR, this.getLanguage(locale));
            headerEntries.add(headerEntry);
        } else {
            headerEntry.setValue(this.getLanguage(locale));
        }

        headerEntry = containedHeaders.get(X_GENERATOR_HDR);
        if (headerEntry == null) {
            headerEntry =
                    new HeaderEntry(X_GENERATOR_HDR, this.getSystemVersion());
            headerEntries.add(headerEntry);
        } else {
            headerEntry.setValue(this.getSystemVersion());
        }

        headerEntry = containedHeaders.get(CONTENT_TYPE_HDR);
        if (headerEntry == null) {
            headerEntry =
                    new HeaderEntry(CONTENT_TYPE_HDR, PO_DEFAULT_CONTENT_TYPE);
            headerEntries.add(headerEntry);
        } else {
            headerEntry.setValue(PO_DEFAULT_CONTENT_TYPE);
        }

        headerEntry = containedHeaders.get(PLURAL_FORMS_HDR);
        if (headerEntry == null) {
            headerEntry =
                    new HeaderEntry(PLURAL_FORMS_HDR,
                            this.getPluralForms(locale));
            headerEntries.add(headerEntry);
        } else {
            // Keep the original if provided
        }
    }

    /**
     * Finds and returns the Revision Date stored in a PO file's header entries.
     *
     * @param headerEntries
     *            A single PO file's header entries.
     * @return The Revision Date header value, or null if no such header is
     *         found or the date cannot be parsed.
     */
    private Date getHeaderRevisionDate(final List<HeaderEntry> headerEntries) {
        Date poFileRevisionDate = null;

        for (HeaderEntry entry : headerEntries) {
            if (entry.getKey().equalsIgnoreCase(PO_REVISION_DATE_HDR)) {
                SimpleDateFormat dateFormat =
                        new SimpleDateFormat(PO_DATE_FORMAT);
                try {
                    poFileRevisionDate = dateFormat.parse(entry.getValue());
                } catch (ParseException e) {
                    // found the header but date could not be parsed
                }

                break;
            }
        }

        return poFileRevisionDate;
    }

    private String
            getHeaderLastTranslator(final List<HeaderEntry> headerEntries) {
        for (HeaderEntry entry : headerEntries) {
            if (entry.getKey().equalsIgnoreCase(LAST_TRANSLATOR_HDR)) {
                return entry.getValue();
            }
        }

        return "";
    }

    /**
     * Returns a PO file's Revision Date based on the values stored in the
     * file's header and in the last translated target. If the system cannot
     * determine a suitable Revision date, a null value is returned.
     */
    private Date getRevisionDate(final List<HeaderEntry> headerEntries,
            final HTextFlowTarget lastTranslated) {
        Date poFileRevisionDate = this.getHeaderRevisionDate(headerEntries);
        Date translationsRevisionDate = null;

        if (lastTranslated != null) {
            translationsRevisionDate = lastTranslated.getLastChanged();
        }

        if (translationsRevisionDate != null) {
            if (poFileRevisionDate != null) {
                return translationsRevisionDate.after(poFileRevisionDate) ? translationsRevisionDate
                        : poFileRevisionDate;
            } else {
                return translationsRevisionDate;
            }
        } else {
            return poFileRevisionDate == null ? null : poFileRevisionDate;
        }
    }

    /**
     * @param translations
     *            A list of Translations for a document.
     * @return The most recently translated target. If there are more than one,
     *         this method will return one of those, no assurances o
     */
    private HTextFlowTarget getLastTranslatedTarget(
            final List<HTextFlowTarget> translations) {
        Date lastUpdate = new Date(Long.MIN_VALUE);
        HTextFlowTarget lastTranslated = null;

        for (HTextFlowTarget trans : translations) {
            if (trans.getLastModifiedBy() != null
                    && trans.getLastChanged().after(lastUpdate)) {
                lastTranslated = trans;
                lastUpdate = trans.getLastChanged();
            }
        }

        return lastTranslated;
    }

    /**
     * Gets the last translator header value for a set of header entries and the
     * last translated target.
     *
     * @param lastTranslated
     *            The most currently translated target.
     * @param headerEntries
     *            The PO header entries.
     * @return A string with the value of the last translator.
     */
    private String getLastTranslator(final HTextFlowTarget lastTranslated,
            final List<HeaderEntry> headerEntries) {
        Date headerRevisionDate = this.getHeaderRevisionDate(headerEntries);
        String lastTranslator = this.getHeaderLastTranslator(headerEntries);

        if (lastTranslated != null) {
            HPerson lastModifiedBy = lastTranslated.getLastModifiedBy();
            Date lastModifiedDate = lastTranslated.getLastChanged();

            // Last translated target is more recent than the Revision Date on
            // the
            // Header
            if (lastModifiedBy != null && lastModifiedDate != null
                    && lastModifiedDate.after(headerRevisionDate)) {
                lastTranslator =
                        lastModifiedBy.getName() + " <"
                                + lastModifiedBy.getEmail() + ">";
            } else if (lastModifiedBy != null && lastModifiedDate == null) {
                lastTranslator =
                        lastModifiedBy.getName() + " <"
                                + lastModifiedBy.getEmail() + ">";
            }
        }

        return lastTranslator;
    }

    /**
     * Returns a string representation of a Date for use in a PO file header.
     *
     * @param aDate
     *            Date object to include in the Header
     * @return A string with the value of the date suitable for a PO file
     *         header.
     */
    private String toPoHeaderString(Date aDate) {
        if (aDate != null) {
            SimpleDateFormat dateFormat = new SimpleDateFormat(PO_DATE_FORMAT);
            return dateFormat.format(aDate);
        } else {
            return "";
        }
    }

    /**
     * Returns the Language Team PO file header for a given locale.
     */
    private String getLanguageTeam(final HLocale hLocale) {
        return hLocale.retrieveDisplayName();
    }

    /**
     * Retrieves the language PO file header for a given locale.
     *
     * @param locale
     */
    private String getLanguage(final HLocale locale) {
        return locale.getLocaleId().toString();
    }

    /**
     * Returns the application version.
     */
    private String getSystemVersion() {
        try {
            return ZANATA_GENERATOR_PREFIX
                    + " "
                    + (ServiceLocator.instance()
                            .getInstance(ApplicationConfiguration.class))
                            .getVersion();
        } catch (Exception e) {
            return ZANATA_GENERATOR_PREFIX + " UNKNOWN";
        }
    }

    /**
     * Returns the appropriate plural form for a given Locale. Returns a default
     * value if there is no plural form information for the provided locale.
     */
    public String getPluralForms(HLocale locale) {
        return getPluralForms(locale.getLocaleId());
    }

    /**
     * Returns the appropriate plural form for a given Locale Id. Returns a
     * default value if there is no plural form information for the provided
     * locale id.
     *
     * @see {@link ResourceUtils#getPluralForms(org.zanata.common.LocaleId, boolean)}
     */
    public String getPluralForms(LocaleId localeId) {
        return getPluralForms(localeId, true);
    }

    /**
     * Returns the appropriate plural from for a given locale Id.
     *
     * @return A default value if useDefault is True. Otherwise, null.
     */
    public String getPluralForms(LocaleId localeId, boolean useDefault) {
        final char[] alternateSeparators = { '.', '@' };

        String javaLocale = localeId.toJavaName().toLowerCase();

        // Replace all alternate separators for the "_" (Java) separator.
        for (char sep : alternateSeparators) {
            javaLocale = javaLocale.replace(sep, '_');
        }

        if (pluralForms.containsKey(javaLocale)) {
            return pluralForms.getProperty(javaLocale);
        }

        // Try out every combination. e.g: for xxx_yyy_zzz, try xxx_yyyy_zzz,
        // then
        // xxx_yyy, then xxx
        while (javaLocale.indexOf('_') > 0) {
            javaLocale = javaLocale.substring(0, javaLocale.lastIndexOf('_'));

            if (pluralForms.containsKey(javaLocale)) {
                return pluralForms.getProperty(javaLocale);
            }
        }

        if (useDefault) {
            return DEFAULT_PLURAL_FORM;
        } else {
            return null;
        }
    }

    public int getNumPlurals(HDocument document, HLocale hLocale) {
        HPoTargetHeader headers = document.getPoTargetHeaders().get(hLocale);
        String headerEntries = headers != null ? headers.getEntries() : "";
        return getNPluralForms(headerEntries, hLocale);
    }

    private int getNPluralForms(String entries, HLocale targetLocale) {
        return getNPluralForms(entries, targetLocale.getLocaleId());
    }

    int getNPluralForms(String entries, LocaleId localeId) {
        int nPlurals = 1;

        try {
            Properties headerList = new Properties();
            String pluralForms;
            if (entries != null && !entries.isEmpty()) {
                headerList.load(new StringReader(entries));
                if (headerList.containsKey(PLURAL_FORMS_HDR)) {
                    pluralForms = headerList.getProperty(PLURAL_FORMS_HDR);
                } else {
                    pluralForms = getPluralForms(localeId);
                }
            } else {
                pluralForms = getPluralForms(localeId);
            }
            if (pluralForms == null) {
                log.error("No plural forms for locale {} found in {}",
                        localeId, PLURALS_FILE);
                throw new RuntimeException(
                        "No plural forms found; contact admin. Locale: "
                                + localeId);
            }
            Matcher nPluralsMatcher = NPLURALS_PATTERN.matcher(pluralForms);
            String nPluralsString = "";
            while (nPluralsMatcher.find()) {
                nPluralsString = nPluralsMatcher.group();
                Matcher nPluralsValueMatcher =
                        NPLURALS_TAG_PATTERN.matcher(nPluralsString);
                nPluralsString = nPluralsValueMatcher.replaceAll("");
                break;
            }
            if (nPluralsString != null && !nPluralsString.isEmpty()) {
                nPlurals = Integer.parseInt(nPluralsString);
            }
        } catch (Exception e) {
            log.error("Error getting nPlurals:" + entries);
        }

        // nPlurals = (nPlurals > MAX_TARGET_CONTENTS || nPlurals < 1) ? 1 :
        // nPlurals;
        return nPlurals;
    }

    /**
     *
     * @param fromHeader
     * @param toHeader
     * @see #pushPoTargetComment
     */
    protected void pullPoTargetComment(HPoTargetHeader fromHeader,
            PoTargetHeader toHeader, List<HTextFlowTarget> hTargets) {
        StringBuilder sb = new StringBuilder();
        HSimpleComment comment = fromHeader.getComment();
        if (comment != null) {
            sb.append(comment.getComment());
        }
        // generate #zanata credit comments
        // order by year, then alphabetically
        Set<TranslatorCredit> zanataCredits = new TreeSet<TranslatorCredit>();
        for (HTextFlowTarget tft : hTargets) {
            HPerson person = tft.getLastModifiedBy();
            if (person != null) {
                Calendar lastChanged = Calendar.getInstance();
                lastChanged.setTime(tft.getLastChanged());
                int year = lastChanged.get(Calendar.YEAR);
                TranslatorCredit credit = new TranslatorCredit();
                credit.setEmail(person.getEmail());
                credit.setName(person.getName());
                credit.setYear(year);
                zanataCredits.add(credit);
            }
        }
        for (TranslatorCredit credit : zanataCredits) {
            if (sb.length() != 0)
                sb.append(NEWLINE);
            sb.append(credit);
            sb.append(' ');
            sb.append(ZANATA_TAG);
        }

        toHeader.setComment(sb.toString());
    }

    public void transferToTextFlow(HTextFlow from, TextFlow to) {
        if (from.isPlural()) {
            to.setContents(from.getContents());
        } else {
            to.setContents(from.getContents().get(0));
        }
        to.setRevision(from.getRevision());
        to.setPlural(from.isPlural());

        // TODO HTextFlow should have a lang
        // to.setLang(from.get)
    }

    public void transferToAbstractResourceMeta(HDocument from,
            AbstractResourceMeta to) {
        to.setContentType(from.getContentType());
        to.setLang(from.getLocale().getLocaleId());
        to.setName(from.getDocId());
        // TODO ADD support within the hibernate model for multiple resource
        // types
        to.setType(ResourceType.FILE);
        to.setRevision(from.getRevision());
    }

    public void transferToResourceExtensions(HDocument from,
            ExtensionSet<AbstractResourceMetaExtension> to,
            Set<String> enabledExtensions) {
        if (enabledExtensions.contains(PoHeader.ID)) {
            PoHeader poHeaderExt = new PoHeader();
            if (from.getPoHeader() != null) {
                transferToPoHeader(from.getPoHeader(), poHeaderExt);
                to.add(poHeaderExt);
            }
        }
    }

    /**
     *
     * @param from
     * @param to
     * @param enabledExtensions
     * @param locale
     * @see #transferFromTranslationsResourceExtensions
     * @return true only if extensions were found
     */
    public boolean transferToTranslationsResourceExtensions(HDocument from,
            ExtensionSet<TranslationsResourceExtension> to,
            Set<String> enabledExtensions, HLocale locale,
            List<HTextFlowTarget> hTargets) {
        boolean found = false;
        if (enabledExtensions.contains(PoTargetHeader.ID)) {
            log.debug("PoTargetHeader requested");
            PoTargetHeader poTargetHeader = new PoTargetHeader();
            HPoTargetHeader fromHeader = from.getPoTargetHeaders().get(locale);
            if (fromHeader != null) {
                found = true;
                log.debug("PoTargetHeader found");
            } else {
                // If no header is found, use a default empty header for
                // generation
                // purposes
                fromHeader = new HPoTargetHeader();
                fromHeader.setEntries("");
            }
            transferToPoTargetHeader(fromHeader, poTargetHeader, hTargets,
                    locale);
            to.add(poTargetHeader);
        }
        return found;
    }

    public void transferToTextFlowExtensions(HTextFlow from,
            ExtensionSet<TextFlowExtension> to, Set<String> enabledExtensions) {
        if (enabledExtensions.contains(PotEntryHeader.ID)
                && from.getPotEntryData() != null) {
            PotEntryHeader header = new PotEntryHeader();
            transferToPotEntryHeader(from.getPotEntryData(), header);
            log.debug("set header:{}", from.getPotEntryData());
            to.add(header);

        }

        if (enabledExtensions.contains(SimpleComment.ID)
                && from.getComment() != null) {
            SimpleComment comment =
                    new SimpleComment(from.getComment().getComment());
            log.debug("set comment:{}", from.getComment().getComment());
            to.add(comment);
        }

    }

    /**
     * @see #transferFromPotEntryHeader(org.zanata.rest.dto.extensions.gettext.PotEntryHeader, org.zanata.model.po.HPotEntryData, org.zanata.rest.dto.resource.TextFlow)
     * @param from
     * @param to
     */
    private void
            transferToPotEntryHeader(HPotEntryData from, PotEntryHeader to) {
        to.setContext(from.getContext());

        List<String> flags = new ArrayList<String>(0);
        if (from.getFlags() != null) {
            flags = StringUtil.split(from.getFlags(), ",");
        }
        to.getFlags().addAll(flags);

        List<String> refs = new ArrayList<String>(0);
        if (from.getReferences() != null) {
            refs = StringUtil.split(from.getReferences(), ",");
        }
        to.getReferences().addAll(refs);
    }

    /**
     *
     * @param from
     * @param to
     * @param enabledExtensions
     * @todo merge with {@link #transferToTextFlowTarget}
     */
    public void transferToTextFlowTargetExtensions(HTextFlowTarget from,
            ExtensionSet<TextFlowTargetExtension> to,
            Set<String> enabledExtensions) {
        if (enabledExtensions.contains(SimpleComment.ID)
                && from.getComment() != null) {
            SimpleComment comment =
                    new SimpleComment(from.getComment().getComment());
            to.add(comment);
        }
    }

    public String encodeDocId(String id) {
        String other = StringUtils.replace(id, "/", ",");
        try {
            other = URLEncoder.encode(other, "UTF-8");
            return StringUtils.replace(other, "%2C", ",");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    public String decodeDocId(String id) {
        try {
            String other = URLDecoder.decode(id, "UTF-8");
            return StringUtils.replace(other, ",", "/");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     *
     *
     *
     * @param from
     * @param to
     * @param apiVersion
     * @todo merge with {@link #transferToTextFlowTargetExtensions}
     */
    public void transferToTextFlowTarget(HTextFlowTarget from,
            TextFlowTarget to, Optional<String> apiVersion) {
        if (from.getTextFlow().isPlural()) {
            to.setContents(from.getContents());
        } else if (!from.getContents().isEmpty()) {
            to.setContents(from.getContents().get(0));
        } else {
            to.setContents(Collections.<String> emptyList());
        }
        // TODO rhbz953734 - at the moment we will map review state into old
        // state for compatibility
        to.setState(mapContentState(apiVersion, from.getState()));
        to.setRevision(from.getVersionNum());
        to.setTextFlowRevision(from.getTextFlowRevision());
        HPerson translator = from.getLastModifiedBy();
        if (translator != null) {
            to.setTranslator(new Person(translator.getEmail(), translator
                    .getName()));
        }
    }

    private static ContentState mapContentState(Optional<String> apiVersion,
            ContentState state) {
        if (!apiVersion.isPresent()) {
            switch (state) {
            case Translated:
                return ContentState.Approved;
            case Rejected:
                return ContentState.NeedReview;
            default:
                return state;
            }
        }
        // TODO for other apiVersion, we will need to handle differently
        return state;
    }

    public Resource buildResource(HDocument document) {
        Set<String> extensions = new HashSet<String>();
        extensions.add("gettext");
        extensions.add("comment");

        Resource entity = new Resource(document.getDocId());
        this.transferToResource(document, entity);
        // handle extensions
        this.transferToResourceExtensions(document, entity.getExtensions(true),
                extensions);

        for (HTextFlow htf : document.getTextFlows()) {
            TextFlow tf =
                    new TextFlow(htf.getResId(), document.getLocale()
                            .getLocaleId());
            this.transferToTextFlowExtensions(htf, tf.getExtensions(true),
                    extensions);
            this.transferToTextFlow(htf, tf);
            entity.getTextFlows().add(tf);
        }

        return entity;
    }

    /**
     *
     *
     * @param transRes
     * @param document
     * @param locale
     * @param enabledExtensions
     * @param hTargets
     * @param apiVersion
     *            TODO this will take api version in the future
     * @return true only if some data was found (text flow targets, or some
     *         metadata extensions)
     */
    public boolean transferToTranslationsResource(
            TranslationsResource transRes, HDocument document, HLocale locale,
            Set<String> enabledExtensions, List<HTextFlowTarget> hTargets,
            Optional<String> apiVersion) {
        boolean found =
                this.transferToTranslationsResourceExtensions(document,
                        transRes.getExtensions(true), enabledExtensions,
                        locale, hTargets);

        for (HTextFlowTarget hTarget : hTargets) {
            found = true;
            TextFlowTarget target = new TextFlowTarget();
            target.setResId(hTarget.getTextFlow().getResId());
            this.transferToTextFlowTarget(hTarget, target, apiVersion);
            this.transferToTextFlowTargetExtensions(hTarget,
                    target.getExtensions(true), enabledExtensions);
            transRes.getTextFlowTargets().add(target);
        }
        return found;
    }

    /**
     * Ensures that any extensions sent with the current query are valid for
     * this context.
     *
     * @param requestedExt
     *            Extensions to be validated
     */
    public static void validateExtensions(Set<String> requestedExt) {
        Set<String> validExtensions = ExtensionType.asStringSet();

        if (!CollectionUtils.isSubCollection(requestedExt, validExtensions)) {
            @SuppressWarnings("unchecked")
            Collection<String> invalidExtensions =
                    CollectionUtils.subtract(requestedExt, validExtensions);
            Response response =
                    Response.status(Status.BAD_REQUEST)
                            .entity("Unsupported Extensions within this context: "
                                    + StringUtils.join(invalidExtensions, ","))
                            .build();
            throw new WebApplicationException(response);
        }
    }

}
TOP

Related Classes of org.zanata.rest.service.ResourceUtils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.