/************************************************************************** OmegaT - Computer Assisted Translation (CAT) tool with fuzzy matching, translation memory, keyword search, glossaries, and translation leveraging into updated projects. Copyright (C) 2000-2006 Keith Godfrey and Maxym Mykhalchuk 2008 Didier Briel, Alex Buloichik 2009 Didier Briel 2012 Didier Briel, Aaron Madlon-Kay 2013 Aaron Madlon-Kay, Guido Leenders 2014 Aaron Madlon-Kay, Alex Buloichik 2015 Aaron Madlon-Kay 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.util; import java.io.ByteArrayInputStream; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.omegat.core.data.ProjectProperties; import org.omegat.filters2.TranslationException; import org.omegat.filters2.master.PluginUtils; import gen.core.project.Masks; import gen.core.project.Omegat; import gen.core.project.Project; import gen.core.project.Project.Repositories; /** * Class that reads and saves project definition file. * * @author Keith Godfrey * @author Maxym Mykhalchuk * @author Didier Briel * @author Alex Buloichik (alex73mail@gmail.com) * @author Aaron Madlon-Kay * @author Guido Leenders */ public class ProjectFileStorage { /** * A marker that tells OmegaT that project's subfolder has default location. */ public static final String DEFAULT_FOLDER_MARKER = "__DEFAULT__"; static private final JAXBContext CONTEXT; static { try { CONTEXT = JAXBContext.newInstance(Omegat.class); } catch (Exception ex) { throw new ExceptionInInitializerError(ex); } } public static Omegat parseProjectFile(File file) throws Exception { return parseProjectFile(FileUtils.readFileToByteArray(file)); } public static Omegat parseProjectFile(byte[] projectFile) throws Exception { return (Omegat) CONTEXT.createUnmarshaller().unmarshal(new ByteArrayInputStream(projectFile)); } /** * Load the project properties file for the project at the specified directory. The properties file is * assumed to exist at the root of the project and have the default name, {@link OConsts#FILE_PROJECT}. * This is a convenience method for {@link #loadPropertiesFile(File, File)}. * <p> * If the supplied {@link File} is not a directory, an {@link IllegalArgumentException} will be thrown. * * @param projectDir * The directory of the project * @return The loaded project properties * @throws Exception */ public static ProjectProperties loadProjectProperties(File projectDir) throws Exception { return loadPropertiesFile(projectDir, new File(projectDir, OConsts.FILE_PROJECT)); } /** * Load the specified project properties file for the project at the specified directory. * <p> * If <code>projectDir</code> is not a directory or <code>projectFile</code> is not a file, an * {@link IllegalArgumentException} will be thrown. * * @param projectDir * The directory of the project * @param projectFile * The project properties file to load * @return The loaded project properties * @throws Exception */ public static ProjectProperties loadPropertiesFile(File projectDir, File projectFile) throws Exception { if (!projectFile.isFile()) { throw new IllegalArgumentException("Project file was not a file"); } Omegat om = parseProjectFile(projectFile); return loadPropertiesFile(projectDir, om); } static ProjectProperties loadPropertiesFile(File projectDir, Omegat om) throws Exception { if (!projectDir.isDirectory()) { throw new IllegalArgumentException("Project directory was not a directory"); } ProjectProperties result = new ProjectProperties(projectDir); if (!OConsts.PROJ_CUR_VERSION.equals(om.getProject().getVersion())) { throw new TranslationException(StringUtil.format( OStrings.getString("PFR_ERROR_UNSUPPORTED_PROJECT_VERSION"), om.getProject().getVersion())); } result.setTargetRoot(normalizeLoadedPath(om.getProject().getTargetDir(), OConsts.DEFAULT_TARGET)); result.setSourceRoot(normalizeLoadedPath(om.getProject().getSourceDir(), OConsts.DEFAULT_SOURCE)); result.getSourceRootExcludes().clear(); if (om.getProject().getSourceDirExcludes() != null) { result.getSourceRootExcludes().addAll(om.getProject().getSourceDirExcludes().getMask()); } else { // sourceRootExclude was not defined result.getSourceRootExcludes().addAll(ProjectProperties.getDefaultExcludes()); } result.setTMRoot(normalizeLoadedPath(om.getProject().getTmDir(), OConsts.DEFAULT_TM)); result.setGlossaryRoot(normalizeLoadedPath(om.getProject().getGlossaryDir(), OConsts.DEFAULT_GLOSSARY)); // Compute glossary file location String glossaryFile = om.getProject().getGlossaryFile(); if (StringUtil.isEmpty(glossaryFile)) { glossaryFile = DEFAULT_FOLDER_MARKER; } if (glossaryFile.equalsIgnoreCase(DEFAULT_FOLDER_MARKER)) { glossaryFile = result.computeDefaultWriteableGlossaryFile(); } else { glossaryFile = result.getGlossaryDir().getAsString() + glossaryFile; } result.setWriteableGlossary(glossaryFile); result.setDictRoot(normalizeLoadedPath(om.getProject().getDictionaryDir(), OConsts.DEFAULT_DICT)); result.setSourceLanguage(om.getProject().getSourceLang()); result.setTargetLanguage(om.getProject().getTargetLang()); result.setSourceTokenizer(loadTokenizer(om.getProject().getSourceTok(), result.getSourceLanguage())); result.setTargetTokenizer(loadTokenizer(om.getProject().getTargetTok(), result.getTargetLanguage())); if (om.getProject().isSentenceSeg() != null) { result.setSentenceSegmentingEnabled(om.getProject().isSentenceSeg()); } if (om.getProject().isSupportDefaultTranslations() != null) { result.setSupportDefaultTranslations(om.getProject().isSupportDefaultTranslations()); } if (om.getProject().isRemoveTags() != null) { result.setRemoveTags(om.getProject().isRemoveTags()); } if (om.getProject().getExternalCommand() != null) { result.setExternalCommand(om.getProject().getExternalCommand()); } if (om.getProject().getRepositories() != null) { result.setRepositories(om.getProject().getRepositories().getRepository()); } return result; } /** * Saves project file to disk. */ public static void writeProjectFile(ProjectProperties props) throws Exception { File outFile = new File(props.getProjectRoot(), OConsts.FILE_PROJECT); String root = outFile.getAbsoluteFile().getParent(); Omegat om = new Omegat(); om.setProject(new Project()); om.getProject().setVersion(OConsts.PROJ_CUR_VERSION); om.getProject().setSourceDir(getPathForStoring(root, props.getSourceRoot(), OConsts.DEFAULT_SOURCE)); om.getProject().setSourceDirExcludes(new Masks()); om.getProject().getSourceDirExcludes().getMask().addAll(props.getSourceRootExcludes()); om.getProject().setTargetDir(getPathForStoring(root, props.getTargetRoot(), OConsts.DEFAULT_TARGET)); om.getProject().setTmDir(getPathForStoring(root, props.getTMRoot(), OConsts.DEFAULT_TM)); String glossaryDir = getPathForStoring(root, props.getGlossaryRoot(), OConsts.DEFAULT_GLOSSARY); om.getProject().setGlossaryDir(glossaryDir); // Compute glossary file location: must be relative to glossary root String glossaryFile = getPathForStoring(props.getGlossaryRoot(), props.getWriteableGlossary(), null); if (glossaryDir.equalsIgnoreCase(DEFAULT_FOLDER_MARKER) && props.isDefaultWriteableGlossaryFile()) { // Everything equals to default glossaryFile = DEFAULT_FOLDER_MARKER; } om.getProject().setGlossaryFile(glossaryFile); om.getProject().setDictionaryDir(getPathForStoring(root, props.getDictRoot(), OConsts.DEFAULT_DICT)); om.getProject().setSourceLang(props.getSourceLanguage().toString()); om.getProject().setTargetLang(props.getTargetLanguage().toString()); om.getProject().setSourceTok(props.getSourceTokenizer().getCanonicalName()); om.getProject().setTargetTok(props.getTargetTokenizer().getCanonicalName()); om.getProject().setSentenceSeg(props.isSentenceSegmentingEnabled()); om.getProject().setSupportDefaultTranslations(props.isSupportDefaultTranslations()); om.getProject().setRemoveTags(props.isRemoveTags()); om.getProject().setExternalCommand(props.getExternalCommand()); if (props.getRepositories() != null && !props.getRepositories().isEmpty()) { om.getProject().setRepositories(new Repositories()); om.getProject().getRepositories().getRepository().addAll(props.getRepositories()); } Marshaller m = CONTEXT.createMarshaller(); m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); m.marshal(om, outFile); } private static String normalizeLoadedPath(String path, String defaultValue) { // Older project files can be missing path definitions, in which case // path will be null here. In that case return the default. if (StringUtil.isEmpty(path) || DEFAULT_FOLDER_MARKER.equals(path)) { return defaultValue; } else { return normalizeSlashes(path); } } /** * Converts a path to the format stored on disk. If * <code>absolutePath</code> has the default location given by * <code>root/defaultName</code>, returns <code>__DEFAULT__</code>. * <p> * Otherwise it attempts to compute a relative path based at * <code>root</code>. If this isn't possible (e.g. the paths don't share a * filesystem root) or if the relative path is more than * {@link OConsts#MAX_PARENT_DIRECTORIES_ABS2REL} levels away then it gives * up and returns the original <code>absolutePath</code>. * * @param root * Root path against which to evaluate * @param absolutePath * Absolute path to a folder * @param defaultName * Default name for the folder * @since 1.6.0 * @see <a href= * "https://sourceforge.net/p/omegat/feature-requests/734/">RFE#734</a> * @see OConsts#MAX_PARENT_DIRECTORIES_ABS2REL */ private static String getPathForStoring(String root, String absolutePath, String defaultName) { if (defaultName != null && new File(absolutePath).equals(new File(root, defaultName))) { return DEFAULT_FOLDER_MARKER; } // Fall back to using the input path if all else fails. String result = absolutePath; try { // Path.normalize() will resolve any remaining "../" Path absPath = Paths.get(absolutePath).normalize(); String rel = Paths.get(root).relativize(absPath).toString(); if (StringUtils.countMatches(rel, ".." + File.separatorChar) <= OConsts.MAX_PARENT_DIRECTORIES_ABS2REL) { // Use the relativized path as it is "near" enough. result = rel; } else { // result = absPath.toString(); } } catch (IllegalArgumentException e) { } return normalizeSlashes(result); } /** * Load a tokenizer class from its canonical name. * @param className Name of tokenizer class * @return Class object of specified tokenizer, or of fallback tokenizer * if the specified one could not be loaded for whatever reason. */ private static Class<?> loadTokenizer(String className, Language fallback) { if (!StringUtil.isEmpty(className)) { try { return ProjectFileStorage.class.getClassLoader().loadClass(className); } catch (ClassNotFoundException e) { Log.log(e.toString()); } } return PluginUtils.getTokenizerClassForLanguage(fallback); } /** * Replace \ with / and remove / from the end if present. Within OmegaT we * generally require a / on the end of directories, but for storage we * prefer no trailing /. */ static String normalizeSlashes(String path) { return withoutTrailingSlash(path.replace('\\', '/')); } static String withoutTrailingSlash(String path) { while (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } return path; } }