blob: 5e43ccbf92489fb3ae2c17f817367e3c36187914 [file] [log] [blame]
package org.eclipse.scout.nls.sdk.services.model.ws.project;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.scout.nls.sdk.extension.INlsProjectProvider;
import org.eclipse.scout.nls.sdk.internal.NlsCore;
import org.eclipse.scout.nls.sdk.internal.jdt.NlsJdtUtility;
import org.eclipse.scout.nls.sdk.model.workspace.project.INlsProject;
import org.eclipse.scout.nls.sdk.services.model.ws.NlsServiceType;
import org.eclipse.scout.sdk.RuntimeClasses;
import org.eclipse.scout.sdk.ScoutSdkCore;
import org.eclipse.scout.sdk.internal.workspace.IScoutBundleConstantes;
import org.eclipse.scout.sdk.util.jdt.JdtUtility;
import org.eclipse.scout.sdk.util.log.ScoutStatus;
import org.eclipse.scout.sdk.util.type.TypeUtility;
import org.eclipse.scout.sdk.util.typecache.TypeCacheAccessor;
import org.eclipse.scout.sdk.workspace.IScoutBundle;
import org.eclipse.scout.sdk.workspace.IScoutProject;
@SuppressWarnings("restriction")
public class ServiceNlsProjectProvider implements INlsProjectProvider {
public ServiceNlsProjectProvider() {
}
/**
* @return All text provider services in the workspace.
* @throws JavaModelException
*/
public static IType[] getRegisteredTextProviderTypes() throws JavaModelException {
return getRegisteredTextProviderTypes(null, null);
}
/**
* Gets the registered (in plugin.xml) text provider service types ordered by priority.
*
* @param returnDocServices
* If true, only Docs text provider services (implementing marker interface
* <code>IDocumentationTextProviderService</code>) are returned. Otherwise only non-docs text provider
* services are
* returned. If the parameter is null, all text services are returned.
* @param projectFilter
* List of project names. Only services that belong to these projects are returned. if null is passed, all
* services are returned.
* @return
* @throws JavaModelException
*/
private static IType[] getRegisteredTextProviderTypes(Boolean returnDocServices, String[] projectFilter) throws JavaModelException {
class TextProviderService {
private final IType textProvider;
private final IJavaProject project;
private TextProviderService(IType t) {
textProvider = t;
project = t.getJavaProject();
}
@Override
public int hashCode() {
int ret = 1;
ret = ret * 27 + textProvider.getFullyQualifiedName().hashCode();
ret = ret * 19 + project.getElementName().hashCode();
return ret;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof TextProviderService) {
TextProviderService o = (TextProviderService) obj;
return textProvider.getFullyQualifiedName().equals(o.textProvider.getFullyQualifiedName()) &&
project.getElementName().equals(o.project.getElementName());
}
else {
return false;
}
}
}
class TextProviderServiceDeclaration {
private final TextProviderService svc;
private final float prio;
private TextProviderServiceDeclaration(TextProviderService s, float p) {
svc = s;
prio = p;
}
}
IType superType = TypeUtility.getType(RuntimeClasses.AbstractDynamicNlsTextProviderService);
if (superType == null) return null;
IType[] serviceImpls = TypeCacheAccessor.getHierarchyCache().getPrimaryTypeHierarchy(superType).getAllSubtypes(superType);
HashMap<TextProviderService, TextProviderServiceDeclaration> result = new HashMap<TextProviderService, TextProviderServiceDeclaration>(serviceImpls.length);
IExtension[] allServiceExtensions = PDECore.getDefault().getExtensionsRegistry().findExtensions(IScoutBundleConstantes.EXTENSION_POINT_SERVICES, true);
for (IExtension e : allServiceExtensions) {
for (IConfigurationElement c : e.getConfigurationElements()) {
for (IType serviceType : serviceImpls) {
if (acceptsFilter(returnDocServices, projectFilter, serviceType)) {
if (IScoutBundleConstantes.EXTENSION_ELEMENT_SERVICE.equals(c.getName())) {
String serviceClassDef = c.getAttribute("class");
if (serviceClassDef != null && serviceClassDef.equals(serviceType.getFullyQualifiedName())) {
TextProviderService s = new TextProviderService(serviceType);
TextProviderServiceDeclaration d = new TextProviderServiceDeclaration(s, getPriority(serviceType, c));
// if the same service is registered more than once with different priorities: use always the one with the higher priority
// meaning: if we have the current service with a higher prio in the list already -> do not overwrite with the new one.
TextProviderServiceDeclaration existing = result.get(s);
if (existing == null || existing.prio < d.prio) {
// we do not have this service or we have a service with lower prio -> overwrite
result.put(s, d);
}
}
}
}
}
}
}
// we have ensured that every service is registered with its highest prio -> sort all services by prio
TextProviderServiceDeclaration[] sortedArrayHighestPrioFirst = result.values().toArray(new TextProviderServiceDeclaration[result.size()]);
Arrays.sort(sortedArrayHighestPrioFirst, new Comparator<TextProviderServiceDeclaration>() {
@Override
public int compare(TextProviderServiceDeclaration o1, TextProviderServiceDeclaration o2) {
return new Float(o2.prio).compareTo(new Float(o1.prio));
}
});
// return the types of the services ordered by priority
IType[] returnValueSorted = new IType[sortedArrayHighestPrioFirst.length];
for (int i = 0; i < returnValueSorted.length; i++) {
returnValueSorted[i] = sortedArrayHighestPrioFirst[i].svc.textProvider;
}
return returnValueSorted;
}
private static boolean acceptsFilter(Boolean returnDocServices, String[] projects, IType candidate) throws JavaModelException {
boolean acceptsDocPart = returnDocServices == null || returnDocServices == isDocsService(candidate);
if (acceptsDocPart) {
if (candidate.isReadOnly()) return true; // always include all text services from the platform
if (projects == null) return true; // no project filter and doc filter is valid -> filter matches
// check project filter
for (String p : projects) {
if (candidate.getJavaProject().getProject().getName().equals(p)) return true;
}
}
return false;
}
private static boolean isDocsService(IType service) throws JavaModelException {
String docsInterfaceClassName = RuntimeClasses.IDocumentationTextProviderService.substring(RuntimeClasses.IDocumentationTextProviderService.lastIndexOf('.') + 1);
for (String ifs : service.getSuperInterfaceNames()) {
if (docsInterfaceClassName.equals(ifs)) {
return true;
}
}
return false;
}
private static float getPriority(IType registration, IConfigurationElement config) {
// first check plugin.xml definition for ranking
String xmlRank = config.getAttribute(IScoutBundleConstantes.EXTENSION_SERVICE_RANKING);
if (xmlRank != null && xmlRank.length() > 0) {
try {
return Float.parseFloat(xmlRank);
}
catch (NumberFormatException e) {
//nop
}
}
// second check class annotation
try {
IAnnotation a = JdtUtility.getAnnotation(registration, RuntimeClasses.Ranking);
Double val = JdtUtility.getNumericAnnotationValue(a, "value");
if (val != null) {
return val.floatValue();
}
}
catch (Exception e) {
//nop
}
// if nothing defined: default ranking = 0
return 0.0f;
}
private ServiceNlsProject getServiceNlsProject(IType serviceType) {
if (!TypeUtility.exists(serviceType)) {
NlsCore.logError("nls service type '" + serviceType.getFullyQualifiedName() + "' does not exist");
return null;
}
NlsServiceType type = new NlsServiceType(serviceType);
if (type.getTranslationsFolderName() == null) {
NlsCore.logWarning("The NLS Service for Type '" + serviceType.getFullyQualifiedName() + "' could not be parsed. Ensure that the method '"
+ NlsServiceType.DYNAMIC_NLS_BASE_NAME_GETTER + "' is available and returns a String literal like \"resources.texts.Texts\" directly.");
return null;
}
return new ServiceNlsProject(type);
}
private INlsProject getNlsProjectTree(boolean returnDocServices, String[] projectFilter) throws CoreException {
return getNlsProjectTree(getRegisteredTextProviderTypes(returnDocServices, projectFilter));
}
private INlsProject getNlsProjectTree(IType[] textProviderServices) throws CoreException {
ServiceNlsProject previous = null;
ServiceNlsProject root = null;
for (IType type : textProviderServices) {
ServiceNlsProject p = getServiceNlsProject(type);
if (p != null) {
// remember the first project (the one with the highest prio)
if (root == null) {
root = p;
}
// set the current as parent of the previous instance -> build up tree
if (previous != null) {
previous.setParent(p);
}
// remember the previous provider for the next iteration
previous = p;
}
else if (root == null) {
// first Type in the chain could not be parsed.
// this is also the Type that e.g. the editor would show -> show error
//NlsCore.logError("The NLS Service for Type " + type.getFullyQualifiedName() + " could not be parsed.");
throw new CoreException(new ScoutStatus("The NLS Service for Type " + type.getFullyQualifiedName() + " could not be parsed."));
}
}
return root;
}
private static String[] getProjectNames(IScoutBundle[] scoutBundles) {
if (scoutBundles == null || scoutBundles.length < 1) return null;
String[] ret = new String[scoutBundles.length];
for (int i = 0; i < scoutBundles.length; i++) {
ret[i] = scoutBundles[i].getBundleName();
}
return ret;
}
private static void addBundlesRec(IScoutProject p, HashSet<IScoutBundle> collector) {
for (IScoutBundle b : p.getAllScoutBundles()) {
collector.add(b);
}
for (IScoutProject subP : p.getSubProjects()) {
addBundlesRec(subP, collector);
}
}
private static IScoutBundle[] getScoutBundlesForType(IType type) {
IScoutBundle b = ScoutSdkCore.getScoutWorkspace().getScoutBundle(type.getJavaProject().getProject());
if (b != null) {
IScoutProject root = b.getScoutProject();
return getScoutBundlesForProject(root);
}
else {
return null;
}
}
private static IScoutBundle[] getScoutBundlesForProject(IScoutProject p) {
if (p == null) return null;
HashSet<IScoutBundle> collector = new HashSet<IScoutBundle>();
// find root scout project of the given project (may be a sub project).
IScoutProject root = p;
while (root.getParentProject() != null) {
root = root.getParentProject();
}
// collect all bundles that belong to the root project that belongs to the project p.
// assume that all these bundles together build the application so all text services of these projects should be available.
addBundlesRec(root, collector);
return collector.toArray(new IScoutBundle[collector.size()]);
}
private INlsProject getNlsProjectTree(IType type) throws CoreException {
IType[] nlsProviders = getRegisteredTextProviderTypes(isDocsService(type), getProjectNames(getScoutBundlesForType(type)));
if (nlsProviders == null) return null;
String searchString = getTypeIdentifyer(type);
ArrayList<IType> filtered = new ArrayList<IType>(nlsProviders.length);
boolean minFound = false;
for (IType t : nlsProviders) {
if (getTypeIdentifyer(t).equals(searchString) && !minFound) {
minFound = true;
}
if (minFound) {
filtered.add(t);
}
}
return getNlsProjectTree(filtered.toArray(new IType[filtered.size()]));
}
private static String getTypeIdentifyer(IType t) {
return t.getJavaProject().getProject().getName() + "/" + t.getFullyQualifiedName();
}
@Override
public INlsProject getProject(Object[] args) {
if (args != null) {
if (args.length == 1) {
if (args[0] instanceof IType) {
// text service type
return getProjectByTextServiceType((IType) args[0]);
}
else if (args[0] instanceof IFile) {
// text service file
return getProjectByTextServiceFile((IFile) args[0]);
}
}
else if (args.length == 2 && args[0] instanceof IType) {
IType t1 = (IType) args[0];
boolean returnDocServices = !(RuntimeClasses.TEXTS.equals(t1.getFullyQualifiedName()));
if (args[1] instanceof IScoutProject || args[1] == null) {
// all text services in the given project with given kind (texts or normal)
return getAllProjects(returnDocServices, getScoutBundlesForProject((IScoutProject) args[1]));
}
else if (args[1] instanceof IType) {
// all text services with given kind available in the plugins defining the given type
return getAllProjects(returnDocServices, getScoutBundlesForType((IType) args[1]));
}
}
}
return null;
}
private INlsProject getAllProjects(boolean returnDocServices, IScoutBundle[] wsBundles) {
try {
return getNlsProjectTree(returnDocServices, getProjectNames(wsBundles));
}
catch (CoreException e) {
NlsCore.logWarning("Could not load full text provider service tree.", e);
return null;
}
}
private INlsProject getProjectByTextServiceFile(IFile f) {
try {
IType type = NlsJdtUtility.getITypeForFile(f);
if (type != null) {
return NlsCore.getNlsWorkspace().getNlsProject(new Object[]{type});
}
}
catch (CoreException e) {
NlsCore.logWarning("Could not load text provider services for file: " + f.getFullPath().toString(), e);
}
return null;
}
private INlsProject getProjectByTextServiceType(IType textservice) {
try {
return getNlsProjectTree(textservice);
}
catch (CoreException e) {
NlsCore.logWarning("Could not load text provider services for " + textservice.getFullyQualifiedName(), e);
return null;
}
}
}