This is an automated email from the ASF dual-hosted git repository.

joshtynjala pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/royale-compiler.git


The following commit(s) were added to refs/heads/develop by this push:
     new 9e580afe5 MXMLJSCNative: support resource bundles for JS target, 
similar to JSRoyale
9e580afe5 is described below

commit 9e580afe579447835df7b4b88c8b45bc148e424b
Author: Josh Tynjala <[email protected]>
AuthorDate: Fri May 22 15:25:36 2026 -0700

    MXMLJSCNative: support resource bundles for JS target, similar to JSRoyale
---
 RELEASE_NOTES.md                                   |   1 +
 .../royale/compiler/clients/MXMLJSCNative.java     | 192 +++++++++++++++++++++
 2 files changed, 193 insertions(+)

diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index c1d1bc817..191ab60d0 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -80,6 +80,7 @@ Apache Royale Compiler 1.0.0
 - compiler: Fixed warning for comparison of boolean or numeric type with 
`null` when using `-js-default-initializers=false` compiler option.
 - compiler: Fixed JavaScript code generation for E4X wildcard (.*) syntax.
 - compiler: Added `[JSDynamicOverride]`, `[JSForInOverride]`, and 
`[JSForEachOverride]` to customize the behavior of certain syntax when 
targeting JavaScript. Useful for emulating the full features of `Dictionary` 
and `ByteArray` from SWF.
+- compiler: Support resource bundles with `JS` target, similar to `JSRoyale`.
 - debugger: Added missing isolate ID to SWF load and unload events.
 - debugger: Fixed debugger targeting the current JDK version instead of the 
intended minimum JDK version.
 - debugger: Fixed localized messages appearing as unprocessed tokens.
diff --git 
a/compiler-jx/src/main/java/org/apache/royale/compiler/clients/MXMLJSCNative.java
 
b/compiler-jx/src/main/java/org/apache/royale/compiler/clients/MXMLJSCNative.java
index c8b799631..b010aa768 100644
--- 
a/compiler-jx/src/main/java/org/apache/royale/compiler/clients/MXMLJSCNative.java
+++ 
b/compiler-jx/src/main/java/org/apache/royale/compiler/clients/MXMLJSCNative.java
@@ -20,17 +20,25 @@
 package org.apache.royale.compiler.clients;
 
 import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.FileWriter;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.TimeZone;
 import java.util.TreeSet;
 
 import org.apache.commons.io.FilenameUtils;
@@ -51,6 +59,7 @@ import 
org.apache.royale.compiler.exceptions.ConfigurationException;
 import org.apache.royale.compiler.exceptions.ConfigurationException.IOError;
 import 
org.apache.royale.compiler.exceptions.ConfigurationException.MustSpecifyTarget;
 import 
org.apache.royale.compiler.exceptions.ConfigurationException.OnlyOneSource;
+import org.apache.royale.compiler.filespecs.IFileSpecification;
 import org.apache.royale.compiler.internal.config.FlashBuilderConfigurator;
 import org.apache.royale.compiler.internal.definitions.DefinitionBase;
 import org.apache.royale.compiler.internal.driver.js.goog.JSGoogConfiguration;
@@ -58,9 +67,13 @@ import 
org.apache.royale.compiler.internal.driver.js.jsc.JSCBackend;
 import org.apache.royale.compiler.internal.parsing.as.RoyaleASDocDelegate;
 import org.apache.royale.compiler.internal.projects.CompilerProject;
 import org.apache.royale.compiler.internal.projects.RoyaleJSProject;
+import 
org.apache.royale.compiler.internal.resourcebundles.PropertiesFileParser;
 import org.apache.royale.compiler.internal.projects.ISourceFileHandler;
 import org.apache.royale.compiler.internal.targets.RoyaleJSTarget;
+import 
org.apache.royale.compiler.internal.tree.properties.ResourceBundleEntryNode;
+import 
org.apache.royale.compiler.internal.tree.properties.ResourceBundleFileNode;
 import org.apache.royale.compiler.internal.targets.JSTarget;
+import org.apache.royale.compiler.internal.units.ResourceBundleCompilationUnit;
 import org.apache.royale.compiler.internal.units.ResourceModuleCompilationUnit;
 import org.apache.royale.compiler.internal.units.SourceCompilationUnitFactory;
 import org.apache.royale.compiler.internal.watcher.WatchThread;
@@ -74,10 +87,15 @@ import 
org.apache.royale.compiler.problems.UnexpectedExceptionProblem;
 import org.apache.royale.compiler.projects.ICompilerProject;
 import org.apache.royale.compiler.targets.ITarget;
 import org.apache.royale.compiler.targets.ITarget.TargetType;
+import org.apache.royale.compiler.tree.as.IASNode;
+import org.apache.royale.compiler.tree.as.ILiteralNode;
 import org.apache.royale.compiler.targets.ITargetSettings;
 import org.apache.royale.compiler.units.ICompilationUnit;
 import org.apache.royale.compiler.units.ICompilationUnit.UnitType;
 import org.apache.royale.compiler.utils.ClosureUtils;
+import org.apache.royale.swc.ISWC;
+import org.apache.royale.swc.ISWCFileEntry;
+import org.apache.royale.swc.ISWCManager;
 import org.apache.flex.tools.FlexTool;
 import org.apache.royale.utils.ArgumentUtil;
 import org.apache.royale.utils.FilenameNormalization;
@@ -411,6 +429,15 @@ public class MXMLJSCNative implements 
JSCompilerEntryPoint, ProblemQueryProvider
                        project.mixinClassNames = new TreeSet<String>();
                        List<ICompilationUnit> reachableCompilationUnits = 
project.getReachableCompilationUnitsInSWFOrder(roots);
                        
((RoyaleJSTarget)target).collectMixinMetaData(project.mixinClassNames, 
reachableCompilationUnits);
+                    // run through looking for resource bundles so we can have 
the list ready for the mainCU in the second loop
+                       for (final ICompilationUnit cu : 
reachableCompilationUnits)
+                       {
+                           ICompilationUnit.UnitType cuType = 
cu.getCompilationUnitType();
+                           if (cuType == 
ICompilationUnit.UnitType.RESOURCE_UNIT)
+                           {
+                               
outputResourceBundle((ResourceBundleCompilationUnit)cu, outputFolder);
+                           }
+                       }
                        for (final ICompilationUnit cu : 
reachableCompilationUnits)
                        {
                            writeCompilationUnit(cu, outputFolder);
@@ -495,6 +522,171 @@ public class MXMLJSCNative implements 
JSCompilerEntryPoint, ProblemQueryProvider
         writer.close();
     }
 
+       private void outputResourceBundle(ResourceBundleCompilationUnit cu, 
File outputFolder) {
+
+        final ISWCManager swcManager = project.getWorkspace().getSWCManager();
+        PropertiesFileParser parser = new 
PropertiesFileParser(project.getWorkspace());
+        ResourceBundleFileNode resourceBundleFileNode;
+
+        // find the SWC
+        final ISWC swc = swcManager.get(new File(cu.getAbsoluteFilename()));
+        if (swc != null)
+        {
+            if (swc.getSWCFile().getAbsolutePath().endsWith(".swc"))
+            {
+                       String bundleName = cu.getBundleNameInColonSyntax();
+                //swapped to using File.separator here instead of "/", because 
it was not finding the included resource files inside swcs (on windows) with 
"/":
+                       String propFileName = 
ResourceBundleCompilationUnit.LOCALE + File.separator + cu.getLocale() + 
File.separator + bundleName + ".properties";
+                       String bundleClassName = cu.getLocale() + "$" + 
bundleName + "_properties";
+                   Map<String, ISWCFileEntry> files = swc.getFiles();
+                for (String key : files.keySet())
+                   {
+                       if (key.equals(propFileName))
+                       {
+                        if 
(!project.compiledResourceBundleNames.contains(bundleName))
+                        {
+                            
project.compiledResourceBundleNames.add(bundleName);
+                        }
+                               
project.compiledResourceBundleClasses.add(bundleClassName);
+                           ISWCFileEntry fileEntry = swc.getFile(key);
+
+                           if (fileEntry != null)
+                           {
+                                                       InputStream is;
+                                                       try {
+                                                               is = 
fileEntry.createInputStream();
+                                                               BufferedReader 
br = new BufferedReader(new InputStreamReader(is));
+                                resourceBundleFileNode = 
parser.parse(cu.getAbsoluteFilename(),cu.getLocale(),br, project.getProblems());
+                                writeResourceBundle(resourceBundleFileNode, 
bundleClassName,outputFolder);
+                                                       } catch (IOException e) 
{
+                                // TODO check this is correct
+                                project.getProblems().add(new 
InternalCompilerProblem(e));
+                                                               
e.printStackTrace();
+                                                       }
+                           }
+                       }
+                   }
+            }
+            else
+            {
+               // it isn't a bundle from a SWC, it is a bundle in the source 
path
+                       String bundleName = cu.getBundleNameInColonSyntax();
+                       String bundleClassName = cu.getLocale() + "$" + 
bundleName + "_properties";
+
+               if (!project.compiledResourceBundleNames.contains(bundleName))
+                {
+                    project.compiledResourceBundleNames.add(bundleName);
+                }
+                   project.compiledResourceBundleClasses.add(bundleClassName);
+                               try {
+                    IFileSpecification fileSpecification = 
project.getWorkspace().getFileSpecification(cu.getAbsoluteFilename());
+                    resourceBundleFileNode = 
parser.parse(cu.getAbsoluteFilename(),cu.getLocale(),fileSpecification.createReader(),
 project.getProblems());
+                    writeResourceBundle(resourceBundleFileNode, 
bundleClassName,outputFolder);
+                               } catch (/*IO*/Exception e) {
+                                       // TODO check this is correct
+                    project.getProblems().add(new InternalCompilerProblem(e));
+                                       e.printStackTrace();
+                               }
+            }
+        }
+       }
+
+    private void writeResourceBundle(ResourceBundleFileNode 
resourceBundleFileNode, String bundleClassName, File outputFolder)
+    {
+        StringBuilder sb = new StringBuilder();
+        //@todo set this up for configuration, so that a base class is 
configurable with the only requirement that getContent() is the method 
'overridden'
+        sb.append("/**\n");
+        sb.append(" * Generated by Apache Royale Compiler from " + 
bundleClassName + ".properties\n");
+        sb.append(" * " + bundleClassName + "\n");
+        sb.append(" *\n");
+        sb.append(" * @fileoverview\n");
+        sb.append(" *\n");
+        sb.append(" * @suppress {checkTypes|accessControls}\n");
+        sb.append(" */\n\n");
+        sb.append("goog.provide('" + bundleClassName + "');\n\n");
+        sb.append("goog.require('mx.resources.IResourceBundle');\n");
+        sb.append("goog.require('mx.resources.ResourceBundle');\n\n\n");
+        sb.append("/**\n");
+        sb.append(" * @constructor\n");
+        sb.append(" * @extends {mx.resources.ResourceBundle}\n");
+        sb.append(" * @implements {mx.resources.IResourceBundle}\n");
+        sb.append(" */\n");
+        sb.append(bundleClassName + " = function() {\n");
+        sb.append("    " + bundleClassName + ".base(this, 'constructor');\n");
+        sb.append("};\n");
+        sb.append("goog.inherits(" + bundleClassName + ", 
mx.resources.ResourceBundle);\n\n");
+        sb.append("/**\n");
+        sb.append(" * Prevent renaming of class. Needed for reflection.\n");
+        sb.append(" */\n");
+        sb.append("goog.exportSymbol('" + bundleClassName + "', " + 
bundleClassName + ");\n\n");
+        sb.append(bundleClassName + ".prototype.getContent = function() { 
return {\n");
+
+        int childCount = resourceBundleFileNode.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            IASNode childNode = resourceBundleFileNode.getChild(i);
+            //this layer of checking may be redundant - added because 
uncertain if anything other than a ResourceBundleEntryNode could be a child
+            if (childNode instanceof ResourceBundleEntryNode) {
+                ResourceBundleEntryNode bundleEntryNode = 
(ResourceBundleEntryNode)childNode;
+                String value;
+                String propName;
+                //this could be redundant checking... maybe some checks can be 
removed:
+                if (bundleEntryNode.getValueNode() instanceof ILiteralNode && 
bundleEntryNode.getKeyNode() != null) {
+                    //not using the rawValue=true argument here can give 
incorrect results (for example if a string ends with " char)
+                    //we are going to assume these are already sanitized via 
the properties parser and avoid the internal logic of the getter
+                    //by passing 'true':
+                    value = 
((ILiteralNode)bundleEntryNode.getValueNode()).getValue(true);
+                    //prep for wrapping in double quotes and output as 'code' 
string:
+                    //escape backslashes
+                    value = value.replace("\\","\\\\");
+                    //escape double-quotes
+                    value = value.replace("\"","\\\"");
+                    //escape LF and CR
+                    value = value.replace("\n","\\n");
+                    value = value.replace("\r","\\r");
+                    propName = 
((ILiteralNode)bundleEntryNode.getKeyNode()).getValue();
+                    //should we consider the possibility that there could be 
two keys with the same name?
+                    //if so, could scan keys first in an initial loop before 
this one, find repeats and add earlier matching indices to an ignore list, and 
ignore the earlier indices for repeats inside this loop.
+                    sb.append("'" + propName + "' : \"" + value + "\",\n");
+                } //else?
+            }
+        }
+        sb.append("__end_of_bundle__: 0\n};};\n");
+
+        final File outputClassFile = getOutputClassFile(
+                bundleClassName, outputFolder);
+        if (config.isVerbose())
+        {
+            System.out.println("Generating resource file: " + outputClassFile);
+        }
+        FileWriter fw;
+        try {
+            fw = new FileWriter(outputClassFile, false);
+            fw.write(sb.toString());
+            fw.close();
+            long fileDate = 0;
+            String metadataDate = targetSettings.getSWFMetadataDate();
+            if (metadataDate != null)
+            {
+                String metadataFormat = 
targetSettings.getSWFMetadataDateFormat();
+                try {
+                    SimpleDateFormat sdf = new 
SimpleDateFormat(metadataFormat);
+                    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+                    fileDate = sdf.parse(metadataDate).getTime();
+                } catch (ParseException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                } catch (IllegalArgumentException e1) {
+                    e1.printStackTrace();
+                }
+                outputClassFile.setLastModified(fileDate);
+            }
+
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
     /**
      * Build target artifact.
      * 

Reply via email to