blob: a04f9a2a237300165e53fd9d84b57aabd3269e76 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.royale.compiler.internal.resourcebundles;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.common.ISourceLocation;
import org.apache.royale.compiler.common.Multiname;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.internal.config.QNameNormalization;
import org.apache.royale.compiler.internal.definitions.ClassDefinition;
import org.apache.royale.compiler.internal.projects.RoyaleProject;
import org.apache.royale.compiler.internal.projects.ResourceBundleSourceFileHandler;
import org.apache.royale.compiler.internal.scopes.ASProjectScope;
import org.apache.royale.compiler.internal.units.ResourceBundleCompilationUnit;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.problems.ResourceBundleNotFoundForLocaleProblem;
import org.apache.royale.compiler.problems.ResourceBundleNotFoundProblem;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.units.ICompilationUnit;
import org.apache.royale.compiler.units.requests.IFileScopeRequestResult;
public class ResourceBundleUtils
{
public static final String CLASS_SUFFIX = "_" + ResourceBundleSourceFileHandler.EXTENSION;
/**
* Returns the qualified name for the auto generated resource bundle class for a
* specified locale and a bundleName. These class names, such as
* "foo.en_US$core_properties", are constructed in such a way that the locale
* and bundle name can be extracted from them by the following two methods.
* Note: The framework class mx.managers.SystemManager has similar logic
* which must be kept in sync with this.
*
* @param locale locale of the resource bundle
* @param bundleName qualified name of the bundle
* @return the fully qualified class name for this bundle and locale
*/
public static String getQualifiedName(String locale, String bundleName)
{
String normalizedBundleName = QNameNormalization.normalize(bundleName);
String packageName = Multiname.getPackageNameForQName(normalizedBundleName);
String className = Multiname.getBaseNameForQName(normalizedBundleName);
StringBuilder sb = new StringBuilder();
if(packageName != null && packageName.length() > 0)
{
sb.append(packageName);
sb.append(".");
}
sb.append(locale);
sb.append("$");
sb.append(className);
sb.append(CLASS_SUFFIX);
return sb.toString();
}
/**
* Extracts the locale from the specified qualified name for an auto generated
* resource bundle class. Class names for ResourceBundles, such as
* "foo.en_US$core_properties", are constructed in such a way that the locale
* and bundle name can be extracted.
* Note: The framework class mx.managers.SystemManager has similar logic
* which must be kept in sync with this.
*
* @param qualifiedName the fully qualified class name for a resource bundle
* @return extracted locale of this qualified name
*/
public static String getLocale(String qualifiedName)
{
String className = Multiname.getBaseNameForQName(qualifiedName);
int indexOf = className.indexOf('$');
if(indexOf > 0)
{
return className.substring(0, indexOf);
}
return null;
}
/**
* This methods converts a resource bundle qualified name in "dotted syntax"
* to use "colon syntax". The reason is that Flex SDK expects the resource
* bundle names in "colon syntax".
* [See Flex SDK's ResourceManagerImpl.installCompiledResourceBundle]
*
* Example: foo.bar.xyz will be converted to foo.bar:xyz
*
* @param bundleName qualified name of resource bundle
* @return qualified name of the resource bundle in "colon syntax".
*/
public static String convertBundleNameToColonSyntax(String bundleName)
{
String normalizedName = QNameNormalization.normalize(bundleName);
String packageName = Multiname.getPackageNameForQName(normalizedName);
if(packageName == null || packageName.length() == 0)
{
return bundleName;
}
String className = Multiname.getBaseNameForQName(normalizedName);
return packageName + ":" + className;
}
/**
* Resolving the references to the specified resourceBundleName and adds the
* necessary dependency from the specified compilation unit to resolved
* resource bundle's compilation unit.
*
* @param bundleName resource bundle name
* @param refCompUnit compilation unit that has a reference to the specified
* resource bundle name
* @param location location of the resource bundle's occurrence in the file
* associated with the specified compilation unit
* @param errors error collection to collect problems
*/
public static void resolveDependencies(String bundleName,
final ICompilationUnit refCompUnit,
final ICompilerProject project,
final ISourceLocation location,
final Collection<ICompilerProblem> errors) throws InterruptedException {
Map<String, ICompilationUnit> qualifiedNameMap = findCompilationUnits(
bundleName, project, refCompUnit.getAbsoluteFilename(), location, errors);
for (Map.Entry<String, ICompilationUnit> entry : qualifiedNameMap.entrySet())
{
((RoyaleProject)project).addDependency(refCompUnit, entry.getValue(),
DependencyType.EXPRESSION, entry.getKey());
}
}
/**
* Find all the compilation units associated with the specified resource bundle name.
*
* @param bundleName name of the resource bundle
* @param project associated project
* @param errors error collection to collect problems
* @return the list of compilation units that exist to handle the specified resource bundle.
*/
public static Collection<ICompilationUnit> findCompilationUnits(String bundleName,
final ICompilerProject project,
final Collection<ICompilerProblem> errors) throws InterruptedException {
return findCompilationUnits(bundleName, project, null, null, errors).values();
}
/**
* Find all the compilation units associated with the specified resource
* bundle name.
*
* @param bundleName name of the resource bundle
* @param cach definition cache
* @param refFilePath path of the file that has a reference to this resource
* bundle or could be <code>null</code>.
* @param location location where the reference in the specified file
* occurred or could be <code>null</code>.
* @param errors error collection to collect problems
* @return the map of qualified name and compilation unit pairs that exist to handle the specified
* resource bundle.
* @throws InterruptedException
*/
private static Map<String, ICompilationUnit> findCompilationUnits(String bundleName,
final ICompilerProject project,
final String refFilePath,
final ISourceLocation location,
final Collection<ICompilerProblem> errors) throws InterruptedException {
Map<String, ICompilationUnit> compilationUnits = new HashMap<String, ICompilationUnit>();
if (project instanceof RoyaleProject)
{
final String normalizedBundleName = QNameNormalization.normalize(bundleName);
final RoyaleProject royaleProject = (RoyaleProject)project;
final ASProjectScope scope = royaleProject.getScope();
final Collection<String> locales = royaleProject.getLocales();
if(locales.size() == 0)
{
//If the project's locale is empty, then don't try to resolve this
//node because the referenced bundle won't go into swf or swc anyways.
return Collections.emptyMap();
}
Collection<String> unresolvedLocales = new ArrayList<String>(locales);
for(String locale : locales)
{
//resolve the generated qualified name for the bundle name referenced in this metadata tag and this locale
String qualifiedName = ResourceBundleUtils.getQualifiedName(locale, normalizedBundleName);
//find the compilation units for this qualified name
IDefinition def = scope.findDefinitionByName(qualifiedName);
if(def != null)
{
ICompilationUnit compUnit = scope.getCompilationUnitForDefinition(def);
if (isValidCompilationUnit(compUnit, project, bundleName))
{
compilationUnits.put(def.getQualifiedName(), compUnit);
unresolvedLocales.remove(locale);
}
}
}
if (unresolvedLocales.size() > 0 || locales.size() == 0)
{
//If we are at this point, it means that either project's locale is empty or
//we could not resolve the resource bundle for some or all locales.
//The last thing we can check is to see whether we can resolve it using the
//pure bundle name (not the generated name that contains locale$..._properties)
//This is also acceptable because "mx.managers.SystemManager" uses the same logic
//to find a resource bundle. First, it searches for the generated class name and then the pure bundle name.
IDefinition def = scope.findDefinitionByName(normalizedBundleName);
if(def != null)
{
ICompilationUnit compUnit = scope.getCompilationUnitForDefinition(def);
if (isValidCompilationUnit(compUnit, project, bundleName))
{
//This means we resolved the reference by using pure bundle name which will apply
//to all locales during runtime, therefore return from this method.
compilationUnits.put(def.getQualifiedName(), compUnit);
return compilationUnits;
}
}
// At this point, it means that we were not able to resolve this resource bundle reference.
// Therefore, report an error.
if (unresolvedLocales.size() == locales.size())
{
//we couldn't resolve it for any locales or there was no locale,
//so show the generic error which doesn't mention any locales.
ResourceBundleNotFoundProblem problem = (location != null) ?
new ResourceBundleNotFoundProblem(location, bundleName) :
new ResourceBundleNotFoundProblem(bundleName);
errors.add(problem);
}
else
{
//some of the locales couldn't be resolved so show errors expressing which locales couldn't be resolved.
for(String locale : unresolvedLocales)
{
ResourceBundleNotFoundForLocaleProblem problem = (location != null) ?
new ResourceBundleNotFoundForLocaleProblem(location, bundleName, locale) :
new ResourceBundleNotFoundForLocaleProblem(bundleName, locale);
errors.add(problem);
}
}
}
}
return compilationUnits;
}
/**
* Checks whether the definition associated with the specified compilation
* unit can be used as a ResourceBundle. A compilation unit is valid if it
* is associated with a properties file or a class that extends
* mx.resources.ResourceBundle.
*
* @param compUnit compilation unit to validate
* @param project owner project
* @param cache project's cache
* @return <code>true</code> if the definition associated with the
* compilation unit can be used as a ResourceBundle, <code>false</code>
* otherwise.
* @throws InterruptedException
*/
private static boolean isValidCompilationUnit(final ICompilationUnit compUnit,
final ICompilerProject project,
final String bundleName) throws InterruptedException
{
if (compUnit != null)
{
//If we have a resource bundle compilation unit, then use it.
if (compUnit instanceof ResourceBundleCompilationUnit)
{
//This guarantees that the bundle name used while referencing is in the same syntax
//as we are expecting. For example, if the properties file is in a package, as of now,
//only "colon" syntax is accepted such as foo.bar:Name. Tehrefore, this check will fail
//if the user references the bundle with "dotted syntax" such as foo.bar.Name
//since Flex SDK does not know how to handle that.
if (((ResourceBundleCompilationUnit)compUnit).getBundleNameInColonSyntax().equals(bundleName))
return true;
}
else
{
IFileScopeRequestResult result = compUnit.getFileScopeRequest().get();
for (IDefinition def : result.getExternallyVisibleDefinitions())
{
if (def instanceof ClassDefinition)
{
//check whether class extends ResourceBundle
if (project instanceof RoyaleProject &&
((ClassDefinition)def).isInstanceOf(((RoyaleProject)project).getResourceBundleClass(), project))
{
return true;
}
}
}
}
}
return false;
}
}