/**************************************************************************
OmegaT - Computer Assisted Translation (CAT) tool
with fuzzy matching, translation memory, keyword search,
glossaries, and translation leveraging into updated projects.
Copyright (C) 2014 Alex Buloichik
Home page: http://www.omegat.org/
Support center: http://groups.yahoo.com/group/OmegaT/
This file is part of OmegaT.
OmegaT 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.
OmegaT 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 this program. If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/
package org.omegat.core.data;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.HeadlessException;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import org.madlonkay.supertmxmerge.StmProperties;
import org.madlonkay.supertmxmerge.SuperTmxMerge;
import org.madlonkay.supertmxmerge.data.ITuv;
import org.madlonkay.supertmxmerge.data.Key;
import org.madlonkay.supertmxmerge.data.ResolutionStrategy;
import org.omegat.core.Core;
import org.omegat.core.CoreEvents;
import org.omegat.core.TestCoreInitializer;
import org.omegat.core.data.IProject.DefaultTranslationsIterator;
import org.omegat.core.events.IProjectEventListener;
import org.omegat.core.team2.RemoteRepositoryProvider;
import org.omegat.core.threads.IAutoSave;
import org.omegat.gui.editor.EditorSettings;
import org.omegat.gui.editor.IEditor;
import org.omegat.gui.editor.IEditorFilter;
import org.omegat.gui.editor.IPopupMenuConstructor;
import org.omegat.gui.editor.autocompleter.IAutoCompleter;
import org.omegat.gui.editor.mark.Mark;
import org.omegat.gui.main.IMainMenu;
import org.omegat.gui.main.IMainWindow;
import org.omegat.util.Log;
import org.omegat.util.OStrings;
import org.omegat.util.Preferences;
import org.omegat.util.ProjectFileStorage;
import org.omegat.util.TestPreferencesInitializer;
import com.vlsolutions.swing.docking.Dockable;
/**
* Child process for concurrent modification.
*
* @author Alex Buloichik (alex73mail@gmail.com)
*/
public class TestTeamIntegrationChild {
static final String CONCURRENT_NAME = "concurrent";
static long finishTime;
static String source;
static String dir;
static String repo;
static int maxDelaySeconds;
static int segCount;
static EntryKey[] key;
static SourceTextEntry[] ste;
static EntryKey keyC;
static SourceTextEntry steC;
static long num = 0;
static long[] v;
static Map<String, Long> values = new HashMap<String, Long>();
public static void main(String[] args) throws Exception {
if (args.length != 6) {
System.err.println("Wrong arguments count");
System.exit(1);
}
try {
source = args[0];
long time = Long.parseLong(args[1]);
dir = args[2];
repo = args[3];
maxDelaySeconds = Integer.parseInt(args[4]);
segCount = Integer.parseInt(args[5]);
finishTime = System.currentTimeMillis() + time;
TestPreferencesInitializer.init();
Preferences.setPreference(Preferences.TEAM_AUTHOR, source);
// Init UI stubs. In a CI environment, errors can occur when
// initializing the RemoteRepositoryProvider, so we need to make
// sure the "main window" is available for logging first.
Core.initializeConsole(Collections.emptyMap());
TestCoreInitializer.initMainWindow(mainWindow);
TestCoreInitializer.initAutoSave(autoSave);
TestCoreInitializer.initEditor(editor);
ProjectProperties config = TestTeamIntegration.createConfig(repo, new File(dir));
new RemoteRepositoryProvider(config.getProjectRootDir(), config.getRepositories());
// load project
ProjectProperties projectProperties = ProjectFileStorage.loadProjectProperties(new File(dir));
projectProperties.autocreateDirectories();
Core.getAutoSave().disable();
RealProject p = new TestRealProject(projectProperties);
Core.setProject(p);
p.loadProject(true);
if (p.isProjectLoaded()) {
Core.getAutoSave().enable();
CoreEvents.fireProjectChange(IProjectEventListener.PROJECT_CHANGE_TYPE.LOAD);
} else {
throw new Exception("Project can't be loaded");
}
key = new EntryKey[segCount];
ste = new SourceTextEntry[segCount];
for (int c = 0; c < segCount; c++) {
key[c] = new EntryKey("file", source + "/" + c, null, null, null, null);
ste[c] = new SourceTextEntry(key[c], 0, null, null, new ArrayList<ProtectedPart>());
}
keyC = new EntryKey("file", CONCURRENT_NAME, null, null, null, null);
steC = new SourceTextEntry(keyC, 0, null, null, new ArrayList<ProtectedPart>());
Random rnd = new Random();
v = new long[segCount];
mc: while (true) {
for (int c = 1; c < segCount; c++) {
// change concurrent segment
changeConcurrent();
if (System.currentTimeMillis() >= finishTime) {
break mc;
}
// change /0 segment
Thread.sleep(rnd.nextInt(maxDelaySeconds * 1000) + 10);
checksavecheck(0);
// change /1..N segment
Thread.sleep(rnd.nextInt(maxDelaySeconds * 1000) + 10);
checksavecheck(c);
}
}
Core.getProject().closeProject();
// load again and check
ProjectFactory.loadProject(projectProperties, true);
checkAll();
System.exit(200);
} catch (Throwable ex) {
ex.printStackTrace();
System.exit(1);
}
}
static void changeConcurrent() throws Exception {
checkAll();
PrepareTMXEntry prep = new PrepareTMXEntry();
prep.translation = "" + System.currentTimeMillis();
Core.getProject().setTranslation(steC, prep, true, null);
Log.log("Wrote: " + prep.source + "=" + prep.translation);
}
static void checksavecheck(int index) throws Exception {
checkAll();
v[index] = ++num;
saveTranslation(ste[index], v[index]);
checkAll();
}
/**
* Check in memory and in file.
*/
static void checkAll() throws Exception {
ProjectTMX tmx = new ProjectTMX(TestTeamIntegration.SRC_LANG, TestTeamIntegration.TRG_LANG, false,
new File(dir + "/omegat/project_save.tmx"), TestTeamIntegration.checkOrphanedCallback);
for (int c = 0; c < segCount; c++) {
checkTranslation(c);
checkTranslationFromFile(tmx, c);
}
Core.getProject().iterateByDefaultTranslations(new DefaultTranslationsIterator() {
public void iterate(String source, TMXEntry trans) {
Long prev = values.get(source);
if (prev == null) {
prev = 0L;
}
long curr = Long.parseLong(trans.translation);
if (curr < prev) {
throw new RuntimeException(source + ": Wrong value in " + source + ": current(" + curr
+ ") less than previous(" + prev + ")");
}
}
});
}
static void checkTranslation(int index) {
TMXEntry en = Core.getProject().getTranslationInfo(ste[index]);
String sv = en == null || !en.isTranslated() ? "" : en.translation;
if (v[index] == 0 && sv.isEmpty()) {
return;
}
if ((v[index] + "").equals(sv)) {
return;
}
throw new RuntimeException(source + ": Wrong value in " + source + "/" + index + ": expected "
+ v[index] + " but contains " + en.translation);
}
static void checkTranslationFromFile(ProjectTMX tmx, int index) throws Exception {
TMXEntry en = tmx.getDefaultTranslation(ste[index].getSrcText());
String sv = en == null || !en.isTranslated() ? "" : en.translation;
if (v[index] == 0 && sv.isEmpty()) {
return;
}
if ((v[index] + "").equals(sv)) {
return;
}
throw new RuntimeException(source + ": Wrong value in TMX " + source + "/" + index + ": expected "
+ v[index] + " but contains " + sv);
}
/**
* Save new translation.
*/
static void saveTranslation(SourceTextEntry ste, long value) {
PrepareTMXEntry prep = new PrepareTMXEntry();
prep.translation = "" + value;
Core.getProject().setTranslation(ste, prep, true, null);
Log.log("Wrote: " + prep.source + "=" + prep.translation);
Core.getProject().saveProject(true);
}
static IAutoSave autoSave = new IAutoSave() {
public void enable() {
}
public void disable() {
}
};
static IEditor editor = new IEditor() {
public void windowDeactivated() {
}
public void undo() {
}
public void setFilter(IEditorFilter filter) {
}
public void setAlternateTranslationForCurrentEntry(boolean alternate) {
}
public void requestFocus() {
}
public void replaceEditTextAndMark(String text) {
}
public void replaceEditText(String text) {
}
public void removeFilter() {
}
public void remarkOneMarker(String markerClassName) {
}
public void registerUntranslated() {
}
public void registerPopupMenuConstructors(int priority, IPopupMenuConstructor constructor) {
}
public void registerIdenticalTranslation() {
}
public void registerEmptyTranslation() {
}
public void refreshViewAfterFix(List<Integer> fixedEntries) {
}
public void refreshView(boolean doCommit) {
}
public void redo() {
}
public void prevEntryWithNote() {
}
public void prevEntry() {
}
public void nextUntranslatedEntry() {
}
public void nextUniqueEntry() {
}
public void nextTranslatedEntry() {
}
public void nextEntryWithNote() {
}
public void nextEntry() {
}
public void markActiveEntrySource(SourceTextEntry requiredActiveEntry, List<Mark> marks,
String markerClassName) {
}
public void insertText(String text) {
}
public void insertTag(String tag) {
}
public void gotoHistoryForward() {
}
public void gotoHistoryBack() {
}
public void gotoFile(int fileIndex) {
}
public void gotoEntryAfterFix(int fixedEntry, String fixedSource) {
}
public void gotoEntry(String srcString, EntryKey key) {
}
public void gotoEntry(int entryNum) {
}
public void gotoEntry(int entryNum, CaretPosition pos) {
}
public EditorSettings getSettings() {
return null;
}
public String getSelectedText() {
return null;
}
public IEditorFilter getFilter() {
return null;
}
public String getCurrentTranslation() {
return null;
}
public String getCurrentFile() {
return null;
}
public int getCurrentEntryNumber() {
return 0;
}
public SourceTextEntry getCurrentEntry() {
return null;
}
public void commitAndLeave() {
}
public void commitAndDeactivate() {
}
public void changeCase(CHANGE_CASE_TO newCase) {
}
public void activateEntry() {
}
@Override
public IAutoCompleter getAutoCompleter() {
return null;
}
@Override
public String getCurrentTargetFile() {
return null;
}
@Override
public void insertTextAndMark(String text) {
}
};
static IMainWindow mainWindow = new IMainWindow() {
public void unlockUI() {
}
public void showStatusMessageRB(String messageKey, Object... params) {
}
public void showTimedStatusMessageRB(String messageKey, Object... params) {
}
public void showProgressMessage(String messageText) {
}
public void showMessageDialog(String message) {
}
public void showLengthMessage(String messageText) {
}
public void showErrorDialogRB(String title, String message, Object... args) {
System.err.println(message);
}
public int showConfirmDialog(Object message, String title, int optionType, int messageType)
throws HeadlessException {
return 0;
}
public void setCursor(Cursor cursor) {
}
public void lockUI() {
}
IMainMenu menu = new IMainMenu() {
public JMenu getToolsMenu() {
return null;
}
public JMenuItem getProjectRecentMenuItem() {
return null;
}
public JMenu getProjectMenu() {
return new JMenu();
}
public JMenu getOptionsMenu() {
return null;
}
public JMenu getMachineTranslationMenu() {
return null;
}
public JMenu getGlossaryMenu() {
return null;
}
public JMenu getAutoCompletionMenu() {
return null;
}
public void invokeAction(String action, int modifiers) {
}
};
public IMainMenu getMainMenu() {
return menu;
}
public Cursor getCursor() {
return null;
}
public JFrame getApplicationFrame() {
return null;
}
public Font getApplicationFont() {
return null;
}
public void displayWarningRB(String warningKey, String supercedesKey, Object... params) {
System.err.println(warningKey);
}
public void displayWarningRB(String warningKey, Object... params) {
System.err.println(warningKey);
}
public void displayErrorRB(Throwable ex, String errorKey, Object... params) {
System.err.println(errorKey);
ex.printStackTrace();
}
public void addDockable(Dockable pane) {
}
};
/**
* Override RealProject for own merge.
*/
static class TestRealProject extends RealProject {
public TestRealProject(final ProjectProperties props) {
super(props);
}
ProjectTMX mergedTMX;
ProjectTMX baseTMX;
ProjectTMX headTMX;
@Override
protected void mergeTMX(ProjectTMX baseTMX, ProjectTMX headTMX, StringBuilder commitDetails) {
Log.log("Base: " + baseTMX);
Log.log("Mine: " + projectTMX);
Log.log("Theirs: " + headTMX);
if (!checkMergeInput(baseTMX, projectTMX)) {
Log.log("'Mine' TM is not a valid derivative of 'Base' TM");
// Exceptions thrown here are suppressed in
// RealProject.saveProject(boolean) so this is the easiest way
// to early-exit
System.exit(1);
}
if (!checkMergeInput(baseTMX, headTMX)) {
Log.log("'Theirs' TM is not a valid derivative of 'Base' TM");
System.exit(1);
}
StmProperties props = new StmProperties()
.setLanguageResource(OStrings.getResourceBundle())
.setResolutionStrategy(new ResolutionStrategy() {
@Override
public ITuv resolveConflict(Key key, ITuv baseTuv, ITuv projectTuv, ITuv headTuv) {
TMXEntry enBase = baseTuv != null ? (TMXEntry) baseTuv
.getUnderlyingRepresentation() : null;
TMXEntry enProject = projectTuv != null ? (TMXEntry) projectTuv
.getUnderlyingRepresentation() : null;
TMXEntry enHead = headTuv != null ? (TMXEntry) headTuv
.getUnderlyingRepresentation() : null;
String s = "Rebase " + src(enProject) + " base=" + tr(enBase) + " head="
+ tr(enHead) + " project=" + tr(enProject);
if (enProject != null && CONCURRENT_NAME.equals(enProject.source)) {
if (v(enHead) < v(enBase)) {
throw new RuntimeException("Rebase HEAD: wrong concurrent: " + s);
}
if (v(enProject) < v(enBase)) {
throw new RuntimeException("Rebase project: wrong concurrent: " + s);
}
if (v(enHead) > v(enProject)) {
System.err.println(s + ": result=head");
return headTuv;
} else {
System.err.println(s + ": result=project");
return projectTuv;
}
} else {
throw new RuntimeException("Rebase error: non-concurrent entry: " + s);
}
}
});
String srcLang = config.getSourceLanguage().getLanguage();
String trgLang = config.getTargetLanguage().getLanguage();
synchronized (projectTMX) {
ProjectTMX mergedTMX = SuperTmxMerge.merge(
new SyncTMX(baseTMX, OStrings.getString("TMX_MERGE_BASE"), srcLang, trgLang),
new SyncTMX(projectTMX, OStrings.getString("TMX_MERGE_MINE"), srcLang, trgLang),
new SyncTMX(headTMX, OStrings.getString("TMX_MERGE_THEIRS"), srcLang, trgLang), props);
Log.log("Merged: " + mergedTMX);
if (!checkMergeInput(baseTMX, mergedTMX)) {
Log.log("'Merged' TM is not a valid derivative of 'Base' TM");
System.exit(1);
}
projectTMX.replaceContent(mergedTMX);
}
commitDetails.append('\n');
commitDetails.append(props.getReport().toString());
}
/**
* Check a TM against the base TM to ensure it's a valid modification of
* the base. This integration test never deletes entries, only adds or
* modifies them, so modified versions must be supersets of their base
* versions.
*
* @param base
* Base TM from which the other TM is derived
* @param other
* Other TM
* @return Valid or not
*/
boolean checkMergeInput(ProjectTMX base, ProjectTMX other) {
return base.defaults.keySet().stream().allMatch(other.defaults::containsKey)
&& base.alternatives.keySet().stream().allMatch(other.alternatives::containsKey);
}
protected void mergeTMXOld(ProjectTMX baseTMX, ProjectTMX headTMX) {
mergedTMX = new ProjectTMX();
this.baseTMX = baseTMX;
this.headTMX = headTMX;
String s = "info";
for (TMXEntry e : baseTMX.getDefaults()) {
use(e);
}
for (TMXEntry e : headTMX.getDefaults()) {
TMXEntry eb = baseTMX.getDefaultTranslation(e.source);
if (CONCURRENT_NAME.equals(e.source)) { // concurrent
if (v(eb) > v(e)) {
throw new RuntimeException("Rebase HEAD: wrong concurrent" + s);
}
use(e);
} else if (e.source.startsWith(source + "/")) { // my segments
if (v(eb) != v(e)) {
throw new RuntimeException("Rebase HEAD: not equals for current project" + s);
}
} else { // other segments
if (v(eb) > v(e)) {
throw new RuntimeException("Rebase HEAD: less value" + s);
}
use(e);
}
}
for (TMXEntry e : projectTMX.getDefaults()) {
TMXEntry em = mergedTMX.getDefaultTranslation(e.source);
if (CONCURRENT_NAME.equals(e.source)) { // concurrent
if (v(e) > v(em)) {
use(e);
}
} else if (e.source.startsWith(source + "/")) { // my segments
if (v(e) < v(em)) {
throw new RuntimeException("Rebase me: less value" + s);
}
use(e);
} else { // other segments
use(em);
}
}
projectTMX.replaceContent(mergedTMX);
}
void use(TMXEntry en) {
EntryKey k = new EntryKey("file", en.source, null, null, null, null);
SourceTextEntry ste = new SourceTextEntry(k, 0, null, en.source, Collections.<ProtectedPart> emptyList());
mergedTMX.setTranslation(ste, en, true);
}
long v(TMXEntry e) {
if (e == null) {
return 0;
} else {
return Long.parseLong(e.translation);
}
}
String src(TMXEntry e) {
if (e == null) {
return "null";
} else {
return e.source;
}
}
String tr(TMXEntry e) {
if (e == null) {
return "null";
} else {
return e.translation;
}
}
String trans(ProjectTMX tmx, TMXEntry e) {
TMXEntry en = tmx.getDefaultTranslation(e.source);
if (en == null) {
return "null";
} else {
return en.translation;
}
}
}
}