blob: c5ef6ec8c9112dceb15cc15e22f8877b058c793d [file] [log] [blame]
/**
* Copyright (c) 2011, 2015 - Lunifera GmbH (Gross Enzersdorf, Austria), Loetz GmbH&Co.KG (69115 Heidelberg, Germany)
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Florian Pirchner - Initial implementation
*/
package org.eclipse.osbp.ide.core.i18n;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.osbp.ide.core.api.i18n.CoreUtil;
import org.eclipse.osbp.ide.core.api.i18n.II18nRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Singleton;
@Singleton
public class I18nRegistry implements II18nRegistry {
private static final ProjectDescription EMPY_PROJECT_DESCRIPTION = new ProjectDescription(
null);
private static final Logger LOGGER = LoggerFactory
.getLogger(I18nRegistry.class);
private Map<IProject, ProjectDescription> cache = Collections
.synchronizedMap(new HashMap<IProject, ProjectDescription>());
public I18nRegistry() {
}
@Override
public String getText(IProject project, Locale locale, String key) {
String result = findTranslation(project, locale, key, locale);
return result;
}
@Override
public List<Proposal> findContentProposals(IProject project, Locale locale,
String packageName, String searchValue) {
AccessPath accessPath = computeAccessPath(project, locale, packageName,
searchValue);
return accessPath.getContentProposals();
}
@Override
public List<Proposal> findStrictKeyMatchingProposals(IProject project,
Locale locale, String packageName, String key) {
AccessPath accessPath = computeBestMatchAccessPath(project, locale,
packageName, key);
return accessPath.getStrictKeyMatchingProposals();
}
@Override
public Proposal findBestMatch(IProject project, Locale locale,
String packageName, String key) {
AccessPath accessPath = computeBestMatchAccessPath(project, locale,
packageName, key);
return accessPath.getBestMatch();
}
/**
* Computes the access path.
* <p>
* Following order will be used:
* <ol>
* <li>Find all entries in current project
* <ul>
* <li>Use given project and locale</li>
* <li>Create more specific locale and repeat until default locale is
* reached.</li>
* </ul>
* </li>
* <li>Iterate the referenced projects and start at 1) for each project with
* the requested locale.</li>
* </ol>
*
* @param project
* @param locale
* @param packageName
* @param searchValue
* @return
*/
private AccessPath computeAccessPath(IProject project, Locale locale,
String packageName, String searchValue) {
String matchingPackage = null;
String valuePatternString = null;
// search in given package
if (searchValue.startsWith(".")) {
valuePatternString = searchValue.replaceFirst(".", "");
matchingPackage = packageName;
} else {
valuePatternString = searchValue;
}
Matcher valueMatcher = null;
if (valuePatternString != null && !valuePatternString.equals("")) {
valuePatternString = Pattern.quote(valuePatternString);
Pattern valuePattern = Pattern.compile(valuePatternString
.toString());
valueMatcher = valuePattern.matcher("");
valueMatcher.reset();
}
AccessPath path = new AccessPath();
int prio = 0;
// all locales for the given project
List<IProject> computedProjects = computeProjects(project);
List<Locale> computedLocales = computeLocales(locale);
for (IProject computedProject : computedProjects) {
for (Locale computedLocale : computedLocales) {
Accessor accessor = new Accessor(computedProject,
computedLocale, ++prio, searchValue, matchingPackage,
valueMatcher);
path.addAccessor(accessor);
}
}
return path;
}
/**
* Computes the access path to find the best matching element. See
* {@link #computeAccessPath(IProject, Locale, String, String)}.
*
* @param project
* @param locale
* @param packageName
* @param searchValue
* @return
*/
private AccessPath computeBestMatchAccessPath(IProject project,
Locale locale, String packageName, String searchValue) {
return computeAccessPath(project, locale, packageName, searchValue);
}
/**
* Computes all projects that should be added to AccessPath
*
* @param project
* @return
*/
private List<IProject> computeProjects(IProject project) {
List<IProject> projects = new LinkedList<IProject>();
// Add first root project
projects.add(project);
try {
for (IProject referenced : project.getReferencedProjects()) {
if (CoreUtil.hasNature(referenced)) {
projects.add(referenced);
}
}
} catch (CoreException e) {
LOGGER.error("{}", e);
}
return projects;
}
/**
* Computes all locales that should be added to AccessPath
*
* @param locale
* @return
*/
private List<Locale> computeLocales(Locale locale) {
List<Locale> locales = new LinkedList<Locale>();
// Add first locale
locales.add(locale);
Locale temp = locale;
while (true) {
String tag = temp.toLanguageTag();
String[] segments = tag.split("-");
if (segments.length > 1) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < segments.length - 1; i++) {
if (builder.length() != 0) {
builder.append("-");
}
builder.append(segments[i]);
}
Locale moreGeneral = Locale.forLanguageTag(builder.toString());
locales.add(moreGeneral);
temp = moreGeneral;
} else {
break;
}
}
locales.add(new Locale(""));
return locales;
}
/**
* Tries to find the translation key.
* <p>
* <ol>
* <li>Tries to find the translation in all resources in the given project.</li>
* <li>Creates a more general locale and continues 1</li>
* <li>If locale is most general, then all references projects are used to
* look for a translation. Continues 1</li>
* <ol>
*
* @param project
* @param locale
* @param key
* @param originalLocale
* @return
*/
private String findTranslation(IProject project, Locale locale, String key,
Locale originalLocale) {
String result = null;
// first try to find a translation with the given locale in the project
//
ProjectDescription projectDescription = getProjectDescription(project);
List<ResourceDescription> descs = projectDescription
.getResourceDescriptions(locale);
for (ResourceDescription desc : descs) {
result = desc.getProperties().getProperty(key);
if (isValid(result)) {
return result;
}
}
// if no translation available, then try to use a more general one
//
String tag = locale.toLanguageTag();
String[] segments = tag.split("-");
if (segments.length > 1) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < segments.length - 1; i++) {
if (builder.length() != 0) {
builder.append("-");
}
builder.append(segments[i]);
}
Locale moreGeneral = Locale.forLanguageTag(builder.toString());
result = findTranslation(project, moreGeneral, key, originalLocale);
if (isValid(result)) {
return result;
}
} else {
try {
for (IProject referenced : project.getReferencedProjects()) {
if (referenced.getDescription().hasNature(
CoreUtil.NATURE_ID)) {
result = getText(referenced, originalLocale, key);
if (isValid(result)) {
return result;
}
}
}
} catch (CoreException e) {
LOGGER.error("{}", e);
}
}
return result;
}
private boolean isValid(String result) {
return result != null && !result.equals("");
}
private ProjectDescription getProjectDescription(IProject project) {
ProjectDescription description = cache.get(project);
return description != null ? description : EMPY_PROJECT_DESCRIPTION;
}
@Override
public void cache(ProjectDescription description) {
cache.put(description.getProject(), description);
}
@Override
public void cache(ResourceDescription description) {
if (cache.containsKey(description.getProject())) {
ProjectDescription projectDesc = cache
.get(description.getProject());
if (projectDesc != null) {
projectDesc.putResource(description);
}
} else {
ProjectDescription projectDesc = new ProjectDescription(
description.getProject());
projectDesc.putResource(description);
cache(projectDesc);
}
}
@Override
public void removeResource(IProject project, Locale locale, IPath location) {
ProjectDescription def = getProjectDescription(project);
def.removeResource(locale, location);
}
@Override
public Set<ProjectDescription> getProjectDescriptions() {
Set<ProjectDescription> result = new HashSet<II18nRegistry.ProjectDescription>(
cache.values());
return Collections.unmodifiableSet(result);
}
@Override
public void removeProject(IProject project) {
cache.remove(project);
}
/**
* Defines how the registry should be searched. For instance the ordering of
* locales, the ordering of projects,...
*
* @author admin
*
*/
private static class AccessPath {
private List<Accessor> accessors = new LinkedList<I18nRegistry.Accessor>();
public AccessPath() {
}
public void addAccessor(Accessor accessor) {
accessors.add(accessor);
}
/**
* Returns a list of proposals. If the searchValue matches parts of the
* value or the key for an i18n record, it is added to the list of
* proposals.
*
* @return
*/
public List<Proposal> getContentProposals() {
List<Proposal> proposals = new LinkedList<II18nRegistry.Proposal>();
for (Accessor accessor : accessors) {
proposals.addAll(accessor.getProposals());
}
return proposals;
}
/**
* Returns a list of proposals. The searchValue matches the entire key
* for an i18n record.
*
* @return
*/
public List<Proposal> getStrictKeyMatchingProposals() {
List<Proposal> proposals = new LinkedList<II18nRegistry.Proposal>();
for (Accessor accessor : accessors) {
proposals.addAll(accessor.getStrictKeyProposals());
}
return proposals;
}
/**
* Best match returns the first found translation. "Best" is ensured by
* the accessor order. If no proposal was found then <code>null</code>
* is returned.
*
* @return
*/
public Proposal getBestMatch() {
for (Accessor accessor : accessors) {
List<Proposal> result = accessor.getStrictKeyProposals();
if (!result.isEmpty()) {
return result.get(0);
}
}
return null;
}
}
/**
* This class will access the registry.
*/
private class Accessor {
private final IProject project;
private final Locale locale;
private final Matcher matcher;
@SuppressWarnings("unused")
private final String searchValue;
private final String keyPackage;
private final int prio;
public Accessor(IProject project, Locale locale, int prio,
String searchValue, String keyPackage, Matcher matcher) {
super();
this.project = project;
this.locale = locale;
this.searchValue = searchValue;
this.keyPackage = keyPackage;
this.matcher = matcher;
this.prio = prio;
}
/**
* Returns all proposals for the defined values. A result is added to
* the list of proposals, if parts of the key match the pattern. Must
* never return <code>null</code>.
*
* @return
*/
public List<Proposal> getProposals() {
ProjectDescription projectDesc = getProjectDescription(project);
if (projectDesc == I18nRegistry.EMPY_PROJECT_DESCRIPTION) {
return Collections.emptyList();
}
List<Proposal> proposals = new LinkedList<II18nRegistry.Proposal>();
List<ResourceDescription> descs = projectDesc
.getResourceDescriptions(locale);
for (ResourceDescription desc : descs) {
for (Map.Entry<Object, Object> entry : desc.getProperties()
.entrySet()) {
if (keyPackage != null
&& !((String) entry.getKey())
.startsWith(keyPackage)) {
continue;
}
if (matcher == null
|| matcher.reset(((String) entry.getValue()))
.find()
|| matcher.reset(((String) entry.getKey())).find()) {
proposals.add(new Proposal((String) entry.getKey(),
(String) entry.getValue(), desc, prio));
}
}
}
return proposals;
}
/**
* Returns all proposals for the defined values. The matcher must match
* the given key. Must never return <code>null</code>.
*
* @return
*/
public List<Proposal> getStrictKeyProposals() {
ProjectDescription projectDesc = getProjectDescription(project);
if (projectDesc == I18nRegistry.EMPY_PROJECT_DESCRIPTION) {
return Collections.emptyList();
}
List<Proposal> proposals = new LinkedList<II18nRegistry.Proposal>();
List<ResourceDescription> descs = projectDesc
.getResourceDescriptions(locale);
for (ResourceDescription desc : descs) {
for (Map.Entry<Object, Object> entry : desc.getProperties()
.entrySet()) {
if (keyPackage != null
&& !((String) entry.getKey())
.startsWith(keyPackage)) {
continue;
}
if (matcher == null
|| matcher.reset(((String) entry.getKey()))
.matches()) {
proposals.add(new Proposal((String) entry.getKey(),
(String) entry.getValue(), desc, prio));
}
}
}
return proposals;
}
}
}