diff --git a/zanata-model/src/main/java/org/zanata/model/HDocument.java b/zanata-model/src/main/java/org/zanata/model/HDocument.java index 4c062866b3..4bb6f3a01c 100644 --- a/zanata-model/src/main/java/org/zanata/model/HDocument.java +++ b/zanata-model/src/main/java/org/zanata/model/HDocument.java @@ -63,6 +63,8 @@ import org.zanata.rest.dto.resource.ResourceMeta; import org.zanata.rest.dto.resource.TranslationsResource; +import com.google.common.collect.ImmutableList; + import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @@ -255,14 +257,12 @@ public List getTextFlows() textFlows = new ArrayList(); } return textFlows; - // return ImmutableList.copyOf(textFlows); } @Override public Iterator iterator() { - return new ArrayList(getTextFlows()).iterator(); -// return ImmutableList.copyOf(getTextFlows()); + return ImmutableList.copyOf(getTextFlows()).iterator(); } public boolean isObsolete() diff --git a/zanata-model/src/main/java/org/zanata/model/HLocale.java b/zanata-model/src/main/java/org/zanata/model/HLocale.java index ae6a09d4c1..ac82fe9149 100644 --- a/zanata-model/src/main/java/org/zanata/model/HLocale.java +++ b/zanata-model/src/main/java/org/zanata/model/HLocale.java @@ -24,6 +24,7 @@ import java.util.HashSet; import java.util.Set; +import javax.annotation.Nonnull; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.JoinColumn; @@ -57,23 +58,24 @@ public class HLocale extends ModelEntityBase implements Serializable { private static final long serialVersionUID = 1L; - private LocaleId localeId; + private @Nonnull LocaleId localeId; private boolean active; private boolean enabledByDefault; private Set supportedProjects; private Set supportedIterations; private Set members; - public HLocale(LocaleId localeId) + public HLocale(@Nonnull LocaleId localeId) { this.localeId = localeId; } // TODO PERF @NaturalId(mutable=false) for better criteria caching + @SuppressWarnings("null") @NaturalId @NotNull @Type(type = "localeId") - public LocaleId getLocaleId() + public @Nonnull LocaleId getLocaleId() { return localeId; } diff --git a/zanata-model/src/main/java/org/zanata/model/HTextFlow.java b/zanata-model/src/main/java/org/zanata/model/HTextFlow.java index 63a8b4499f..975ca20b32 100644 --- a/zanata-model/src/main/java/org/zanata/model/HTextFlow.java +++ b/zanata-model/src/main/java/org/zanata/model/HTextFlow.java @@ -408,6 +408,7 @@ public Map getTargets() @Override public TargetContents getTargetContents(LocaleId localeId) { + // TODO performance: need efficient way to look up a target by LocaleId Collection targets = getTargets().values(); for (HTextFlowTarget tft : targets) { @@ -422,11 +423,6 @@ public Iterable getAllTargetContents() { return ImmutableList.copyOf(getTargets().values()); } -// @Override -// public Map getITargets() -// { -// return new HashMap(getTargets()); -// } @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = true) public HPotEntryData getPotEntryData() diff --git a/zanata-model/src/main/java/org/zanata/model/HTextFlowTarget.java b/zanata-model/src/main/java/org/zanata/model/HTextFlowTarget.java index 249dafc3b6..dcbde12a8b 100644 --- a/zanata-model/src/main/java/org/zanata/model/HTextFlowTarget.java +++ b/zanata-model/src/main/java/org/zanata/model/HTextFlowTarget.java @@ -27,6 +27,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; + +import javax.annotation.Nonnull; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; @@ -90,7 +92,7 @@ public class HTextFlowTarget extends ModelEntityBase implements HasContents, Has private static final long serialVersionUID = 302308010797605435L; private HTextFlow textFlow; - private HLocale locale; + private @Nonnull HLocale locale; private String content0; private String content1; @@ -118,7 +120,7 @@ public class HTextFlowTarget extends ModelEntityBase implements HasContents, Has @Setter(AccessLevel.PRIVATE) private HTextFlowTargetHistory initialState; - public HTextFlowTarget(HTextFlow textFlow, HLocale locale) + public HTextFlowTarget(HTextFlow textFlow, @Nonnull HLocale locale) { this.locale = locale; this.textFlow = textFlow; @@ -131,13 +133,13 @@ public HTextFlowTarget(HTextFlow textFlow, HLocale locale) @JoinColumn(name = "locale", nullable = false) @Field(index = Index.UN_TOKENIZED) @FieldBridge(impl = LocaleIdBridge.class) - public HLocale getLocale() + public @Nonnull HLocale getLocale() { return locale; } @Override - public LocaleId getLocaleId() + public @Nonnull LocaleId getLocaleId() { return locale.getLocaleId(); } @@ -146,7 +148,7 @@ public LocaleId getLocaleId() @Field(index = Index.UN_TOKENIZED) @FieldBridge(impl = ContentStateBridge.class) @Override - public ContentState getState() + public @Nonnull ContentState getState() { return state; } diff --git a/zanata-model/src/main/java/org/zanata/model/SourceContents.java b/zanata-model/src/main/java/org/zanata/model/SourceContents.java index da4eaa48dc..e83859a73d 100644 --- a/zanata-model/src/main/java/org/zanata/model/SourceContents.java +++ b/zanata-model/src/main/java/org/zanata/model/SourceContents.java @@ -31,6 +31,16 @@ public interface SourceContents extends HasContents { public String getResId(); + /** + * Gets the TargetContents for a single locale. + * Note that default implementation in HTextFlow requires a lot of database I/O + * @param localeId + * @return + */ public TargetContents getTargetContents(LocaleId localeId); + /** + * Gets the TargetContents for all available locales. + * @return + */ public Iterable getAllTargetContents(); } diff --git a/zanata-model/src/main/java/org/zanata/model/TargetContents.java b/zanata-model/src/main/java/org/zanata/model/TargetContents.java index f94b38be73..11f5b51906 100644 --- a/zanata-model/src/main/java/org/zanata/model/TargetContents.java +++ b/zanata-model/src/main/java/org/zanata/model/TargetContents.java @@ -21,6 +21,8 @@ package org.zanata.model; +import javax.annotation.Nonnull; + import org.zanata.common.ContentState; import org.zanata.common.HasContents; import org.zanata.common.LocaleId; @@ -31,6 +33,6 @@ */ public interface TargetContents extends HasContents { - public LocaleId getLocaleId(); - public ContentState getState(); + public @Nonnull LocaleId getLocaleId(); + public @Nonnull ContentState getState(); } diff --git a/zanata-model/src/main/java/org/zanata/util/OkapiUtil.java b/zanata-model/src/main/java/org/zanata/util/OkapiUtil.java index dc97b1a239..e684dc366c 100644 --- a/zanata-model/src/main/java/org/zanata/util/OkapiUtil.java +++ b/zanata-model/src/main/java/org/zanata/util/OkapiUtil.java @@ -20,6 +20,8 @@ */ package org.zanata.util; +import javax.annotation.Nonnull; + import net.sf.okapi.common.LocaleId; import net.sf.okapi.steps.tokenization.Tokenizer; import net.sf.okapi.steps.tokenization.tokens.Tokens; @@ -35,12 +37,9 @@ private OkapiUtil() { } - public static LocaleId toOkapiLocale(org.zanata.common.LocaleId zanataLocale) + @SuppressWarnings("null") + public static @Nonnull LocaleId toOkapiLocale(@Nonnull org.zanata.common.LocaleId zanataLocale) { - if (zanataLocale == null) - { - return null; - } return LocaleId.fromBCP47(zanataLocale.getId()); } diff --git a/zanata-war/src/main/java/org/zanata/rest/service/ExportAllLocalesStrategy.java b/zanata-war/src/main/java/org/zanata/rest/service/ExportAllLocalesStrategy.java deleted file mode 100644 index 913f14c854..0000000000 --- a/zanata-war/src/main/java/org/zanata/rest/service/ExportAllLocalesStrategy.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2013, Red Hat, Inc. and individual contributors - * as indicated by the @author tags. See the copyright.txt file in the - * distribution for a full listing of individual contributors. - * - * This is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2.1 of - * the License, or (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this software; if not, write to the Free - * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA, or see the FSF site: http://www.fsf.org. - */ - -package org.zanata.rest.service; - -import net.sf.okapi.common.LocaleId; -import net.sf.okapi.common.filterwriter.TMXWriter; -import net.sf.okapi.common.resource.ITextUnit; -import net.sf.okapi.common.resource.TextFragment; -import net.sf.okapi.common.resource.TextUnit; - -import org.zanata.common.ContentState; -import org.zanata.model.NamedDocument; -import org.zanata.model.SourceContents; -import org.zanata.model.TargetContents; -import org.zanata.util.OkapiUtil; - -/** - * @author Sean Flanigan sflaniga@redhat.com - * - */ -public class ExportAllLocalesStrategy implements ExportTUStrategy -{ - @Override - public void exportTranslationUnit(TMXWriter tmxWriter, NamedDocument doc, SourceContents tf) - { - String id = tf.getResId(); - String tuid = doc.getName() + ":" + id; - // Perhaps we could encode plurals using TMX attributes? - String srcContent = tf.getContents().get(0); - - Iterable allTargetContents = tf.getAllTargetContents(); - ITextUnit textUnit = new TextUnit(id, srcContent); - textUnit.setName(tuid); - for (TargetContents tfTarget : allTargetContents) - { - addTargetToTextUnit(textUnit, tfTarget); - } - tmxWriter.writeTUFull(textUnit); - } - - private void addTargetToTextUnit(ITextUnit textUnit, TargetContents tfTarget) - { - // TODO handle ContentState.Reviewed - if (tfTarget != null && tfTarget.getState() == ContentState.Approved) - { - String trgContent = tfTarget.getContents().get(0); - LocaleId locId = OkapiUtil.toOkapiLocale(tfTarget.getLocaleId()); - TextFragment target = new TextFragment(trgContent); - textUnit.setTargetContent(locId, target); - } - } - -} diff --git a/zanata-war/src/main/java/org/zanata/rest/service/ExportSingleLocaleStrategy.java b/zanata-war/src/main/java/org/zanata/rest/service/ExportSingleLocaleStrategy.java deleted file mode 100644 index 44562c9745..0000000000 --- a/zanata-war/src/main/java/org/zanata/rest/service/ExportSingleLocaleStrategy.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2013, Red Hat, Inc. and individual contributors - * as indicated by the @author tags. See the copyright.txt file in the - * distribution for a full listing of individual contributors. - * - * This is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2.1 of - * the License, or (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this software; if not, write to the Free - * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA, or see the FSF site: http://www.fsf.org. - */ - -package org.zanata.rest.service; - -import java.util.Collections; -import java.util.Map; - -import net.sf.okapi.common.filterwriter.TMXWriter; -import net.sf.okapi.common.resource.TextFragment; - -import org.zanata.common.ContentState; -import org.zanata.common.LocaleId; -import org.zanata.model.NamedDocument; -import org.zanata.model.SourceContents; -import org.zanata.model.TargetContents; - -/** - * @author Sean Flanigan sflaniga@redhat.com - * - */ -public class ExportSingleLocaleStrategy implements ExportTUStrategy -{ - - private LocaleId targetLocaleId; - - public ExportSingleLocaleStrategy(LocaleId targetLocaleId) - { - this.targetLocaleId = targetLocaleId; - } - - @Override - public void exportTranslationUnit(TMXWriter tmxWriter, NamedDocument doc, SourceContents tf) - { - // FIXME handle multiple locales: use writeTUFull - TargetContents tfTarget = tf.getTargetContents(targetLocaleId); - // TODO handle ContentState.Reviewed - if (tfTarget != null && tfTarget.getState() == ContentState.Approved) - { - // Perhaps we could encode plurals using TMX attributes? - String srcContent = tf.getContents().get(0); - TextFragment source = new TextFragment(srcContent); - String trgContent = tfTarget.getContents().get(0); - TextFragment target = new TextFragment(trgContent); - String tuid = doc.getName() + ":" + tf.getResId(); - Map attributes = Collections.emptyMap(); - tmxWriter.writeTU(source, target, tuid, attributes); - } - } - -} diff --git a/zanata-war/src/main/java/org/zanata/rest/service/ExportTUStrategy.java b/zanata-war/src/main/java/org/zanata/rest/service/ExportTUStrategy.java index 3736f041c1..701d4de293 100644 --- a/zanata-war/src/main/java/org/zanata/rest/service/ExportTUStrategy.java +++ b/zanata-war/src/main/java/org/zanata/rest/service/ExportTUStrategy.java @@ -22,17 +22,82 @@ package org.zanata.rest.service; import net.sf.okapi.common.filterwriter.TMXWriter; +import net.sf.okapi.common.resource.ITextUnit; +import net.sf.okapi.common.resource.TextFragment; +import net.sf.okapi.common.resource.TextUnit; -import org.zanata.model.NamedDocument; +import org.zanata.common.ContentState; +import org.zanata.common.LocaleId; import org.zanata.model.SourceContents; +import org.zanata.model.TargetContents; +import org.zanata.util.OkapiUtil; /** + * Writes one or more translations for a single TextFlow as a TMX translation unit. * @author Sean Flanigan sflaniga@redhat.com * */ -public interface ExportTUStrategy +public class ExportTUStrategy { + private final LocaleId localeId; - public void exportTranslationUnit(TMXWriter tmxWriter, NamedDocument doc, SourceContents tf); + /** + * Exports one or all locales. If localeId is null, export all locales. + * @param localeId + */ + public ExportTUStrategy(LocaleId localeId) + { + this.localeId = localeId; + } -} \ No newline at end of file + /** + * Writes the specified SourceContents (TextFlow) and one or all of its translations to the TMXWriter. + * @param tmxWriter + * @param tuidPrefix String to be prepended to all resIds when generating tuids + * @param tf the SourceContents (TextFlow) whose contents and translations are to be exported + */ + public void exportTranslationUnit(TMXWriter tmxWriter, String tuidPrefix, SourceContents tf) + { + String resId = tf.getResId(); + String tuid = tuidPrefix + resId; + // Perhaps we could encode plurals using TMX attributes? + String srcContent = tf.getContents().get(0); + + ITextUnit textUnit = new TextUnit(resId, srcContent); + textUnit.setName(tuid); + if (localeId != null) + { + TargetContents tfTarget = tf.getTargetContents(localeId); + addTargetToTextUnit(textUnit, tfTarget); + } + else + { + Iterable allTargetContents = tf.getAllTargetContents(); + for (TargetContents tfTarget : allTargetContents) + { + addTargetToTextUnit(textUnit, tfTarget); + } + } + // If there aren't any translations for this TU, we shouldn't include it. + // From the TMX spec: "Logically, a complete translation-memory + // database will contain at least two elements in each translation + // unit." + if (!textUnit.getTargetLocales().isEmpty()) + { + tmxWriter.writeTUFull(textUnit); + } + } + + private void addTargetToTextUnit(ITextUnit textUnit, TargetContents tfTarget) + { + // TODO handle ContentState.Reviewed + if (tfTarget != null && tfTarget.getState() == ContentState.Approved) + { + String trgContent = tfTarget.getContents().get(0); + net.sf.okapi.common.LocaleId locId = OkapiUtil.toOkapiLocale(tfTarget.getLocaleId()); + TextFragment target = new TextFragment(trgContent); + textUnit.setTargetContent(locId, target); + } + } + +} diff --git a/zanata-war/src/main/java/org/zanata/rest/service/TMXStreamingOutput.java b/zanata-war/src/main/java/org/zanata/rest/service/TMXStreamingOutput.java index c271a7045d..416dfb9aaf 100644 --- a/zanata-war/src/main/java/org/zanata/rest/service/TMXStreamingOutput.java +++ b/zanata-war/src/main/java/org/zanata/rest/service/TMXStreamingOutput.java @@ -40,6 +40,8 @@ import org.zanata.util.VersionUtility; /** + * Exports a collection of NamedDocument (ie a project iteration) to an + * OutputStream in TMX format. * @author Sean Flanigan sflaniga@redhat.com * */ @@ -51,7 +53,7 @@ public class TMXStreamingOutput implements StreamingOutput private final Iterable documents; private final LocaleId sourceLocale; private final LocaleId targetLocale; - private ExportTUStrategy exportTUStrategy; + private final ExportTUStrategy exportTUStrategy; public TMXStreamingOutput(Iterable documents, LocaleId sourceLocale, LocaleId targetLocale) @@ -59,24 +61,18 @@ public TMXStreamingOutput(Iterable documents, this.documents = documents; this.sourceLocale = sourceLocale; this.targetLocale = targetLocale; - if (targetLocale == null) - { - this.exportTUStrategy = new ExportAllLocalesStrategy(); - } - else - { - this.exportTUStrategy = new ExportSingleLocaleStrategy(targetLocale); - } + this.exportTUStrategy = new ExportTUStrategy(targetLocale); } net.sf.okapi.common.LocaleId toOkapiLocaleOrEmpty(LocaleId locale) { - net.sf.okapi.common.LocaleId okapiLocale = OkapiUtil.toOkapiLocale(locale); - if (okapiLocale == null) + if (locale == null) { + // TMXWriter demands a non-null target locale, but if you write + // your TUs with writeTUFull(), it is never actually used. return net.sf.okapi.common.LocaleId.EMPTY; } - return okapiLocale; + return OkapiUtil.toOkapiLocale(locale); } @Override @@ -106,10 +102,11 @@ public void write(OutputStream output) throws IOException, WebApplicationExcepti private void exportDocument(TMXWriter tmxWriter, NamedDocument doc) { + String tuidPrefix = doc.getName() + ":"; // TODO option to export obsolete TFs to TMX? for (SourceContents tf : doc) { - exportTUStrategy.exportTranslationUnit(tmxWriter, doc, tf); + exportTUStrategy.exportTranslationUnit(tmxWriter, tuidPrefix, tf); } } diff --git a/zanata-war/src/test/java/org/zanata/rest/service/TMXStreamingOutputTest.java b/zanata-war/src/test/java/org/zanata/rest/service/TMXStreamingOutputTest.java index 83af264a83..18a6b16d80 100644 --- a/zanata-war/src/test/java/org/zanata/rest/service/TMXStreamingOutputTest.java +++ b/zanata-war/src/test/java/org/zanata/rest/service/TMXStreamingOutputTest.java @@ -9,6 +9,7 @@ import java.io.StringReader; import java.net.MalformedURLException; import java.util.ArrayList; +import java.util.Map; import javax.ws.rs.core.StreamingOutput; @@ -32,6 +33,7 @@ import org.zanata.model.TargetContents; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; public class TMXStreamingOutputTest { @@ -42,48 +44,6 @@ public class TMXStreamingOutputTest XMLUnit.setXpathNamespaceContext(ctx); } - private ArrayList createTestDocs() - { - LocaleId fr = LocaleId.FR; - LocaleId de = LocaleId.DE; - NamedDocument doc0 = new SimpleNamedDocument( - "doc0", - new SimpleSourceContents( - "resId0", - ImmutableMap.of( - fr, new SimpleTargetContents(fr, Approved, "targetFR0", "targetFR1"), - de, new SimpleTargetContents(de, Approved, "targetDE0", "targetDE1") - ), - "source0", "source1" - ), - new SimpleSourceContents( - "resId1", - ImmutableMap.of( - fr, - new SimpleTargetContents(fr, Approved, "TARGETfr0", "TARGETfr1")), - "SOURCE0", "SOURCE1" - ) - ); - NamedDocument doc1 = new SimpleNamedDocument( - "doc1", - new SimpleSourceContents( - "resId0", - ImmutableMap.of( - fr, - new SimpleTargetContents(fr, Approved, "targetFR0", "targetFR1")), - "source0", "source1" - ), - new SimpleSourceContents( - "resId1", - ImmutableMap.of( - de, - new SimpleTargetContents(de, Approved, "TARGETde0", "TARGETde1")), - "SOURCE0", "SOURCE1" - ) - ); - return newArrayList(doc0, doc1); - } - @Test public void exportAllLocales() throws Exception { @@ -130,6 +90,50 @@ public void exportGerman() throws Exception assertLangAbsent("fr", doc); } + private ArrayList createTestDocs() + { + LocaleId fr = LocaleId.FR; + LocaleId de = LocaleId.DE; + NamedDocument doc0 = new SimpleNamedDocument( + "doc0", + new SimpleSourceContents( + "resId0", + toMap(new SimpleTargetContents(fr, Approved, "targetFR0", "targetFR1"), + new SimpleTargetContents(de, Approved, "targetDE0", "targetDE1")), + "source0", "source1" + ), + new SimpleSourceContents( + "resId1", + toMap(new SimpleTargetContents(fr, Approved, "TARGETfr0", "TARGETfr1")), + "SOURCE0", "SOURCE1" + ) + ); + NamedDocument doc1 = new SimpleNamedDocument( + "doc1", + new SimpleSourceContents( + "resId0", + toMap(new SimpleTargetContents(fr, Approved, "targetFR0", "targetFR1")), + "source0", "source1" + ), + new SimpleSourceContents( + "resId1", + toMap(new SimpleTargetContents(de, Approved, "TARGETde0", "TARGETde1")), + "SOURCE0", "SOURCE1" + ) + ); + return newArrayList(doc0, doc1); + } + + private Map toMap(SimpleTargetContents... targetContents) + { + Map map = Maps.newHashMap(); + for (SimpleTargetContents target : targetContents) + { + map.put(target.getLocaleId(), target); + } + return map; + } + private void assertContainsFrenchTUs(Document doc) throws XpathException, SAXException, IOException { assertSingleTU("doc0", "resId0", doc);