/** * (C) Copyright 2013 Jabylon (http://www.jabylon.org) and others. * * 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 */ package org.jabylon.properties.util; import java.io.File; import java.util.Collections; import java.util.Locale; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.util.EcoreUtil; import org.jabylon.properties.Project; import org.jabylon.properties.ProjectLocale; import org.jabylon.properties.ProjectVersion; import org.jabylon.properties.PropertiesFactory; import org.jabylon.properties.PropertiesPackage; import org.jabylon.properties.PropertyFile; import org.jabylon.properties.PropertyFileDescriptor; import org.jabylon.properties.Resolvable; import org.jabylon.properties.ResourceFolder; import org.jabylon.properties.types.PropertyScanner; import org.jabylon.properties.types.impl.JavaPropertyScanner; import org.jabylon.properties.types.impl.JavaPropertyScannerUTF8; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; import org.osgi.util.tracker.ServiceTrackerCustomizer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PropertyResourceUtil { private static ConcurrentMap<String, PropertyScanner> PROPERTY_SCANNERS; private static final Logger LOG = LoggerFactory.getLogger(PropertyResourceUtil.class); static { PROPERTY_SCANNERS = new ConcurrentHashMap<String, PropertyScanner>(); Bundle bundle = FrameworkUtil.getBundle(PropertyResourceUtil.class); if(bundle==null){ //fallback for unit tests PROPERTY_SCANNERS.put(JavaPropertyScanner.TYPE, new JavaPropertyScanner()); PROPERTY_SCANNERS.put(JavaPropertyScannerUTF8.TYPE, new JavaPropertyScannerUTF8()); } else { final BundleContext context = FrameworkUtil.getBundle(PropertyResourceUtil.class).getBundleContext(); ServiceTracker<PropertyScanner, PropertyScanner> tracker = new ServiceTracker<PropertyScanner, PropertyScanner>(context, PropertyScanner.class, new ServiceTrackerCustomizer<PropertyScanner, PropertyScanner>() { @Override public PropertyScanner addingService(ServiceReference<PropertyScanner> reference){ Object value = reference.getProperty(PropertyScanner.TYPE); PropertyScanner service = context.getService(reference); if (value instanceof String) { String type = (String) value; PROPERTY_SCANNERS.put(type, service); LOG.debug("Added property type {} {}",type,service); } else LOG.error("PropertyScanner has no valid type {}",service); return service; } @Override public void removedService(ServiceReference<PropertyScanner> reference, PropertyScanner service) { Object value = reference.getProperty(PropertyScanner.TYPE); context.ungetService(reference); if(value!=null) PROPERTY_SCANNERS.remove(value); LOG.debug("Removed property type {} {}",value); } @Override public void modifiedService(ServiceReference<PropertyScanner> reference, PropertyScanner service) { // nothing to do } }); tracker.open(true); } } public static PropertyScanner createScanner(ProjectVersion version) { Project project = version.getParent(); String propertyType = project.getPropertyType(); return createScanner(propertyType); } public static PropertyScanner createScanner(String propertyType) { PropertyScanner scanner = PROPERTY_SCANNERS.get(propertyType); if(scanner==null) throw new UnsupportedOperationException("unsupported property type: "+propertyType); return scanner; } public static Map<String,PropertyScanner> getPropertyScanners() { TreeMap<String, PropertyScanner> sortedMap = new TreeMap<String, PropertyScanner>(PROPERTY_SCANNERS); return Collections.unmodifiableMap(sortedMap); } public static void createMissingDescriptorEntries(ProjectVersion parent, IProgressMonitor monitor) { EList<ProjectLocale> children = parent.getChildren(); monitor.beginTask("Adding missing localized resources", children.size() - 1); ProjectLocale template = parent.getTemplate(); for (ProjectLocale locale : children) { if (locale == template) continue; if (locale != null && locale.getLocale() != null) monitor.subTask("Add missing entries for " + locale.getLocale().getDisplayName()); createMissingChildren(template, locale); monitor.worked(1); } monitor.subTask(""); monitor.done(); } /** * adds a new descriptor to the version (i.e. it creates any missing folders * in all the locales) <strong>important:<strong> the feature * {@link PropertyFileDescriptor#getLocation()} must be set for this to work * * @param descriptor * @param version */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static void addNewTemplateDescriptor(PropertyFileDescriptor descriptor, ProjectVersion version) { if (!descriptor.eIsSet(PropertiesPackage.Literals.PROPERTY_FILE_DESCRIPTOR__LOCATION)) throw new IllegalArgumentException("Property File Descriptor location is not set"); for (ProjectLocale locale : version.getChildren()) { Resolvable parent = locale; String[] descriptorPart = descriptor.getLocation().segments(); // add any missing folders for (int i = 0; i < descriptorPart.length - 1; i++) { Resolvable child = parent.getChild(descriptorPart[i]); if (child == null) { child = PropertiesFactory.eINSTANCE.createResourceFolder(); //get rid of encodings like %20 for space String childName = URI.decode(descriptorPart[i]); child.setName(childName); parent.getChildren().add(child); } parent = child; } // if it is not the template language, create a new derived // descriptor if (!locale.isMaster()) { URI derivedLocation = computeLocaleResourceLocation(locale.getLocale(), version, descriptor.getLocation()); PropertyFileDescriptor translatedDescriptor = (PropertyFileDescriptor) parent.getChild(derivedLocation.lastSegment()); if (translatedDescriptor == null) { translatedDescriptor = PropertiesFactory.eINSTANCE.createPropertyFileDescriptor(); locale.getDescriptors().add(translatedDescriptor); parent.getChildren().add(translatedDescriptor); } translatedDescriptor.setVariant(locale.getLocale()); translatedDescriptor.setLocation(derivedLocation); translatedDescriptor.setName(derivedLocation.lastSegment()); translatedDescriptor.setMaster(descriptor); if(new File(translatedDescriptor.absoluteFilePath().path()).isFile()) { // load file to initialize statistics; PropertyFile translatedFile = translatedDescriptor.loadProperties(); int size = translatedFile.getProperties().size(); translatedDescriptor.setKeys(size); translatedDescriptor.updatePercentComplete(); } else { /* * the file wasn't already there, but we still * must inform the parent that there is more children now */ parent.updatePercentComplete(); } } // otherwise add it to the template language else if (parent.getChild(descriptor.getName()) == null) { parent.getChildren().add(descriptor); descriptor.setName(descriptor.getLocation().lastSegment()); version.getTemplate().getDescriptors().add(descriptor); descriptor.updatePercentComplete(); } } } @SuppressWarnings({ "unchecked", "rawtypes" }) public static void addNewLocalizedDescriptor(PropertyFileDescriptor descriptor, ProjectLocale locale) { if (!descriptor.eIsSet(PropertiesPackage.Literals.PROPERTY_FILE_DESCRIPTOR__LOCATION)) throw new IllegalArgumentException("Property File Descriptor location is not set"); ProjectVersion version = locale.getParent(); ProjectLocale templateLocale = version.getTemplate(); URI templateResourceLocation = computeTemplateResourceLocation(locale.getLocale(), descriptor.getLocation(), templateLocale.getLocale(),version); Resolvable<?, ?> resolved = templateLocale.resolveChild(templateResourceLocation); PropertyFileDescriptor template = null; if (resolved instanceof PropertyFileDescriptor) { template = (PropertyFileDescriptor) resolved; } if (template == null) throw new IllegalArgumentException("Template property " + templateResourceLocation + " doesn't exist"); Resolvable container = getOrCreateFolder(locale, descriptor.getLocation().trimSegments(1).segments()); if (container.getChild(descriptor.getLocation().lastSegment()) != null) { PropertyFileDescriptor child = (PropertyFileDescriptor) container.getChild(descriptor.getLocation().lastSegment()); child.setMaster(null); container.getChildren().set(container.getChildren().indexOf(child), descriptor); locale.getDescriptors().remove(child); } else { container.getChildren().add(descriptor); } descriptor.setMaster(template); locale.getDescriptors().add(descriptor); } public static void addNewLocale(ProjectLocale locale, ProjectVersion version) { ProjectLocale template = version.getTemplate(); version.getChildren().add(locale); if(template==null) { //we always need a template template = PropertiesFactory.eINSTANCE.createProjectLocale(); version.setTemplate(template); template.setName("template"); version.getChildren().add(template); } createMissingChildren(template, locale); } private static void createMissingChildren(ProjectLocale template, ProjectLocale other) { EList<PropertyFileDescriptor> descriptors = template.getDescriptors(); for (PropertyFileDescriptor descriptor : descriptors) { URI derivedLocation = computeLocaleResourceLocation(other.getLocale(), other.getParent(), descriptor.getLocation()); Resolvable<?, ?> child = other.resolveChild(derivedLocation); if(child==null){ Resolvable<?, ?> folder = getOrCreateFolder(other, derivedLocation.trimSegments(1).segments()); PropertyFileDescriptor localeDescriptor = PropertiesFactory.eINSTANCE.createPropertyFileDescriptor(); localeDescriptor.setMaster(descriptor); localeDescriptor.setVariant(other.getLocale()); localeDescriptor.setLocation(derivedLocation); localeDescriptor.setProjectLocale(other); localeDescriptor.setParent(folder); localeDescriptor.setName(URI.decode(derivedLocation.lastSegment())); } } } @SuppressWarnings({ "rawtypes", "unchecked" }) public static Resolvable<?, ?> getOrCreateFolder(Resolvable<?, ?> parent, String... segments) { Resolvable currentParent = parent; if (segments == null || segments.length==0) return parent; for (String segment : segments) { //in case there was encoded characters in the segment //see https://github.com/jutzig/jabylon/issues/195 segment = URI.decode(segment); Resolvable child = currentParent.getChild(segment); if (child == null) { child = PropertiesFactory.eINSTANCE.createResourceFolder(); child.setName(segment); currentParent.getChildren().add(child); } currentParent = child; } return (ResourceFolder) currentParent; } public static URI computeTemplateResourceLocation(Locale locale, URI translationLocation, Locale masterLocale, ProjectVersion version) { PropertyScanner scanner = createScanner(version); URI parentPath = version.absoluteFilePath(); File path = scanner.findTemplate(new File(parentPath.path()+translationLocation.toString()), null); URI location = URI.createFileURI(path.getAbsolutePath()); URI trimmedLocation = URI.createURI(location.segment(parentPath.segmentCount())); for (int i = parentPath.segmentCount()+1; i < location.segmentCount(); i++) { //append the other segments trimmedLocation = trimmedLocation.appendSegment(location.segment(i)); } return trimmedLocation; } public static URI computeLocaleResourceLocation(Locale locale, ProjectVersion version, URI templateLocation) { PropertyScanner scanner = createScanner(version); URI parentPath = version.absoluteFilePath(); String childPath = templateLocation.path(); if (childPath != null && !childPath.startsWith("/")) childPath = "/" + childPath; File path = scanner.computeTranslationPath(new File(URI.decode(parentPath.path()) + URI.decode(childPath)), version.getTemplate().getLocale(), locale); /* * workaround for https://github.com/jutzig/jabylon/issues/238 certain * issues seem to trigger a bug in EMFs createFileURI */ URI location = URI.createURI(path.getAbsolutePath().replace('\\','/')); URI trimmedLocation = URI.createURI(location.segment(parentPath.segmentCount())); for (int i = parentPath.segmentCount() + 1; i < location.segmentCount(); i++) { // append the other segments trimmedLocation = trimmedLocation.appendSegment(location.segment(i)); } return trimmedLocation; } public static void removeDescriptor(PropertyFileDescriptor descriptor) { if (descriptor.isMaster()) { EList<PropertyFileDescriptor> derivedDescriptors = descriptor.getDerivedDescriptors(); for (PropertyFileDescriptor derived : derivedDescriptors) { derived.setProjectLocale(null); Resolvable<?, ?> parent = derived.getParent(); if(parent!=null) { //update the percentage of all derived resources parent.updatePercentComplete(); if(parent.getChildren().size()==1 && parent instanceof ResourceFolder) { deleteFolder((ResourceFolder) parent); } } EcoreUtil.remove(derived); } } else { descriptor.setMaster(null); } Resolvable<?, ?> parent = descriptor.getParent(); if(parent!=null && parent.getChildren().size()==1 && parent instanceof ResourceFolder) deleteFolder((ResourceFolder) parent); EcoreUtil.remove(descriptor); descriptor.getProjectLocale().getDescriptors().remove(descriptor); } /** * deletes a folder (and recursively all parents above) if there's only one child left in it * <p> * this is to clean up no longer needed folders when a descriptor is removed * @param parent */ @SuppressWarnings("rawtypes") private static void deleteFolder(ResourceFolder folder) { Resolvable currentParent = folder; Resolvable lastParent = folder; while(currentParent instanceof ResourceFolder) { if(currentParent.getChildren().size()<=1) { lastParent = currentParent; currentParent = currentParent.getParent(); } else break; } EcoreUtil.remove(lastParent); } }